Skip to content

Commit 61d2590

Browse files
committed
feat(flipcash): add deposit menu flow
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 40d135a commit 61d2590

13 files changed

Lines changed: 371 additions & 36 deletions

File tree

apps/flipcash/app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ dependencies {
127127
implementation(project(":apps:flipcash:features:send"))
128128
implementation(project(":apps:flipcash:features:menu"))
129129
implementation(project(":apps:flipcash:features:lab"))
130+
implementation(project(":apps:flipcash:features:deposit"))
130131

131132
implementation(project(":libs:datetime"))
132133
implementation(project(":libs:locale:bindings"))

apps/flipcash/app/src/main/kotlin/com/flipcash/app/App.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,9 @@ private fun AppNavHost(content: @Composable () -> Unit) {
196196
sheetBackgroundColor = LocalCodeColors.current.background,
197197
sheetContentColor = LocalCodeColors.current.onBackground,
198198
sheetContent = { sheetNav ->
199-
combinedNavigator = combinedNavigator?.apply { sheetNavigator = sheetNav }
200-
?: CombinedNavigator(sheetNav)
199+
if (combinedNavigator == null) {
200+
combinedNavigator = CombinedNavigator(sheetNav)
201+
}
201202
combinedNavigator?.let {
202203
CompositionLocalProvider(LocalCodeNavigator provides it) {
203204
SheetSlideTransition(navigator = it)
@@ -207,8 +208,9 @@ private fun AppNavHost(content: @Composable () -> Unit) {
207208
},
208209
onHide = ModalManager::clear
209210
) { sheetNav ->
210-
combinedNavigator =
211-
combinedNavigator?.apply { sheetNavigator = sheetNav } ?: CombinedNavigator(sheetNav)
211+
if (combinedNavigator == null) {
212+
combinedNavigator = CombinedNavigator(sheetNav)
213+
}
212214
combinedNavigator?.let {
213215
CompositionLocalProvider(LocalCodeNavigator provides it) {
214216
content()

apps/flipcash/app/src/main/kotlin/com/flipcash/app/ui/navigation/AppScreenContent.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.flipcash.app.balance.BalanceScreen
77
import com.flipcash.app.balance.PreloadBalance
88
import com.flipcash.app.core.NavScreenProvider
99
import com.flipcash.app.currency.CurrencySelectionModal
10+
import com.flipcash.app.deposit.DepositScreen
1011
import com.flipcash.app.give.GiveScreen
1112
import com.flipcash.app.lab.LabScreen
1213
import com.flipcash.app.login.accesskey.AccessKeyScreen
@@ -74,6 +75,10 @@ fun AppScreenContent(content: @Composable () -> Unit) {
7475
register<NavScreenProvider.HomeScreen.Menu.Lab> {
7576
LabScreen()
7677
}
78+
79+
register<NavScreenProvider.HomeScreen.Menu.Deposit> {
80+
DepositScreen()
81+
}
7782
}
7883

7984
PreloadBalance()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build/
2+
.gradle/
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
plugins {
2+
id(Plugins.android_library)
3+
id(Plugins.kotlin_android)
4+
id(Plugins.kotlin_ksp)
5+
id(Plugins.hilt)
6+
id(Plugins.kotlin_parcelize)
7+
id(Plugins.jetbrains_compose_compiler)
8+
}
9+
10+
android {
11+
namespace = "${Android.flipcashNamespace}.features.deposit"
12+
compileSdk = Android.compileSdkVersion
13+
defaultConfig {
14+
minSdk = Android.minSdkVersion
15+
targetSdk = Android.targetSdkVersion
16+
buildToolsVersion = Android.buildToolsVersion
17+
testInstrumentationRunner = Android.testInstrumentationRunner
18+
}
19+
20+
kotlinOptions {
21+
jvmTarget = Versions.java
22+
freeCompilerArgs += listOf(
23+
"-opt-in=kotlin.ExperimentalUnsignedTypes",
24+
"-opt-in=kotlin.RequiresOptIn"
25+
)
26+
}
27+
28+
java {
29+
toolchain {
30+
languageVersion.set(JavaLanguageVersion.of(Versions.java))
31+
}
32+
}
33+
34+
buildFeatures {
35+
buildConfig = true
36+
compose = true
37+
}
38+
}
39+
40+
dependencies {
41+
implementation(Libs.inject)
42+
implementation(Libs.hilt)
43+
ksp(Libs.hilt_android_compiler)
44+
ksp(Libs.hilt_compiler)
45+
46+
implementation(platform(Libs.compose_bom))
47+
implementation(Libs.compose_ui)
48+
implementation(Libs.compose_foundation)
49+
implementation(Libs.compose_material)
50+
implementation(Libs.compose_materialIconsExtended)
51+
52+
implementation(project(":apps:flipcash:core"))
53+
implementation(project(":libs:logging"))
54+
implementation(project(":libs:messaging"))
55+
56+
implementation(project(":services:flipcash"))
57+
58+
implementation(project(":ui:analytics"))
59+
implementation(project(":ui:core"))
60+
implementation(project(":ui:components"))
61+
implementation(project(":ui:navigation"))
62+
implementation(project(":ui:resources"))
63+
implementation(project(":ui:theme"))
64+
65+
implementation(Libs.rinku_compose)
66+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.flipcash.app.deposit
2+
3+
import android.os.Parcelable
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.fillMaxSize
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.Alignment
8+
import androidx.compose.ui.Modifier
9+
import androidx.compose.ui.res.stringResource
10+
import cafe.adriel.voyager.core.screen.ScreenKey
11+
import cafe.adriel.voyager.core.screen.uniqueScreenKey
12+
import cafe.adriel.voyager.hilt.getViewModel
13+
import com.flipcash.app.deposit.internal.DepositScreen
14+
import com.flipcash.app.deposit.internal.DepositViewModel
15+
import com.flipcash.core.R
16+
import com.getcode.navigation.core.LocalCodeNavigator
17+
import com.getcode.navigation.modal.ModalScreen
18+
import com.getcode.navigation.screens.NamedScreen
19+
import com.getcode.ui.components.AppBarWithTitle
20+
import kotlinx.parcelize.IgnoredOnParcel
21+
import kotlinx.parcelize.Parcelize
22+
23+
@Parcelize
24+
class DepositScreen : ModalScreen, NamedScreen, Parcelable {
25+
@IgnoredOnParcel
26+
override val key: ScreenKey = uniqueScreenKey
27+
28+
override val name: String
29+
@Composable get() = stringResource(R.string.title_depositUsdc)
30+
@Composable
31+
override fun ModalContent() {
32+
val navigator = LocalCodeNavigator.current
33+
34+
val viewModel = getViewModel<DepositViewModel>()
35+
36+
Column(
37+
modifier = Modifier.fillMaxSize(),
38+
horizontalAlignment = Alignment.CenterHorizontally,
39+
) {
40+
AppBarWithTitle(
41+
title = name,
42+
isInModal = true,
43+
titleAlignment = Alignment.CenterHorizontally,
44+
backButton = true,
45+
onBackIconClicked = { navigator.pop() },
46+
)
47+
DepositScreen(viewModel)
48+
}
49+
}
50+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.flipcash.app.deposit.internal
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.border
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.foundation.layout.height
8+
import androidx.compose.foundation.layout.navigationBarsPadding
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.material.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.getValue
13+
import androidx.compose.ui.Alignment
14+
import androidx.compose.ui.Modifier
15+
import androidx.compose.ui.draw.clip
16+
import androidx.compose.ui.res.stringResource
17+
import androidx.compose.ui.text.style.TextAlign
18+
import androidx.compose.ui.text.style.TextOverflow
19+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
20+
import com.flipcash.features.deposit.R
21+
import com.getcode.theme.CodeTheme
22+
import com.getcode.theme.White
23+
import com.getcode.theme.White05
24+
import com.getcode.theme.extraSmall
25+
import com.getcode.ui.core.rememberedClickable
26+
import com.getcode.ui.theme.ButtonState
27+
import com.getcode.ui.theme.CodeButton
28+
import com.getcode.ui.theme.CodeScaffold
29+
30+
@Composable
31+
internal fun DepositScreen(viewModel: DepositViewModel) {
32+
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
33+
DepositScreenContent(state, viewModel::dispatchEvent)
34+
}
35+
36+
@Composable
37+
private fun DepositScreenContent(state: DepositViewModel.State, dispatchEvent: (DepositViewModel.Event) -> Unit) {
38+
CodeScaffold(
39+
topBar = {
40+
Text(
41+
modifier = Modifier
42+
.padding(horizontal = CodeTheme.dimens.inset)
43+
.fillMaxWidth(),
44+
text = stringResource(R.string.subtitle_howToDeposit),
45+
color = CodeTheme.colors.textSecondary,
46+
style = CodeTheme.typography.textSmall.copy(textAlign = TextAlign.Center,),
47+
)
48+
},
49+
bottomBar = {
50+
CodeButton(
51+
modifier = Modifier
52+
.fillMaxWidth()
53+
.navigationBarsPadding()
54+
.padding(horizontal = CodeTheme.dimens.inset)
55+
.padding(bottom = CodeTheme.dimens.grid.x2),
56+
onClick = {
57+
dispatchEvent(DepositViewModel.Event.CopyAddress)
58+
},
59+
text = stringResource(if (state.isCopied) R.string.action_copied else R.string.action_copyAddress),
60+
enabled = !state.isCopied,
61+
isSuccess = state.isCopied,
62+
buttonState = ButtonState.Filled,
63+
)
64+
}
65+
) { padding ->
66+
Row(
67+
modifier = Modifier
68+
.padding(padding)
69+
.padding(horizontal = CodeTheme.dimens.inset)
70+
.padding(vertical = CodeTheme.dimens.grid.x3)
71+
.clip(CodeTheme.shapes.extraSmall)
72+
.border(
73+
width = CodeTheme.dimens.border,
74+
color = CodeTheme.colors.brandLight,
75+
shape = CodeTheme.shapes.extraSmall
76+
)
77+
.fillMaxWidth()
78+
.height(CodeTheme.dimens.grid.x10)
79+
.background(White05)
80+
.rememberedClickable { dispatchEvent(DepositViewModel.Event.CopyAddress) }
81+
.padding(CodeTheme.dimens.grid.x2),
82+
) {
83+
Text(
84+
modifier = Modifier
85+
.align(Alignment.CenterVertically)
86+
.weight(1f)
87+
.padding(top = CodeTheme.dimens.grid.x1),
88+
text = state.depositAddress,
89+
color = White,
90+
style = CodeTheme.typography.textMedium.copy(textAlign = TextAlign.Center),
91+
overflow = TextOverflow.MiddleEllipsis,
92+
maxLines = 1
93+
)
94+
}
95+
}
96+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.flipcash.app.deposit.internal
2+
3+
import androidx.lifecycle.viewModelScope
4+
import com.flipcash.services.user.UserManager
5+
import com.getcode.solana.keys.base58
6+
import com.getcode.view.BaseViewModel2
7+
import dagger.hilt.android.lifecycle.HiltViewModel
8+
import kotlinx.coroutines.delay
9+
import kotlinx.coroutines.flow.filterIsInstance
10+
import kotlinx.coroutines.flow.launchIn
11+
import kotlinx.coroutines.flow.map
12+
import kotlinx.coroutines.flow.mapNotNull
13+
import kotlinx.coroutines.flow.onEach
14+
import javax.inject.Inject
15+
import kotlin.time.Duration.Companion.seconds
16+
17+
@HiltViewModel
18+
internal class DepositViewModel @Inject constructor(
19+
userManager: UserManager
20+
) : BaseViewModel2<DepositViewModel.State, DepositViewModel.Event>(
21+
initialState = State(),
22+
updateStateForEvent = updateStateForEvent
23+
) {
24+
internal data class State(
25+
val depositAddress: String = "",
26+
val isCopied: Boolean = false
27+
)
28+
29+
internal sealed interface Event {
30+
data class OnDepositAddressChanged(val address: String) : Event
31+
data object CopyAddress : Event
32+
data class SetCopied(val isCopied: Boolean) : Event
33+
}
34+
35+
init {
36+
userManager.state
37+
.mapNotNull { it.cluster?.depositAddress?.base58() }
38+
.onEach { address -> dispatchEvent(Event.OnDepositAddressChanged(address)) }
39+
.launchIn(viewModelScope)
40+
41+
eventFlow
42+
.filterIsInstance<Event.CopyAddress>()
43+
.onEach {
44+
dispatchEvent(Event.SetCopied(true))
45+
delay(2.seconds)
46+
dispatchEvent(Event.SetCopied(false))
47+
}.launchIn(viewModelScope)
48+
}
49+
50+
internal companion object {
51+
val updateStateForEvent: (Event) -> ((State) -> State) = { event ->
52+
when (event) {
53+
is Event.OnDepositAddressChanged -> { state ->
54+
state.copy(depositAddress = event.address)
55+
}
56+
57+
is Event.CopyAddress -> { state -> state }
58+
is Event.SetCopied -> { state -> state.copy(isCopied = event.isCopied) }
59+
}
60+
}
61+
}
62+
}

apps/flipcash/features/give/src/main/kotlin/com/flipcash/app/give/internal/GiveScreenViewModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.getcode.utils.replaceParam
2323
import com.getcode.view.BaseViewModel2
2424
import com.getcode.view.LoadingSuccessState
2525
import dagger.hilt.android.lifecycle.HiltViewModel
26+
import kotlinx.coroutines.Dispatchers
2627
import kotlinx.coroutines.flow.combine
2728
import kotlinx.coroutines.flow.filter
2829
import kotlinx.coroutines.flow.filterIsInstance
@@ -133,7 +134,7 @@ internal class GiveScreenViewModel @Inject constructor(
133134
init {
134135
numberInputHelper.reset()
135136

136-
viewModelScope.launch {
137+
viewModelScope.launch(Dispatchers.IO) {
137138
exchange.fetchRatesIfNeeded()
138139
}
139140

apps/flipcash/features/menu/src/main/kotlin/com/flipcash/app/menu/MenuScreen.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,13 @@ class MenuScreen : ModalScreen, Parcelable {
5252
navigator.replaceAll(ScreenRegistry.get(NavScreenProvider.Login.Home(it))) }
5353
.launchIn(this)
5454
}
55+
56+
LaunchedEffect(viewModel) {
57+
viewModel.eventFlow
58+
.filterIsInstance<MenuScreenViewModel.Event.OnDepositClicked>()
59+
.onEach {
60+
navigator.push(ScreenRegistry.get(NavScreenProvider.HomeScreen.Menu.Deposit)) }
61+
.launchIn(this)
62+
}
5563
}
5664
}

0 commit comments

Comments
 (0)