From 3d4390d00f630552e9fba48dc719b7b15e410471 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 13 Oct 2023 13:56:28 +0200 Subject: [PATCH 01/34] Create new module for connect-payment --- app/feature/feature-connect-payment/build.gradle.kts | 11 +++++++++++ .../src/main/AndroidManifest.xml | 2 ++ 2 files changed, 13 insertions(+) create mode 100644 app/feature/feature-connect-payment/build.gradle.kts create mode 100644 app/feature/feature-connect-payment/src/main/AndroidManifest.xml diff --git a/app/feature/feature-connect-payment/build.gradle.kts b/app/feature/feature-connect-payment/build.gradle.kts new file mode 100644 index 0000000000..0d8839cb4c --- /dev/null +++ b/app/feature/feature-connect-payment/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("hedvig.android.ktlint") + id("hedvig.android.library") + id("hedvig.android.library.compose") + alias(libs.plugins.squareSortDependencies) +} + +dependencies { + implementation(libs.kiwi.navigationCompose) + implementation(projects.navigationCore) +} diff --git a/app/feature/feature-connect-payment/src/main/AndroidManifest.xml b/app/feature/feature-connect-payment/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..568741e54f --- /dev/null +++ b/app/feature/feature-connect-payment/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 7fcd3f8f9f0ae2e25a5522fa31914c1e1eaf49f9 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 13 Oct 2023 14:40:17 +0200 Subject: [PATCH 02/34] Add connect payment destinations skeleton --- app/feature/feature-connect-payment/build.gradle.kts | 3 +++ .../feature/connect/payment/ConnectPaymentGraph.kt | 12 ++++++++++++ .../hedvig/android/navigation/core/AppDestination.kt | 8 ++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 app/feature/feature-connect-payment/src/main/kotlin/com/hedvig/android/feature/connect/payment/ConnectPaymentGraph.kt diff --git a/app/feature/feature-connect-payment/build.gradle.kts b/app/feature/feature-connect-payment/build.gradle.kts index 0d8839cb4c..ddb9903c08 100644 --- a/app/feature/feature-connect-payment/build.gradle.kts +++ b/app/feature/feature-connect-payment/build.gradle.kts @@ -6,6 +6,9 @@ plugins { } dependencies { + implementation(libs.androidx.navigation.common) + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.navigation.runtime) implementation(libs.kiwi.navigationCompose) implementation(projects.navigationCore) } diff --git a/app/feature/feature-connect-payment/src/main/kotlin/com/hedvig/android/feature/connect/payment/ConnectPaymentGraph.kt b/app/feature/feature-connect-payment/src/main/kotlin/com/hedvig/android/feature/connect/payment/ConnectPaymentGraph.kt new file mode 100644 index 0000000000..09d9115890 --- /dev/null +++ b/app/feature/feature-connect-payment/src/main/kotlin/com/hedvig/android/feature/connect/payment/ConnectPaymentGraph.kt @@ -0,0 +1,12 @@ +package com.hedvig.android.feature.connect.payment + +import androidx.navigation.NavGraphBuilder +import com.hedvig.android.navigation.core.AppDestination +import com.kiwi.navigationcompose.typed.composable + +fun NavGraphBuilder.connectPaymentGraph() { + composable() { + } + composable() { + } +} diff --git a/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt b/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt index a757969411..5d1c9185b8 100644 --- a/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt +++ b/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt @@ -59,6 +59,10 @@ sealed interface AppDestination : Destination { @Serializable object PaymentHistory : AppDestination -// @Serializable -// object LegacyClaimsTriaging : AppDestination + + @Serializable + object ConnectPaymentTrustly : AppDestination + + @Serializable + object ConnectPaymentAdyen : AppDestination } From 9164c805ddba28537f114800233f6517ee1aea24 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Mon, 16 Oct 2023 13:11:52 +0200 Subject: [PATCH 03/34] Fork accompanist webview as suggested in the docs --- app/ui/compose-webview/README.md | 2 + app/ui/compose-webview/build.gradle.kts | 15 + .../src/main/AndroidManifest.xml | 2 + .../hedvig/android/composewebview/WebView.kt | 655 ++++++++++++++++++ 4 files changed, 674 insertions(+) create mode 100644 app/ui/compose-webview/README.md create mode 100644 app/ui/compose-webview/build.gradle.kts create mode 100644 app/ui/compose-webview/src/main/AndroidManifest.xml create mode 100644 app/ui/compose-webview/src/main/kotlin/com/hedvig/android/composewebview/WebView.kt diff --git a/app/ui/compose-webview/README.md b/app/ui/compose-webview/README.md new file mode 100644 index 0000000000..c9c8995bef --- /dev/null +++ b/app/ui/compose-webview/README.md @@ -0,0 +1,2 @@ +Accompanist WebView is deprecated https://medium.com/androiddevelopers/an-update-on-jetpack-compose-accompanist-libraries-august-2023-ac4cbbf059f1 +This is a fork of it so we can still use it as suggested in the article above \ No newline at end of file diff --git a/app/ui/compose-webview/build.gradle.kts b/app/ui/compose-webview/build.gradle.kts new file mode 100644 index 0000000000..cb334149b0 --- /dev/null +++ b/app/ui/compose-webview/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("hedvig.android.ktlint") + id("hedvig.android.library") + id("hedvig.android.library.compose") + alias(libs.plugins.squareSortDependencies) +} + +dependencies { + implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.uiUtil) + implementation(libs.androidx.lifecycle.common) + implementation(libs.androidx.lifecycle.runtime) + implementation(libs.androidx.other.activityCompose) +} diff --git a/app/ui/compose-webview/src/main/AndroidManifest.xml b/app/ui/compose-webview/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..568741e54f --- /dev/null +++ b/app/ui/compose-webview/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/ui/compose-webview/src/main/kotlin/com/hedvig/android/composewebview/WebView.kt b/app/ui/compose-webview/src/main/kotlin/com/hedvig/android/composewebview/WebView.kt new file mode 100644 index 0000000000..acafbdf4d3 --- /dev/null +++ b/app/ui/compose-webview/src/main/kotlin/com/hedvig/android/composewebview/WebView.kt @@ -0,0 +1,655 @@ +package com.hedvig.android.composewebview + +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.view.ViewGroup.LayoutParams +import android.webkit.WebChromeClient +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.FrameLayout +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.mapSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import com.hedvig.android.composewebview.LoadingState.Finished +import com.hedvig.android.composewebview.LoadingState.Loading +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * A wrapper around the Android View WebView to provide a basic WebView composable. + * + * @param state The webview state holder where the Uri to load is defined. + * @param modifier A compose modifier + * @param captureBackPresses Set to true to have this Composable capture back presses and navigate + * the WebView back. + * @param navigator An optional navigator object that can be used to control the WebView's + * navigation from outside the composable. + * @param onCreated Called when the WebView is first created, this can be used to set additional + * settings on the WebView. WebChromeClient and WebViewClient should not be set here as they will be + * subsequently overwritten after this lambda is called. + * @param onDispose Called when the WebView is destroyed. Provides a bundle which can be saved + * if you need to save and restore state in this WebView. + * @param client Provides access to WebViewClient via subclassing + * @param chromeClient Provides access to WebChromeClient via subclassing + * @param factory An optional WebView factory for using a custom subclass of WebView + * @sample com.google.accompanist.sample.webview.BasicWebViewSample + */ +@Composable +fun WebView( + state: WebViewState, + modifier: Modifier = Modifier, + captureBackPresses: Boolean = true, + navigator: WebViewNavigator = rememberWebViewNavigator(), + onCreated: (WebView) -> Unit = {}, + onDispose: (WebView) -> Unit = {}, + client: AccompanistWebViewClient = remember { AccompanistWebViewClient() }, + chromeClient: AccompanistWebChromeClient = remember { AccompanistWebChromeClient() }, + factory: ((Context) -> WebView)? = null, +) { + BoxWithConstraints(modifier) { + // WebView changes it's layout strategy based on + // it's layoutParams. We convert from Compose Modifier to + // layout params here. + val width = + if (constraints.hasFixedWidth) { + LayoutParams.MATCH_PARENT + } else { + LayoutParams.WRAP_CONTENT + } + val height = + if (constraints.hasFixedHeight) { + LayoutParams.MATCH_PARENT + } else { + LayoutParams.WRAP_CONTENT + } + + val layoutParams = FrameLayout.LayoutParams( + width, + height, + ) + + WebView( + state, + layoutParams, + Modifier, + captureBackPresses, + navigator, + onCreated, + onDispose, + client, + chromeClient, + factory, + ) + } +} + +@Composable +private fun WebView( + state: WebViewState, + layoutParams: FrameLayout.LayoutParams, + modifier: Modifier = Modifier, + captureBackPresses: Boolean = true, + navigator: WebViewNavigator = rememberWebViewNavigator(), + onCreated: (WebView) -> Unit = {}, + onDispose: (WebView) -> Unit = {}, + client: AccompanistWebViewClient = remember { AccompanistWebViewClient() }, + chromeClient: AccompanistWebChromeClient = remember { AccompanistWebChromeClient() }, + factory: ((Context) -> WebView)? = null, +) { + val webView = state.webView + + BackHandler(captureBackPresses && navigator.canGoBack) { + webView?.goBack() + } + + @Suppress("NAME_SHADOWING") + webView?.let { webView -> + LaunchedEffect(webView, navigator) { + with(navigator) { + webView.handleNavigationEvents() + } + } + + LaunchedEffect(webView, state) { + snapshotFlow { state.content }.collect { content -> + when (content) { + is WebContent.Url -> { + webView.loadUrl(content.url, content.additionalHttpHeaders) + } + + is WebContent.Data -> { + webView.loadDataWithBaseURL( + content.baseUrl, + content.data, + content.mimeType, + content.encoding, + content.historyUrl, + ) + } + + is WebContent.Post -> { + webView.postUrl( + content.url, + content.postData, + ) + } + + is WebContent.NavigatorOnly -> { + // NO-OP + } + } + } + } + } + + // Set the state of the client and chrome client + // This is done internally to ensure they always are the same instance as the + // parent Web composable + client.state = state + client.navigator = navigator + chromeClient.state = state + + AndroidView( + factory = { context -> + (factory?.invoke(context) ?: WebView(context)).apply { + onCreated(this) + + this.layoutParams = layoutParams + + state.viewState?.let { + this.restoreState(it) + } + + webChromeClient = chromeClient + webViewClient = client + }.also { state.webView = it } + }, + modifier = modifier, + onRelease = { + onDispose(it) + }, + ) +} + +/** + * AccompanistWebViewClient + * + * A parent class implementation of WebViewClient that can be subclassed to add custom behaviour. + * + * As Accompanist Web needs to set its own web client to function, it provides this intermediary + * class that can be overriden if further custom behaviour is required. + */ +open class AccompanistWebViewClient : WebViewClient() { + open lateinit var state: WebViewState + internal set + open lateinit var navigator: WebViewNavigator + internal set + + override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + state.loadingState = Loading(0.0f) + state.errorsForCurrentRequest.clear() + state.pageTitle = null + state.pageIcon = null + + state.lastLoadedUrl = url + } + + override fun onPageFinished(view: WebView, url: String?) { + super.onPageFinished(view, url) + state.loadingState = Finished + } + + override fun doUpdateVisitedHistory(view: WebView, url: String?, isReload: Boolean) { + super.doUpdateVisitedHistory(view, url, isReload) + + navigator.canGoBack = view.canGoBack() + navigator.canGoForward = view.canGoForward() + } + + override fun onReceivedError( + view: WebView, + request: WebResourceRequest?, + error: WebResourceError?, + ) { + super.onReceivedError(view, request, error) + + if (error != null) { + state.errorsForCurrentRequest.add(WebViewError(request, error)) + } + } +} + +/** + * AccompanistWebChromeClient + * + * A parent class implementation of WebChromeClient that can be subclassed to add custom behaviour. + * + * As Accompanist Web needs to set its own web client to function, it provides this intermediary + * class that can be overriden if further custom behaviour is required. + */ +open class AccompanistWebChromeClient : WebChromeClient() { + open lateinit var state: WebViewState + internal set + + override fun onReceivedTitle(view: WebView, title: String?) { + super.onReceivedTitle(view, title) + state.pageTitle = title + } + + override fun onReceivedIcon(view: WebView, icon: Bitmap?) { + super.onReceivedIcon(view, icon) + state.pageIcon = icon + } + + override fun onProgressChanged(view: WebView, newProgress: Int) { + super.onProgressChanged(view, newProgress) + if (state.loadingState is Finished) return + state.loadingState = Loading(newProgress / 100.0f) + } +} + +sealed class WebContent { + data class Url( + val url: String, + val additionalHttpHeaders: Map = emptyMap(), + ) : WebContent() + + data class Data( + val data: String, + val baseUrl: String? = null, + val encoding: String = "utf-8", + val mimeType: String? = null, + val historyUrl: String? = null, + ) : WebContent() + + data class Post( + val url: String, + val postData: ByteArray, + ) : WebContent() { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Post + + if (url != other.url) return false + if (!postData.contentEquals(other.postData)) return false + + return true + } + + override fun hashCode(): Int { + var result = url.hashCode() + result = 31 * result + postData.contentHashCode() + return result + } + } + + object NavigatorOnly : WebContent() +} + +internal fun WebContent.withUrl(url: String) = when (this) { + is WebContent.Url -> copy(url = url) + else -> WebContent.Url(url) +} + +/** + * Sealed class for constraining possible loading states. + * See [Loading] and [Finished]. + */ +sealed class LoadingState { + /** + * Describes a WebView that has not yet loaded for the first time. + */ + object Initializing : LoadingState() + + /** + * Describes a webview between `onPageStarted` and `onPageFinished` events, contains a + * [progress] property which is updated by the webview. + */ + data class Loading(val progress: Float) : LoadingState() + + /** + * Describes a webview that has finished loading content. + */ + object Finished : LoadingState() +} + +/** + * A state holder to hold the state for the WebView. In most cases this will be remembered + * using the rememberWebViewState(uri) function. + */ +class WebViewState(webContent: WebContent) { + var lastLoadedUrl: String? by mutableStateOf(null) + internal set + + /** + * The content being loaded by the WebView + */ + var content: WebContent by mutableStateOf(webContent) + + /** + * Whether the WebView is currently [LoadingState.Loading] data in its main frame (along with + * progress) or the data loading has [LoadingState.Finished]. See [LoadingState] + */ + var loadingState: LoadingState by mutableStateOf(LoadingState.Initializing) + internal set + + /** + * Whether the webview is currently loading data in its main frame + */ + val isLoading: Boolean + get() = loadingState !is Finished + + /** + * The title received from the loaded content of the current page + */ + var pageTitle: String? by mutableStateOf(null) + internal set + + /** + * the favicon received from the loaded content of the current page + */ + var pageIcon: Bitmap? by mutableStateOf(null) + internal set + + /** + * A list for errors captured in the last load. Reset when a new page is loaded. + * Errors could be from any resource (iframe, image, etc.), not just for the main page. + * For more fine grained control use the OnError callback of the WebView. + */ + val errorsForCurrentRequest: SnapshotStateList = mutableStateListOf() + + /** + * The saved view state from when the view was destroyed last. To restore state, + * use the navigator and only call loadUrl if the bundle is null. + * See WebViewSaveStateSample. + */ + var viewState: Bundle? = null + internal set + + // We need access to this in the state saver. An internal DisposableEffect or AndroidView + // onDestroy is called after the state saver and so can't be used. + internal var webView by mutableStateOf(null) +} + +/** + * Allows control over the navigation of a WebView from outside the composable. E.g. for performing + * a back navigation in response to the user clicking the "up" button in a TopAppBar. + * + * @see [rememberWebViewNavigator] + */ +@Stable +class WebViewNavigator(private val coroutineScope: CoroutineScope) { + private sealed interface NavigationEvent { + object Back : NavigationEvent + object Forward : NavigationEvent + object Reload : NavigationEvent + object StopLoading : NavigationEvent + + data class LoadUrl( + val url: String, + val additionalHttpHeaders: Map = emptyMap(), + ) : NavigationEvent + + data class LoadHtml( + val html: String, + val baseUrl: String? = null, + val mimeType: String? = null, + val encoding: String? = "utf-8", + val historyUrl: String? = null, + ) : NavigationEvent + + data class PostUrl( + val url: String, + val postData: ByteArray, + ) : NavigationEvent { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PostUrl + + if (url != other.url) return false + if (!postData.contentEquals(other.postData)) return false + + return true + } + + override fun hashCode(): Int { + var result = url.hashCode() + result = 31 * result + postData.contentHashCode() + return result + } + } + } + + private val navigationEvents: MutableSharedFlow = MutableSharedFlow(replay = 1) + + // Use Dispatchers.Main to ensure that the webview methods are called on UI thread + internal suspend fun WebView.handleNavigationEvents(): Nothing = withContext(Dispatchers.Main) { + navigationEvents.collect { event -> + when (event) { + is NavigationEvent.Back -> goBack() + is NavigationEvent.Forward -> goForward() + is NavigationEvent.Reload -> reload() + is NavigationEvent.StopLoading -> stopLoading() + is NavigationEvent.LoadHtml -> loadDataWithBaseURL( + event.baseUrl, + event.html, + event.mimeType, + event.encoding, + event.historyUrl, + ) + + is NavigationEvent.LoadUrl -> { + loadUrl(event.url, event.additionalHttpHeaders) + } + + is NavigationEvent.PostUrl -> { + postUrl(event.url, event.postData) + } + } + } + } + + /** + * True when the web view is able to navigate backwards, false otherwise. + */ + var canGoBack: Boolean by mutableStateOf(false) + internal set + + /** + * True when the web view is able to navigate forwards, false otherwise. + */ + var canGoForward: Boolean by mutableStateOf(false) + internal set + + fun loadUrl(url: String, additionalHttpHeaders: Map = emptyMap()) { + coroutineScope.launch { + navigationEvents.emit( + NavigationEvent.LoadUrl( + url, + additionalHttpHeaders, + ), + ) + } + } + + fun loadHtml( + html: String, + baseUrl: String? = null, + mimeType: String? = null, + encoding: String? = "utf-8", + historyUrl: String? = null, + ) { + coroutineScope.launch { + navigationEvents.emit( + NavigationEvent.LoadHtml( + html, + baseUrl, + mimeType, + encoding, + historyUrl, + ), + ) + } + } + + fun postUrl( + url: String, + postData: ByteArray, + ) { + coroutineScope.launch { + navigationEvents.emit( + NavigationEvent.PostUrl( + url, + postData, + ), + ) + } + } + + /** + * Navigates the webview back to the previous page. + */ + fun navigateBack() { + coroutineScope.launch { navigationEvents.emit(NavigationEvent.Back) } + } + + /** + * Navigates the webview forward after going back from a page. + */ + fun navigateForward() { + coroutineScope.launch { navigationEvents.emit(NavigationEvent.Forward) } + } + + /** + * Reloads the current page in the webview. + */ + fun reload() { + coroutineScope.launch { navigationEvents.emit(NavigationEvent.Reload) } + } + + /** + * Stops the current page load (if one is loading). + */ + fun stopLoading() { + coroutineScope.launch { navigationEvents.emit(NavigationEvent.StopLoading) } + } +} + +/** + * Creates and remembers a [WebViewNavigator] using the default [CoroutineScope] or a provided + * override. + */ +@Composable +fun rememberWebViewNavigator( + coroutineScope: CoroutineScope = rememberCoroutineScope(), +): WebViewNavigator = remember(coroutineScope) { WebViewNavigator(coroutineScope) } + +/** + * A wrapper class to hold errors from the WebView. + */ +@Immutable +data class WebViewError( + /** + * The request the error came from. + */ + val request: WebResourceRequest?, + /** + * The error that was reported. + */ + val error: WebResourceError, +) + +/** + * Creates a WebView state that is remembered across Compositions. + * + * @param url The url to load in the WebView + * @param additionalHttpHeaders Optional, additional HTTP headers that are passed to [WebView.loadUrl]. + * Note that these headers are used for all subsequent requests of the WebView. + */ +@Composable +fun rememberWebViewState( + url: String, + additionalHttpHeaders: Map = emptyMap(), +): WebViewState = +// Rather than using .apply {} here we will recreate the state, this prevents + // a recomposition loop when the webview updates the url itself. + remember { + WebViewState( + WebContent.Url( + url = url, + additionalHttpHeaders = additionalHttpHeaders, + ), + ) + }.apply { + this.content = WebContent.Url( + url = url, + additionalHttpHeaders = additionalHttpHeaders, + ) + } + +/** + * Creates a WebView state that is remembered across Compositions and saved + * across activity recreation. + * When using saved state, you cannot change the URL via recomposition. The only way to load + * a URL is via a WebViewNavigator. + * + * @param data The uri to load in the WebView + * @sample com.google.accompanist.sample.webview.WebViewSaveStateSample + */ +@Composable +fun rememberSaveableWebViewState(): WebViewState { + return rememberSaveable(saver = WebStateSaver) { + WebViewState(WebContent.NavigatorOnly) + } +} + +val WebStateSaver: Saver = run { + val pageTitleKey = "pagetitle" + val lastLoadedUrlKey = "lastloaded" + val stateBundle = "bundle" + + mapSaver( + save = { + val viewState = Bundle().apply { it.webView?.saveState(this) } + mapOf( + pageTitleKey to it.pageTitle, + lastLoadedUrlKey to it.lastLoadedUrl, + stateBundle to viewState, + ) + }, + restore = { + WebViewState(WebContent.NavigatorOnly).apply { + this.pageTitle = it[pageTitleKey] as String? + this.lastLoadedUrl = it[lastLoadedUrlKey] as String? + this.viewState = it[stateBundle] as Bundle? + } + }, + ) +} From 205afedf05542e78bbce68ffd91b9a31a2fc65b2 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Mon, 16 Oct 2023 13:12:32 +0200 Subject: [PATCH 04/34] Create separate feature for adyen and for trustly --- app/app/build.gradle.kts | 2 ++ .../android/app/navigation/HedvigNavHost.kt | 4 ++++ .../build.gradle.kts | 0 .../src/main/AndroidManifest.xml | 0 .../payment/adyen/ConnectAdyenPaymentGraph.kt | 10 ++++++++++ .../build.gradle.kts | 15 +++++++++++++++ .../src/main/AndroidManifest.xml | 2 ++ .../trustly/ConnectTrustlyPaymentGraph.kt} | 4 +--- 8 files changed, 34 insertions(+), 3 deletions(-) rename app/feature/{feature-connect-payment => feature-connect-payment-adyen}/build.gradle.kts (100%) rename app/feature/{feature-connect-payment => feature-connect-payment-adyen}/src/main/AndroidManifest.xml (100%) create mode 100644 app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/ConnectAdyenPaymentGraph.kt create mode 100644 app/feature/feature-connect-payment-trustly/build.gradle.kts create mode 100644 app/feature/feature-connect-payment-trustly/src/main/AndroidManifest.xml rename app/feature/{feature-connect-payment/src/main/kotlin/com/hedvig/android/feature/connect/payment/ConnectPaymentGraph.kt => feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt} (72%) diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index a80e47e5df..8a2ae33edd 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -220,6 +220,8 @@ dependencies { implementation(projects.featureChangeaddress) implementation(projects.featureChat) implementation(projects.featureClaimTriaging) + implementation(projects.featureConnectPaymentAdyen) + implementation(projects.featureConnectPaymentTrustly) implementation(projects.featureForever) implementation(projects.featureHome) implementation(projects.featureInsurances) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index 9781ad43c1..34b57ff47e 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -27,6 +27,8 @@ import com.hedvig.android.feature.changeaddress.navigation.changeAddressGraph import com.hedvig.android.feature.chat.navigation.chatGraph import com.hedvig.android.feature.claimtriaging.ClaimTriagingDestination import com.hedvig.android.feature.claimtriaging.claimTriagingDestinations +import com.hedvig.android.feature.connect.payment.adyen.connectAdyenPaymentGraph +import com.hedvig.android.feature.connect.payment.connectTrustlyPaymentGraph import com.hedvig.android.feature.forever.navigation.foreverGraph import com.hedvig.android.feature.home.claims.pledge.HonestyPledgeBottomSheet import com.hedvig.android.feature.home.home.navigation.homeGraph @@ -229,6 +231,8 @@ internal fun HedvigNavHost( hedvigDeepLinkContainer = hedvigDeepLinkContainer, navigator = navigator, ) + connectAdyenPaymentGraph() + connectTrustlyPaymentGraph() } } diff --git a/app/feature/feature-connect-payment/build.gradle.kts b/app/feature/feature-connect-payment-adyen/build.gradle.kts similarity index 100% rename from app/feature/feature-connect-payment/build.gradle.kts rename to app/feature/feature-connect-payment-adyen/build.gradle.kts diff --git a/app/feature/feature-connect-payment/src/main/AndroidManifest.xml b/app/feature/feature-connect-payment-adyen/src/main/AndroidManifest.xml similarity index 100% rename from app/feature/feature-connect-payment/src/main/AndroidManifest.xml rename to app/feature/feature-connect-payment-adyen/src/main/AndroidManifest.xml diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/ConnectAdyenPaymentGraph.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/ConnectAdyenPaymentGraph.kt new file mode 100644 index 0000000000..1f8272d66e --- /dev/null +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/ConnectAdyenPaymentGraph.kt @@ -0,0 +1,10 @@ +package com.hedvig.android.feature.connect.payment.adyen + +import androidx.navigation.NavGraphBuilder +import com.hedvig.android.navigation.core.AppDestination +import com.kiwi.navigationcompose.typed.composable + +fun NavGraphBuilder.connectAdyenPaymentGraph() { + composable() { + } +} diff --git a/app/feature/feature-connect-payment-trustly/build.gradle.kts b/app/feature/feature-connect-payment-trustly/build.gradle.kts new file mode 100644 index 0000000000..51ad6d4e1b --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("hedvig.android.ktlint") + id("hedvig.android.library") + id("hedvig.android.library.compose") + alias(libs.plugins.squareSortDependencies) +} + +dependencies { + implementation(libs.androidx.navigation.common) + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.navigation.runtime) + implementation(libs.kiwi.navigationCompose) + implementation(projects.composeWebview) + implementation(projects.navigationCore) +} diff --git a/app/feature/feature-connect-payment-trustly/src/main/AndroidManifest.xml b/app/feature/feature-connect-payment-trustly/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..568741e54f --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/feature/feature-connect-payment/src/main/kotlin/com/hedvig/android/feature/connect/payment/ConnectPaymentGraph.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt similarity index 72% rename from app/feature/feature-connect-payment/src/main/kotlin/com/hedvig/android/feature/connect/payment/ConnectPaymentGraph.kt rename to app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt index 09d9115890..fcbbf7770b 100644 --- a/app/feature/feature-connect-payment/src/main/kotlin/com/hedvig/android/feature/connect/payment/ConnectPaymentGraph.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt @@ -4,9 +4,7 @@ import androidx.navigation.NavGraphBuilder import com.hedvig.android.navigation.core.AppDestination import com.kiwi.navigationcompose.typed.composable -fun NavGraphBuilder.connectPaymentGraph() { +fun NavGraphBuilder.connectTrustlyPaymentGraph() { composable() { } - composable() { - } } From 0c466f5741ae347fa0cb48471b2dcbe00d14800c Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Mon, 16 Oct 2023 13:47:42 +0200 Subject: [PATCH 05/34] Add function to replace `connectPayinIntent` --- .../hedvig/android/app/navigation/HedvigNavHost.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index 34b57ff47e..77c689f332 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -339,3 +339,15 @@ private fun rememberNavigator(navController: NavController): Navigator { } } } + +private fun navigateToConnectPayment( + navigator: Navigator, + market: Market, +) { + when (market) { + Market.SE -> navigator.navigateUnsafe(AppDestination.ConnectPaymentTrustly) + Market.NO, + Market.DK, + -> navigator.navigateUnsafe(AppDestination.ConnectPaymentAdyen) + } +} From 4b2946dbc0c267cc91241cf0ae126449078d0e22 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Mon, 16 Oct 2023 16:13:56 +0200 Subject: [PATCH 06/34] Add a generic connect payment destination For the deep link to point to for now while we still have one payment connection link. --- .../android/app/navigation/HedvigNavHost.kt | 39 ++++++++++++++++++- .../android/navigation/core/AppDestination.kt | 3 ++ .../core/HedvigDeepLinkContainer.kt | 4 ++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index 77c689f332..f88549a485 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -231,8 +231,11 @@ internal fun HedvigNavHost( hedvigDeepLinkContainer = hedvigDeepLinkContainer, navigator = navigator, ) - connectAdyenPaymentGraph() - connectTrustlyPaymentGraph() + connectPaymentGraph( + navigator = navigator, + market = market, + hedvigDeepLinkContainer = hedvigDeepLinkContainer, + ) } } @@ -307,6 +310,38 @@ private fun NavGraphBuilder.nestedHomeGraphs( ) } +private fun NavGraphBuilder.connectPaymentGraph( + navigator: Navigator, + market: Market, + hedvigDeepLinkContainer: HedvigDeepLinkContainer, +) { + composable( + deepLinks = listOf( + navDeepLink { uriPattern = hedvigDeepLinkContainer.connectPayment }, + navDeepLink { uriPattern = hedvigDeepLinkContainer.directDebit }, + ), + enterTransition = { EnterTransition.None }, + exitTransition = { ExitTransition.None }, + ) { + LaunchedEffect(Unit) { + val navOptions = navOptions { + popUpTo { + inclusive = true + } + } + when (market) { + Market.SE -> navigator.navigateUnsafe(AppDestination.ConnectPaymentTrustly, navOptions) + Market.NO, + Market.DK, + -> navigator.navigateUnsafe(AppDestination.ConnectPaymentAdyen, navOptions) + } + } + HedvigFullScreenCenterAlignedProgress() + } + connectAdyenPaymentGraph() + connectTrustlyPaymentGraph() +} + @Composable private fun rememberNavigator(navController: NavController): Navigator { return remember(navController) { diff --git a/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt b/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt index 5d1c9185b8..9060eddd95 100644 --- a/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt +++ b/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt @@ -60,6 +60,9 @@ sealed interface AppDestination : Destination { @Serializable object PaymentHistory : AppDestination + @Serializable + object ConnectPaymentGeneric : AppDestination // Auto-navigates to ConnectPaymentTrustly or ConnectPaymentAdyen + @Serializable object ConnectPaymentTrustly : AppDestination diff --git a/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/HedvigDeepLinkContainer.kt b/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/HedvigDeepLinkContainer.kt index 61999b72a0..e00e8b2044 100644 --- a/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/HedvigDeepLinkContainer.kt +++ b/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/HedvigDeepLinkContainer.kt @@ -7,6 +7,8 @@ interface HedvigDeepLinkContainer { val profile: String // The profile screen, which acts as a gateway to several app settings val eurobonus: String // The destination allowing to edit your current Eurobonus (SAS) number val chat: String // Hedvig Chat + val connectPayment: String // Screen where the member can connect their payment method to Hedvig to pay for insurance + val directDebit: String // Same as connectPayment but to support an old link to it } internal class HedvigDeepLinkContainerImpl( @@ -28,4 +30,6 @@ internal class HedvigDeepLinkContainerImpl( override val profile: String = "$baseFirebaseLink/profile" override val eurobonus: String = "$baseFirebaseLink/eurobonus" override val chat: String = "$baseFirebaseLink/chat" + override val connectPayment: String = "$baseFirebaseLink/connect-payment" + override val directDebit: String = "$baseFirebaseLink/direct-debit" } From c15e94c2a8a0027c21549e6b8e156f57ab2a6316 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Mon, 16 Oct 2023 16:14:47 +0200 Subject: [PATCH 07/34] Migrate connectPayinIntent callers The ones that are still relevant do it through androidx.navigation instead. The ones left are to be deleted when moving flow is out which allows us to delete the offer page. --- .../android/app/navigation/HedvigNavHost.kt | 37 +++++-------------- .../app/feature/offer/ui/OfferActivity.kt | 2 +- .../hedvig/app/feature/payment/PaymentType.kt | 1 + .../sign/SwedishBankIdSignDialog.kt | 2 +- 4 files changed, 13 insertions(+), 29 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index f88549a485..e1a315c2b1 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -2,7 +2,10 @@ package com.hedvig.android.app.navigation import android.content.Context import android.net.Uri +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -17,9 +20,12 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.NavHost +import androidx.navigation.navDeepLink +import androidx.navigation.navOptions import coil.ImageLoader import com.hedvig.android.app.ui.HedvigAppState import com.hedvig.android.core.buildconstants.HedvigBuildConstants +import com.hedvig.android.core.designsystem.component.progress.HedvigFullScreenCenterAlignedProgress import com.hedvig.android.core.designsystem.material3.motion.MotionDefaults import com.hedvig.android.data.claimflow.ClaimFlowStep import com.hedvig.android.data.claimflow.toClaimFlowDestination @@ -53,10 +59,10 @@ import com.hedvig.app.BuildConfig import com.hedvig.app.feature.adyen.AdyenCurrency import com.hedvig.app.feature.adyen.payout.AdyenConnectPayoutActivity import com.hedvig.app.feature.embark.ui.EmbarkActivity -import com.hedvig.app.feature.payment.connectPayinIntent import com.hedvig.hanalytics.AppScreen import com.hedvig.hanalytics.HAnalytics import com.kiwi.navigationcompose.typed.Destination +import com.kiwi.navigationcompose.typed.composable import com.kiwi.navigationcompose.typed.createRoutePattern import com.kiwi.navigationcompose.typed.navigate import com.kiwi.navigationcompose.typed.popBackStack @@ -68,6 +74,7 @@ internal fun HedvigNavHost( hedvigAppState: HedvigAppState, hedvigDeepLinkContainer: HedvigDeepLinkContainer, activityNavigator: ActivityNavigator, + navigateToConnectPayment: () -> Unit, shouldShowRequestPermissionRationale: (String) -> Boolean, imageLoader: ImageLoader, market: Market, @@ -96,18 +103,6 @@ internal fun HedvigNavHost( } } - fun navigateToPayinScreen() { - coroutineScope.launch { - context.startActivity( - connectPayinIntent( - context, - market, - false, - ), - ) - } - } - fun openUrl(url: String) { activityNavigator.openWebsite( context, @@ -170,7 +165,7 @@ internal fun HedvigNavHost( onGenerateTravelCertificateClicked = { hedvigAppState.navController.navigate(AppDestination.GenerateTravelCertificate) }, - navigateToPayinScreen = ::navigateToPayinScreen, + navigateToPayinScreen = navigateToConnectPayment, openAppSettings = { activityNavigator.openAppSettings(context) }, openUrl = ::openUrl, imageLoader = imageLoader, @@ -222,7 +217,7 @@ internal fun HedvigNavHost( val intent = AdyenConnectPayoutActivity.newInstance(context, AdyenCurrency.fromMarket(market)) context.startActivity(intent) }, - navigateToPayinScreen = ::navigateToPayinScreen, + navigateToPayinScreen = navigateToConnectPayment, openAppSettings = { activityNavigator.openAppSettings(context) }, openUrl = ::openUrl, market = market, @@ -374,15 +369,3 @@ private fun rememberNavigator(navController: NavController): Navigator { } } } - -private fun navigateToConnectPayment( - navigator: Navigator, - market: Market, -) { - when (market) { - Market.SE -> navigator.navigateUnsafe(AppDestination.ConnectPaymentTrustly) - Market.NO, - Market.DK, - -> navigator.navigateUnsafe(AppDestination.ConnectPaymentAdyen) - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/offer/ui/OfferActivity.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/offer/ui/OfferActivity.kt index 5777debf4f..de3f289333 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/offer/ui/OfferActivity.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/feature/offer/ui/OfferActivity.kt @@ -287,7 +287,7 @@ class OfferActivity : AppCompatActivity(R.layout.activity_offer) { PostSignScreen.CONNECT_PAYIN -> { val market = marketManager.market.value startActivity( - connectPayinIntent( + connectPayinIntent( // todo delete along with entire offer screen this, market, true, diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/payment/PaymentType.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/payment/PaymentType.kt index 6040724c02..34ee915fa4 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/payment/PaymentType.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/feature/payment/PaymentType.kt @@ -6,6 +6,7 @@ import com.hedvig.app.feature.adyen.AdyenCurrency import com.hedvig.app.feature.adyen.payin.AdyenConnectPayinActivity import com.hedvig.app.feature.trustly.TrustlyConnectPayinActivity +@Deprecated("Replace with navigating to AppDestination.ConnectPaymentTrustly|AppDestination.ConnectPaymentAdyen") fun connectPayinIntent( context: Context, market: Market, diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/swedishbankid/sign/SwedishBankIdSignDialog.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/swedishbankid/sign/SwedishBankIdSignDialog.kt index d99f6c8e45..4a1104de5f 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/swedishbankid/sign/SwedishBankIdSignDialog.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/feature/swedishbankid/sign/SwedishBankIdSignDialog.kt @@ -75,7 +75,7 @@ class SwedishBankIdSignDialog : DialogFragment() { } } else if (state is BankIdSignViewState.StartDirectDebit) { startActivity( - connectPayinIntent( + connectPayinIntent( // todo delete along with entire offer screen requireContext(), marketManager.market.value, true, From 9379ee80f6573b9bbc3133b640a6c9b257399290 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Mon, 16 Oct 2023 16:15:31 +0200 Subject: [PATCH 08/34] Remove the custom link check for connect-payment Should now just be handled by the androidx.navigation deep link handling --- .../feature/loggedin/ui/LoggedInActivity.kt | 50 ++++++------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/loggedin/ui/LoggedInActivity.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/loggedin/ui/LoggedInActivity.kt index ce0722460a..0753ac4704 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/loggedin/ui/LoggedInActivity.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/feature/loggedin/ui/LoggedInActivity.kt @@ -2,7 +2,6 @@ package com.hedvig.app.feature.loggedin.ui import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Build import android.os.Bundle import androidx.activity.compose.setContent @@ -51,6 +50,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.NavController import arrow.fx.coroutines.raceN import coil.ImageLoader import com.hedvig.android.app.navigation.HedvigNavHost @@ -73,15 +73,15 @@ import com.hedvig.android.logger.logcat import com.hedvig.android.market.Market import com.hedvig.android.market.MarketManager import com.hedvig.android.navigation.activity.ActivityNavigator +import com.hedvig.android.navigation.core.AppDestination import com.hedvig.android.navigation.core.HedvigDeepLinkContainer import com.hedvig.android.navigation.core.TopLevelGraph import com.hedvig.android.notification.badge.data.tab.TabNotificationBadgeService import com.hedvig.android.theme.Theme -import com.hedvig.app.feature.payment.connectPayinIntent import com.hedvig.app.feature.sunsetting.ForceUpgradeActivity -import com.hedvig.app.service.DynamicLink import com.hedvig.app.util.extensions.showReviewDialog import com.hedvig.hanalytics.HAnalytics +import com.kiwi.navigationcompose.typed.navigate import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterIsInstance @@ -124,7 +124,6 @@ class LoggedInActivity : AppCompatActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) val intent: Intent = intent - val uri: Uri? = intent.data lifecycleScope.launch { if (featureManager.isFeatureEnabled(Feature.UPDATE_NECESSARY)) { applicationContext.startActivity(ForceUpgradeActivity.newInstance(applicationContext)) @@ -159,36 +158,6 @@ class LoggedInActivity : AppCompatActivity() { showReviewWithDelay() } } - if (uri != null) { - launch { - lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - authTokenService.authStatus.first { it is AuthStatus.LoggedIn } - val pathSegments = uri.pathSegments - val dynamicLink: DynamicLink = when { - pathSegments.contains("direct-debit") -> DynamicLink.DirectDebit - pathSegments.contains("connect-payment") -> DynamicLink.DirectDebit - pathSegments.isEmpty() -> DynamicLink.None - else -> DynamicLink.Unknown - } - logcat(LogPriority.INFO) { - "Deep link was found:$dynamicLink, with segments: ${pathSegments.joinToString(",")}" - } - if (dynamicLink is DynamicLink.DirectDebit) { - hAnalytics.deepLinkOpened(dynamicLink.type) - val market = marketManager.market.first() - this@LoggedInActivity.startActivity( - connectPayinIntent( - this@LoggedInActivity, - market, - false, - ), - ) - } else { - logcat { "Deep link $dynamicLink did not open some specific activity" } - } - } - } - } lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { authTokenService.authStatus .onEach { authStatus -> @@ -324,6 +293,7 @@ private fun HedvigApp( hedvigAppState = hedvigAppState, hedvigDeepLinkContainer = hedvigDeepLinkContainer, activityNavigator = activityNavigator, + navigateToConnectPayment = { navigateToConnectPayment(hedvigAppState.navController, market) }, shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale, imageLoader = imageLoader, market = market, @@ -422,3 +392,15 @@ private fun Theme.apply() = when (this) { } } } + +private fun navigateToConnectPayment( + navController: NavController, + market: Market, +) { + when (market) { + Market.SE -> navController.navigate(AppDestination.ConnectPaymentTrustly) + Market.NO, + Market.DK, + -> navController.navigate(AppDestination.ConnectPaymentAdyen) + } +} From 0edbd552c6c7755a4a28fda4cf668ef648aaa062 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Mon, 16 Oct 2023 16:38:57 +0200 Subject: [PATCH 09/34] Add some skeleton for TrustlyDestination --- .../build.gradle.kts | 6 ++++ .../trustly/ConnectTrustlyPaymentGraph.kt | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/app/feature/feature-connect-payment-trustly/build.gradle.kts b/app/feature/feature-connect-payment-trustly/build.gradle.kts index 51ad6d4e1b..025bbaf0d1 100644 --- a/app/feature/feature-connect-payment-trustly/build.gradle.kts +++ b/app/feature/feature-connect-payment-trustly/build.gradle.kts @@ -6,10 +6,16 @@ plugins { } dependencies { + implementation(libs.androidx.lifecycle.compose) + implementation(libs.androidx.lifecycle.viewModel) implementation(libs.androidx.navigation.common) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.runtime) implementation(libs.kiwi.navigationCompose) + implementation(libs.koin.compose) + implementation(libs.koin.core) implementation(projects.composeWebview) + implementation(projects.moleculeAndroid) + implementation(projects.moleculePublic) implementation(projects.navigationCore) } diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt index fcbbf7770b..b6a5fc270b 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt @@ -1,10 +1,44 @@ package com.hedvig.android.feature.connect.payment +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder +import com.hedvig.android.molecule.android.MoleculeViewModel +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.navigation.core.AppDestination import com.kiwi.navigationcompose.typed.composable +import org.koin.androidx.compose.koinViewModel fun NavGraphBuilder.connectTrustlyPaymentGraph() { composable() { + val viewModel: TrustlyViewModel = koinViewModel() + TrustlyDestination(viewModel) } } + +@Composable +internal fun TrustlyDestination(viewModel: TrustlyViewModel) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + TrustlyScreen(uiState) +} + +@Composable +private fun TrustlyScreen(uiState: TrustlyUiState) { +} + +internal class TrustlyViewModel : MoleculeViewModel( + TrustlyUiState, + TrustlyPresenter(), +) + +internal class TrustlyPresenter : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present(lastState: TrustlyUiState): TrustlyUiState { + return TrustlyUiState + } +} + +internal object TrustlyEvent +internal object TrustlyUiState From ff584249310965364d8aaa2250c68f61d1e04113 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Tue, 17 Oct 2023 15:45:04 +0200 Subject: [PATCH 10/34] Create TrustlyCallback To be passed to Trustly to get informed about success/failure --- .../payment/trustly/data/TrustlyCallback.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/data/TrustlyCallback.kt diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/data/TrustlyCallback.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/data/TrustlyCallback.kt new file mode 100644 index 0000000000..f9b983bb0e --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/data/TrustlyCallback.kt @@ -0,0 +1,20 @@ +package com.hedvig.android.feature.connect.payment.trustly.data + +import com.hedvig.android.code.buildoconstants.HedvigBuildConstants + +/** + * Trustly asks for two urls to redirect to when they are done processing the payment connection. + * We need to pass these when we start the process, and we can then listen in on them to know when the process is done + * and what the outcome of it was. + */ +internal interface TrustlyCallback { + val successUrl: String + val failureUrl: String +} + +internal class TrustlyCallbackImpl( + hedvigBuildConstants: HedvigBuildConstants, +) : TrustlyCallback { + override val successUrl: String = "${hedvigBuildConstants.urlBaseWeb}/payment-success" + override val failureUrl: String = "${hedvigBuildConstants.urlBaseWeb}/payment-failure" +} From 11f0ea4e5dc317ab6328393012227d43051a9403 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Tue, 17 Oct 2023 15:44:24 +0200 Subject: [PATCH 11/34] Create StartTrustlySessionUseCase and relevant gql file Octopus has to also contribute its metadata so that dependant modules can use its schema. In this case InitiateTrustlyConnectPaymentSession.graphql. --- .../apollo-octopus-public/build.gradle.kts | 1 + .../build.gradle.kts | 15 ++++++++ ...itiateTrustlyConnectPaymentSession.graphql | 6 ++++ .../trustly/StartTrustlySessionUseCase.kt | 36 +++++++++++++++++++ .../member-reminders-public/build.gradle.kts | 2 +- 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 app/feature/feature-connect-payment-trustly/src/main/graphql/InitiateTrustlyConnectPaymentSession.graphql create mode 100644 app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/StartTrustlySessionUseCase.kt diff --git a/app/apollo/apollo-octopus-public/build.gradle.kts b/app/apollo/apollo-octopus-public/build.gradle.kts index 954d8cfbf3..a3e8720744 100644 --- a/app/apollo/apollo-octopus-public/build.gradle.kts +++ b/app/apollo/apollo-octopus-public/build.gradle.kts @@ -29,6 +29,7 @@ apollo { packageName.set("octopus") codegenModels.set(com.apollographql.apollo3.compiler.MODELS_RESPONSE_BASED) + generateApolloMetadata.set(true) generateDataBuilders.set(true) // https://www.apollographql.com/docs/android/advanced/operation-variables/#make-nullable-variables-non-optional diff --git a/app/feature/feature-connect-payment-trustly/build.gradle.kts b/app/feature/feature-connect-payment-trustly/build.gradle.kts index 025bbaf0d1..4c862574ea 100644 --- a/app/feature/feature-connect-payment-trustly/build.gradle.kts +++ b/app/feature/feature-connect-payment-trustly/build.gradle.kts @@ -2,20 +2,35 @@ plugins { id("hedvig.android.ktlint") id("hedvig.android.library") id("hedvig.android.library.compose") + alias(libs.plugins.apollo) alias(libs.plugins.squareSortDependencies) } dependencies { + apolloMetadata(projects.apolloOctopusPublic) + + implementation(libs.androidx.compose.material3) implementation(libs.androidx.lifecycle.compose) implementation(libs.androidx.lifecycle.viewModel) implementation(libs.androidx.navigation.common) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.runtime) + implementation(libs.apollo.api) + implementation(libs.apollo.runtime) + implementation(libs.arrow.core) implementation(libs.kiwi.navigationCompose) implementation(libs.koin.compose) implementation(libs.koin.core) + implementation(projects.apolloCore) + implementation(projects.apolloOctopusPublic) implementation(projects.composeWebview) implementation(projects.moleculeAndroid) implementation(projects.moleculePublic) implementation(projects.navigationCore) } + +apollo { + service("octopus") { + packageName.set("octopus") + } +} diff --git a/app/feature/feature-connect-payment-trustly/src/main/graphql/InitiateTrustlyConnectPaymentSession.graphql b/app/feature/feature-connect-payment-trustly/src/main/graphql/InitiateTrustlyConnectPaymentSession.graphql new file mode 100644 index 0000000000..95ada42b81 --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/src/main/graphql/InitiateTrustlyConnectPaymentSession.graphql @@ -0,0 +1,6 @@ +mutation InitiateTrustlyConnectPaymentSession($successUrl: String!, $failureUrl: String!) { + registerDirectDebit2(clientContext: { successUrl: $successUrl, failureUrl: $failureUrl }) { + url + orderId + } +} diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/StartTrustlySessionUseCase.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/StartTrustlySessionUseCase.kt new file mode 100644 index 0000000000..b745f6091e --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/StartTrustlySessionUseCase.kt @@ -0,0 +1,36 @@ +package com.hedvig.android.feature.connect.payment.trustly + +import arrow.core.Either +import arrow.core.raise.either +import com.apollographql.apollo3.ApolloClient +import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.apollo.toEither +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallback +import com.hedvig.android.logger.logcat +import octopus.InitiateTrustlyConnectPaymentSessionMutation + +internal class StartTrustlySessionUseCase( + val apolloClient: ApolloClient, + val trustlyCallback: TrustlyCallback, +) { + suspend fun invoke(): Either { + return either { + val data = apolloClient + .mutation( + InitiateTrustlyConnectPaymentSessionMutation( + successUrl = trustlyCallback.successUrl, + failureUrl = trustlyCallback.failureUrl, + ), + ) + .safeExecute() + .toEither(::ErrorMessage) + .bind() + logcat { "StartTrustlySessionUseCase received: ${data.registerDirectDebit2}" } + TrustlyInitiateProcessUrl(data.registerDirectDebit2.url) + } + } +} + +@JvmInline +value class TrustlyInitiateProcessUrl(val url: String) diff --git a/app/member-reminders/member-reminders-public/build.gradle.kts b/app/member-reminders/member-reminders-public/build.gradle.kts index 947c1f2628..5e3d3bd374 100644 --- a/app/member-reminders/member-reminders-public/build.gradle.kts +++ b/app/member-reminders/member-reminders-public/build.gradle.kts @@ -1,8 +1,8 @@ plugins { id("hedvig.android.ktlint") id("hedvig.android.library") - alias(libs.plugins.squareSortDependencies) alias(libs.plugins.apollo) + alias(libs.plugins.squareSortDependencies) } dependencies { From b173be029826d21c4b835dc28c983190e636d872 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Tue, 17 Oct 2023 15:46:48 +0200 Subject: [PATCH 12/34] Split trustly related files for presenters VMs etc Some sample untested UI code for all of this to be tested when the presenter is hooked up correctly. --- .../build.gradle.kts | 5 + .../trustly/ConnectTrustlyPaymentGraph.kt | 44 ---- .../payment/trustly/TrustlyPresenter.kt | 34 +++ .../payment/trustly/TrustlyViewModel.kt | 15 ++ .../navigation/ConnectTrustlyPaymentGraph.kt | 22 ++ .../payment/trustly/ui/TrustlyDestination.kt | 203 ++++++++++++++++++ .../hedvig/android/composewebview/WebView.kt | 2 +- 7 files changed, 280 insertions(+), 45 deletions(-) delete mode 100644 app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt create mode 100644 app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt create mode 100644 app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyViewModel.kt create mode 100644 app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt create mode 100644 app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt diff --git a/app/feature/feature-connect-payment-trustly/build.gradle.kts b/app/feature/feature-connect-payment-trustly/build.gradle.kts index 4c862574ea..5450f02e45 100644 --- a/app/feature/feature-connect-payment-trustly/build.gradle.kts +++ b/app/feature/feature-connect-payment-trustly/build.gradle.kts @@ -24,6 +24,11 @@ dependencies { implementation(projects.apolloCore) implementation(projects.apolloOctopusPublic) implementation(projects.composeWebview) + implementation(projects.coreBuildConstants) + implementation(projects.coreCommonPublic) + implementation(projects.coreDesignSystem) + implementation(projects.coreResources) + implementation(projects.coreUi) implementation(projects.moleculeAndroid) implementation(projects.moleculePublic) implementation(projects.navigationCore) diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt deleted file mode 100644 index b6a5fc270b..0000000000 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ConnectTrustlyPaymentGraph.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.hedvig.android.feature.connect.payment - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavGraphBuilder -import com.hedvig.android.molecule.android.MoleculeViewModel -import com.hedvig.android.molecule.public.MoleculePresenter -import com.hedvig.android.molecule.public.MoleculePresenterScope -import com.hedvig.android.navigation.core.AppDestination -import com.kiwi.navigationcompose.typed.composable -import org.koin.androidx.compose.koinViewModel - -fun NavGraphBuilder.connectTrustlyPaymentGraph() { - composable() { - val viewModel: TrustlyViewModel = koinViewModel() - TrustlyDestination(viewModel) - } -} - -@Composable -internal fun TrustlyDestination(viewModel: TrustlyViewModel) { - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - TrustlyScreen(uiState) -} - -@Composable -private fun TrustlyScreen(uiState: TrustlyUiState) { -} - -internal class TrustlyViewModel : MoleculeViewModel( - TrustlyUiState, - TrustlyPresenter(), -) - -internal class TrustlyPresenter : MoleculePresenter { - @Composable - override fun MoleculePresenterScope.present(lastState: TrustlyUiState): TrustlyUiState { - return TrustlyUiState - } -} - -internal object TrustlyEvent -internal object TrustlyUiState diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt new file mode 100644 index 0000000000..8fb4f3d856 --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt @@ -0,0 +1,34 @@ +package com.hedvig.android.feature.connect.payment.trustly + +import androidx.compose.runtime.Composable +import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallback +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope + +internal class TrustlyPresenter( + trustlyCallback: TrustlyCallback, + startTrustlySessionUseCase: StartTrustlySessionUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present(lastState: TrustlyUiState): TrustlyUiState { + return TrustlyUiState.Loading + } +} + +internal sealed interface TrustlyEvent { + data object ConnectingCardSucceeded : TrustlyEvent + data object ConnectingCardFailed : TrustlyEvent + data object RetryConnectingCard : TrustlyEvent +} + +internal interface TrustlyUiState { + data object Loading : TrustlyUiState + + data class Browsing( + val url: String, + val trustlyCallback: TrustlyCallback, + ) : TrustlyUiState + + data object FailedToConnectCard : TrustlyUiState + data object SucceededInConnectingCard : TrustlyUiState +} diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyViewModel.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyViewModel.kt new file mode 100644 index 0000000000..d7269eb402 --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyViewModel.kt @@ -0,0 +1,15 @@ +package com.hedvig.android.feature.connect.payment.trustly + +import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallback +import com.hedvig.android.molecule.android.MoleculeViewModel + +internal class TrustlyViewModel( + trustlyCallback: TrustlyCallback, + startTrustlySessionUseCase: StartTrustlySessionUseCase, +) : MoleculeViewModel( + TrustlyUiState.Loading, + TrustlyPresenter( + trustlyCallback, + startTrustlySessionUseCase, + ), +) diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt new file mode 100644 index 0000000000..75eaec6d96 --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt @@ -0,0 +1,22 @@ +package com.hedvig.android.feature.connect.payment + +import androidx.navigation.NavGraphBuilder +import com.hedvig.android.feature.connect.payment.trustly.TrustlyViewModel +import com.hedvig.android.feature.connect.payment.trustly.ui.TrustlyDestination +import com.hedvig.android.navigation.core.AppDestination +import com.hedvig.android.navigation.core.Navigator +import com.kiwi.navigationcompose.typed.composable +import org.koin.androidx.compose.koinViewModel + +fun NavGraphBuilder.connectTrustlyPaymentGraph( + navigator: Navigator, +) { + composable() { + val viewModel: TrustlyViewModel = koinViewModel() + TrustlyDestination( + viewModel = viewModel, + navigateUp = navigator::navigateUp, + finishTrustlyFlow = navigator::popBackStack, + ) + } +} diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt new file mode 100644 index 0000000000..d51c207144 --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt @@ -0,0 +1,203 @@ +package com.hedvig.android.feature.connect.payment.trustly.ui + +import android.annotation.SuppressLint +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebView +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.composewebview.AccompanistWebViewClient +import com.hedvig.android.composewebview.LoadingState +import com.hedvig.android.composewebview.WebView +import com.hedvig.android.composewebview.rememberSaveableWebViewState +import com.hedvig.android.composewebview.rememberWebViewNavigator +import com.hedvig.android.core.designsystem.component.button.HedvigContainedSmallButton +import com.hedvig.android.core.designsystem.component.error.HedvigErrorSection +import com.hedvig.android.core.designsystem.component.progress.HedvigFullScreenCenterAlignedProgress +import com.hedvig.android.core.designsystem.component.success.HedvigSuccessSection +import com.hedvig.android.core.ui.appbar.m3.TopAppBarWithBack +import com.hedvig.android.feature.connect.payment.trustly.TrustlyEvent +import com.hedvig.android.feature.connect.payment.trustly.TrustlyUiState +import com.hedvig.android.feature.connect.payment.trustly.TrustlyViewModel +import com.hedvig.android.logger.LogPriority +import com.hedvig.android.logger.logcat +import hedvig.resources.R + +@Composable +internal fun TrustlyDestination( + viewModel: TrustlyViewModel, + navigateUp: () -> Unit, + finishTrustlyFlow: () -> Unit, +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + TrustlyScreen( + uiState = uiState, + navigateUp = navigateUp, + connectingCardSucceeded = { viewModel.emit(TrustlyEvent.ConnectingCardSucceeded) }, + connectingCardFailed = { viewModel.emit(TrustlyEvent.ConnectingCardFailed) }, + retryConnectingCard = { viewModel.emit(TrustlyEvent.RetryConnectingCard) }, + finishTrustlyFlow = finishTrustlyFlow, + ) +} + +@Composable +private fun TrustlyScreen( + uiState: TrustlyUiState, + navigateUp: () -> Unit, + connectingCardSucceeded: () -> Unit, + connectingCardFailed: () -> Unit, + retryConnectingCard: () -> Unit, + finishTrustlyFlow: () -> Unit, +) { + Surface( + color = MaterialTheme.colorScheme.background, + modifier = Modifier.fillMaxSize(), + ) { + when (uiState) { + TrustlyUiState.Loading -> { + HedvigFullScreenCenterAlignedProgress() + } + is TrustlyUiState.Browsing -> { + TrustlyBrowser( + uiState, + navigateUp, + connectingCardSucceeded, + connectingCardFailed, + ) + } + TrustlyUiState.FailedToConnectCard -> { + HedvigErrorSection( + retry = retryConnectingCard, + title = stringResource(R.string.something_went_wrong), + subTitle = stringResource(R.string.pay_in_confirmation_direct_debit_headline), + ) + } + TrustlyUiState.SucceededInConnectingCard -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + HedvigSuccessSection( + title = stringResource(R.string.something_went_wrong), + subTitle = stringResource(R.string.pay_in_confirmation_direct_debit_headline), + withDefaultVerticalSpacing = false, + ) + Spacer(Modifier.height(24.dp)) + HedvigContainedSmallButton( + text = stringResource(R.string.general_close_button), + onClick = finishTrustlyFlow, + ) + } + } + } + } +} + +@SuppressLint("SetJavaScriptEnabled") +@Composable +private fun TrustlyBrowser( + uiState: TrustlyUiState.Browsing, + navigateUp: () -> Unit, + connectingCardSucceeded: () -> Unit, + connectingCardFailed: () -> Unit, +) { + val context = LocalContext.current + val webViewState = rememberSaveableWebViewState() + val webViewNavigator = rememberWebViewNavigator() + val webViewClient = remember { + object : AccompanistWebViewClient() { + override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + logcat { "Webview loading url:$url" } + + if (url?.startsWith("bankid") == true) { + logcat(LogPriority.ERROR) { "Url did in fact try to open bankid" } + view.stopLoading() + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + context.startActivity(intent) + return + } + if (url == uiState.trustlyCallback.successUrl) { + view.stopLoading() + connectingCardSucceeded() + return + } + if (url == uiState.trustlyCallback.failureUrl) { + view.stopLoading() + connectingCardFailed() + return + } + } + + override fun onPageFinished(view: WebView, url: String?) { + super.onPageFinished(view, url) + logcat { "Webview finished loading url:$url" } + } + + override fun onReceivedError(view: WebView, request: WebResourceRequest?, error: WebResourceError?) { + super.onReceivedError(view, request, error) + logcat(LogPriority.WARN) { "Webview got error:$error for request:$request" } + } + } + } + + val url = (uiState as? TrustlyUiState.Browsing)?.url + LaunchedEffect(url) { + if (url != null) { + webViewNavigator.loadUrl(url) + } + } + + Column { + TopAppBarWithBack( + title = stringResource(R.string.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_TITLE), + onClick = { + if (webViewNavigator.canGoBack) { + webViewNavigator.navigateBack() + } else { + navigateUp() + } + }, + ) + val loadingState = webViewState.loadingState + if (loadingState is LoadingState.Loading) { + LinearProgressIndicator( + progress = loadingState.progress, + modifier = Modifier.fillMaxWidth(), + ) + } + WebView( + state = webViewState, + navigator = webViewNavigator, + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + onCreated = { webView -> + webView.settings.javaScriptEnabled = true + webView.settings.javaScriptCanOpenWindowsAutomatically = true + webView.settings.setSupportMultipleWindows(true) + }, + client = webViewClient, + ) + } +} diff --git a/app/ui/compose-webview/src/main/kotlin/com/hedvig/android/composewebview/WebView.kt b/app/ui/compose-webview/src/main/kotlin/com/hedvig/android/composewebview/WebView.kt index acafbdf4d3..a377bf55ad 100644 --- a/app/ui/compose-webview/src/main/kotlin/com/hedvig/android/composewebview/WebView.kt +++ b/app/ui/compose-webview/src/main/kotlin/com/hedvig/android/composewebview/WebView.kt @@ -630,7 +630,7 @@ fun rememberSaveableWebViewState(): WebViewState { } } -val WebStateSaver: Saver = run { +private val WebStateSaver: Saver = run { val pageTitleKey = "pagetitle" val lastLoadedUrlKey = "lastloaded" val stateBundle = "bundle" From 1a3ff3f995125cd551e21b78434ac1f25b6403b0 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Tue, 17 Oct 2023 15:47:25 +0200 Subject: [PATCH 13/34] Contribute trustly feature module dependencies to DI graph --- .../com/hedvig/app/ApplicationModule.kt | 2 ++ .../trustly/di/ConnectPaymentTrustlyModule.kt | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt diff --git a/app/app/src/main/kotlin/com/hedvig/app/ApplicationModule.kt b/app/app/src/main/kotlin/com/hedvig/app/ApplicationModule.kt index 553f73181a..bd2021a991 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/ApplicationModule.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/ApplicationModule.kt @@ -45,6 +45,7 @@ import com.hedvig.android.feature.changeaddress.di.changeAddressModule import com.hedvig.android.feature.chat.ChatRepository import com.hedvig.android.feature.chat.di.chatModule import com.hedvig.android.feature.claimtriaging.di.claimTriagingModule +import com.hedvig.android.feature.connect.payment.trustly.di.connectPaymentTrustlyModule import com.hedvig.android.feature.forever.di.foreverModule import com.hedvig.android.feature.home.di.homeModule import com.hedvig.android.feature.insurances.di.insurancesModule @@ -585,6 +586,7 @@ val applicationModule = module { clockModule, coilModule, connectPaymentModule, + connectPaymentTrustlyModule, coreCommonModule, dataStoreModule, datadogDemoTrackingModule, diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt new file mode 100644 index 0000000000..950b4e9ae9 --- /dev/null +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt @@ -0,0 +1,19 @@ +package com.hedvig.android.feature.connect.payment.trustly.di + +import com.apollographql.apollo3.ApolloClient +import com.hedvig.android.apollo.octopus.di.octopusClient +import com.hedvig.android.code.buildoconstants.HedvigBuildConstants +import com.hedvig.android.feature.connect.payment.trustly.StartTrustlySessionUseCase +import com.hedvig.android.feature.connect.payment.trustly.TrustlyViewModel +import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallback +import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallbackImpl +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val connectPaymentTrustlyModule = module { + single { TrustlyCallbackImpl(get()) } + single { + StartTrustlySessionUseCase(get(octopusClient), get()) + } + viewModel { TrustlyViewModel(get(), get()) } +} From f97353bb340a0a1abaa8171f090872aa038a928b Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Wed, 25 Oct 2023 17:16:44 +0200 Subject: [PATCH 14/34] Make connect payment destination be flatter Just make the normal connect-payment, which is trustly for SE. Then add the second one for Adyen. This way the deep link goes to the Trustly one by default, but if you are in SE/NO you just get navigated to the right place. If not, nothing happens. --- .../android/app/navigation/HedvigNavHost.kt | 51 +++++-------------- .../feature/loggedin/ui/LoggedInActivity.kt | 2 +- .../build.gradle.kts | 1 + .../payment/trustly/data/TrustlyCallback.kt | 2 +- .../trustly/di/ConnectPaymentTrustlyModule.kt | 2 +- .../navigation/ConnectTrustlyPaymentGraph.kt | 24 ++++++++- .../android/navigation/core/AppDestination.kt | 7 +-- 7 files changed, 40 insertions(+), 49 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index e1a315c2b1..fb2100a1ba 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -2,10 +2,7 @@ package com.hedvig.android.app.navigation import android.content.Context import android.net.Uri -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -20,12 +17,10 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.NavHost -import androidx.navigation.navDeepLink import androidx.navigation.navOptions import coil.ImageLoader import com.hedvig.android.app.ui.HedvigAppState import com.hedvig.android.core.buildconstants.HedvigBuildConstants -import com.hedvig.android.core.designsystem.component.progress.HedvigFullScreenCenterAlignedProgress import com.hedvig.android.core.designsystem.material3.motion.MotionDefaults import com.hedvig.android.data.claimflow.ClaimFlowStep import com.hedvig.android.data.claimflow.toClaimFlowDestination @@ -34,7 +29,7 @@ import com.hedvig.android.feature.chat.navigation.chatGraph import com.hedvig.android.feature.claimtriaging.ClaimTriagingDestination import com.hedvig.android.feature.claimtriaging.claimTriagingDestinations import com.hedvig.android.feature.connect.payment.adyen.connectAdyenPaymentGraph -import com.hedvig.android.feature.connect.payment.connectTrustlyPaymentGraph +import com.hedvig.android.feature.connect.payment.connectPaymentGraph import com.hedvig.android.feature.forever.navigation.foreverGraph import com.hedvig.android.feature.home.claims.pledge.HonestyPledgeBottomSheet import com.hedvig.android.feature.home.home.navigation.homeGraph @@ -62,7 +57,6 @@ import com.hedvig.app.feature.embark.ui.EmbarkActivity import com.hedvig.hanalytics.AppScreen import com.hedvig.hanalytics.HAnalytics import com.kiwi.navigationcompose.typed.Destination -import com.kiwi.navigationcompose.typed.composable import com.kiwi.navigationcompose.typed.createRoutePattern import com.kiwi.navigationcompose.typed.navigate import com.kiwi.navigationcompose.typed.popBackStack @@ -230,7 +224,18 @@ internal fun HedvigNavHost( navigator = navigator, market = market, hedvigDeepLinkContainer = hedvigDeepLinkContainer, + navigateToAdyenConnectPayment = { + navigator.navigateUnsafe( + AppDestination.ConnectPaymentAdyen, + navOptions { + popUpTo(createRoutePattern()) { + inclusive = true + } + }, + ) + }, ) + connectAdyenPaymentGraph() } } @@ -305,38 +310,6 @@ private fun NavGraphBuilder.nestedHomeGraphs( ) } -private fun NavGraphBuilder.connectPaymentGraph( - navigator: Navigator, - market: Market, - hedvigDeepLinkContainer: HedvigDeepLinkContainer, -) { - composable( - deepLinks = listOf( - navDeepLink { uriPattern = hedvigDeepLinkContainer.connectPayment }, - navDeepLink { uriPattern = hedvigDeepLinkContainer.directDebit }, - ), - enterTransition = { EnterTransition.None }, - exitTransition = { ExitTransition.None }, - ) { - LaunchedEffect(Unit) { - val navOptions = navOptions { - popUpTo { - inclusive = true - } - } - when (market) { - Market.SE -> navigator.navigateUnsafe(AppDestination.ConnectPaymentTrustly, navOptions) - Market.NO, - Market.DK, - -> navigator.navigateUnsafe(AppDestination.ConnectPaymentAdyen, navOptions) - } - } - HedvigFullScreenCenterAlignedProgress() - } - connectAdyenPaymentGraph() - connectTrustlyPaymentGraph() -} - @Composable private fun rememberNavigator(navController: NavController): Navigator { return remember(navController) { diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/loggedin/ui/LoggedInActivity.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/loggedin/ui/LoggedInActivity.kt index 0753ac4704..e40a0fc2f6 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/loggedin/ui/LoggedInActivity.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/feature/loggedin/ui/LoggedInActivity.kt @@ -398,7 +398,7 @@ private fun navigateToConnectPayment( market: Market, ) { when (market) { - Market.SE -> navController.navigate(AppDestination.ConnectPaymentTrustly) + Market.SE -> navController.navigate(AppDestination.ConnectPayment) Market.NO, Market.DK, -> navController.navigate(AppDestination.ConnectPaymentAdyen) diff --git a/app/feature/feature-connect-payment-trustly/build.gradle.kts b/app/feature/feature-connect-payment-trustly/build.gradle.kts index 5450f02e45..8edfe8ff3e 100644 --- a/app/feature/feature-connect-payment-trustly/build.gradle.kts +++ b/app/feature/feature-connect-payment-trustly/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(projects.coreDesignSystem) implementation(projects.coreResources) implementation(projects.coreUi) + implementation(projects.marketCore) implementation(projects.moleculeAndroid) implementation(projects.moleculePublic) implementation(projects.navigationCore) diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/data/TrustlyCallback.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/data/TrustlyCallback.kt index f9b983bb0e..944253a82c 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/data/TrustlyCallback.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/data/TrustlyCallback.kt @@ -1,6 +1,6 @@ package com.hedvig.android.feature.connect.payment.trustly.data -import com.hedvig.android.code.buildoconstants.HedvigBuildConstants +import com.hedvig.android.core.buildconstants.HedvigBuildConstants /** * Trustly asks for two urls to redirect to when they are done processing the payment connection. diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt index 950b4e9ae9..d499a5e4e2 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt @@ -2,7 +2,7 @@ package com.hedvig.android.feature.connect.payment.trustly.di import com.apollographql.apollo3.ApolloClient import com.hedvig.android.apollo.octopus.di.octopusClient -import com.hedvig.android.code.buildoconstants.HedvigBuildConstants +import com.hedvig.android.core.buildconstants.HedvigBuildConstants import com.hedvig.android.feature.connect.payment.trustly.StartTrustlySessionUseCase import com.hedvig.android.feature.connect.payment.trustly.TrustlyViewModel import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallback diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt index 75eaec6d96..3b6ed1b5c3 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt @@ -1,17 +1,37 @@ package com.hedvig.android.feature.connect.payment +import androidx.compose.runtime.LaunchedEffect import androidx.navigation.NavGraphBuilder +import androidx.navigation.navDeepLink import com.hedvig.android.feature.connect.payment.trustly.TrustlyViewModel import com.hedvig.android.feature.connect.payment.trustly.ui.TrustlyDestination +import com.hedvig.android.market.Market import com.hedvig.android.navigation.core.AppDestination +import com.hedvig.android.navigation.core.HedvigDeepLinkContainer import com.hedvig.android.navigation.core.Navigator import com.kiwi.navigationcompose.typed.composable import org.koin.androidx.compose.koinViewModel -fun NavGraphBuilder.connectTrustlyPaymentGraph( +fun NavGraphBuilder.connectPaymentGraph( navigator: Navigator, + market: Market, + hedvigDeepLinkContainer: HedvigDeepLinkContainer, + navigateToAdyenConnectPayment: () -> Unit, ) { - composable() { + composable( + deepLinks = listOf( + navDeepLink { uriPattern = hedvigDeepLinkContainer.connectPayment }, + navDeepLink { uriPattern = hedvigDeepLinkContainer.directDebit }, + ), + ) { + LaunchedEffect(Unit) { + when (market) { + Market.SE -> {} + Market.NO, + Market.DK, + -> navigateToAdyenConnectPayment() + } + } val viewModel: TrustlyViewModel = koinViewModel() TrustlyDestination( viewModel = viewModel, diff --git a/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt b/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt index 9060eddd95..8eb2e815b1 100644 --- a/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt +++ b/app/navigation/navigation-core/src/main/kotlin/com/hedvig/android/navigation/core/AppDestination.kt @@ -61,11 +61,8 @@ sealed interface AppDestination : Destination { object PaymentHistory : AppDestination @Serializable - object ConnectPaymentGeneric : AppDestination // Auto-navigates to ConnectPaymentTrustly or ConnectPaymentAdyen + object ConnectPayment : AppDestination // Handles connecting payment with Trustly. Auto-navigates to Adyen for NO/DK @Serializable - object ConnectPaymentTrustly : AppDestination - - @Serializable - object ConnectPaymentAdyen : AppDestination + object ConnectPaymentAdyen : AppDestination // To be deprecated as soon as Adyen support is dropped } From 7fa79eeab2ac2f4d4f43373a995cd403cf86444b Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Wed, 25 Oct 2023 19:33:00 +0200 Subject: [PATCH 15/34] Move cast to core-common --- .../src/main/kotlin/com/hedvig/android/core/common/Cast.kt | 5 +++++ .../kotlin/com/hedvig/android/feature/chat/ChatRepository.kt | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 app/core/core-common-public/src/main/kotlin/com/hedvig/android/core/common/Cast.kt diff --git a/app/core/core-common-public/src/main/kotlin/com/hedvig/android/core/common/Cast.kt b/app/core/core-common-public/src/main/kotlin/com/hedvig/android/core/common/Cast.kt new file mode 100644 index 0000000000..f75d420c50 --- /dev/null +++ b/app/core/core-common-public/src/main/kotlin/com/hedvig/android/core/common/Cast.kt @@ -0,0 +1,5 @@ +package com.hedvig.android.core.common + +inline fun Any?.cast(): T = this as T + +inline fun Any?.safeCast(): T? = this as? T diff --git a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ChatRepository.kt b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ChatRepository.kt index 8017f444b4..2204178194 100644 --- a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ChatRepository.kt +++ b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ChatRepository.kt @@ -19,6 +19,7 @@ import com.apollographql.apollo3.cache.normalized.watch import com.hedvig.android.apollo.OperationResult import com.hedvig.android.apollo.safeExecute import com.hedvig.android.apollo.toEither +import com.hedvig.android.core.common.cast import com.hedvig.android.logger.LogPriority import com.hedvig.android.logger.logcat import giraffe.ChatMessageIdQuery @@ -281,5 +282,3 @@ class ChatRepository( const val MESSAGES_QUERY_NAME = "messages" } } - -private inline fun Any?.cast() = this as T From 9b8f22aa698bb7f0ff12970060bc2111a86d47f0 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Wed, 25 Oct 2023 19:54:00 +0200 Subject: [PATCH 16/34] Finish TrustlyDestination handling Wire up TrustlyPresenter Navigate to Adyen connect-payment in non SE market Use the right strings Use the right insets for the WebView --- .../trustly/StartTrustlySessionUseCase.kt | 6 +- .../payment/trustly/TrustlyPresenter.kt | 72 ++++++++++++++++++- .../payment/trustly/TrustlyViewModel.kt | 3 + .../trustly/di/ConnectPaymentTrustlyModule.kt | 9 ++- .../navigation/ConnectTrustlyPaymentGraph.kt | 3 +- .../payment/trustly/ui/TrustlyDestination.kt | 18 +++-- 6 files changed, 98 insertions(+), 13 deletions(-) diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/StartTrustlySessionUseCase.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/StartTrustlySessionUseCase.kt index b745f6091e..8d7e31c8dc 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/StartTrustlySessionUseCase.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/StartTrustlySessionUseCase.kt @@ -11,8 +11,8 @@ import com.hedvig.android.logger.logcat import octopus.InitiateTrustlyConnectPaymentSessionMutation internal class StartTrustlySessionUseCase( - val apolloClient: ApolloClient, - val trustlyCallback: TrustlyCallback, + private val apolloClient: ApolloClient, + private val trustlyCallback: TrustlyCallback, ) { suspend fun invoke(): Either { return either { @@ -33,4 +33,4 @@ internal class StartTrustlySessionUseCase( } @JvmInline -value class TrustlyInitiateProcessUrl(val url: String) +internal value class TrustlyInitiateProcessUrl(val url: String) diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt index 8fb4f3d856..89b07419ec 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt @@ -1,16 +1,84 @@ package com.hedvig.android.feature.connect.payment.trustly import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.safeCast import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallback +import com.hedvig.android.market.Market import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope internal class TrustlyPresenter( - trustlyCallback: TrustlyCallback, - startTrustlySessionUseCase: StartTrustlySessionUseCase, + private val trustlyCallback: TrustlyCallback, + private val startTrustlySessionUseCase: StartTrustlySessionUseCase, + private val market: Market, ) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present(lastState: TrustlyUiState): TrustlyUiState { + if (market != Market.SE) return TrustlyUiState.Loading + + var browsing: TrustlyUiState.Browsing? by remember { + mutableStateOf(lastState.safeCast()) + } + var startSessionError: ErrorMessage? by remember { mutableStateOf(null) } + var connectingCardFailed by remember { mutableStateOf(lastState is TrustlyUiState.FailedToConnectCard) } + var succeededInConnectingCard by remember { mutableStateOf(lastState is TrustlyUiState.SucceededInConnectingCard) } + + var loadIteration by remember { mutableIntStateOf(0) } + + LaunchedEffect(loadIteration) { + if (browsing != null) return@LaunchedEffect + if (startSessionError != null) return@LaunchedEffect + if (connectingCardFailed) return@LaunchedEffect + if (succeededInConnectingCard) return@LaunchedEffect + startTrustlySessionUseCase.invoke().fold( + ifLeft = { + startSessionError = it + browsing = null + }, + ifRight = { + startSessionError = null + browsing = TrustlyUiState.Browsing(it.url, trustlyCallback) + }, + ) + } + + CollectEvents { event -> + when (event) { + TrustlyEvent.ConnectingCardFailed -> { + connectingCardFailed = true + } + TrustlyEvent.ConnectingCardSucceeded -> { + succeededInConnectingCard = true + } + TrustlyEvent.RetryConnectingCard -> { + browsing = null + startSessionError = null + connectingCardFailed = false + succeededInConnectingCard = false + loadIteration++ + } + } + } + if (succeededInConnectingCard) { + return TrustlyUiState.SucceededInConnectingCard + } + if (connectingCardFailed) { + return TrustlyUiState.FailedToConnectCard + } + if (startSessionError != null) { + return TrustlyUiState.FailedToConnectCard + } + val browsingValue = browsing + if (browsingValue != null) { + return browsingValue + } return TrustlyUiState.Loading } } diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyViewModel.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyViewModel.kt index d7269eb402..9bb2ac8477 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyViewModel.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyViewModel.kt @@ -1,9 +1,11 @@ package com.hedvig.android.feature.connect.payment.trustly import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallback +import com.hedvig.android.market.Market import com.hedvig.android.molecule.android.MoleculeViewModel internal class TrustlyViewModel( + market: Market, trustlyCallback: TrustlyCallback, startTrustlySessionUseCase: StartTrustlySessionUseCase, ) : MoleculeViewModel( @@ -11,5 +13,6 @@ internal class TrustlyViewModel( TrustlyPresenter( trustlyCallback, startTrustlySessionUseCase, + market, ), ) diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt index d499a5e4e2..1c0dd3e964 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/di/ConnectPaymentTrustlyModule.kt @@ -7,6 +7,7 @@ import com.hedvig.android.feature.connect.payment.trustly.StartTrustlySessionUse import com.hedvig.android.feature.connect.payment.trustly.TrustlyViewModel import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallback import com.hedvig.android.feature.connect.payment.trustly.data.TrustlyCallbackImpl +import com.hedvig.android.market.Market import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module @@ -15,5 +16,11 @@ val connectPaymentTrustlyModule = module { single { StartTrustlySessionUseCase(get(octopusClient), get()) } - viewModel { TrustlyViewModel(get(), get()) } + viewModel { (market: Market) -> + TrustlyViewModel( + market = market, + trustlyCallback = get(), + startTrustlySessionUseCase = get(), + ) + } } diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt index 3b6ed1b5c3..0fd4ea0bce 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt @@ -11,6 +11,7 @@ import com.hedvig.android.navigation.core.HedvigDeepLinkContainer import com.hedvig.android.navigation.core.Navigator import com.kiwi.navigationcompose.typed.composable import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf fun NavGraphBuilder.connectPaymentGraph( navigator: Navigator, @@ -32,7 +33,7 @@ fun NavGraphBuilder.connectPaymentGraph( -> navigateToAdyenConnectPayment() } } - val viewModel: TrustlyViewModel = koinViewModel() + val viewModel: TrustlyViewModel = koinViewModel { parametersOf(market) } TrustlyDestination( viewModel = viewModel, navigateUp = navigator::navigateUp, diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt index d51c207144..74b53b4465 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt @@ -10,9 +10,14 @@ import android.webkit.WebView import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -89,7 +94,7 @@ private fun TrustlyScreen( HedvigErrorSection( retry = retryConnectingCard, title = stringResource(R.string.something_went_wrong), - subTitle = stringResource(R.string.pay_in_confirmation_direct_debit_headline), + subTitle = null, ) } TrustlyUiState.SucceededInConnectingCard -> { @@ -98,8 +103,8 @@ private fun TrustlyScreen( verticalArrangement = Arrangement.Center, ) { HedvigSuccessSection( - title = stringResource(R.string.something_went_wrong), - subTitle = stringResource(R.string.pay_in_confirmation_direct_debit_headline), + title = stringResource(R.string.pay_in_confirmation_direct_debit_headline), + subTitle = null, withDefaultVerticalSpacing = false, ) Spacer(Modifier.height(24.dp)) @@ -189,15 +194,16 @@ private fun TrustlyBrowser( WebView( state = webViewState, navigator = webViewNavigator, - modifier = Modifier - .weight(1f) - .fillMaxWidth(), onCreated = { webView -> webView.settings.javaScriptEnabled = true webView.settings.javaScriptCanOpenWindowsAutomatically = true webView.settings.setSupportMultipleWindows(true) }, client = webViewClient, + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal)), ) } } From 5780324ee6769ac481c80127e38ef7a6d5503154 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Wed, 25 Oct 2023 20:02:00 +0200 Subject: [PATCH 17/34] Fix home screen jumpiness Simply do not render the layout until we get the screen size. This way the HomeLayout won't first assume the screen size has 0 height and try to lay out everything in a column at the top of the screen. This way the animated placement on the cards won't do the jump that it used to --- .../feature/home/home/ui/HomeDestination.kt | 157 +++++++++--------- 1 file changed, 81 insertions(+), 76 deletions(-) diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index d98a94434c..ae03bdfce6 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -291,8 +291,8 @@ private fun HomeScreenSuccess( ) } - var fullScreenSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) } - Column( + var fullScreenSize: IntSize? by remember { mutableStateOf(null) } + Box( modifier = modifier .fillMaxSize() .onSizeChanged { fullScreenSize = it } @@ -300,82 +300,87 @@ private fun HomeScreenSuccess( .verticalScroll(rememberScrollState()), ) { NotificationPermissionDialog(notificationPermissionState, openAppSettings) - HomeLayout( - welcomeMessage = { - WelcomeMessage( - homeText = uiState.homeText, - modifier = Modifier - .padding(horizontal = 24.dp) - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)) - .testTag("welcome_message"), - ) - }, - claimStatusCards = { - if (uiState.claimStatusCardsData != null) { - var consumedWindowInsets by remember { mutableStateOf(WindowInsets(0.dp)) } - ClaimStatusCards( - goToDetailScreen = onClaimDetailCardClicked, - claimStatusCardsData = uiState.claimStatusCardsData, - contentPadding = PaddingValues(horizontal = 16.dp) + WindowInsets.safeDrawing - .exclude(consumedWindowInsets) - .only(WindowInsetsSides.Horizontal) - .asPaddingValues(), - modifier = Modifier.onConsumedWindowInsetsChanged { consumedWindowInsets = it }, + val fullScreenSizeValue = fullScreenSize + if (fullScreenSizeValue != null) { + HomeLayout( + fullScreenSize = fullScreenSizeValue, + welcomeMessage = { + WelcomeMessage( + homeText = uiState.homeText, + modifier = Modifier + .padding(horizontal = 24.dp) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)) + .testTag("welcome_message"), ) - } - }, - veryImportantMessages = { - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), - ) { - for (veryImportantMessage in uiState.veryImportantMessages) { - VeryImportantMessageCard(openUrl, veryImportantMessage) + }, + claimStatusCards = { + if (uiState.claimStatusCardsData != null) { + var consumedWindowInsets by remember { mutableStateOf(WindowInsets(0.dp)) } + ClaimStatusCards( + goToDetailScreen = onClaimDetailCardClicked, + claimStatusCardsData = uiState.claimStatusCardsData, + contentPadding = PaddingValues(horizontal = 16.dp) + WindowInsets.safeDrawing + .exclude(consumedWindowInsets) + .only(WindowInsetsSides.Horizontal) + .asPaddingValues(), + modifier = Modifier.onConsumedWindowInsetsChanged { consumedWindowInsets = it }, + ) } - } - }, - memberReminderCards = { - val memberReminders = - uiState.memberReminders.onlyApplicableReminders(notificationPermissionState.status.isGranted) - MemberReminderCards( - memberReminders = memberReminders, - navigateToConnectPayment = navigateToConnectPayment, - openUrl = openUrl, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), - ) - }, - startClaimButton = { - HedvigContainedButton( - text = stringResource(R.string.home_tab_claim_button_text), - onClick = onStartClaimClicked, - modifier = Modifier - .padding(horizontal = 16.dp) - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), - ) - }, - otherServicesButton = { - HedvigTextButton( - text = stringResource(R.string.home_tab_other_services), - onClick = { showEditYourInfoBottomSheet = true }, - modifier = Modifier - .padding(horizontal = 16.dp) - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), - ) - }, - topSpacer = { - Spacer(Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top)).height(toolbarHeight)) - }, - bottomSpacer = { - Spacer(Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)).height(16.dp)) - }, - fullScreenSize = fullScreenSize, - ) + }, + veryImportantMessages = { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), + ) { + for (veryImportantMessage in uiState.veryImportantMessages) { + VeryImportantMessageCard(openUrl, veryImportantMessage) + } + } + }, + memberReminderCards = { + val memberReminders = + uiState.memberReminders.onlyApplicableReminders(notificationPermissionState.status.isGranted) + MemberReminderCards( + memberReminders = memberReminders, + navigateToConnectPayment = navigateToConnectPayment, + openUrl = openUrl, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), + ) + }, + startClaimButton = { + HedvigContainedButton( + text = stringResource(R.string.home_tab_claim_button_text), + onClick = onStartClaimClicked, + modifier = Modifier + .padding(horizontal = 16.dp) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), + ) + }, + otherServicesButton = { + HedvigTextButton( + text = stringResource(R.string.home_tab_other_services), + onClick = { showEditYourInfoBottomSheet = true }, + modifier = Modifier + .padding(horizontal = 16.dp) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), + ) + }, + topSpacer = { + Spacer( + Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top)).height(toolbarHeight), + ) + }, + bottomSpacer = { + Spacer(Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)).height(16.dp)) + }, + ) + } } } From b736f1d031d956171643de3914d28f8df484ff31 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Thu, 26 Oct 2023 11:11:32 +0200 Subject: [PATCH 18/34] Simplify nav to adyen for non SE markets --- .../trustly/navigation/ConnectTrustlyPaymentGraph.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt index 0fd4ea0bce..ca0072fa5b 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt @@ -26,11 +26,8 @@ fun NavGraphBuilder.connectPaymentGraph( ), ) { LaunchedEffect(Unit) { - when (market) { - Market.SE -> {} - Market.NO, - Market.DK, - -> navigateToAdyenConnectPayment() + if (market != Market.SE) { + navigateToAdyenConnectPayment() } } val viewModel: TrustlyViewModel = koinViewModel { parametersOf(market) } From cb1e842a3143de8f380f9f3b14168caad43fa776 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 27 Oct 2023 07:32:00 +0200 Subject: [PATCH 19/34] Update authlib --- .../com/hedvig/android/auth/di/AuthModule.kt | 2 +- .../android/auth/test/FakeAuthRepository.kt | 5 ++ .../connect/payment/adyen/AdyenDestination.kt | 15 ++++ .../connect/payment/adyen/AdyenPresenter.kt | 84 +++++++++++++++++++ .../connect/payment/adyen/AdyenViewModel.kt | 11 +++ .../adyen/data/GetAdyenPaymentUrlUseCase.kt | 14 ++++ .../payment/adyen/di/AdyenFeatureModule.kt | 15 ++++ gradle/libs.versions.toml | 2 +- 8 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenDestination.kt create mode 100644 app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenPresenter.kt create mode 100644 app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenViewModel.kt create mode 100644 app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/data/GetAdyenPaymentUrlUseCase.kt create mode 100644 app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/di/AdyenFeatureModule.kt diff --git a/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/di/AuthModule.kt b/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/di/AuthModule.kt index b19fa8489a..ce2435f064 100644 --- a/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/di/AuthModule.kt +++ b/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/di/AuthModule.kt @@ -54,7 +54,7 @@ val authModule = module { } else { AuthEnvironment.STAGING }, - additionalHttpHeaders = mapOf(), + additionalHttpHeadersProvider = { mapOf() }, callbacks = Callbacks("https://hedvig.com?q=success", "https://hedvig.com?q=failure)"), // Not used okHttpClientBuilder = get(), ) diff --git a/app/auth/auth-core-test/src/main/kotlin/com/hedvig/android/auth/test/FakeAuthRepository.kt b/app/auth/auth-core-test/src/main/kotlin/com/hedvig/android/auth/test/FakeAuthRepository.kt index 891d9f84f5..9d92621b76 100644 --- a/app/auth/auth-core-test/src/main/kotlin/com/hedvig/android/auth/test/FakeAuthRepository.kt +++ b/app/auth/auth-core-test/src/main/kotlin/com/hedvig/android/auth/test/FakeAuthRepository.kt @@ -7,6 +7,7 @@ import com.hedvig.authlib.AuthTokenResult import com.hedvig.authlib.Grant import com.hedvig.authlib.LoginMethod import com.hedvig.authlib.LoginStatusResult +import com.hedvig.authlib.MemberAuthorizationCodeResult import com.hedvig.authlib.ResendOtpResult import com.hedvig.authlib.RevokeResult import com.hedvig.authlib.StatusUrl @@ -58,4 +59,8 @@ class FakeAuthRepository : AuthRepository { override suspend fun revoke(token: String): RevokeResult { error("Not implemented") } + + override suspend fun getMemberAuthorizationCode(webLocale: String): MemberAuthorizationCodeResult { + error("Not implemented") + } } diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenDestination.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenDestination.kt new file mode 100644 index 0000000000..2ae9fafa0d --- /dev/null +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenDestination.kt @@ -0,0 +1,15 @@ +package com.hedvig.android.feature.connect.payment.adyen + +import androidx.compose.runtime.Composable + +@Composable +internal fun AdyenDestination( + adyenViewModel: AdyenViewModel, +) { + AdyenScreen() +} + +@Composable +private fun AdyenScreen() { + +} diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenPresenter.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenPresenter.kt new file mode 100644 index 0000000000..a3aca43711 --- /dev/null +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenPresenter.kt @@ -0,0 +1,84 @@ +package com.hedvig.android.feature.connect.payment.adyen + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.safeCast +import com.hedvig.android.feature.connect.payment.adyen.data.AdyenPaymentUrl +import com.hedvig.android.feature.connect.payment.adyen.data.GetAdyenPaymentUrlUseCase +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope + +internal class AdyenPresenter( + private val getAdyenPaymentUrlUseCase: GetAdyenPaymentUrlUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present(lastState: AdyenUiState): AdyenUiState { + var browsing: AdyenUiState.Browsing? by remember { + mutableStateOf(lastState.safeCast()) + } + + var getPaymentLinkError: ErrorMessage? by remember { mutableStateOf(null) } + var connectingCardFailed by remember { mutableStateOf(lastState is AdyenUiState.FailedToConnectCard) } + var succeededInConnectingCard by remember { mutableStateOf(lastState is AdyenUiState.SucceededInConnectingCard) } + + var loadIteration by remember { mutableIntStateOf(0) } + LaunchedEffect(loadIteration) { + if (browsing != null) return@LaunchedEffect + if (getPaymentLinkError != null) return@LaunchedEffect + if (connectingCardFailed) return@LaunchedEffect + if (succeededInConnectingCard) return@LaunchedEffect + getAdyenPaymentUrlUseCase.invoke().fold( + ifLeft = { + getPaymentLinkError = it + browsing = null + }, + ifRight = { adyenPaymentUrl -> + getPaymentLinkError = null + browsing = AdyenUiState.Browsing(adyenPaymentUrl) + }, + ) + } + + CollectEvents { event -> + when (event) { + AdyenEvent.ConnectingCardFailed -> { + connectingCardFailed = true + } + AdyenEvent.ConnectingCardSucceeded -> { + succeededInConnectingCard = true + } + AdyenEvent.RetryLoadingPaymentLink -> { + browsing = null + getPaymentLinkError = null + connectingCardFailed = false + succeededInConnectingCard = false + loadIteration++ + } + } + } + + return AdyenUiState.Loading + } +} + +internal sealed interface AdyenEvent { + data object RetryLoadingPaymentLink : AdyenEvent + data object ConnectingCardFailed : AdyenEvent + data object ConnectingCardSucceeded : AdyenEvent +} + +internal interface AdyenUiState { + data object Loading : AdyenUiState + data class Browsing( + val adyenPaymentUrl: AdyenPaymentUrl, + ) : AdyenUiState + + data object FailedToConnectCard : AdyenUiState + data object SucceededInConnectingCard : AdyenUiState +} diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenViewModel.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenViewModel.kt new file mode 100644 index 0000000000..cf02cae34f --- /dev/null +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenViewModel.kt @@ -0,0 +1,11 @@ +package com.hedvig.android.feature.connect.payment.adyen + +import com.hedvig.android.feature.connect.payment.adyen.data.GetAdyenPaymentUrlUseCase +import com.hedvig.android.molecule.android.MoleculeViewModel + +internal class AdyenViewModel( + getAdyenPaymentUrlUseCase: GetAdyenPaymentUrlUseCase, +): MoleculeViewModel( + AdyenUiState.Loading, + AdyenPresenter(getAdyenPaymentUrlUseCase), +) diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/data/GetAdyenPaymentUrlUseCase.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/data/GetAdyenPaymentUrlUseCase.kt new file mode 100644 index 0000000000..fff5992620 --- /dev/null +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/data/GetAdyenPaymentUrlUseCase.kt @@ -0,0 +1,14 @@ +package com.hedvig.android.feature.connect.payment.adyen.data + +import arrow.core.Either +import arrow.core.right +import com.hedvig.android.core.common.ErrorMessage + +internal class GetAdyenPaymentUrlUseCase { + suspend fun invoke(): Either { + return AdyenPaymentUrl("").right() + } +} + +@JvmInline +internal value class AdyenPaymentUrl(val url: String) diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/di/AdyenFeatureModule.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/di/AdyenFeatureModule.kt new file mode 100644 index 0000000000..409cea5b43 --- /dev/null +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/di/AdyenFeatureModule.kt @@ -0,0 +1,15 @@ +package com.hedvig.android.feature.connect.payment.adyen.di + +import com.hedvig.android.feature.connect.payment.adyen.AdyenViewModel +import com.hedvig.android.feature.connect.payment.adyen.data.GetAdyenPaymentUrlUseCase +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val adyenFeatureModule = module { + single { + GetAdyenPaymentUrlUseCase() // todo add REST service here + } + viewModel { + AdyenViewModel(get()) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39621e0b1c..8f335cc751 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -68,7 +68,7 @@ firebaseCrashlyticsBuildtools = "2.9.9" flexbox = "3.0.0" fragmentViewBindingDelegate = "1.0.2" hAnalytics = "v0.307.0" -hedvigAuthlib = "1.3.3" +hedvigAuthlib = "1.3.4-alpha-2023-10-26-22-33-10" insetter = "0.6.1" jsonTest = "20231013" junit = "4.13.2" From 1ec7b4b28e556967ab6752d45f6de1c5cf1ba136 Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 27 Oct 2023 12:38:32 +0200 Subject: [PATCH 20/34] Finish connect-payment implementation Bump authlib and use the exposed PaymentRepository to fetch the right payment connection URL from there directly Add adyen screen and Presenter Add a special error case for when the initialization of connect payment fails, and not the connecting of the payment itself. Fix the loading bar in the WebViews by making it go on top of the WebView instead of pushing it when it appears. --- .idea/codeInsightSettings.xml | 1 + .../android/app/navigation/HedvigNavHost.kt | 2 +- .../com/hedvig/app/ApplicationModule.kt | 10 +- .../com/hedvig/android/auth/di/AuthModule.kt | 15 +- .../android/auth/test/FakeAuthRepository.kt | 5 - .../build.gradle.kts | 15 ++ .../connect/payment/adyen/AdyenDestination.kt | 195 +++++++++++++++++- .../connect/payment/adyen/AdyenPresenter.kt | 14 ++ .../connect/payment/adyen/AdyenViewModel.kt | 2 +- .../payment/adyen/ConnectAdyenPaymentGraph.kt | 12 +- .../adyen/data/GetAdyenPaymentUrlUseCase.kt | 20 +- .../payment/adyen/di/AdyenFeatureModule.kt | 7 +- .../payment/trustly/TrustlyPresenter.kt | 6 +- .../payment/trustly/ui/TrustlyDestination.kt | 50 +++-- gradle/libs.versions.toml | 2 +- 15 files changed, 314 insertions(+), 42 deletions(-) diff --git a/.idea/codeInsightSettings.xml b/.idea/codeInsightSettings.xml index 3016593538..ae6cbc294a 100644 --- a/.idea/codeInsightSettings.xml +++ b/.idea/codeInsightSettings.xml @@ -16,6 +16,7 @@ androidx.navigation.compose.navigation com.google.errorprone.annotations.Modifier com.google.firebase.crashlytics.internal.model.ImmutableList + java.lang.LinkageError java.lang.reflect.Modifier java.nio.file.WatchEvent.Modifier java.time.format.TextStyle diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index fb2100a1ba..00139a1b27 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -235,7 +235,7 @@ internal fun HedvigNavHost( ) }, ) - connectAdyenPaymentGraph() + connectAdyenPaymentGraph(navigator) } } diff --git a/app/app/src/main/kotlin/com/hedvig/app/ApplicationModule.kt b/app/app/src/main/kotlin/com/hedvig/app/ApplicationModule.kt index bd2021a991..6b0c2c8a83 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/ApplicationModule.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/ApplicationModule.kt @@ -45,6 +45,7 @@ import com.hedvig.android.feature.changeaddress.di.changeAddressModule import com.hedvig.android.feature.chat.ChatRepository import com.hedvig.android.feature.chat.di.chatModule import com.hedvig.android.feature.claimtriaging.di.claimTriagingModule +import com.hedvig.android.feature.connect.payment.adyen.di.adyenFeatureModule import com.hedvig.android.feature.connect.payment.trustly.di.connectPaymentTrustlyModule import com.hedvig.android.feature.forever.di.foreverModule import com.hedvig.android.feature.home.di.homeModule @@ -211,9 +212,9 @@ private val networkModule = module { builder } single { - // Add auth interceptor only on the OkHttpClient itself which is used by GraphQL - // The OkHttpClient.Builder configuration is shared with the client provided with the Coil ImageLoader and the Ktor - // client which targets the auth-service, both of which do not want to get this automatic token refreshing behavior + // Add auth interceptor on the OkHttpClient itself which is used by GraphQL + // The OkHttpClient.Builder configuration does not need to get this automatic token refreshing behavior because + // there are callers which do not need it, or would even stop working if they did, like the coil implementation val okHttpBuilder = get().addInterceptor(get()) okHttpBuilder.build() } @@ -394,7 +395,7 @@ private val numberActionSetModule = module { viewModel { (data: NumberActionParams) -> NumberActionViewModel(data) } } -private val connectPaymentModule = module { +private val connectPaymentModule = module { // todo delete along with the entire legacy connect-payment feat viewModel { ConnectPaymentViewModel( get(), @@ -570,6 +571,7 @@ val applicationModule = module { includes( listOf( activityNavigatorModule, + adyenFeatureModule, adyenModule, apolloAuthListenersModule, apolloClientModule, diff --git a/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/di/AuthModule.kt b/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/di/AuthModule.kt index ce2435f064..0596e13c7f 100644 --- a/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/di/AuthModule.kt +++ b/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/di/AuthModule.kt @@ -19,6 +19,8 @@ import com.hedvig.authlib.AuthEnvironment import com.hedvig.authlib.AuthRepository import com.hedvig.authlib.Callbacks import com.hedvig.authlib.OkHttpNetworkAuthRepository +import com.hedvig.authlib.connectpayment.OkHttpNetworkPaymentRepository +import com.hedvig.authlib.connectpayment.PaymentRepository import okhttp3.OkHttpClient import org.koin.dsl.bind import org.koin.dsl.module @@ -54,9 +56,20 @@ val authModule = module { } else { AuthEnvironment.STAGING }, - additionalHttpHeadersProvider = { mapOf() }, + additionalHttpHeadersProvider = { emptyMap() }, callbacks = Callbacks("https://hedvig.com?q=success", "https://hedvig.com?q=failure)"), // Not used okHttpClientBuilder = get(), ) } + single { + OkHttpNetworkPaymentRepository( + environment = if (get().isProduction) { + AuthEnvironment.PRODUCTION + } else { + AuthEnvironment.STAGING + }, + additionalHttpHeadersProvider = { emptyMap() }, + okHttpClientBuilder = get().addInterceptor(get()), + ) + } } diff --git a/app/auth/auth-core-test/src/main/kotlin/com/hedvig/android/auth/test/FakeAuthRepository.kt b/app/auth/auth-core-test/src/main/kotlin/com/hedvig/android/auth/test/FakeAuthRepository.kt index 9d92621b76..891d9f84f5 100644 --- a/app/auth/auth-core-test/src/main/kotlin/com/hedvig/android/auth/test/FakeAuthRepository.kt +++ b/app/auth/auth-core-test/src/main/kotlin/com/hedvig/android/auth/test/FakeAuthRepository.kt @@ -7,7 +7,6 @@ import com.hedvig.authlib.AuthTokenResult import com.hedvig.authlib.Grant import com.hedvig.authlib.LoginMethod import com.hedvig.authlib.LoginStatusResult -import com.hedvig.authlib.MemberAuthorizationCodeResult import com.hedvig.authlib.ResendOtpResult import com.hedvig.authlib.RevokeResult import com.hedvig.authlib.StatusUrl @@ -59,8 +58,4 @@ class FakeAuthRepository : AuthRepository { override suspend fun revoke(token: String): RevokeResult { error("Not implemented") } - - override suspend fun getMemberAuthorizationCode(webLocale: String): MemberAuthorizationCodeResult { - error("Not implemented") - } } diff --git a/app/feature/feature-connect-payment-adyen/build.gradle.kts b/app/feature/feature-connect-payment-adyen/build.gradle.kts index ddb9903c08..d280e31050 100644 --- a/app/feature/feature-connect-payment-adyen/build.gradle.kts +++ b/app/feature/feature-connect-payment-adyen/build.gradle.kts @@ -6,9 +6,24 @@ plugins { } dependencies { + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.lifecycle.compose) + implementation(libs.androidx.lifecycle.viewModel) implementation(libs.androidx.navigation.common) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.runtime) + implementation(libs.arrow.core) + implementation(libs.hedvig.authlib) implementation(libs.kiwi.navigationCompose) + implementation(libs.koin.compose) + implementation(libs.koin.core) + implementation(projects.composeWebview) + implementation(projects.coreCommonPublic) + implementation(projects.coreDesignSystem) + implementation(projects.coreResources) + implementation(projects.coreUi) + implementation(projects.languageCore) + implementation(projects.moleculeAndroid) + implementation(projects.moleculePublic) implementation(projects.navigationCore) } diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenDestination.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenDestination.kt index 2ae9fafa0d..0228683d74 100644 --- a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenDestination.kt +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenDestination.kt @@ -1,15 +1,204 @@ package com.hedvig.android.feature.connect.payment.adyen +import android.annotation.SuppressLint +import android.graphics.Bitmap +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebView +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.composewebview.AccompanistWebViewClient +import com.hedvig.android.composewebview.LoadingState +import com.hedvig.android.composewebview.WebView +import com.hedvig.android.composewebview.rememberSaveableWebViewState +import com.hedvig.android.composewebview.rememberWebViewNavigator +import com.hedvig.android.core.designsystem.component.button.HedvigContainedSmallButton +import com.hedvig.android.core.designsystem.component.error.HedvigErrorSection +import com.hedvig.android.core.designsystem.component.progress.HedvigFullScreenCenterAlignedProgress +import com.hedvig.android.core.designsystem.component.success.HedvigSuccessSection +import com.hedvig.android.core.ui.appbar.m3.TopAppBarWithBack +import com.hedvig.android.logger.LogPriority +import com.hedvig.android.logger.logcat +import hedvig.resources.R @Composable internal fun AdyenDestination( - adyenViewModel: AdyenViewModel, + viewModel: AdyenViewModel, + navigateUp: () -> Unit, + finishAdyenFlow: () -> Unit, ) { - AdyenScreen() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + AdyenScreen( + uiState = uiState, + navigateUp = navigateUp, + connectingCardSucceeded = { viewModel.emit(AdyenEvent.ConnectingCardSucceeded) }, + connectingCardFailed = { viewModel.emit(AdyenEvent.ConnectingCardFailed) }, + retryConnectingCard = { viewModel.emit(AdyenEvent.RetryLoadingPaymentLink) }, + finishAdyenFlow = finishAdyenFlow, + ) } @Composable -private fun AdyenScreen() { +private fun AdyenScreen( + uiState: AdyenUiState, + navigateUp: () -> Unit, + connectingCardSucceeded: () -> Unit, + connectingCardFailed: () -> Unit, + retryConnectingCard: () -> Unit, + finishAdyenFlow: () -> Unit, +) { + Surface( + color = MaterialTheme.colorScheme.background, + modifier = Modifier.fillMaxSize(), + ) { + when (uiState) { + AdyenUiState.Loading -> { + HedvigFullScreenCenterAlignedProgress() + } + is AdyenUiState.Browsing -> { + AdyenBrowser( + uiState, + navigateUp, + connectingCardSucceeded, + connectingCardFailed, + ) + } + AdyenUiState.FailedToConnectCard -> { + HedvigErrorSection( + retry = retryConnectingCard, + title = stringResource(R.string.something_went_wrong), + subTitle = stringResource(R.string.pay_in_error_body), + ) + } + AdyenUiState.FailedToGetPaymentLink -> { + HedvigErrorSection( + retry = retryConnectingCard, + title = stringResource(R.string.something_went_wrong), + subTitle = null, + ) + } + AdyenUiState.SucceededInConnectingCard -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + HedvigSuccessSection( + title = stringResource(R.string.pay_in_confirmation_headline), + subTitle = null, + withDefaultVerticalSpacing = false, + ) + Spacer(Modifier.height(24.dp)) + HedvigContainedSmallButton( + text = stringResource(R.string.general_close_button), + onClick = finishAdyenFlow, + ) + } + } + } + } +} + +@SuppressLint("SetJavaScriptEnabled") +@Composable +private fun AdyenBrowser( + uiState: AdyenUiState.Browsing, + navigateUp: () -> Unit, + connectingCardSucceeded: () -> Unit, + connectingCardFailed: () -> Unit, +) { + val webViewState = rememberSaveableWebViewState() + val webViewNavigator = rememberWebViewNavigator() + val webViewClient = remember { + object : AccompanistWebViewClient() { + override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + logcat { "Adyen Webview loading url:$url" } + if (url?.contains("success") == true) { + view.stopLoading() + connectingCardSucceeded() + return + } + if (url?.contains("fail") == true) { + view.stopLoading() + connectingCardFailed() + return + } + } + + override fun onPageFinished(view: WebView, url: String?) { + super.onPageFinished(view, url) + logcat { "Webview finished loading url:$url" } + } + + override fun onReceivedError(view: WebView, request: WebResourceRequest?, error: WebResourceError?) { + super.onReceivedError(view, request, error) + logcat(LogPriority.WARN) { "Webview got error:$error for request:$request" } + } + } + } + + val adyenPaymentUrl = (uiState as? AdyenUiState.Browsing)?.adyenPaymentUrl + LaunchedEffect(adyenPaymentUrl) { + if (adyenPaymentUrl != null) { + webViewNavigator.loadUrl(adyenPaymentUrl.url) + } + } + Column { + TopAppBarWithBack( + title = stringResource(R.string.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_TITLE), + onClick = { + if (webViewNavigator.canGoBack) { + webViewNavigator.navigateBack() + } else { + navigateUp() + } + }, + ) + Box( + Modifier + .weight(1f) + .fillMaxWidth() + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal)), + ) { + val loadingState = webViewState.loadingState + if (loadingState is LoadingState.Loading) { + LinearProgressIndicator( + progress = loadingState.progress, + modifier = Modifier.fillMaxWidth(), + ) + } + WebView( + state = webViewState, + navigator = webViewNavigator, + onCreated = { webView -> + webView.settings.javaScriptEnabled = true + }, + client = webViewClient, + modifier = Modifier.matchParentSize(), + ) + } + } } diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenPresenter.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenPresenter.kt index a3aca43711..349a168fa9 100644 --- a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenPresenter.kt +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenPresenter.kt @@ -63,6 +63,19 @@ internal class AdyenPresenter( } } + if (succeededInConnectingCard) { + return AdyenUiState.SucceededInConnectingCard + } + if (connectingCardFailed) { + return AdyenUiState.FailedToConnectCard + } + if (getPaymentLinkError != null) { + return AdyenUiState.FailedToGetPaymentLink + } + val browsingValue = browsing + if (browsingValue != null) { + return browsingValue + } return AdyenUiState.Loading } } @@ -80,5 +93,6 @@ internal interface AdyenUiState { ) : AdyenUiState data object FailedToConnectCard : AdyenUiState + data object FailedToGetPaymentLink : AdyenUiState data object SucceededInConnectingCard : AdyenUiState } diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenViewModel.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenViewModel.kt index cf02cae34f..8a85074de6 100644 --- a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenViewModel.kt +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/AdyenViewModel.kt @@ -5,7 +5,7 @@ import com.hedvig.android.molecule.android.MoleculeViewModel internal class AdyenViewModel( getAdyenPaymentUrlUseCase: GetAdyenPaymentUrlUseCase, -): MoleculeViewModel( +) : MoleculeViewModel( AdyenUiState.Loading, AdyenPresenter(getAdyenPaymentUrlUseCase), ) diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/ConnectAdyenPaymentGraph.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/ConnectAdyenPaymentGraph.kt index 1f8272d66e..96705bbf9a 100644 --- a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/ConnectAdyenPaymentGraph.kt +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/ConnectAdyenPaymentGraph.kt @@ -2,9 +2,19 @@ package com.hedvig.android.feature.connect.payment.adyen import androidx.navigation.NavGraphBuilder import com.hedvig.android.navigation.core.AppDestination +import com.hedvig.android.navigation.core.Navigator import com.kiwi.navigationcompose.typed.composable +import org.koin.androidx.compose.koinViewModel -fun NavGraphBuilder.connectAdyenPaymentGraph() { +fun NavGraphBuilder.connectAdyenPaymentGraph( + navigator: Navigator, +) { composable() { + val viewModel: AdyenViewModel = koinViewModel() + AdyenDestination( + viewModel = viewModel, + navigateUp = navigator::navigateUp, + finishAdyenFlow = navigator::popBackStack, + ) } } diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/data/GetAdyenPaymentUrlUseCase.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/data/GetAdyenPaymentUrlUseCase.kt index fff5992620..f91f9ce178 100644 --- a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/data/GetAdyenPaymentUrlUseCase.kt +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/data/GetAdyenPaymentUrlUseCase.kt @@ -1,12 +1,26 @@ package com.hedvig.android.feature.connect.payment.adyen.data import arrow.core.Either -import arrow.core.right +import arrow.core.raise.either import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.language.LanguageService +import com.hedvig.authlib.MemberAuthorizationCodeResult +import com.hedvig.authlib.MemberPaymentUrl +import com.hedvig.authlib.connectpayment.PaymentRepository -internal class GetAdyenPaymentUrlUseCase { +internal class GetAdyenPaymentUrlUseCase( + private val paymentRepository: PaymentRepository, + private val languageService: LanguageService, +) { suspend fun invoke(): Either { - return AdyenPaymentUrl("").right() + return either { + val response = paymentRepository.getMemberAuthorizationCode(languageService.getLanguage().webPath()) + val memberPaymentUrl: MemberPaymentUrl = when (response) { + is MemberAuthorizationCodeResult.Error -> raise(ErrorMessage(response.error.message)) + is MemberAuthorizationCodeResult.Success -> response.memberPaymentUrl + } + AdyenPaymentUrl(memberPaymentUrl.url) + } } } diff --git a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/di/AdyenFeatureModule.kt b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/di/AdyenFeatureModule.kt index 409cea5b43..b6a5de5ff3 100644 --- a/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/di/AdyenFeatureModule.kt +++ b/app/feature/feature-connect-payment-adyen/src/main/kotlin/com/hedvig/android/feature/connect/payment/adyen/di/AdyenFeatureModule.kt @@ -2,12 +2,17 @@ package com.hedvig.android.feature.connect.payment.adyen.di import com.hedvig.android.feature.connect.payment.adyen.AdyenViewModel import com.hedvig.android.feature.connect.payment.adyen.data.GetAdyenPaymentUrlUseCase +import com.hedvig.android.language.LanguageService +import com.hedvig.authlib.connectpayment.PaymentRepository import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module val adyenFeatureModule = module { single { - GetAdyenPaymentUrlUseCase() // todo add REST service here + GetAdyenPaymentUrlUseCase( + get(), + get(), + ) } viewModel { AdyenViewModel(get()) diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt index 89b07419ec..96cc56f3e9 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/TrustlyPresenter.kt @@ -66,6 +66,7 @@ internal class TrustlyPresenter( } } } + if (succeededInConnectingCard) { return TrustlyUiState.SucceededInConnectingCard } @@ -73,7 +74,7 @@ internal class TrustlyPresenter( return TrustlyUiState.FailedToConnectCard } if (startSessionError != null) { - return TrustlyUiState.FailedToConnectCard + return TrustlyUiState.FailedToStartSession } val browsingValue = browsing if (browsingValue != null) { @@ -89,7 +90,7 @@ internal sealed interface TrustlyEvent { data object RetryConnectingCard : TrustlyEvent } -internal interface TrustlyUiState { +internal sealed interface TrustlyUiState { data object Loading : TrustlyUiState data class Browsing( @@ -98,5 +99,6 @@ internal interface TrustlyUiState { ) : TrustlyUiState data object FailedToConnectCard : TrustlyUiState + data object FailedToStartSession : TrustlyUiState data object SucceededInConnectingCard : TrustlyUiState } diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt index 74b53b4465..48b423811d 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/ui/TrustlyDestination.kt @@ -8,6 +8,7 @@ import android.webkit.WebResourceError import android.webkit.WebResourceRequest import android.webkit.WebView import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets @@ -91,6 +92,13 @@ private fun TrustlyScreen( ) } TrustlyUiState.FailedToConnectCard -> { + HedvigErrorSection( + retry = retryConnectingCard, + title = stringResource(R.string.something_went_wrong), + subTitle = stringResource(R.string.pay_in_error_body), + ) + } + TrustlyUiState.FailedToStartSession -> { HedvigErrorSection( retry = retryConnectingCard, title = stringResource(R.string.something_went_wrong), @@ -133,7 +141,7 @@ private fun TrustlyBrowser( object : AccompanistWebViewClient() { override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) - logcat { "Webview loading url:$url" } + logcat { "Trustly Webview loading url:$url" } if (url?.startsWith("bankid") == true) { logcat(LogPriority.ERROR) { "Url did in fact try to open bankid" } @@ -184,26 +192,30 @@ private fun TrustlyBrowser( } }, ) - val loadingState = webViewState.loadingState - if (loadingState is LoadingState.Loading) { - LinearProgressIndicator( - progress = loadingState.progress, - modifier = Modifier.fillMaxWidth(), - ) - } - WebView( - state = webViewState, - navigator = webViewNavigator, - onCreated = { webView -> - webView.settings.javaScriptEnabled = true - webView.settings.javaScriptCanOpenWindowsAutomatically = true - webView.settings.setSupportMultipleWindows(true) - }, - client = webViewClient, - modifier = Modifier + Box( + Modifier .weight(1f) .fillMaxWidth() .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal)), - ) + ) { + val loadingState = webViewState.loadingState + if (loadingState is LoadingState.Loading) { + LinearProgressIndicator( + progress = loadingState.progress, + modifier = Modifier.fillMaxWidth(), + ) + } + WebView( + state = webViewState, + navigator = webViewNavigator, + onCreated = { webView -> + webView.settings.javaScriptEnabled = true + webView.settings.javaScriptCanOpenWindowsAutomatically = true + webView.settings.setSupportMultipleWindows(true) + }, + client = webViewClient, + modifier = Modifier.matchParentSize(), + ) + } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8f335cc751..237e97be07 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -68,7 +68,7 @@ firebaseCrashlyticsBuildtools = "2.9.9" flexbox = "3.0.0" fragmentViewBindingDelegate = "1.0.2" hAnalytics = "v0.307.0" -hedvigAuthlib = "1.3.4-alpha-2023-10-26-22-33-10" +hedvigAuthlib = "1.3.4-alpha-2023-10-27-09-58-56" insetter = "0.6.1" jsonTest = "20231013" junit = "4.13.2" From c1bee8c55b0691726d30f9ae15b46b5ec897c0bc Mon Sep 17 00:00:00 2001 From: stylianosgakis Date: Fri, 27 Oct 2023 17:05:08 +0200 Subject: [PATCH 21/34] Remove Adyen and Trustly legacy activities Remove the entire Adyen SDK and all adjacent classes which use adyen related content, like the serializer in apollo-giraffe Remove the unused queries and mutations Update the readme with the updated instructions to get setup, and remove the Adyen related strings that were setup in CI Shrink PaymentRepository to only use the public API it needs, and remove the unused ones todo: Remove OfferActivity and Embark which also were depending on these classes --- .github/workflows/integration-test.yml | 1 - .github/workflows/pr.yml | 4 - .github/workflows/staging.yml | 1 - .github/workflows/unused-resources.yml | 1 - .github/workflows/upload-to-play-store.yml | 1 - README.md | 12 +- .../apollo-giraffe-public/build.gradle.kts | 6 - .../graphql/adyenPaymentMethods.graphql | 5 - .../graphql/adyenPayoutMethods.graphql | 5 - ...dyenSubmitAdditionalPaymentDetails.graphql | 15 -- .../giraffe/graphql/payinStatus.graphql | 3 - .../startDirectDebitRegistration.graphql | 3 - .../graphql/tokenizePayoutDetails.graphql | 20 -- .../PaymentMethodsApiResponseAdapter.kt | 35 ---- app/app/build.gradle.kts | 1 - .../app/feature/payment/PaymentScreen.kt | 3 - app/app/src/main/AndroidManifest.xml | 14 -- .../android/app/navigation/HedvigNavHost.kt | 9 +- .../com/hedvig/app/ApplicationModule.kt | 41 ---- .../app/data/debit/PayinStatusRepository.kt | 38 ---- .../hedvig/app/feature/adyen/AdyenCurrency.kt | 17 -- .../app/feature/adyen/AdyenRepository.kt | 41 ---- .../feature/adyen/ConnectPaymentUseCase.kt | 62 ------- .../app/feature/adyen/ConnectPayoutUseCase.kt | 47 ----- .../app/feature/adyen/PaymentTokenId.kt | 4 - .../SubmitAdditionalPaymentDetailsUseCase.kt | 39 ---- .../adyen/payin/AdyenConnectPayinActivity.kt | 136 -------------- .../adyen/payin/AdyenConnectPayinViewModel.kt | 44 ----- .../feature/adyen/payin/AdyenExtensions.kt | 51 ----- .../adyen/payin/AdyenPayinDropInService.kt | 62 ------- .../payout/AdyenConnectPayoutActivity.kt | 102 ---------- .../payout/AdyenConnectPayoutViewModel.kt | 17 -- .../payout/AdyenConnectPayoutViewModelImpl.kt | 18 -- .../adyen/payout/AdyenPayoutDropInService.kt | 78 -------- .../payout/ConnectPayoutResultFragment.kt | 30 --- .../feature/connectpayin/ConnectPayinType.kt | 6 - .../ConnectPaymentResultFragment.kt | 92 --------- .../connectpayin/ConnectPaymentViewModel.kt | 77 -------- .../connectpayin/PostSignExplainerFragment.kt | 65 ------- .../hedvig/app/feature/connectpayin/Util.kt | 21 --- .../app/feature/offer/OfferViewModel.kt | 1 - .../app/feature/offer/ui/OfferActivity.kt | 4 +- .../offer/usecase/AddPaymentTokenUseCase.kt | 1 - .../hedvig/app/feature/payment/PaymentType.kt | 19 +- .../feature/trustly/TrustlyConnectFragment.kt | 175 ------------------ .../trustly/TrustlyConnectPayinActivity.kt | 89 --------- .../trustly/TrustlyJavascriptInterface.kt | 41 ---- .../app/feature/trustly/TrustlyRepository.kt | 13 -- .../app/feature/trustly/TrustlyViewModel.kt | 33 ---- .../feature/trustly/TrustlyWebChromeClient.kt | 39 ---- .../com/hedvig/app/ui/view/SafeWebView.kt | 32 ---- .../connect_payment_explainer_fragment.xml | 65 ------- .../connect_payment_result_fragment.xml | 79 -------- .../layout/connect_payout_result_fragment.xml | 48 ----- .../layout/fragment_container_activity.xml | 5 - .../res/layout/trustly_connect_fragment.xml | 40 ---- app/app/src/main/res/menu/connect_payin.xml | 8 - app/feature/feature-chat/build.gradle.kts | 1 - .../android/feature/chat/ui/ChatFragment.kt | 4 +- .../profile/payment/PaymentDestination.kt | 54 ------ .../feature/profile/tab/ProfileGraph.kt | 11 +- .../android/payment/PaymentRepository.kt | 7 - .../android/payment/PaymentRepositoryDemo.kt | 28 --- .../android/payment/PaymentRepositoryImpl.kt | 35 +--- app/testdata/build.gradle.kts | 1 - gradle/libs.versions.toml | 4 +- scripts/ci-prebuild.sh | 7 - scripts/ci-release-prebuild.sh | 7 - scripts/ci-staging-prebuild.sh | 7 - 69 files changed, 23 insertions(+), 2062 deletions(-) delete mode 100644 app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenPaymentMethods.graphql delete mode 100644 app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenPayoutMethods.graphql delete mode 100644 app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenSubmitAdditionalPaymentDetails.graphql delete mode 100644 app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/payinStatus.graphql delete mode 100644 app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/startDirectDebitRegistration.graphql delete mode 100644 app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/tokenizePayoutDetails.graphql delete mode 100644 app/apollo/apollo-giraffe-public/src/main/kotlin/com/hedvig/android/apollo/giraffe/typeadapter/PaymentMethodsApiResponseAdapter.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/data/debit/PayinStatusRepository.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/AdyenCurrency.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/AdyenRepository.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/ConnectPaymentUseCase.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/ConnectPayoutUseCase.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/PaymentTokenId.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/SubmitAdditionalPaymentDetailsUseCase.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenConnectPayinActivity.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenConnectPayinViewModel.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenExtensions.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenPayinDropInService.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutActivity.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutViewModel.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutViewModelImpl.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenPayoutDropInService.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/ConnectPayoutResultFragment.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPayinType.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPaymentResultFragment.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPaymentViewModel.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/PostSignExplainerFragment.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/Util.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyConnectFragment.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyConnectPayinActivity.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyJavascriptInterface.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyRepository.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyViewModel.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyWebChromeClient.kt delete mode 100644 app/app/src/main/kotlin/com/hedvig/app/ui/view/SafeWebView.kt delete mode 100644 app/app/src/main/res/layout/connect_payment_explainer_fragment.xml delete mode 100644 app/app/src/main/res/layout/connect_payment_result_fragment.xml delete mode 100644 app/app/src/main/res/layout/connect_payout_result_fragment.xml delete mode 100644 app/app/src/main/res/layout/fragment_container_activity.xml delete mode 100644 app/app/src/main/res/layout/trustly_connect_fragment.xml delete mode 100644 app/app/src/main/res/menu/connect_payin.xml diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index e8478f906b..2ef8c5d0cc 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -37,7 +37,6 @@ jobs: env: LOKALISE_ID: ${{ secrets.LOKALISE_ID }} LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} - ADYEN_CLIENT_KEY: ${{ secrets.ADYEN_CLIENT_KEY_TEST }} - name: Instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 546c5bb3bc..adefce971e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -34,7 +34,6 @@ jobs: env: LOKALISE_ID: ${{ secrets.LOKALISE_ID }} LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} - ADYEN_CLIENT_KEY: ${{ secrets.ADYEN_CLIENT_KEY_TEST }} - name: Unit tests run: ./gradlew testDebugUnitTest - uses: test-summary/action@v2 @@ -68,7 +67,6 @@ jobs: env: LOKALISE_ID: ${{ secrets.LOKALISE_ID }} LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} - ADYEN_CLIENT_KEY: ${{ secrets.ADYEN_CLIENT_KEY_TEST }} - run: ./gradlew lint - uses: yutailang0119/action-android-lint@v3.1.0 with: @@ -101,7 +99,6 @@ jobs: env: LOKALISE_ID: ${{ secrets.LOKALISE_ID }} LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} - ADYEN_CLIENT_KEY: ${{ secrets.ADYEN_CLIENT_KEY_TEST }} - name: run ktlint from gradle continue-on-error: true run: ./gradlew ktlintCheck @@ -143,7 +140,6 @@ jobs: env: LOKALISE_ID: ${{ secrets.LOKALISE_ID }} LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} - ADYEN_CLIENT_KEY: ${{ secrets.ADYEN_CLIENT_KEY_TEST }} - name: Run license release report run: ./gradlew licenseReleaseReport --no-configuration-cache --continue continue-on-error: true diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index f3c5c89d82..ef5e59432d 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -42,7 +42,6 @@ jobs: env: LOKALISE_ID: ${{ secrets.LOKALISE_ID }} LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} - ADYEN_CLIENT_KEY: ${{ secrets.ADYEN_TEST_CLIENT_KEY }} - name: Run license release report run: ./gradlew licenseReleaseReport --no-configuration-cache --continue continue-on-error: true diff --git a/.github/workflows/unused-resources.yml b/.github/workflows/unused-resources.yml index 446a7aeaeb..5b08827c0e 100644 --- a/.github/workflows/unused-resources.yml +++ b/.github/workflows/unused-resources.yml @@ -33,7 +33,6 @@ jobs: env: LOKALISE_ID: ${{ secrets.LOKALISE_ID }} LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} - ADYEN_CLIENT_KEY: ${{ secrets.ADYEN_CLIENT_KEY_TEST }} - name: Run android lint run: ./gradlew :app:lintDebug -Prur.lint.onlyUnusedResources diff --git a/.github/workflows/upload-to-play-store.yml b/.github/workflows/upload-to-play-store.yml index 2c467d059e..885b2aac35 100644 --- a/.github/workflows/upload-to-play-store.yml +++ b/.github/workflows/upload-to-play-store.yml @@ -43,7 +43,6 @@ jobs: env: LOKALISE_ID: ${{ secrets.LOKALISE_ID }} LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} - ADYEN_CLIENT_KEY: ${{ secrets.ADYEN_LIVE_CLIENT_KEY }} - name: Run license release report run: ./gradlew licenseReleaseReport --no-configuration-cache --continue continue-on-error: true diff --git a/README.md b/README.md index 1a32324b57..f646fd9566 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,18 @@ ## Develop -1. Acquire Adyen credentials (you can find them in 1Password), place in the following paths: - - `app/app/src/${debug|staging|release}/res/values/adyen.xml` -2. Acquire Lokalise credentials (you can find them in 1Password), place in the following file: +1. Acquire Lokalise credentials (you can find them in 1Password), place in the following file: - `lokalise.properties` -3. Acquire gradle.properties which contain a token for Github Packages authentication. +2. Acquire gradle.properties which contain a token for Github Packages authentication. Generate your own at GitHub > Settings > Developer Settings > PAT > Tokens (Classic) > Generate New Token > Give the read:packages permission Append (or create) your global gradle.properties in: - `~/.gradle/gradle.properties` Look inside [ci-github-packages-properties](scripts/ci-github-packages-properties.sh) for inspiration. -4. Download the schema (required to consume any changes in schema as well): +3. Download the schema (required to consume any changes in schema as well): `./gradlew downloadApolloSchemasFromIntrospection` -5. Download lokalise translations (required to consume latest translations as well): +4. Download lokalise translations (required to consume latest translations as well): `./gradlew downloadStrings` -6. Build and install via Android Studio +5. Build and install via Android Studio ## Formatting diff --git a/app/apollo/apollo-giraffe-public/build.gradle.kts b/app/apollo/apollo-giraffe-public/build.gradle.kts index aef2dfccac..ee21ae5382 100644 --- a/app/apollo/apollo-giraffe-public/build.gradle.kts +++ b/app/apollo/apollo-giraffe-public/build.gradle.kts @@ -8,7 +8,6 @@ plugins { dependencies { api(libs.apollo.api) - implementation(libs.adyen) implementation(libs.apollo.adapters) implementation(libs.apollo.runtime) implementation(libs.koin.core) @@ -54,11 +53,6 @@ apollo { "java.time.LocalDate", "com.hedvig.android.apollo.giraffe.typeadapter.PromiscuousLocalDateAdapter", ) - mapScalar( - "PaymentMethodsResponse", - "com.adyen.checkout.components.model.PaymentMethodsApiResponse", - "com.hedvig.android.apollo.giraffe.typeadapter.PaymentMethodsApiResponseAdapter", - ) sealedClassesForEnumsMatching.set( listOf( diff --git a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenPaymentMethods.graphql b/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenPaymentMethods.graphql deleted file mode 100644 index 2b0d3f0ea0..0000000000 --- a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenPaymentMethods.graphql +++ /dev/null @@ -1,5 +0,0 @@ -query AdyenPaymentMethods { - availablePaymentMethods { - paymentMethodsResponse - } -} diff --git a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenPayoutMethods.graphql b/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenPayoutMethods.graphql deleted file mode 100644 index e4fb6f6924..0000000000 --- a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenPayoutMethods.graphql +++ /dev/null @@ -1,5 +0,0 @@ -query AdyenPayoutMethods { - availablePayoutMethods { - paymentMethodsResponse - } -} diff --git a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenSubmitAdditionalPaymentDetails.graphql b/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenSubmitAdditionalPaymentDetails.graphql deleted file mode 100644 index c1ae38926c..0000000000 --- a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/adyenSubmitAdditionalPaymentDetails.graphql +++ /dev/null @@ -1,15 +0,0 @@ -mutation SubmitAdditionalPaymentDetails( - $paymentsDetailsRequest: PaymentsDetailsRequest! -) { - submitAdditionalPaymentDetails( - req: { paymentsDetailsRequest: $paymentsDetailsRequest } - ) { - ... on AdditionalPaymentsDetailsResponseAction { - action - } - ... on AdditionalPaymentsDetailsResponseFinished { - resultCode - tokenizationResult - } - } -} diff --git a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/payinStatus.graphql b/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/payinStatus.graphql deleted file mode 100644 index d861ff6d96..0000000000 --- a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/payinStatus.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query PayinStatus { - payinMethodStatus -} diff --git a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/startDirectDebitRegistration.graphql b/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/startDirectDebitRegistration.graphql deleted file mode 100644 index 221e584cb8..0000000000 --- a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/startDirectDebitRegistration.graphql +++ /dev/null @@ -1,3 +0,0 @@ -mutation StartDirectDebitRegistration { - startDirectDebitRegistration -} diff --git a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/tokenizePayoutDetails.graphql b/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/tokenizePayoutDetails.graphql deleted file mode 100644 index 212523851b..0000000000 --- a/app/apollo/apollo-giraffe-public/src/main/graphql/com/hedvig/android/apollo/giraffe/graphql/tokenizePayoutDetails.graphql +++ /dev/null @@ -1,20 +0,0 @@ -mutation TokenizePayoutDetails( - $paymentMethodDetails: PaymentMethodDetails! - $returnUrl: String! -) { - tokenizePayoutDetails( - req: { - paymentMethodDetails: $paymentMethodDetails - channel: ANDROID - returnUrl: $returnUrl - } - ) { - ... on TokenizationResponseFinished { - resultCode - tokenizationResult - } - ... on TokenizationResponseAction { - action - } - } -} diff --git a/app/apollo/apollo-giraffe-public/src/main/kotlin/com/hedvig/android/apollo/giraffe/typeadapter/PaymentMethodsApiResponseAdapter.kt b/app/apollo/apollo-giraffe-public/src/main/kotlin/com/hedvig/android/apollo/giraffe/typeadapter/PaymentMethodsApiResponseAdapter.kt deleted file mode 100644 index 545a0008e1..0000000000 --- a/app/apollo/apollo-giraffe-public/src/main/kotlin/com/hedvig/android/apollo/giraffe/typeadapter/PaymentMethodsApiResponseAdapter.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.hedvig.android.apollo.giraffe.typeadapter - -import com.adyen.checkout.components.model.PaymentMethodsApiResponse -import com.apollographql.apollo3.api.Adapter -import com.apollographql.apollo3.api.AnyAdapter -import com.apollographql.apollo3.api.CustomScalarAdapters -import com.apollographql.apollo3.api.json.JsonReader -import com.apollographql.apollo3.api.json.JsonWriter -import org.json.JSONObject - -/** - * PaymentMethodsApiResponse is sometimes read as a map from the JsonReader. - * This adapter handles both cases, therefore this special deserialization checking for a Map is required. - */ -@Suppress("unused") // Used inside the `apollo` block inside build.gradle.kts -internal object PaymentMethodsApiResponseAdapter : Adapter { - override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): PaymentMethodsApiResponse { - val data = AnyAdapter.fromJson(reader, customScalarAdapters) - return if (data is Map<*, *>) { - PaymentMethodsApiResponse.SERIALIZER.deserialize(JSONObject(data)) - } else { - val jsonString = data.toString().replace("\\", "") - PaymentMethodsApiResponse.SERIALIZER.deserialize(JSONObject(jsonString)) - } - } - - override fun toJson( - writer: JsonWriter, - customScalarAdapters: CustomScalarAdapters, - value: PaymentMethodsApiResponse, - ) { - val jsonString = PaymentMethodsApiResponse.SERIALIZER.serialize(value).toString() - writer.value(jsonString) - } -} diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index 8a2ae33edd..128c2ccc89 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -114,7 +114,6 @@ dependencies { implementation(libs.accompanist.insetsUi) implementation(libs.accompanist.pagerIndicators) implementation(libs.accompanist.systemUiController) - implementation(libs.adyen) implementation(libs.androidx.compose.animation) implementation(libs.androidx.compose.foundation) implementation(libs.androidx.compose.material) diff --git a/app/app/src/androidTest/kotlin/com/hedvig/app/feature/payment/PaymentScreen.kt b/app/app/src/androidTest/kotlin/com/hedvig/app/feature/payment/PaymentScreen.kt index 6b0bf71ab0..143326dca2 100644 --- a/app/app/src/androidTest/kotlin/com/hedvig/app/feature/payment/PaymentScreen.kt +++ b/app/app/src/androidTest/kotlin/com/hedvig/app/feature/payment/PaymentScreen.kt @@ -2,10 +2,7 @@ package com.hedvig.app.feature.payment import android.view.View import com.hedvig.app.R -import com.hedvig.app.feature.adyen.payin.AdyenConnectPayinActivity -import com.hedvig.app.feature.adyen.payout.AdyenConnectPayoutActivity import com.hedvig.app.feature.referrals.ui.redeemcode.RedeemCodeBottomSheet -import com.hedvig.app.feature.trustly.TrustlyConnectPayinActivity import com.kaspersky.kaspresso.screens.KScreen import io.github.kakaocup.kakao.intent.KIntent import io.github.kakaocup.kakao.recycler.KRecyclerItem diff --git a/app/app/src/main/AndroidManifest.xml b/app/app/src/main/AndroidManifest.xml index b462ecdf91..08e904b12b 100644 --- a/app/app/src/main/AndroidManifest.xml +++ b/app/app/src/main/AndroidManifest.xml @@ -74,23 +74,12 @@ - - - - - - { SelectedVariantStore() } } -private val adyenModule = module { - viewModel { AdyenConnectPayinViewModelImpl(get(), get()) } - viewModel { AdyenConnectPayoutViewModelImpl(get()) } -} - private val embarkModule = module { viewModel { (storyName: String) -> EmbarkViewModelImpl( @@ -395,20 +376,6 @@ private val numberActionSetModule = module { viewModel { (data: NumberActionParams) -> NumberActionViewModel(data) } } -private val connectPaymentModule = module { // todo delete along with the entire legacy connect-payment feat - viewModel { - ConnectPaymentViewModel( - get(), - get(), - get(), - ) - } -} - -private val trustlyModule = module { - viewModel { TrustlyViewModelImpl(get(), get()) } -} - private val changeDateBottomSheetModule = module { viewModel { (data: ChangeDateBottomSheetData) -> ChangeDateBottomSheetViewModel(get(), data, get()) } } @@ -457,12 +424,9 @@ private val externalInsuranceModule = module { } private val repositoriesModule = module { - single { PayinStatusRepository(get(giraffeClient)) } single { UserRepository(get(giraffeClient)) } - single { AdyenRepository(get(giraffeClient), get()) } single { EmbarkRepository(get(giraffeClient), get()) } single { LoggedInRepository(get(giraffeClient), get()) } - single { TrustlyRepository(get(giraffeClient)) } single { GetMemberIdUseCase(get(giraffeClient)) } } @@ -511,8 +475,6 @@ private val useCaseModule = module { single { QuoteCartEditStartDateUseCase(get(giraffeClient), get()) } single { EditCampaignUseCase(get(giraffeClient), get()) } single { AddPaymentTokenUseCase(get(giraffeClient)) } - single { ConnectPaymentUseCase(get(), get(), get()) } - single { ConnectPayoutUseCase(get(giraffeClient), get()) } single { ObserveOfferStateUseCase(get(), get()) } } @@ -572,7 +534,6 @@ val applicationModule = module { listOf( activityNavigatorModule, adyenFeatureModule, - adyenModule, apolloAuthListenersModule, apolloClientModule, appModule, @@ -587,7 +548,6 @@ val applicationModule = module { claimTriagingModule, clockModule, coilModule, - connectPaymentModule, connectPaymentTrustlyModule, coreCommonModule, dataStoreModule, @@ -627,7 +587,6 @@ val applicationModule = module { textActionSetModule, travelCertificateDataModule, travelCertificateModule, - trustlyModule, useCaseModule, valueStoreModule, viewModelModule, diff --git a/app/app/src/main/kotlin/com/hedvig/app/data/debit/PayinStatusRepository.kt b/app/app/src/main/kotlin/com/hedvig/app/data/debit/PayinStatusRepository.kt deleted file mode 100644 index a995c87ffb..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/data/debit/PayinStatusRepository.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.hedvig.app.data.debit - -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.cache.normalized.FetchPolicy -import com.apollographql.apollo3.cache.normalized.apolloStore -import com.apollographql.apollo3.cache.normalized.fetchPolicy -import com.apollographql.apollo3.cache.normalized.watch -import giraffe.PayinStatusQuery -import kotlinx.coroutines.flow.Flow - -class PayinStatusRepository( - private val apolloClient: ApolloClient, -) { - private val payinStatusQuery = PayinStatusQuery() - - fun payinStatusFlow(): Flow> = apolloClient - .query(payinStatusQuery) - .watch() - - suspend fun refreshPayinStatus() { - val response = apolloClient - .query(payinStatusQuery) - .fetchPolicy(FetchPolicy.NetworkOnly) - .execute() - - response.data?.let { data -> - val cachedData = apolloClient - .apolloStore - .readOperation(payinStatusQuery) - - val newData = cachedData.copy(payinMethodStatus = data.payinMethodStatus) - apolloClient - .apolloStore - .writeOperation(payinStatusQuery, newData) - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/AdyenCurrency.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/AdyenCurrency.kt deleted file mode 100644 index e5115dd3a6..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/AdyenCurrency.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.hedvig.app.feature.adyen - -import com.hedvig.android.market.Market - -enum class AdyenCurrency { - NOK, - DKK, - ; - - companion object { - fun fromMarket(market: Market) = when (market) { - Market.NO -> NOK - Market.DK -> DKK - else -> error("Market $market is not supported by Adyen") - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/AdyenRepository.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/AdyenRepository.kt deleted file mode 100644 index eb88f64222..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/AdyenRepository.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.hedvig.app.feature.adyen - -import android.content.Context -import com.adyen.checkout.components.model.PaymentMethodsApiResponse -import com.adyen.checkout.redirect.RedirectComponent -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import giraffe.AdyenPaymentMethodsQuery -import giraffe.AdyenPayoutMethodsQuery -import giraffe.TokenizePayoutDetailsMutation -import org.json.JSONObject - -class AdyenRepository( - private val apolloClient: ApolloClient, - private val context: Context, -) { - - suspend fun paymentMethods(): ApolloResponse = apolloClient - .query(AdyenPaymentMethodsQuery()) - .execute() - - suspend fun payoutMethods(): ApolloResponse = apolloClient - .query(AdyenPayoutMethodsQuery()) - .execute() - - suspend fun tokenizePayoutDetails(data: JSONObject) = apolloClient - .mutation( - TokenizePayoutDetailsMutation( - data.getJSONObject("paymentMethod").toString(), - RedirectComponent.getReturnUrl(context), - ), - ) - .execute() - - suspend fun paymentMethodsResponse(): PaymentMethodsApiResponse? { - return paymentMethods() - .data - ?.availablePaymentMethods - ?.paymentMethodsResponse - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/ConnectPaymentUseCase.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/ConnectPaymentUseCase.kt deleted file mode 100644 index 52fbe38789..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/ConnectPaymentUseCase.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.hedvig.app.feature.adyen - -import android.content.Context -import arrow.core.Either -import arrow.core.flatMap -import arrow.core.right -import com.adyen.checkout.redirect.RedirectComponent -import com.hedvig.android.apollo.toEither -import com.hedvig.android.market.Market -import com.hedvig.android.market.MarketManager -import com.hedvig.app.util.apollo.GraphQLQueryHandler -import giraffe.ConnectPaymentMutation -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import org.json.JSONObject - -class ConnectPaymentUseCase( - private val context: Context, - private val marketManager: MarketManager, - private val graphQLQueryHandler: GraphQLQueryHandler, -) { - - sealed interface Error { - data class CheckoutPaymentAction(val action: String) : Error - data class ErrorMessage(val message: String?) : Error - } - - suspend fun getPaymentTokenId(data: JSONObject): Either { - return connectPayment(data) - } - - private suspend fun connectPayment(data: JSONObject): Either = graphQLQueryHandler - .graphQLQuery( - query = ConnectPaymentMutation.OPERATION_DOCUMENT, - variables = createConnectPaymentVariables(data), - files = emptyList(), - ) - .toEither() - .mapLeft { Error.ErrorMessage(null) } - .flatMap { jsonResponse -> - val id = jsonResponse - .getJSONObject("data") - .getJSONObject("paymentConnection_connectPayment") - .getString("paymentTokenId") - PaymentTokenId(id).right() - } - - private fun createConnectPaymentVariables(data: JSONObject): JSONObject? { - val market = when (marketManager.market.value) { - Market.SE -> giraffe.type.Market.SWEDEN - Market.NO -> giraffe.type.Market.NORWAY - Market.DK -> giraffe.type.Market.DENMARK - }.toString() - - return buildJsonObject { - put("market", market) - put("returnUrl", RedirectComponent.getReturnUrl(context)) - } - .toString() - .let { JSONObject(it) }.put("paymentMethodDetails", data.getJSONObject("paymentMethod")) - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/ConnectPayoutUseCase.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/ConnectPayoutUseCase.kt deleted file mode 100644 index 6869f736ab..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/ConnectPayoutUseCase.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.hedvig.app.feature.adyen - -import android.content.Context -import arrow.core.Either -import arrow.core.flatMap -import arrow.core.left -import arrow.core.right -import com.adyen.checkout.redirect.RedirectComponent -import com.apollographql.apollo3.ApolloClient -import com.hedvig.android.apollo.safeExecute -import com.hedvig.android.apollo.toEither -import giraffe.TokenizePayoutDetailsMutation -import giraffe.type.TokenizationResultType -import org.json.JSONObject - -class ConnectPayoutUseCase( - private val apolloClient: ApolloClient, - private val context: Context, -) { - - data class PayOutResult( - val code: String, - val tokenizationResultType: TokenizationResultType, - ) - - sealed interface Error { - data class CheckoutPaymentAction(val action: String) : Error - data class ErrorMessage(val message: String?) : Error - } - - suspend fun connectPayout(data: JSONObject): Either = apolloClient - .mutation(createTokenizePayoutDetailsMutation(data)) - .safeExecute() - .toEither { message, _ -> Error.ErrorMessage(message) } - .flatMap { - it.tokenizePayoutDetails?.asTokenizationResponseAction?.let { - Error.CheckoutPaymentAction(it.action).left() - } ?: it.tokenizePayoutDetails?.asTokenizationResponseFinished?.let { - PayOutResult(it.resultCode, it.tokenizationResult).right() - } ?: Error.ErrorMessage(null).left() - } - - private fun createTokenizePayoutDetailsMutation(data: JSONObject) = TokenizePayoutDetailsMutation( - data.getJSONObject("paymentMethod").toString(), - RedirectComponent.getReturnUrl(context), - ) -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/PaymentTokenId.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/PaymentTokenId.kt deleted file mode 100644 index 4aa8201b87..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/PaymentTokenId.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.hedvig.app.feature.adyen - -@JvmInline -value class PaymentTokenId(val id: String) diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/SubmitAdditionalPaymentDetailsUseCase.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/SubmitAdditionalPaymentDetailsUseCase.kt deleted file mode 100644 index 04db6c62cb..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/SubmitAdditionalPaymentDetailsUseCase.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.hedvig.app.feature.adyen - -import arrow.core.flatMap -import arrow.core.left -import arrow.core.right -import com.apollographql.apollo3.ApolloClient -import com.hedvig.android.apollo.safeExecute -import com.hedvig.android.apollo.toEither -import giraffe.SubmitAdditionalPaymentDetailsMutation -import giraffe.type.TokenizationResultType -import org.json.JSONObject - -class SubmitAdditionalPaymentDetailsUseCase( - private val apolloClient: ApolloClient, -) { - - data class PaymentResult( - val code: String, - val tokenizationResultType: TokenizationResultType, - ) - - sealed class Error { - data class CheckoutPaymentAction(val action: String) : Error() - data class ErrorMessage(val message: String?) : Error() - } - - suspend fun submitAdditionalPaymentDetails(data: JSONObject) = apolloClient - .mutation(SubmitAdditionalPaymentDetailsMutation(data.toString())) - .safeExecute() - .toEither() - .mapLeft { Error.ErrorMessage(it.message) } - .flatMap { - it.submitAdditionalPaymentDetails.asAdditionalPaymentsDetailsResponseAction?.action?.let { - Error.CheckoutPaymentAction(it).left() - } ?: it.submitAdditionalPaymentDetails.asAdditionalPaymentsDetailsResponseFinished?.let { - PaymentResult(it.resultCode, it.tokenizationResult).right() - } ?: Error.ErrorMessage(null).left() - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenConnectPayinActivity.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenConnectPayinActivity.kt deleted file mode 100644 index 627820ffa3..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenConnectPayinActivity.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.hedvig.app.feature.adyen.payin - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import com.adyen.checkout.components.model.PaymentMethodsApiResponse -import com.adyen.checkout.dropin.DropIn -import com.adyen.checkout.dropin.DropInResult -import com.hedvig.android.auth.android.AuthenticatedObserver -import com.hedvig.android.core.common.android.serializableExtra -import com.hedvig.android.language.LanguageService -import com.hedvig.android.logger.LogPriority -import com.hedvig.android.logger.logcat -import com.hedvig.app.R -import com.hedvig.app.feature.adyen.AdyenCurrency -import com.hedvig.app.feature.connectpayin.ConnectPayinType -import com.hedvig.app.feature.connectpayin.ConnectPaymentResultFragment -import com.hedvig.app.feature.connectpayin.ConnectPaymentScreenState -import com.hedvig.app.feature.connectpayin.ConnectPaymentViewModel -import com.hedvig.app.feature.connectpayin.PostSignExplainerFragment -import com.hedvig.app.feature.loggedin.ui.LoggedInActivity -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel - -class AdyenConnectPayinActivity : AppCompatActivity(R.layout.fragment_container_activity) { - - private val connectPaymentViewModel: ConnectPaymentViewModel by viewModel() - private val adyenConnectPayinViewModel: AdyenConnectPayinViewModel by viewModel() - - private val languageService: LanguageService by inject() - private lateinit var paymentMethods: PaymentMethodsApiResponse - private lateinit var currency: AdyenCurrency - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - lifecycle.addObserver(AuthenticatedObserver()) - - val c = intent.serializableExtra(CURRENCY) - - if (c == null) { - logcat(LogPriority.ERROR) { "Programmer error: CURRENCY not provided to ${this.javaClass.name}" } - finish() - return - } - - currency = c - - if (isPostSign()) { - connectPaymentViewModel.setInitialNavigationDestination(ConnectPaymentScreenState.Explainer) - } - - connectPaymentViewModel.navigationState.observe(this) { state -> - when (state) { - ConnectPaymentScreenState.Explainer -> - supportFragmentManager - .beginTransaction() - .replace( - R.id.container, - PostSignExplainerFragment.newInstance(ConnectPayinType.ADYEN), - ) - .commitAllowingStateLoss() - is ConnectPaymentScreenState.Connect -> startAdyenPayment(languageService.getLocale(), paymentMethods) - is ConnectPaymentScreenState.Result -> - supportFragmentManager - .beginTransaction() - .replace( - R.id.container, - ConnectPaymentResultFragment.newInstance( - state.success, - ConnectPayinType.ADYEN, - ), - ) - .commitAllowingStateLoss() - } - } - - connectPaymentViewModel.shouldClose.observe(this) { shouldClose -> - if (shouldClose) { - if (isPostSign()) { - startActivity( - LoggedInActivity.newInstance( - this, - withoutHistory = true, - ), - ) - return@observe - } - finish() - } - } - - adyenConnectPayinViewModel.paymentMethods.observe(this) { - paymentMethods = it - - if (isPostSign()) { - connectPaymentViewModel.isReadyToStart() - } else { - startAdyenPayment(languageService.getLocale(), paymentMethods) - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - // Replace with new result API when adyens handleActivityResult is updated - super.onActivityResult(requestCode, resultCode, data) - - when (DropIn.handleActivityResult(requestCode, resultCode, data)) { - is DropInResult.CancelledByUser -> finish() - is DropInResult.Finished -> { - connectPaymentViewModel.navigateTo(ConnectPaymentScreenState.Result(success = true)) - } - is DropInResult.Error, - null, - -> connectPaymentViewModel.navigateTo( - ConnectPaymentScreenState.Result(success = false), - ) - } - } - - private fun isPostSign() = intent.getBooleanExtra(IS_POST_SIGN, false) - - companion object { - - const val GOOGLE_WALLET_ENVIRONMENT_PRODUCTION = 1 - const val GOOGLE_WALLET_ENVIRONMENT_TEST = 3 - fun newInstance(context: Context, currency: AdyenCurrency, isPostSign: Boolean = false) = - Intent(context, AdyenConnectPayinActivity::class.java).apply { - putExtra(IS_POST_SIGN, isPostSign) - putExtra(CURRENCY, currency) - } - - private const val IS_POST_SIGN = "IS_POST_SIGN" - private const val CURRENCY = "CURRENCY" - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenConnectPayinViewModel.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenConnectPayinViewModel.kt deleted file mode 100644 index 5efcb7c12a..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenConnectPayinViewModel.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.hedvig.app.feature.adyen.payin - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.adyen.checkout.components.model.PaymentMethodsApiResponse -import com.hedvig.android.logger.LogPriority -import com.hedvig.android.logger.logcat -import com.hedvig.app.feature.adyen.AdyenRepository -import com.hedvig.hanalytics.AppScreen -import com.hedvig.hanalytics.HAnalytics -import kotlinx.coroutines.launch - -abstract class AdyenConnectPayinViewModel : ViewModel() { - protected val _paymentMethods = MutableLiveData() - val paymentMethods: LiveData = _paymentMethods -} - -class AdyenConnectPayinViewModelImpl( - private val adyenRepository: AdyenRepository, - hAnalytics: HAnalytics, -) : AdyenConnectPayinViewModel() { - - init { - hAnalytics.screenView(AppScreen.CONNECT_PAYMENT_ADYEN) - viewModelScope.launch { - val response = runCatching { - adyenRepository.paymentMethods() - } - - if (response.isFailure) { - response.exceptionOrNull()?.let { logcat(LogPriority.ERROR, it) { "Loading Adyen payment methods failed" } } - return@launch - } - - response.getOrNull()?.data?.availablePaymentMethods?.paymentMethodsResponse?.let { - _paymentMethods.postValue( - it, - ) - } - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenExtensions.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenExtensions.kt deleted file mode 100644 index 1ae6330a80..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenExtensions.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.hedvig.app.feature.adyen.payin - -import android.app.Activity -import com.adyen.checkout.card.CardConfiguration -import com.adyen.checkout.components.model.PaymentMethodsApiResponse -import com.adyen.checkout.core.api.Environment -import com.adyen.checkout.dropin.DropIn -import com.adyen.checkout.dropin.DropInConfiguration -import com.adyen.checkout.googlepay.GooglePayConfiguration -import com.hedvig.app.R -import com.hedvig.app.isDebug -import java.util.Locale - -fun Activity.startAdyenPayment(locale: Locale, paymentMethods: PaymentMethodsApiResponse) { - val cardConfig = CardConfiguration.Builder(this, getString(R.string.ADYEN_CLIENT_KEY)) - .setShowStorePaymentField(false) - .setEnvironment(getEnvironment()) - .build() - - val googlePayConfig = GooglePayConfiguration.Builder(this, getString(R.string.ADYEN_CLIENT_KEY)) - .setEnvironment(getEnvironment()) - .setGooglePayEnvironment( - if (isDebug()) { - AdyenConnectPayinActivity.GOOGLE_WALLET_ENVIRONMENT_TEST - } else { - AdyenConnectPayinActivity.GOOGLE_WALLET_ENVIRONMENT_PRODUCTION - }, - ) - .build() - - val dropInConfiguration = DropInConfiguration - .Builder( - this, - AdyenPayinDropInService::class.java, - getString(R.string.ADYEN_CLIENT_KEY), - ) - .addCardConfiguration(cardConfig) - .addGooglePayConfiguration(googlePayConfig) - .setShopperLocale(locale) - .setEnvironment(getEnvironment()) - .build() - - DropIn.startPayment(this, paymentMethods, dropInConfiguration) - // trackingFacade.track("connect_payment_visible") -} - -private fun getEnvironment() = if (isDebug()) { - Environment.TEST -} else { - Environment.EUROPE -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenPayinDropInService.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenPayinDropInService.kt deleted file mode 100644 index 22fb15fafb..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payin/AdyenPayinDropInService.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.hedvig.app.feature.adyen.payin - -import com.adyen.checkout.components.ActionComponentData -import com.adyen.checkout.components.PaymentComponentState -import com.adyen.checkout.dropin.service.DropInService -import com.adyen.checkout.dropin.service.DropInServiceResult -import com.hedvig.app.feature.adyen.ConnectPaymentUseCase -import com.hedvig.app.feature.adyen.SubmitAdditionalPaymentDetailsUseCase -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import org.json.JSONObject -import org.koin.android.ext.android.inject -import kotlin.coroutines.CoroutineContext - -class AdyenPayinDropInService : DropInService(), CoroutineScope { - private val connectPaymentUseCase: ConnectPaymentUseCase by inject() - private val submitAdditionalPaymentDetailsUseCase: SubmitAdditionalPaymentDetailsUseCase by inject() - - private val coroutineJob = Job() - override val coroutineContext: CoroutineContext - get() = Dispatchers.IO + coroutineJob - - override fun onDetailsCallRequested( - actionComponentData: ActionComponentData, - actionComponentJson: JSONObject, - ) { - launch(coroutineContext) { - submitAdditionalPaymentDetailsUseCase.submitAdditionalPaymentDetails(actionComponentJson) - .mapLeft { it.toDropInServiceResult() } - .fold( - ifLeft = { sendResult(it) }, - ifRight = { sendResult(DropInServiceResult.Finished(it.code)) }, - ) - } - } - - override fun onPaymentsCallRequested( - paymentComponentState: PaymentComponentState<*>, - paymentComponentJson: JSONObject, - ) { - launch(coroutineContext) { - connectPaymentUseCase.getPaymentTokenId(paymentComponentJson) - .mapLeft { it.toError() } - .fold( - ifLeft = { sendResult(it) }, - ifRight = { sendResult(DropInServiceResult.Finished(it.id)) }, - ) - } - } - - private fun ConnectPaymentUseCase.Error.toError() = when (this) { - is ConnectPaymentUseCase.Error.CheckoutPaymentAction -> DropInServiceResult.Action(action) - is ConnectPaymentUseCase.Error.ErrorMessage -> DropInServiceResult.Error(message) - } -} - -fun SubmitAdditionalPaymentDetailsUseCase.Error.toDropInServiceResult() = when (this) { - is SubmitAdditionalPaymentDetailsUseCase.Error.CheckoutPaymentAction -> DropInServiceResult.Action(action) - is SubmitAdditionalPaymentDetailsUseCase.Error.ErrorMessage -> DropInServiceResult.Error(message) -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutActivity.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutActivity.kt deleted file mode 100644 index 4601044646..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutActivity.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.hedvig.app.feature.adyen.payout - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import com.adyen.checkout.components.model.payments.Amount -import com.adyen.checkout.core.api.Environment -import com.adyen.checkout.dropin.DropIn -import com.adyen.checkout.dropin.DropInConfiguration -import com.adyen.checkout.dropin.DropInResult -import com.hedvig.android.auth.android.AuthenticatedObserver -import com.hedvig.android.core.buildconstants.HedvigBuildConstants -import com.hedvig.android.core.common.android.serializableExtra -import com.hedvig.android.language.LanguageService -import com.hedvig.android.logger.LogPriority -import com.hedvig.android.logger.logcat -import com.hedvig.app.R -import com.hedvig.app.feature.adyen.AdyenCurrency -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel - -/** - * Hedvig paying to member - */ -class AdyenConnectPayoutActivity : AppCompatActivity(R.layout.fragment_container_activity) { - private val viewModel: AdyenConnectPayoutViewModel by viewModel() - private val languageService: LanguageService by inject() - private val hedvigBuildConstants: HedvigBuildConstants by inject() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - lifecycle.addObserver(AuthenticatedObserver()) - - val adyenCurrency = intent.serializableExtra(CURRENCY) - - if (adyenCurrency == null) { - logcat(LogPriority.ERROR) { "Programmer error: CURRENCY not provided to ${this.javaClass.name}" } - finish() - return - } - - viewModel.payoutMethods.observe(this) { response -> - val dropInConfiguration = DropInConfiguration - .Builder( - this, - AdyenPayoutDropInService::class.java, - getString(R.string.ADYEN_CLIENT_KEY), - ) - .setShopperLocale(languageService.getLocale()) - .setEnvironment( - if (hedvigBuildConstants.isProduction) { - Environment.EUROPE - } else { - Environment.TEST - }, - ) - .setAmount( - Amount().apply { - currency = adyenCurrency.toString() - value = 0 - }, - ) - .build() - - DropIn.startPayment(this, response, dropInConfiguration) - } - - viewModel.shouldClose.observe(this) { shouldClose -> - if (shouldClose) { - finish() - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - // Replace with new result API when adyens handleActivityResult is updated - super.onActivityResult(requestCode, resultCode, data) - - when (DropIn.handleActivityResult(requestCode, resultCode, data)) { - is DropInResult.CancelledByUser -> finish() - is DropInResult.Finished -> { - supportFragmentManager - .beginTransaction() - .replace(R.id.container, ConnectPayoutResultFragment.newInstance()) - .commitAllowingStateLoss() - } - is DropInResult.Error, - null, - -> { - } - } - } - - companion object { - private const val CURRENCY = "CURRENCY" - fun newInstance(context: Context, currency: AdyenCurrency) = - Intent(context, AdyenConnectPayoutActivity::class.java).apply { - putExtra(CURRENCY, currency) - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutViewModel.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutViewModel.kt deleted file mode 100644 index ad97094486..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutViewModel.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.hedvig.app.feature.adyen.payout - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.adyen.checkout.components.model.PaymentMethodsApiResponse -import com.hedvig.app.util.LiveEvent - -abstract class AdyenConnectPayoutViewModel : ViewModel() { - protected val _payoutMethods = MutableLiveData() - val payoutMethods: LiveData = _payoutMethods - val shouldClose = LiveEvent() - - fun close() { - shouldClose.postValue(true) - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutViewModelImpl.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutViewModelImpl.kt deleted file mode 100644 index b455ff1d97..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenConnectPayoutViewModelImpl.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.hedvig.app.feature.adyen.payout - -import androidx.lifecycle.viewModelScope -import com.hedvig.app.feature.adyen.AdyenRepository -import kotlinx.coroutines.launch - -class AdyenConnectPayoutViewModelImpl( - private val repository: AdyenRepository, -) : AdyenConnectPayoutViewModel() { - init { - viewModelScope.launch { - val response = runCatching { repository.payoutMethods() } - response.getOrNull()?.data?.let { - _payoutMethods.postValue(it.availablePayoutMethods.paymentMethodsResponse) - } - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenPayoutDropInService.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenPayoutDropInService.kt deleted file mode 100644 index aab7a85f79..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/AdyenPayoutDropInService.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.hedvig.app.feature.adyen.payout - -import com.adyen.checkout.components.ActionComponentData -import com.adyen.checkout.components.PaymentComponentState -import com.adyen.checkout.dropin.service.DropInService -import com.adyen.checkout.dropin.service.DropInServiceResult -import com.hedvig.android.core.demomode.Provider -import com.hedvig.android.payment.PaymentRepository -import com.hedvig.android.payment.di.PaymentRepositoryProvider -import com.hedvig.app.feature.adyen.ConnectPayoutUseCase -import com.hedvig.app.feature.adyen.SubmitAdditionalPaymentDetailsUseCase -import com.hedvig.app.feature.adyen.payin.toDropInServiceResult -import giraffe.type.PayoutMethodStatus -import giraffe.type.TokenizationResultType -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import org.json.JSONObject -import org.koin.android.ext.android.inject -import kotlin.coroutines.CoroutineContext - -class AdyenPayoutDropInService : DropInService(), CoroutineScope { - private val paymentRepositoryProvider: Provider by inject() - private val submitAdditionalPaymentDetailsUseCase: SubmitAdditionalPaymentDetailsUseCase by inject() - private val connectPayoutUseCase: ConnectPayoutUseCase by inject() - - private val coroutineJob = Job() - override val coroutineContext: CoroutineContext - get() = Dispatchers.IO + coroutineJob - - override fun onDetailsCallRequested(actionComponentData: ActionComponentData, actionComponentJson: JSONObject) { - launch(coroutineContext) { - submitAdditionalPaymentDetailsUseCase.submitAdditionalPaymentDetails(actionComponentJson) - .mapLeft { it.toDropInServiceResult() } - .fold( - ifLeft = { sendResult(it) }, - ifRight = { - it.tokenizationResultType.toPayoutMethodStatusOrNull() - ?.let { payoutMethodStatus -> - runCatching { paymentRepositoryProvider.provide().writeActivePayoutMethodStatus(payoutMethodStatus) } - } - sendResult(DropInServiceResult.Finished(it.code)) - }, - ) - } - } - - override fun onPaymentsCallRequested( - paymentComponentState: PaymentComponentState<*>, - paymentComponentJson: JSONObject, - ) { - launch(coroutineContext) { - connectPayoutUseCase.connectPayout(paymentComponentJson) - .mapLeft { it.toError() } - .fold( - ifLeft = { sendResult(it) }, - ifRight = { - it.tokenizationResultType.toPayoutMethodStatusOrNull()?.let { payoutMethodStatus -> - runCatching { paymentRepositoryProvider.provide().writeActivePayoutMethodStatus(payoutMethodStatus) } - } - sendResult(DropInServiceResult.Finished(it.code)) - }, - ) - } - } - - private fun TokenizationResultType.toPayoutMethodStatusOrNull() = when (this) { - TokenizationResultType.COMPLETED -> PayoutMethodStatus.ACTIVE - TokenizationResultType.PENDING -> PayoutMethodStatus.PENDING - else -> null - } - - private fun ConnectPayoutUseCase.Error.toError() = when (this) { - is ConnectPayoutUseCase.Error.CheckoutPaymentAction -> DropInServiceResult.Action(action) - is ConnectPayoutUseCase.Error.ErrorMessage -> DropInServiceResult.Error(message) - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/ConnectPayoutResultFragment.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/ConnectPayoutResultFragment.kt deleted file mode 100644 index e82c3a66ae..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/adyen/payout/ConnectPayoutResultFragment.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.hedvig.app.feature.adyen.payout - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import com.google.android.material.transition.MaterialFadeThrough -import com.hedvig.app.R -import com.hedvig.app.databinding.ConnectPayoutResultFragmentBinding -import com.hedvig.app.util.extensions.view.setHapticClickListener -import com.zhuinden.fragmentviewbindingdelegatekt.viewBinding -import org.koin.androidx.viewmodel.ext.android.activityViewModel - -class ConnectPayoutResultFragment : Fragment(R.layout.connect_payout_result_fragment) { - private val viewModel: AdyenConnectPayoutViewModel by activityViewModel() - private val binding by viewBinding(ConnectPayoutResultFragmentBinding::bind) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialFadeThrough() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - binding.close.setHapticClickListener { - viewModel.close() - } - } - - companion object { - fun newInstance() = ConnectPayoutResultFragment() - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPayinType.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPayinType.kt deleted file mode 100644 index 138ef3cda1..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPayinType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.hedvig.app.feature.connectpayin - -enum class ConnectPayinType { - TRUSTLY, - ADYEN, -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPaymentResultFragment.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPaymentResultFragment.kt deleted file mode 100644 index c52b61af23..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPaymentResultFragment.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.hedvig.app.feature.connectpayin - -import android.os.Bundle -import android.view.View -import androidx.core.os.bundleOf -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import com.google.android.material.transition.MaterialSharedAxis -import com.hedvig.android.core.common.android.serializableExtra -import com.hedvig.android.logger.LogPriority -import com.hedvig.android.logger.logcat -import com.hedvig.app.R -import com.hedvig.app.databinding.ConnectPaymentResultFragmentBinding -import com.hedvig.app.util.extensions.view.setHapticClickListener -import com.zhuinden.fragmentviewbindingdelegatekt.viewBinding -import org.koin.androidx.viewmodel.ext.android.activityViewModel - -class ConnectPaymentResultFragment : Fragment(R.layout.connect_payment_result_fragment) { - - private val binding by viewBinding(ConnectPaymentResultFragmentBinding::bind) - private val connectPaymentViewModel: ConnectPaymentViewModel by activityViewModel() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - binding.apply { - val success = requireArguments().getBoolean(SUCCESS) - - val payinType = requireArguments().serializableExtra(PAYIN_TYPE) - - if (payinType == null) { - logcat(LogPriority.ERROR) { "Programmer error: PAYIN_TYPE not supplied to ${this.javaClass.name}" } - return - } - - if (success) { - connectPaymentViewModel.onPaymentSuccess() - icon.setImageResource(com.hedvig.android.core.design.system.R.drawable.ic_checkmark_in_circle) - title.setText( - when (payinType) { - ConnectPayinType.ADYEN -> hedvig.resources.R.string.pay_in_confirmation_headline - ConnectPayinType.TRUSTLY -> hedvig.resources.R.string.pay_in_confirmation_direct_debit_headline - }, - ) - doItLater.isVisible = false - close.setText(hedvig.resources.R.string.pay_in_confirmation_continue_button) - close.setHapticClickListener { - connectPaymentViewModel.close() - } - } else { - icon.setImageResource(com.hedvig.android.core.design.system.R.drawable.ic_warning_triangle) - title.setText(hedvig.resources.R.string.pay_in_error_headline) - body.setText( - when (payinType) { - ConnectPayinType.TRUSTLY -> hedvig.resources.R.string.pay_in_error_direct_debit_body - ConnectPayinType.ADYEN -> hedvig.resources.R.string.pay_in_error_body - }, - ) - body.isVisible = true - doItLater.isVisible = true - doItLater.setHapticClickListener { - connectPaymentViewModel.close() - } - close.setText(hedvig.resources.R.string.pay_in_error_retry_button) - close.setHapticClickListener { - connectPaymentViewModel.navigateTo( - ConnectPaymentScreenState.Connect( - TransitionType.ENTER_RIGHT_EXIT_RIGHT, - ), - ) - } - } - } - } - - companion object { - private const val SUCCESS = "SUCCESS" - private const val PAYIN_TYPE = "PAYIN_TYPE" - - fun newInstance(success: Boolean, payinType: ConnectPayinType) = - ConnectPaymentResultFragment().apply { - arguments = bundleOf( - SUCCESS to success, - PAYIN_TYPE to payinType, - ) - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPaymentViewModel.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPaymentViewModel.kt deleted file mode 100644 index 4ac208be7b..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/ConnectPaymentViewModel.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.hedvig.app.feature.connectpayin - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.hedvig.android.core.demomode.Provider -import com.hedvig.android.payment.PaymentRepository -import com.hedvig.app.data.debit.PayinStatusRepository -import com.hedvig.app.util.LiveEvent -import com.hedvig.hanalytics.AppScreen -import com.hedvig.hanalytics.HAnalytics -import kotlinx.coroutines.launch - -class ConnectPaymentViewModel( - private val payinStatusRepository: PayinStatusRepository, - private val paymentRepositoryProvider: Provider, - private val hAnalytics: HAnalytics, -) : ViewModel() { - private val _navigationState = MutableLiveData() - val navigationState: LiveData = _navigationState - - private val _readyToStart = MutableLiveData() - val readyToStart: LiveData = _readyToStart - - val shouldClose = LiveEvent() - - fun navigateTo(screen: ConnectPaymentScreenState) { - _navigationState.postValue(screen) - if (screen is ConnectPaymentScreenState.Result) { - if (screen.success) { - hAnalytics.screenView(AppScreen.CONNECT_PAYMENT_SUCCESS) - viewModelScope.launch { - runCatching { - payinStatusRepository.refreshPayinStatus() - paymentRepositoryProvider.provide().refresh() - } - } - } else { - hAnalytics.screenView(AppScreen.CONNECT_PAYMENT_FAILED) - } - } - } - - fun isReadyToStart() { - _readyToStart.postValue(true) - } - - fun setInitialNavigationDestination(screen: ConnectPaymentScreenState) { - if (_navigationState.value == null) { - _navigationState.postValue(screen) - } - } - - fun close() { - shouldClose.postValue(true) - } - - fun onPaymentSuccess() { - hAnalytics.paymentConnected() - } -} - -sealed class ConnectPaymentScreenState { - object Explainer : ConnectPaymentScreenState() - data class Connect( - val transitionType: TransitionType, - ) : ConnectPaymentScreenState() - - data class Result(val success: Boolean) : ConnectPaymentScreenState() -} - -enum class TransitionType { - NO_ENTER_EXIT_RIGHT, - ENTER_LEFT_EXIT_RIGHT, - ENTER_RIGHT_EXIT_RIGHT, -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/PostSignExplainerFragment.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/PostSignExplainerFragment.kt deleted file mode 100644 index 5eaca6d2f2..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/PostSignExplainerFragment.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.hedvig.app.feature.connectpayin - -import android.os.Bundle -import android.view.View -import androidx.activity.addCallback -import androidx.core.os.bundleOf -import androidx.fragment.app.Fragment -import com.google.android.material.transition.MaterialSharedAxis -import com.hedvig.android.core.common.android.serializableExtra -import com.hedvig.android.logger.LogPriority -import com.hedvig.android.logger.logcat -import com.hedvig.app.R -import com.hedvig.app.databinding.ConnectPaymentExplainerFragmentBinding -import com.hedvig.app.util.extensions.view.setHapticClickListener -import com.zhuinden.fragmentviewbindingdelegatekt.viewBinding -import org.koin.androidx.viewmodel.ext.android.activityViewModel - -class PostSignExplainerFragment : Fragment(R.layout.connect_payment_explainer_fragment) { - private val viewModel: ConnectPaymentViewModel by activityViewModel() - private val binding by viewBinding(ConnectPaymentExplainerFragmentBinding::bind) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val paymentType = requireArguments().serializableExtra(PAYIN_TYPE) - if (paymentType == null) { - logcat(LogPriority.ERROR) { "Programmer error: PAYIN_TYPE not supplied to ${this.javaClass.name}" } - return - } - - requireActivity().onBackPressedDispatcher.addCallback(this) { - showConfirmCloseDialog(requireContext(), paymentType, viewModel::close) - } - - binding.apply { - when (paymentType) { - ConnectPayinType.TRUSTLY -> { - explainerTitle.setText(hedvig.resources.R.string.pay_in_explainer_direct_debit_headline) - explainerButton.setText(hedvig.resources.R.string.pay_in_explainer_direct_debit_button_text) - } - ConnectPayinType.ADYEN -> { - explainerTitle.setText(hedvig.resources.R.string.pay_in_explainer_headline) - explainerButton.setText(hedvig.resources.R.string.pay_in_explainer_button_text) - } - } - explainerButton.setHapticClickListener { - viewModel.navigateTo(ConnectPaymentScreenState.Connect(TransitionType.ENTER_LEFT_EXIT_RIGHT)) - } - } - - viewModel.readyToStart.observe(viewLifecycleOwner) { binding.explainerButton.isEnabled = it } - } - - companion object { - private const val PAYIN_TYPE = "PAYIN_TYPE" - fun newInstance(payinType: ConnectPayinType) = PostSignExplainerFragment().apply { - arguments = bundleOf(PAYIN_TYPE to payinType) - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/Util.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/Util.kt deleted file mode 100644 index 1c55ddeb33..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/connectpayin/Util.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.hedvig.app.feature.connectpayin - -import android.content.Context -import com.hedvig.app.util.extensions.showAlert - -fun showConfirmCloseDialog( - context: Context, - connectPayinType: ConnectPayinType, - close: () -> Unit, -) = context.showAlert( - title = hedvig.resources.R.string.pay_in_iframe_post_sign_skip_alert_title, - message = when (connectPayinType) { - ConnectPayinType.ADYEN -> hedvig.resources.R.string.pay_in_iframe_post_sign_skip_alert_body - ConnectPayinType.TRUSTLY -> hedvig.resources.R.string.pay_in_iframe_in_app_skip_alert_direct_debit_body - }, - positiveLabel = hedvig.resources.R.string.pay_in_iframe_post_sign_skip_alert_proceed_button, - negativeLabel = hedvig.resources.R.string.pay_in_iframe_post_sign_skip_alert_dismiss_button, - positiveAction = { - close() - }, -) diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/offer/OfferViewModel.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/offer/OfferViewModel.kt index a1ceff8083..fa82346f0e 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/offer/OfferViewModel.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/feature/offer/OfferViewModel.kt @@ -9,7 +9,6 @@ import com.adyen.checkout.components.model.PaymentMethodsApiResponse import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.android.QuoteCartId import com.hedvig.android.hanalytics.featureflags.FeatureManager -import com.hedvig.app.feature.adyen.PaymentTokenId import com.hedvig.app.feature.checkout.CheckoutParameter import com.hedvig.app.feature.documents.DocumentItems import com.hedvig.app.feature.embark.util.SelectedContractType diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/offer/ui/OfferActivity.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/offer/ui/OfferActivity.kt index de3f289333..60c14bcfad 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/offer/ui/OfferActivity.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/feature/offer/ui/OfferActivity.kt @@ -31,8 +31,6 @@ import com.hedvig.android.market.MarketManager import com.hedvig.android.navigation.core.HedvigDeepLinkContainer import com.hedvig.app.R import com.hedvig.app.databinding.ActivityOfferBinding -import com.hedvig.app.feature.adyen.PaymentTokenId -import com.hedvig.app.feature.adyen.payin.startAdyenPayment import com.hedvig.app.feature.checkout.CheckoutActivity import com.hedvig.app.feature.crossselling.ui.CrossSellingResult import com.hedvig.app.feature.crossselling.ui.CrossSellingResultActivity @@ -360,7 +358,7 @@ class OfferActivity : AppCompatActivity(R.layout.activity_offer) { CheckoutMethod.SWEDISH_BANK_ID -> viewModel.onSwedishBankIdSign() CheckoutMethod.SIMPLE_SIGN -> { if (paymentMethods != null) { - startAdyenPayment(languageService.getLocale(), paymentMethods) + error("Adyen is no longer supported. Should never reach the OfferActivity") } else { viewModel.onOpenCheckout() } diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/offer/usecase/AddPaymentTokenUseCase.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/offer/usecase/AddPaymentTokenUseCase.kt index e0270cd3a4..66d7881112 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/offer/usecase/AddPaymentTokenUseCase.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/feature/offer/usecase/AddPaymentTokenUseCase.kt @@ -8,7 +8,6 @@ import com.hedvig.android.apollo.safeExecute import com.hedvig.android.apollo.toEither import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.android.QuoteCartId -import com.hedvig.app.feature.adyen.PaymentTokenId import giraffe.AddPaymentTokenIdMutation class AddPaymentTokenUseCase( diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/payment/PaymentType.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/payment/PaymentType.kt index 34ee915fa4..97d550fb72 100644 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/payment/PaymentType.kt +++ b/app/app/src/main/kotlin/com/hedvig/app/feature/payment/PaymentType.kt @@ -2,22 +2,17 @@ package com.hedvig.app.feature.payment import android.content.Context import com.hedvig.android.market.Market -import com.hedvig.app.feature.adyen.AdyenCurrency -import com.hedvig.app.feature.adyen.payin.AdyenConnectPayinActivity -import com.hedvig.app.feature.trustly.TrustlyConnectPayinActivity -@Deprecated("Replace with navigating to AppDestination.ConnectPaymentTrustly|AppDestination.ConnectPaymentAdyen") +@Deprecated( + "Replace with navigating to AppDestination.ConnectPaymentTrustly|AppDestination.ConnectPaymentAdyen", + level = DeprecationLevel.ERROR, +) fun connectPayinIntent( context: Context, market: Market, isPostSign: Boolean, ) = when (market) { - Market.SE -> { - TrustlyConnectPayinActivity.newInstance(context, isPostSign) - } - Market.NO, - Market.DK, - -> { - AdyenConnectPayinActivity.newInstance(context, AdyenCurrency.fromMarket(market), isPostSign) - } + Market.SE -> {} + Market.NO -> {} + Market.DK -> {} } diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyConnectFragment.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyConnectFragment.kt deleted file mode 100644 index d8e61097cc..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyConnectFragment.kt +++ /dev/null @@ -1,175 +0,0 @@ -package com.hedvig.app.feature.trustly - -import android.annotation.SuppressLint -import android.content.Intent -import android.graphics.Bitmap -import android.net.Uri -import android.os.Bundle -import android.view.View -import android.webkit.WebView -import android.webkit.WebViewClient -import androidx.activity.addCallback -import androidx.core.os.bundleOf -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.transition.TransitionManager -import com.google.android.material.transition.MaterialFadeThrough -import com.google.android.material.transition.MaterialSharedAxis -import com.hedvig.android.core.common.android.serializableExtra -import com.hedvig.app.R -import com.hedvig.app.databinding.TrustlyConnectFragmentBinding -import com.hedvig.app.feature.connectpayin.ConnectPayinType -import com.hedvig.app.feature.connectpayin.ConnectPaymentScreenState -import com.hedvig.app.feature.connectpayin.ConnectPaymentViewModel -import com.hedvig.app.feature.connectpayin.TransitionType -import com.hedvig.app.feature.connectpayin.showConfirmCloseDialog -import org.koin.androidx.viewmodel.ext.android.activityViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel - -class TrustlyConnectFragment : Fragment(R.layout.trustly_connect_fragment) { - private var binding: TrustlyConnectFragmentBinding? = null - private val trustlyViewModel: TrustlyViewModel by viewModel() - private val connectPaymentViewModel: ConnectPaymentViewModel by activityViewModel() - - private var hasLoadedWebView = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val transitionType = requireArguments().serializableExtra(TRANSITION_TYPE) ?: return - - if (transitionType != TransitionType.NO_ENTER_EXIT_RIGHT) { - enterTransition = MaterialSharedAxis( - MaterialSharedAxis.X, - transitionType == TransitionType.ENTER_LEFT_EXIT_RIGHT, - ) - } - exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - } - - @SuppressLint("SetJavaScriptEnabled") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding = TrustlyConnectFragmentBinding.bind(view) - - val isPostSign = requireArguments().getBoolean(IS_POST_SIGN) - - if (isPostSign) { - requireActivity().onBackPressedDispatcher.addCallback(this) { - showConfirmCloseDialog( - requireContext(), - ConnectPayinType.TRUSTLY, - connectPaymentViewModel::close, - ) - } - } - - binding?.toolbar?.apply { - if (isPostSign) { - inflateMenu(R.menu.connect_payin) - setOnMenuItemClickListener { menuItem -> - when (menuItem.itemId) { - R.id.skip -> { - showConfirmCloseDialog( - requireContext(), - ConnectPayinType.TRUSTLY, - connectPaymentViewModel::close, - ) - true - } - else -> false - } - } - } else { - setNavigationIcon(hedvig.resources.R.drawable.ic_close) - setNavigationOnClickListener { - connectPaymentViewModel.close() - } - } - } - binding?.trustly?.apply { - settings.apply { - javaScriptEnabled = true - loadWithOverviewMode = true - useWideViewPort = true - domStorageEnabled = true - setSupportMultipleWindows(true) - } - webChromeClient = TrustlyWebChromeClient() - addJavascriptInterface( - TrustlyJavascriptInterface(requireActivity()), - TrustlyJavascriptInterface.NAME, - ) - webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView?, url: String?) { - super.onPageFinished(view, url) - binding?.apply { - if (!hasLoadedWebView) { - TransitionManager.beginDelayedTransition( - root, - MaterialFadeThrough(), - ) - loadingContainer.isVisible = false - trustly.isVisible = true - hasLoadedWebView = true - } - } - } - - override fun onPageStarted(view: WebView?, url: String, favicon: Bitmap?) { - if (url.startsWith("bankid")) { - view?.stopLoading() - val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(url) - startActivity(intent) - return - } - - if (url.contains("success")) { - view?.stopLoading() - connectPaymentViewModel.navigateTo(ConnectPaymentScreenState.Result(true)) - return - } - if (url.contains("fail")) { - view?.stopLoading() - connectPaymentViewModel.navigateTo( - ConnectPaymentScreenState.Result( - false, - ), - ) - return - } - } - } - } - trustlyViewModel.data.observe(viewLifecycleOwner) { url -> - binding?.trustly?.loadUrl(url) - } - } - - override fun onDestroyView() { - destroyWebView() - binding = null - super.onDestroyView() - } - - private fun destroyWebView() = binding?.apply { - trustly.removeAllViews() - trustly.clearHistory() - trustly.clearCache(true) - trustly.destroy() - } - - companion object { - private const val IS_POST_SIGN = "IS_POST_SIGN" - private const val TRANSITION_TYPE = "TRANSITION_TYPE" - - fun newInstance(isPostSign: Boolean, transitionType: TransitionType) = - TrustlyConnectFragment().apply { - arguments = bundleOf( - IS_POST_SIGN to isPostSign, - TRANSITION_TYPE to transitionType, - ) - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyConnectPayinActivity.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyConnectPayinActivity.kt deleted file mode 100644 index a79a77cee7..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyConnectPayinActivity.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.hedvig.app.feature.trustly - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import com.hedvig.android.auth.android.AuthenticatedObserver -import com.hedvig.app.R -import com.hedvig.app.feature.connectpayin.ConnectPayinType -import com.hedvig.app.feature.connectpayin.ConnectPaymentResultFragment -import com.hedvig.app.feature.connectpayin.ConnectPaymentScreenState -import com.hedvig.app.feature.connectpayin.ConnectPaymentViewModel -import com.hedvig.app.feature.connectpayin.PostSignExplainerFragment -import com.hedvig.app.feature.connectpayin.TransitionType -import com.hedvig.app.feature.loggedin.ui.LoggedInActivity -import org.koin.androidx.viewmodel.ext.android.viewModel - -class TrustlyConnectPayinActivity : AppCompatActivity(R.layout.fragment_container_activity) { - - private val connectPaymentViewModel: ConnectPaymentViewModel by viewModel() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - lifecycle.addObserver(AuthenticatedObserver()) - - if (isPostSign()) { - connectPaymentViewModel.setInitialNavigationDestination(ConnectPaymentScreenState.Explainer) - connectPaymentViewModel.isReadyToStart() - } else { - connectPaymentViewModel.navigateTo(ConnectPaymentScreenState.Connect(TransitionType.NO_ENTER_EXIT_RIGHT)) - } - - connectPaymentViewModel.navigationState.observe(this) { state -> - when (state) { - ConnectPaymentScreenState.Explainer -> - supportFragmentManager - .beginTransaction() - .replace( - R.id.container, - PostSignExplainerFragment.newInstance(ConnectPayinType.TRUSTLY), - ) - .commitAllowingStateLoss() - is ConnectPaymentScreenState.Connect -> - supportFragmentManager - .beginTransaction() - .replace( - R.id.container, - TrustlyConnectFragment.newInstance(isPostSign(), state.transitionType), - ) - .commitAllowingStateLoss() - is ConnectPaymentScreenState.Result -> - supportFragmentManager - .beginTransaction() - .replace( - R.id.container, - ConnectPaymentResultFragment.newInstance( - state.success, - ConnectPayinType.TRUSTLY, - ), - ) - .commitAllowingStateLoss() - } - } - connectPaymentViewModel.shouldClose.observe(this) { shouldClose -> - if (shouldClose) { - if (isPostSign()) { - startActivity( - LoggedInActivity.newInstance( - this, - withoutHistory = true, - ), - ) - return@observe - } - finish() - } - } - } - - private fun isPostSign() = intent.getBooleanExtra(IS_POST_SIGN, false) - - companion object { - private const val IS_POST_SIGN = "IS_POST_SIGN" - fun newInstance(context: Context, isPostSign: Boolean = false) = - Intent(context, TrustlyConnectPayinActivity::class.java).apply { - putExtra(IS_POST_SIGN, isPostSign) - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyJavascriptInterface.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyJavascriptInterface.kt deleted file mode 100644 index 13e6b144b9..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyJavascriptInterface.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.hedvig.app.feature.trustly - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri -import android.webkit.JavascriptInterface - -class TrustlyJavascriptInterface( - private val activity: Activity, -) { - @JavascriptInterface - fun openURLScheme(packageName: String, URIScheme: String): Boolean { - if (activity.isPackageInstalledAndEnabled(packageName)) { - activity.startActivityForResult( - Intent().apply { - `package` = packageName - action = Intent.ACTION_VIEW - data = Uri.parse(URIScheme) - }, - 0, - ) - return true - } - - return false - } - - companion object { - const val NAME = "TrustlyAndroid" - - private fun Context.isPackageInstalledAndEnabled(packageName: String) = - try { - packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES) - packageManager.getApplicationInfo(packageName, 0).enabled - } catch (e: PackageManager.NameNotFoundException) { - false - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyRepository.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyRepository.kt deleted file mode 100644 index 96f8844882..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.hedvig.app.feature.trustly - -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import giraffe.StartDirectDebitRegistrationMutation - -class TrustlyRepository( - private val apolloClient: ApolloClient, -) { - suspend fun startTrustlySession(): ApolloResponse = apolloClient - .mutation(StartDirectDebitRegistrationMutation()) - .execute() -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyViewModel.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyViewModel.kt deleted file mode 100644 index 83eadf025a..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyViewModel.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.hedvig.app.feature.trustly - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.hedvig.android.logger.LogPriority -import com.hedvig.android.logger.logcat -import com.hedvig.hanalytics.AppScreen -import com.hedvig.hanalytics.HAnalytics -import kotlinx.coroutines.launch - -abstract class TrustlyViewModel : ViewModel() { - protected val _data = MutableLiveData() - val data: LiveData = _data -} - -class TrustlyViewModelImpl( - private val repository: TrustlyRepository, - hAnalytics: HAnalytics, -) : TrustlyViewModel() { - init { - hAnalytics.screenView(AppScreen.CONNECT_PAYMENT_TRUSTLY) - viewModelScope.launch { - val response = runCatching { repository.startTrustlySession() } - if (response.isFailure) { - response.exceptionOrNull()?.let { logcat(LogPriority.ERROR, it) { "Trustly session failed to start" } } - return@launch - } - response.getOrNull()?.data?.startDirectDebitRegistration?.let { _data.postValue(it) } - } - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyWebChromeClient.kt b/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyWebChromeClient.kt deleted file mode 100644 index 07e1705183..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/feature/trustly/TrustlyWebChromeClient.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.hedvig.app.feature.trustly - -import android.os.Message -import android.webkit.WebChromeClient -import android.webkit.WebResourceRequest -import android.webkit.WebView -import android.webkit.WebViewClient -import androidx.browser.customtabs.CustomTabsIntent -import java.lang.ref.WeakReference - -class TrustlyWebChromeClient : WebChromeClient() { - override fun onCreateWindow( - view: WebView, - isDialog: Boolean, - isUserGesture: Boolean, - resultMsg: Message, - ): Boolean { - val tabView = WeakReference(WebView(view.context)) - tabView.get()?.let { webView -> - webView.webViewClient = object : WebViewClient() { - override fun shouldOverrideUrlLoading( - view: WebView, - request: WebResourceRequest, - ): Boolean { - CustomTabsIntent.Builder().build().let { customTab -> - customTab.launchUrl(view.context, request.url) - } - return true - } - } - (resultMsg.obj as? WebView.WebViewTransport)?.let { transport -> - transport.webView = webView - } - resultMsg.sendToTarget() - return true - } - return false - } -} diff --git a/app/app/src/main/kotlin/com/hedvig/app/ui/view/SafeWebView.kt b/app/app/src/main/kotlin/com/hedvig/app/ui/view/SafeWebView.kt deleted file mode 100644 index 8b08d8c269..0000000000 --- a/app/app/src/main/kotlin/com/hedvig/app/ui/view/SafeWebView.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.hedvig.app.ui.view - -import android.content.Context -import android.content.res.Configuration -import android.os.Build -import android.util.AttributeSet -import android.webkit.WebView - -class SafeWebView : WebView { - constructor(context: Context) : super(safeContext(context)) - constructor(context: Context, attributeSet: AttributeSet?) : super( - safeContext(context), - attributeSet, - ) - - constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super( - safeContext( - context, - ), - attributeSet, - defStyle, - ) - - companion object { - private fun safeContext(context: Context) = - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) { - context.createConfigurationContext(Configuration()) - } else { - context - } - } -} diff --git a/app/app/src/main/res/layout/connect_payment_explainer_fragment.xml b/app/app/src/main/res/layout/connect_payment_explainer_fragment.xml deleted file mode 100644 index 1517ff58a5..0000000000 --- a/app/app/src/main/res/layout/connect_payment_explainer_fragment.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - -