Skip to content

Commit eee3f79

Browse files
committed
fix: scope HomeViewModel to screen only; allow immediate auto airdrops after logout -> login
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent f1467f7 commit eee3f79

8 files changed

Lines changed: 67 additions & 26 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class SessionManager @Inject constructor() {
2626
fun set(context: Context, entropyB64: String) {
2727
val mnemonic = MnemonicPhrase.fromEntropyB64(context, entropyB64)
2828
if (getOrganizer()?.mnemonic?.words == mnemonic.words
29-
&& getOrganizer()?.ownerKeyPair == authState.value?.keyPair
29+
&& getOrganizer()?.ownerKeyPair == authState.value.keyPair
3030
) return
3131
val organizer = Organizer.newInstance(
3232
context = context,
@@ -38,7 +38,7 @@ class SessionManager @Inject constructor() {
3838
entropyB64 = entropyB64,
3939
keyPair = organizer.ownerKeyPair,
4040
isAuthenticated = true,
41-
organizer = organizer
41+
organizer = organizer,
4242
)
4343
}
4444
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ class HistoryController @Inject constructor(
5151

5252
private val pagingConfig = PagingConfig(pageSize = 20)
5353

54+
55+
fun reset() {
56+
pagerMap.clear()
57+
chatFlows.clear()
58+
}
59+
5460
private fun chatMessagePager(chatId: ID) = Pager(pagingConfig) {
5561
pagerMap[chatId] ?: ChatMessagePagingSource(client, owner()!!, chatId).also {
5662
pagerMap[chatId] = it

api/src/main/java/com/getcode/network/client/Client_Transaction.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ fun Client.sendRemotely(
346346
suspend fun Client.requestFirstKinAirdrop(
347347
owner: KeyPair,
348348
): Result<KinAmount> {
349+
Timber.d("requesting airdrop")
349350
return transactionRepository.requestFirstKinAirdrop(owner)
350351
}
351352

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.getcode.network.repository
33
import com.codeinc.gen.phone.v1.PhoneVerificationService
44
import com.codeinc.gen.user.v1.IdentityService
55
import com.getcode.db.Database
6-
import com.getcode.db.InMemoryDao
76
import com.getcode.ed25519.Ed25519
87
import com.getcode.model.AirdropType
98
import com.getcode.model.PrefsBool
@@ -92,11 +91,11 @@ class IdentityRepository @Inject constructor(
9291

9392
fun getUserLocal(): Flowable<GetUserResponse> {
9493
return Flowable.zip(
95-
prefRepository.get(PrefsString.KEY_USER_ID),
96-
prefRepository.get(PrefsString.KEY_DATA_CONTAINER_ID),
97-
prefRepository.get(PrefsBool.IS_DEBUG_ALLOWED),
98-
prefRepository.get(PrefsBool.IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP),
99-
prefRepository.get(PrefsBool.IS_ELIGIBLE_GIVE_FIRST_KIN_AIRDROP),
94+
prefRepository.getFlowable(PrefsString.KEY_USER_ID),
95+
prefRepository.getFlowable(PrefsString.KEY_DATA_CONTAINER_ID),
96+
prefRepository.getFlowable(PrefsBool.IS_DEBUG_ALLOWED),
97+
prefRepository.getFlowable(PrefsBool.IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP),
98+
prefRepository.getFlowable(PrefsBool.IS_ELIGIBLE_GIVE_FIRST_KIN_AIRDROP),
10099
Flowable.just(phoneRepository.phoneLinked)
101100
) { userId, dataContainerId, isDebugAllowed, isEligibleGetFirstKinAirdrop, isEligibleGiveFirstKinAirdrop, isPhoneNumberLinked ->
102101
var eligibleAirdrops = Sets.newHashSet<AirdropType>()

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ import io.reactivex.rxjava3.schedulers.Schedulers
88
import kotlinx.coroutines.CoroutineScope
99
import kotlinx.coroutines.Dispatchers
1010
import kotlinx.coroutines.flow.Flow
11-
import kotlinx.coroutines.flow.MutableStateFlow
1211
import kotlinx.coroutines.flow.distinctUntilChanged
12+
import kotlinx.coroutines.flow.emptyFlow
13+
import kotlinx.coroutines.flow.firstOrNull
14+
import kotlinx.coroutines.flow.flatMapLatest
15+
import kotlinx.coroutines.flow.flow
1316
import kotlinx.coroutines.flow.flowOf
1417
import kotlinx.coroutines.flow.flowOn
1518
import kotlinx.coroutines.flow.map
16-
import kotlinx.coroutines.flow.onEach
19+
import kotlinx.coroutines.flow.onEmpty
1720
import kotlinx.coroutines.launch
1821
import timber.log.Timber
1922
import javax.inject.Inject
@@ -22,15 +25,31 @@ import javax.inject.Inject
2225

2326
class PrefRepository @Inject constructor(): CoroutineScope by CoroutineScope(Dispatchers.IO) {
2427

25-
fun get(key: PrefsString): Flowable<String> {
28+
suspend fun get(key: PrefsString, default: String): String {
29+
return observeOrDefault(key, default).firstOrNull() ?: default
30+
}
31+
32+
suspend fun get(key: PrefsBool, default: Boolean): Boolean {
33+
return observeOrDefault(key, default).firstOrNull() ?: default
34+
}
35+
36+
suspend fun get(key: PrefDouble, default: Double): Double {
37+
return observeOrDefault(key, default).firstOrNull() ?: default
38+
}
39+
40+
suspend fun get(key: PrefInt, default: Long): Long {
41+
return observeOrDefault(key, default).firstOrNull() ?: default
42+
}
43+
44+
fun getFlowable(key: PrefsString): Flowable<String> {
2645
val db = Database.getInstance() ?: return Flowable.empty()
2746
return db.prefStringDao().get(key.value)
2847
.subscribeOn(Schedulers.computation())
2948
.map { it.value }
3049
.distinctUntilChanged()
3150
}
3251

33-
fun get(key: PrefsBool): Flowable<Boolean> {
52+
fun getFlowable(key: PrefsBool): Flowable<Boolean> {
3453
val db = Database.getInstance() ?: return Flowable.empty()
3554
return db.prefBoolDao().get(key.value)
3655
.subscribeOn(Schedulers.computation())
@@ -39,11 +58,10 @@ class PrefRepository @Inject constructor(): CoroutineScope by CoroutineScope(Dis
3958
}
4059

4160
fun observeOrDefault(key: PrefsBool, default: Boolean): Flow<Boolean> {
42-
val db = Database.getInstance() ?: return flowOf(default)
61+
val db = Database.getInstance() ?: return flowOf(default).also { Timber.e("observe bool ; DB not available") }
4362
return db.prefBoolDao().observe(key.value)
4463
.flowOn(Dispatchers.IO)
4564
.map { it?.value ?: default }
46-
.distinctUntilChanged()
4765
}
4866

4967
fun observeOrDefault(key: PrefsString, default: String): Flow<String> {
@@ -70,7 +88,7 @@ class PrefRepository @Inject constructor(): CoroutineScope by CoroutineScope(Dis
7088
.distinctUntilChanged()
7189
}
7290

73-
fun get(key: String): Flowable<Long> {
91+
fun getFlowable(key: String): Flowable<Long> {
7492
val db = Database.getInstance() ?: return Flowable.empty()
7593
return db.prefIntDao().get(key)
7694
.subscribeOn(Schedulers.computation())
@@ -124,7 +142,10 @@ class PrefRepository @Inject constructor(): CoroutineScope by CoroutineScope(Dis
124142

125143
fun set(key: PrefsBool, value: Boolean) {
126144
launch {
127-
Database.getInstance()?.prefBoolDao()?.insert(PrefBool(key.value, value))
145+
runCatching {
146+
val db = Database.getInstance() ?: throw IllegalStateException("No DB")
147+
db.prefBoolDao().insert(PrefBool(key.value, value))
148+
}.onFailure { Timber.d(it.message) }.onSuccess { Timber.d("saved ${key.value} => $value") }
128149
}
129150
}
130151

app/src/main/java/com/getcode/manager/AuthManager.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ class AuthManager @Inject constructor(
177177
private fun fetchData(context: Context, entropyB64: String):
178178
Single<Pair<PhoneRepository.GetAssociatedPhoneNumberResponse, IdentityRepository.GetUserResponse>> {
179179

180-
var owner = SessionManager.authState.value?.keyPair
181-
if (owner == null || SessionManager.authState.value?.entropyB64 != entropyB64) {
180+
var owner = SessionManager.authState.value.keyPair
181+
if (owner == null || SessionManager.authState.value.entropyB64 != entropyB64) {
182182
owner = MnemonicPhrase.fromEntropyB64(context, entropyB64).getSolanaKeyPair(context)
183183
}
184184

@@ -200,7 +200,7 @@ class AuthManager @Inject constructor(
200200
}
201201
.flatMap {
202202
user = it
203-
if (SessionManager.authState.value?.entropyB64 != entropyB64) {
203+
if (SessionManager.authState.value.entropyB64 != entropyB64) {
204204
sessionManager.set(context, entropyB64)
205205
}
206206
balanceController.fetchBalance()
@@ -210,8 +210,6 @@ class AuthManager @Inject constructor(
210210
savePrefs(phone!!, user!!)
211211
updateFcmToken(owner, user!!.dataContainerId.toByteArray())
212212
launch { exchange.fetchRatesIfNeeded() }
213-
214-
// launch { historyController.fetchAllTransactions() }
215213
launch { historyController.fetchChats() }
216214
if (!BuildConfig.DEBUG) Bugsnag.setUser(null, phone?.phoneNumber, null)
217215
}
@@ -232,15 +230,16 @@ class AuthManager @Inject constructor(
232230
analytics.logout()
233231
sessionManager.clear()
234232
Database.close()
233+
historyController.reset()
235234
inMemoryDao.clear()
236235
Database.delete(context)
237236
if (!BuildConfig.DEBUG) Bugsnag.setUser(null, null, null)
238237
}
239-
240238
private fun savePrefs(
241239
phone: PhoneRepository.GetAssociatedPhoneNumberResponse,
242240
user: IdentityRepository.GetUserResponse
243241
) {
242+
Timber.d("saving prefs")
244243
phoneRepository.phoneNumber = phone.phoneNumber
245244
prefRepository.set(
246245
Pair(
@@ -258,6 +257,8 @@ class AuthManager @Inject constructor(
258257
PrefsBool.IS_DEBUG_ALLOWED,
259258
user.enableDebugOptions,
260259
)
260+
261+
Timber.d("airdrops eligible = ${user.eligibleAirdrops.joinToString { it.name }}")
261262
prefRepository.set(
262263
PrefsBool.IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP,
263264
user.eligibleAirdrops.contains(AirdropType.GetFirstKin),

app/src/main/java/com/getcode/navigation/screens/MainScreens.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.getcode.navigation.core.LocalCodeNavigator
1515
import com.getcode.ui.components.startupLog
1616
import com.getcode.ui.utils.RepeatOnLifecycle
1717
import com.getcode.ui.utils.getActivityScopedViewModel
18+
import com.getcode.ui.utils.getStackScopedViewModel
1819
import com.getcode.view.main.account.AccountHome
1920
import com.getcode.view.main.account.AccountSheetViewModel
2021
import com.getcode.view.main.getKin.GetKinSheet
@@ -45,7 +46,7 @@ data class HomeScreen(
4546

4647
@Composable
4748
override fun Content() {
48-
val vm = getActivityScopedViewModel<HomeViewModel>()
49+
val vm = getViewModel<HomeViewModel>()
4950
startupLog("home rendered")
5051
HomeScreen(vm, cashLink, requestPayload)
5152

app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import com.getcode.network.client.RemoteSendException
4545
import com.getcode.network.client.awaitEstablishRelationship
4646
import com.getcode.network.client.cancelRemoteSend
4747
import com.getcode.network.client.fetchLimits
48+
import com.getcode.network.client.receiveFromPrimaryIfWithinLimits
4849
import com.getcode.network.client.receiveRemoteSuspend
4950
import com.getcode.network.client.requestFirstKinAirdrop
5051
import com.getcode.network.client.sendRemotely
@@ -182,7 +183,7 @@ class HomeViewModel @Inject constructor(
182183
init {
183184
onDrawn()
184185
Database.isInit
185-
.flatMap { prefRepository.get(PrefsBool.DISPLAY_ERRORS) }
186+
.flatMap { prefRepository.getFlowable(PrefsBool.DISPLAY_ERRORS) }
186187
.subscribe(ErrorUtils::setDisplayErrors)
187188

188189
StatusRepository().getIsUpgradeRequired(BuildConfig.VERSION_CODE)
@@ -193,20 +194,31 @@ class HomeViewModel @Inject constructor(
193194
}
194195
}
195196

196-
prefRepository.observeOrDefault(PrefsBool.IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP, false)
197+
prefRepository.observeOrDefault(PrefsBool.IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP, true)
197198
.map { it }
199+
.distinctUntilChanged()
200+
.onEach { Timber.d("airdrop eligible=$it") }
198201
.filter { it }
199202
.mapNotNull { SessionManager.getKeyPair() }
200203
.catchSafely(
201204
action = { owner ->
202205
val amount = client.requestFirstKinAirdrop(owner).getOrThrow()
206+
prefRepository.set(PrefsBool.IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP, false)
203207
balanceController.fetchBalanceSuspend()
204208

209+
val organizer = SessionManager.getOrganizer()
210+
val receiveWithinLimits = organizer?.let {
211+
client.receiveFromPrimaryIfWithinLimits(it)
212+
} ?: Completable.complete()
213+
receiveWithinLimits.subscribe({}, {})
214+
205215
showToast(amount = amount, isDeposit = true)
206216

207217
historyController.fetchChats()
208-
prefRepository.set(PrefsBool.IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP, false)
209218
},
219+
onFailure = {
220+
Timber.e(t = it, message = "Auto airdrop failed")
221+
}
210222
)
211223
.launchIn(viewModelScope)
212224

0 commit comments

Comments
 (0)