Skip to content

Commit f806f09

Browse files
authored
Merge pull request #435 from code-payments/feat/add-download-link-to-app
feat: add scan to download embedded QR for sharing app
2 parents b6997fb + 73ed2e7 commit f806f09

File tree

51 files changed

+2027
-152
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2027
-152
lines changed

api/src/androidTest/java/com/getcode/solana/organizer/OrganizerTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ class OrganizerTest {
141141
claimState = AccountInfo.ClaimState.Unknown,
142142
mustRotate = false,
143143
originalKinAmount = null,
144-
relationship = null
144+
relationship = null,
145+
createdAt = System.currentTimeMillis(),
145146
)
146147
)
147148
)
@@ -153,4 +154,4 @@ class OrganizerTest {
153154
}
154155
}
155156
}
156-
}
157+
}

api/src/main/java/com/getcode/manager/SessionManager.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
1414
import kotlinx.coroutines.flow.StateFlow
1515
import kotlinx.coroutines.flow.asStateFlow
1616
import kotlinx.coroutines.flow.update
17-
import timber.log.Timber
1817
import javax.inject.Inject
1918
import javax.inject.Singleton
2019

api/src/main/java/com/getcode/model/AccountInfo.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,12 @@ data class AccountInfo (
6161

6262
/// The relationship with a third party that this account has established with.
6363
/// This only applies to relevant account types (eg. RELATIONSHIP).
64-
var relationship: Relationship?
64+
var relationship: Relationship?,
65+
66+
// Time the account was created, if available. For Code accounts, this is
67+
// the time of intent submission. Otherwise, for external accounts, it is
68+
// the time created on the blockchain.
69+
var createdAt: Long?,
6570

6671
) {
6772
companion object {
@@ -105,9 +110,9 @@ data class AccountInfo (
105110
claimState = claimState,
106111
mustRotate = info.mustRotate,
107112
originalKinAmount = originalKinAmount,
108-
relationship = relationship
113+
relationship = relationship,
114+
createdAt = info.createdAt.seconds * 1000L
109115
)
110-
111116
}
112117
}
113118

@@ -259,4 +264,4 @@ val AccountInfo.unusable: Boolean
259264
false
260265
} else {
261266
managementState != AccountInfo.ManagementState.Locked
262-
}
267+
}

api/src/main/java/com/getcode/model/PrefInt.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@ import androidx.room.PrimaryKey
77
data class PrefInt(
88
@PrimaryKey val key: String,
99
val value: Long
10-
)
10+
)
11+
12+
sealed class PrefsInt(val value: String) {
13+
data object AccountCreated: PrefsInt("account_created")
14+
}

api/src/main/java/com/getcode/network/BalanceController.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ open class BalanceController @Inject constructor(
106106
val organizer = SessionManager.getOrganizer()
107107
?: return@flatMapCompletable Completable.error(IllegalStateException("Missing Organizer"))
108108

109-
scope.launch { organizer.setAccountInfo(infos) }
109+
scope.launch {
110+
organizer.setAccountInfo(infos)
111+
SessionManager.update { it.copy(organizer = organizer) }
112+
}
110113
balanceRepository.setBalance(organizer.availableBalance.toKinValueDouble())
111114
transactionReceiver.receiveFromIncomingCompletable(organizer)
112115
}
@@ -164,6 +167,7 @@ open class BalanceController @Inject constructor(
164167
SessionManager.getOrganizer() ?: throw IllegalStateException("Missing Organizer")
165168

166169
organizer.setAccountInfo(accountInfo)
170+
SessionManager.update { it.copy(organizer = organizer) }
167171
balanceRepository.setBalance(organizer.availableBalance.toKinValueDouble())
168172
transactionReceiver.receiveFromIncoming(organizer)
169173
transactionRepository.swapIfNeeded(organizer)
@@ -227,4 +231,4 @@ open class BalanceController @Inject constructor(
227231
String.format(Locale.getDefault(), "%,.2f", amount)
228232
}
229233
}
230-
}
234+
}

api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,4 @@ class BetaFlagsRepository @Inject constructor(
9696
b.takeIf { a } ?: default
9797
}
9898
}
99-
}
99+
}

api/src/main/java/com/getcode/network/repository/IdentityRepository.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import io.reactivex.rxjava3.core.Single
2424
import kotlinx.coroutines.flow.first
2525
import kotlinx.coroutines.flow.map
2626
import timber.log.Timber
27+
import java.time.Instant
2728
import java.util.Locale
2829
import java.util.concurrent.TimeUnit
2930
import javax.inject.Inject
@@ -54,7 +55,7 @@ class IdentityRepository @Inject constructor(
5455
enableDebugOptions = false,
5556
eligibleAirdrops = setOf(),
5657
isPhoneNumberLinked = true,
57-
buyModuleAvailable = false
58+
buyModuleAvailable = false,
5859
)
5960
)
6061

@@ -400,4 +401,4 @@ sealed class TwitterUserFetchError : Exception() {
400401
class UnrecognizedRequest: TwitterUserFetchError()
401402
class NotFound: TwitterUserFetchError()
402403
class FailedToParse: TwitterUserFetchError()
403-
}
404+
}

api/src/main/java/com/getcode/network/repository/PrefRepository.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class PrefRepository @Inject constructor(): CoroutineScope by CoroutineScope(Dis
3434
return observeOrDefault(key, default).firstOrNull() ?: default
3535
}
3636

37+
suspend fun get(key: PrefsInt, default: Long): Long {
38+
return observeOrDefault(key, default).firstOrNull() ?: default
39+
}
40+
3741

3842
fun getFlowable(key: PrefsString): Flowable<String> {
3943
val db = Database.getInstance() ?: return Flowable.empty()
@@ -75,6 +79,18 @@ class PrefRepository @Inject constructor(): CoroutineScope by CoroutineScope(Dis
7579
.flowOn(Dispatchers.IO)
7680
}
7781

82+
fun observeOrDefault(key: PrefsInt, default: Long): Flow<Long> {
83+
return Database.isInit
84+
.asFlow()
85+
.map { Database.getInstance() }
86+
.flatMapLatest {
87+
it ?: return@flatMapLatest flowOf(default).also { Timber.e("observe long ; DB not available") }
88+
it.prefIntDao().observe(key.value)
89+
.map { it?.value ?: default }
90+
}
91+
.flowOn(Dispatchers.IO)
92+
}
93+
7894
fun getFlowable(key: String): Flowable<Long> {
7995
val db = Database.getInstance() ?: return Flowable.empty()
8096
return db.prefIntDao().get(key)
@@ -134,4 +150,4 @@ class PrefRepository @Inject constructor(): CoroutineScope by CoroutineScope(Dis
134150
}
135151
}
136152

137-
}
153+
}

api/src/main/java/com/getcode/solana/organizer/Organizer.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.getcode.solana.organizer
22

3-
import android.content.Context
43
import com.getcode.crypt.DerivePath
5-
import com.getcode.crypt.DerivedKey
64
import com.getcode.crypt.MnemonicPhrase
75
import com.getcode.model.AccountInfo
86
import com.getcode.model.Domain
@@ -11,7 +9,6 @@ import com.getcode.model.unusable
119
import com.getcode.network.repository.getPublicKeyBase58
1210
import com.getcode.solana.keys.*
1311
import com.getcode.utils.timedTrace
14-
import com.getcode.utils.trace
1512
import timber.log.Timber
1613

1714
class Organizer(
@@ -32,6 +29,8 @@ class Organizer(
3229

3330
val isUnuseable: Boolean get() = accountInfos.any { it.value.unusable }
3431

32+
val createdAtMillis: Long? get() = accountInfos.mapNotNull { it.value.createdAt }.minOrNull()
33+
3534
val isUnlocked: Boolean
3635
get() = accountInfos.values.any { info ->
3736
info.managementState != AccountInfo.ManagementState.Locked
@@ -151,4 +150,4 @@ enum class Denomination {
151150
millions -> DerivePath.bucket1m
152151
}
153152
}
154-
}
153+
}

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ dependencies {
116116
implementation(project(":api"))
117117
implementation(project(":common:resources"))
118118
implementation(project(":common:theme"))
119+
implementation(project(":vendor:tipkit-m2"))
119120

120121
//standard libraries
121122
implementation(Libs.kotlinx_collections_immutable)

app/src/main/java/com/getcode/CodeApp.kt

Lines changed: 64 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ import com.getcode.ui.components.TopBarContainer
5353
import com.getcode.ui.utils.getActivity
5454
import com.getcode.ui.utils.getActivityScopedViewModel
5555
import com.getcode.ui.utils.measured
56+
import dev.bmcreations.tipkit.TipScaffold
57+
import dev.bmcreations.tipkit.engines.TipsEngine
5658
import kotlinx.coroutines.delay
5759

5860
@Composable
59-
fun CodeApp() {
61+
fun CodeApp(tipsEngine: TipsEngine) {
6062
val tlvm = MainRoot.getActivityScopedViewModel<TopLevelViewModel>()
6163
val activity = LocalContext.current.getActivity()
6264

@@ -65,76 +67,77 @@ fun CodeApp() {
6567
AppNavHost {
6668
val codeNavigator = LocalCodeNavigator.current
6769

68-
CodeScaffold(
69-
scaffoldState = appState.scaffoldState
70-
) { innerPaddingModifier ->
71-
72-
Navigator(
73-
screen = MainRoot,
74-
) { navigator ->
75-
appState.navigator = codeNavigator
76-
77-
LaunchedEffect(navigator.lastItem) {
78-
// update global navigator for platform access to support push/pop from a single
79-
// navigator current
80-
codeNavigator.screensNavigator = navigator
81-
}
70+
TipScaffold(tipsEngine = tipsEngine) {
71+
CodeScaffold(
72+
scaffoldState = appState.scaffoldState
73+
) { innerPaddingModifier ->
74+
Navigator(
75+
screen = MainRoot,
76+
) { navigator ->
77+
appState.navigator = codeNavigator
78+
79+
LaunchedEffect(navigator.lastItem) {
80+
// update global navigator for platform access to support push/pop from a single
81+
// navigator current
82+
codeNavigator.screensNavigator = navigator
83+
}
8284

83-
var topBarHeight by remember {
84-
mutableStateOf(0.dp)
85-
}
85+
var topBarHeight by remember {
86+
mutableStateOf(0.dp)
87+
}
8688

87-
val (isVisibleTopBar, isVisibleBackButton) = appState.isVisibleTopBar
88-
if (isVisibleTopBar && appState.currentTitle.isNotBlank()) {
89-
TitleBar(
90-
modifier = Modifier.measured { topBarHeight = it.height },
91-
title = appState.currentTitle,
92-
backButton = isVisibleBackButton,
93-
onBackIconClicked = appState::upPress
94-
)
95-
} else {
96-
topBarHeight = 0.dp
97-
}
89+
val (isVisibleTopBar, isVisibleBackButton) = appState.isVisibleTopBar
90+
if (isVisibleTopBar && appState.currentTitle.isNotBlank()) {
91+
TitleBar(
92+
modifier = Modifier.measured { topBarHeight = it.height },
93+
title = appState.currentTitle,
94+
backButton = isVisibleBackButton,
95+
onBackIconClicked = appState::upPress
96+
)
97+
} else {
98+
topBarHeight = 0.dp
99+
}
98100

99-
CompositionLocalProvider(
100-
LocalTopBarPadding provides PaddingValues(top = topBarHeight),
101-
) {
102-
Box(
103-
modifier = Modifier
104-
.padding(innerPaddingModifier)
101+
CompositionLocalProvider(
102+
LocalTopBarPadding provides PaddingValues(top = topBarHeight),
105103
) {
106-
when (navigator.lastEvent) {
107-
StackEvent.Push,
108-
StackEvent.Pop -> {
109-
when (navigator.lastItem) {
110-
is LoginScreen, is MainRoot -> CrossfadeTransition(
111-
navigator = navigator
112-
)
113-
114-
else -> SlideTransition(navigator = navigator)
104+
Box(
105+
modifier = Modifier
106+
.padding(innerPaddingModifier)
107+
) {
108+
when (navigator.lastEvent) {
109+
StackEvent.Push,
110+
StackEvent.Pop -> {
111+
when (navigator.lastItem) {
112+
is LoginScreen, is MainRoot -> CrossfadeTransition(
113+
navigator = navigator
114+
)
115+
116+
else -> SlideTransition(navigator = navigator)
117+
}
115118
}
116-
}
117119

118-
StackEvent.Idle,
119-
StackEvent.Replace -> CurrentScreen()
120+
StackEvent.Idle,
121+
StackEvent.Replace -> CurrentScreen()
122+
}
120123
}
121124
}
122-
}
123125

124-
//Listen for authentication changes here
125-
AuthCheck(
126-
navigator = codeNavigator,
127-
onNavigate = { screens ->
128-
codeNavigator.replaceAll(screens, inSheet = false)
129-
},
130-
onSwitchAccounts = { seed ->
131-
activity?.let {
132-
tlvm.logout(it) {
133-
appState.navigator.replaceAll(LoginScreen(seed))
126+
//Listen for authentication changes here
127+
AuthCheck(
128+
navigator = codeNavigator,
129+
onNavigate = { screens ->
130+
codeNavigator.replaceAll(screens, inSheet = false)
131+
},
132+
onSwitchAccounts = { seed ->
133+
activity?.let {
134+
tlvm.logout(it) {
135+
appState.navigator.replaceAll(LoginScreen(seed))
136+
}
134137
}
135138
}
136-
}
137-
)
139+
)
140+
}
138141
}
139142
}
140143
}
@@ -203,4 +206,4 @@ internal data object MainRoot : Screen {
203206
.background(CodeTheme.colors.background)
204207
)
205208
}
206-
}
209+
}

app/src/main/java/com/getcode/Locals.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.getcode
33
import androidx.compose.foundation.layout.PaddingValues
44
import androidx.compose.runtime.ProvidableCompositionLocal
55
import androidx.compose.runtime.staticCompositionLocalOf
6+
import androidx.compose.ui.graphics.painter.BitmapPainter
67
import com.getcode.analytics.AnalyticsService
78
import com.getcode.analytics.AnalyticsServiceNull
89
import com.getcode.network.exchange.Exchange
@@ -22,4 +23,5 @@ val LocalExchange: ProvidableCompositionLocal<Exchange> = staticCompositionLocal
2223
val LocalDeeplinks: ProvidableCompositionLocal<DeeplinkHandler?> = staticCompositionLocalOf { null }
2324
val LocalTopBarPadding: ProvidableCompositionLocal<PaddingValues> = staticCompositionLocalOf { PaddingValues() }
2425
val LocalBetaFlags: ProvidableCompositionLocal<BetaOptions> = staticCompositionLocalOf { BetaOptions.Defaults }
26+
val LocalDownloadQrCode: ProvidableCompositionLocal<BitmapPainter?> = staticCompositionLocalOf { null }
2527

0 commit comments

Comments
 (0)