Skip to content

Commit 064491d

Browse files
committed
feat: complete e2e auth flow when associating twitter
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent fa6c716 commit 064491d

17 files changed

Lines changed: 285 additions & 52 deletions

File tree

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ class SessionManager @Inject constructor(
7272
client.registerInstallation(organizer.ownerKeyPair, installationId)
7373
}
7474

75-
tipController.checkForConnection()
76-
7775
return client.updatePreferences(organizer)
7876
.onSuccess {
7977
update { it.copy(userPrefsUpdated = true) }

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ sealed class PrefsBool(val value: String) {
2020
data object IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP: PrefsBool("is_eligible_get_first_kin_airdrop")
2121
data object IS_ELIGIBLE_GIVE_FIRST_KIN_AIRDROP: PrefsBool("is_eligible_give_first_kin_airdrop")
2222
data object HAS_REMOVED_LOCAL_CURRENCY: PrefsBool("removed_local_currency")
23+
data object SEEN_TIP_CARD : PrefsBool("seen_tip_card")
2324

2425
// beta flags
2526
data object BUCKET_DEBUGGER_ENABLED: PrefsBool("debug_buckets"), BetaFlag
@@ -29,9 +30,10 @@ sealed class PrefsBool(val value: String) {
2930
data object SHOW_CONNECTIVITY_STATUS: PrefsBool("debug_no_network"), BetaFlag
3031
data object GIVE_REQUESTS_ENABLED: PrefsBool("give_requests_enabled"), BetaFlag
3132
data object BUY_KIN_ENABLED : PrefsBool("buy_kin_enabled"), BetaFlag
32-
data object ESTABLISH_CODE_RELATIONSHIP : PrefsBool("establish_code_relationship_enabled")
33-
data object CHAT_UNSUB_ENABLED: PrefsBool("chat_unsub_enabled")
34-
data object TIPS_ENABLED : PrefsBool("tips_enabled")
33+
data object ESTABLISH_CODE_RELATIONSHIP : PrefsBool("establish_code_relationship_enabled"), BetaFlag
34+
data object CHAT_UNSUB_ENABLED: PrefsBool("chat_unsub_enabled"), BetaFlag
35+
data object TIPS_ENABLED : PrefsBool("tips_enabled"), BetaFlag
36+
3537
}
3638

3739
object BetaFlags {

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

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,29 @@ package com.getcode.network
22

33
import com.getcode.manager.SessionManager
44
import com.getcode.model.CodePayload
5+
import com.getcode.model.PrefsBool
56
import com.getcode.model.PrefsString
67
import com.getcode.model.TwitterUser
78
import com.getcode.network.client.Client
89
import com.getcode.network.client.fetchTwitterUser
910
import com.getcode.network.repository.PrefRepository
11+
import com.getcode.utils.combine
1012
import com.getcode.utils.getOrPutIfNonNull
13+
import kotlinx.coroutines.CoroutineScope
14+
import kotlinx.coroutines.Dispatchers
1115
import kotlinx.coroutines.flow.Flow
16+
import kotlinx.coroutines.flow.SharingStarted
17+
import kotlinx.coroutines.flow.StateFlow
18+
import kotlinx.coroutines.flow.combine
19+
import kotlinx.coroutines.flow.filterNotNull
1220
import kotlinx.coroutines.flow.map
21+
import kotlinx.coroutines.flow.stateIn
22+
import kotlinx.coroutines.launch
1323
import timber.log.Timber
24+
import java.util.Timer
1425
import javax.inject.Inject
1526
import javax.inject.Singleton
27+
import kotlin.concurrent.fixedRateTimer
1628

1729
typealias TipUser = Pair<String, CodePayload>
1830

@@ -22,9 +34,25 @@ class TipController @Inject constructor(
2234
private val prefRepository: PrefRepository,
2335
) {
2436

25-
val connectedAccount: Flow<String?> = prefRepository.observeOrDefault(PrefsString.KEY_TWITTER_USERNAME, "")
37+
private var pollTimer: Timer? = null
38+
private var lastPoll: Long = 0L
39+
private val scope = CoroutineScope(Dispatchers.IO)
40+
41+
val connectedAccount: StateFlow<String?> = prefRepository.observeOrDefault(PrefsString.KEY_TWITTER_USERNAME, "")
2642
.map {
2743
it.ifEmpty { null }
44+
}.stateIn(
45+
scope = scope,
46+
started = SharingStarted.Eagerly,
47+
initialValue = null
48+
)
49+
50+
val showTwitterSplat: Flow<Boolean> =
51+
combine(
52+
connectedAccount,
53+
prefRepository.observeOrDefault(PrefsBool.SEEN_TIP_CARD, false)
54+
) { connected, seen ->
55+
connected != null && !seen
2856
}
2957

3058
var scannedUserData: TipUser? = null
@@ -34,19 +62,37 @@ class TipController @Inject constructor(
3462

3563
private var cachedUsers = mutableMapOf<String, TwitterUser>()
3664

37-
suspend fun checkForConnection() {
65+
private fun startPollTimer() {
66+
pollTimer?.cancel()
67+
pollTimer = fixedRateTimer("twitterPollTimer", false, 0, 1000 * 20) {
68+
scope.launch {
69+
val time = System.currentTimeMillis()
70+
val isPastThrottle = time - lastPoll > 1000 * 10 || lastPoll == 0L
71+
72+
if (connectedAccount.value == null && isPastThrottle) {
73+
callForConnectedUser()
74+
lastPoll = time
75+
}
76+
}
77+
}
78+
}
79+
80+
private suspend fun callForConnectedUser() {
3881
val tipAddress = SessionManager.getOrganizer()?.primaryVault ?: return
3982
client.fetchTwitterUser(tipAddress)
4083
.onSuccess {
4184
Timber.d("current user twitter connected @ ${it.username}")
4285
prefRepository.set(PrefsString.KEY_TWITTER_USERNAME, it.username)
4386
}
4487
.onFailure {
45-
it.printStackTrace()
4688
prefRepository.set(PrefsString.KEY_TWITTER_USERNAME, "")
4789
}
4890
}
4991

92+
fun checkForConnection() {
93+
startPollTimer()
94+
}
95+
5096
suspend fun fetch(username: String, payload: CodePayload) {
5197
val metadata = fetch(username)
5298
scannedUserData = username to payload
@@ -65,4 +111,12 @@ class TipController @Inject constructor(
65111
scannedUserData = null
66112
userMetadata = null
67113
}
114+
115+
fun clearTwitterSplat() {
116+
prefRepository.set(PrefsBool.SEEN_TIP_CARD, true)
117+
}
118+
119+
fun stopTimer() {
120+
pollTimer?.cancel()
121+
}
68122
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ data class BetaOptions(
1616
val displayErrors: Boolean = false,
1717
val remoteSendEnabled: Boolean = false,
1818
val giveRequestsEnabled: Boolean = false,
19-
val buyKinEnabled: Boolean = false,
19+
val buyKinEnabled: Boolean = true,
2020
val establishCodeRelationship: Boolean = false,
2121
val chatUnsubEnabled: Boolean = false,
2222
val tipsEnabled: Boolean = false,
@@ -42,7 +42,7 @@ class BetaFlagsRepository @Inject constructor(
4242
observeBetaFlag(PrefsBool.VIBRATE_ON_SCAN),
4343
observeBetaFlag(PrefsBool.LOG_SCAN_TIMES),
4444
observeBetaFlag(PrefsBool.GIVE_REQUESTS_ENABLED),
45-
observeBetaFlag(PrefsBool.BUY_KIN_ENABLED),
45+
observeBetaFlag(PrefsBool.BUY_KIN_ENABLED, default = true),
4646
observeBetaFlag(PrefsBool.ESTABLISH_CODE_RELATIONSHIP),
4747
observeBetaFlag(PrefsBool.CHAT_UNSUB_ENABLED),
4848
observeBetaFlag(PrefsBool.TIPS_ENABLED),

app/src/main/AndroidManifest.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@
8181
android:scheme="https" />
8282
</intent-filter>
8383

84+
<intent-filter android:autoVerify="true">
85+
<action android:name="android.intent.action.VIEW" />
86+
<category android:name="android.intent.category.DEFAULT" />
87+
<category android:name="android.intent.category.BROWSABLE" />
88+
<data
89+
android:host="@string/root_url_tipcard_no_protocol"
90+
android:pathPattern="/x/.*"
91+
android:scheme="https" />
92+
</intent-filter>
93+
8494
<intent-filter android:autoVerify="true">
8595
<action android:name="android.intent.action.VIEW" />
8696
<category android:name="android.intent.category.DEFAULT" />

app/src/main/java/com/getcode/navigation/core/CodeNavigator.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class NavigatorNull : CodeNavigator {
1717
override val sheetStackRoot: Screen? = null
1818
override val lastEvent: StackEvent = StackEvent.Idle
1919
override val isVisible: Boolean = false
20+
override val sheetFullyVisible: Boolean = false
2021
override val progress: Float = 0f
2122

2223
override var screensNavigator: Navigator? = null
@@ -26,7 +27,7 @@ class NavigatorNull : CodeNavigator {
2627
override fun hide() = Unit
2728
override fun <T> hideWithResult(result: T) = Unit
2829

29-
override fun push(item: Screen) = Unit
30+
override fun push(item: Screen, delay: Long) = Unit
3031

3132
override fun push(items: List<Screen>) = Unit
3233

@@ -60,13 +61,14 @@ interface CodeNavigator {
6061
val sheetStackRoot: Screen?
6162
val lastEvent: StackEvent
6263
val isVisible: Boolean
64+
val sheetFullyVisible: Boolean
6365
val progress: Float
6466
var screensNavigator: Navigator?
6567

6668
fun show(screen: Screen)
6769
fun hide()
6870
fun <T> hideWithResult(result: T)
69-
infix fun push(item: Screen)
71+
fun push(item: Screen, delay: Long = 0)
7072

7173
infix fun push(items: List<Screen>)
7274

app/src/main/java/com/getcode/navigation/core/CombinedNavigator.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@ import cafe.adriel.voyager.core.screen.Screen
88
import cafe.adriel.voyager.core.stack.StackEvent
99
import cafe.adriel.voyager.navigator.Navigator
1010
import com.getcode.navigation.screens.AppScreen
11+
import kotlinx.coroutines.CoroutineScope
12+
import kotlinx.coroutines.Dispatchers
13+
import kotlinx.coroutines.coroutineScope
14+
import kotlinx.coroutines.delay
15+
import kotlinx.coroutines.launch
1116
import timber.log.Timber
1217

1318
class CombinedNavigator(
1419
var sheetNavigator: BottomSheetNavigator
15-
) : CodeNavigator {
20+
) : CodeNavigator, CoroutineScope by CoroutineScope(Dispatchers.Main) {
1621
override var screensNavigator: Navigator? = null
1722

1823
override val lastItem: Screen?
@@ -31,6 +36,9 @@ class CombinedNavigator(
3136
override val isVisible: Boolean
3237
get() = sheetNavigator.isVisible
3338

39+
override val sheetFullyVisible: Boolean
40+
get() = sheetNavigator.isVisible && sheetNavigator.progress == 1f
41+
3442
override val progress: Float
3543
get() = sheetNavigator.progress
3644

@@ -59,11 +67,14 @@ class CombinedNavigator(
5967
}
6068
}
6169

62-
override fun push(item: Screen) {
63-
if (isVisible) {
64-
sheetNavigator.push(item)
65-
} else {
66-
screensNavigator?.push(item)
70+
override fun push(item: Screen, delay: Long) {
71+
launch {
72+
delay(delay)
73+
if (isVisible) {
74+
sheetNavigator.push(item)
75+
} else {
76+
screensNavigator?.push(item)
77+
}
6778
}
6879
}
6980

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.getcode.navigation.screens
22

33
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.LaunchedEffect
45
import androidx.compose.ui.platform.LocalLifecycleOwner
56
import androidx.compose.ui.res.stringResource
67
import cafe.adriel.voyager.core.screen.ScreenKey
@@ -27,12 +28,19 @@ import com.getcode.view.main.currency.CurrencySelectionSheet
2728
import com.getcode.view.main.getKin.BuyAndSellKin
2829
import com.getcode.view.main.getKin.BuyKinScreen
2930
import com.getcode.view.main.getKin.GetKinSheet
31+
import com.getcode.view.main.getKin.GetKinSheetViewModel
3032
import com.getcode.view.main.getKin.ReferFriend
3133
import com.getcode.view.main.tip.EnterTipScreen
3234
import com.getcode.view.main.tip.RequestTipScreen
35+
import kotlinx.coroutines.delay
36+
import kotlinx.coroutines.flow.delayFlow
37+
import kotlinx.coroutines.flow.filterIsInstance
38+
import kotlinx.coroutines.flow.launchIn
39+
import kotlinx.coroutines.flow.onEach
40+
import kotlinx.coroutines.flow.onStart
3341
import kotlinx.parcelize.IgnoredOnParcel
3442
import kotlinx.parcelize.Parcelize
35-
43+
import timber.log.Timber
3644

3745

3846
@Parcelize
@@ -418,6 +426,7 @@ data object GetKinModal : MainGraph, ModalRoot {
418426
override fun Content() {
419427
val navigator = LocalCodeNavigator.current
420428

429+
val viewModel = getViewModel<GetKinSheetViewModel>()
421430
ModalContainer(
422431
closeButton = {
423432
if (navigator.isVisible) {
@@ -427,7 +436,7 @@ data object GetKinModal : MainGraph, ModalRoot {
427436
}
428437
},
429438
) {
430-
GetKinSheet(getViewModel())
439+
GetKinSheet(viewModel)
431440
}
432441

433442
AnalyticsScreenWatcher(

app/src/main/java/com/getcode/view/MainActivity.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.getcode.LocalPhoneFormatter
1616
import com.getcode.analytics.AnalyticsService
1717
import com.getcode.manager.AuthManager
1818
import com.getcode.manager.SessionManager
19+
import com.getcode.network.TipController
1920
import com.getcode.network.client.Client
2021
import com.getcode.network.exchange.Exchange
2122
import com.getcode.network.repository.PrefRepository
@@ -45,6 +46,9 @@ class MainActivity : FragmentActivity() {
4546
@Inject
4647
lateinit var client: Client
4748

49+
@Inject
50+
lateinit var tipController: TipController
51+
4852
@Inject
4953
lateinit var analyticsManager: AnalyticsService
5054

@@ -111,11 +115,13 @@ class MainActivity : FragmentActivity() {
111115
override fun onResume() {
112116
super.onResume()
113117
client.startTimer()
118+
tipController.checkForConnection()
114119
}
115120

116121
override fun onPause() {
117122
super.onPause()
118123
client.stopTimer()
124+
tipController.stopTimer()
119125
}
120126

121127
private fun setFullscreen() {

app/src/main/java/com/getcode/view/main/getKin/GetKinSheet.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import androidx.compose.foundation.layout.padding
1212
import androidx.compose.foundation.layout.size
1313
import androidx.compose.material.Text
1414
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.LaunchedEffect
1516
import androidx.compose.runtime.collectAsState
1617
import androidx.compose.runtime.getValue
18+
import androidx.compose.runtime.snapshotFlow
1719
import androidx.compose.ui.Alignment
1820
import androidx.compose.ui.Modifier
1921
import androidx.compose.ui.graphics.Color
@@ -35,6 +37,18 @@ import com.getcode.theme.White05
3537
import com.getcode.ui.components.CodeCircularProgressIndicator
3638
import com.getcode.ui.utils.addIf
3739
import com.getcode.ui.utils.rememberedClickable
40+
import kotlinx.coroutines.delay
41+
import kotlinx.coroutines.flow.debounce
42+
import kotlinx.coroutines.flow.delayFlow
43+
import kotlinx.coroutines.flow.filter
44+
import kotlinx.coroutines.flow.filterIsInstance
45+
import kotlinx.coroutines.flow.flatMapLatest
46+
import kotlinx.coroutines.flow.launchIn
47+
import kotlinx.coroutines.flow.onEach
48+
import kotlinx.coroutines.flow.onStart
49+
import kotlinx.coroutines.flow.sample
50+
import kotlinx.coroutines.flow.take
51+
import timber.log.Timber
3852

3953
data class GetKinItem(
4054
val imageResId: Int,
@@ -102,6 +116,16 @@ fun GetKinSheet(
102116
),
103117
)
104118

119+
LaunchedEffect(viewModel) {
120+
viewModel.eventFlow
121+
.filterIsInstance<GetKinSheetViewModel.Event.ShowConnectionSuccess>()
122+
.flatMapLatest { snapshotFlow { navigator.sheetFullyVisible } }
123+
.filter { it }
124+
.onEach {
125+
navigator.push(RequestTip, delay = 150)
126+
}.launchIn(this)
127+
}
128+
105129
ConstraintLayout(
106130
modifier = Modifier
107131
.fillMaxWidth()

0 commit comments

Comments
 (0)