Skip to content

Commit 9276ea1

Browse files
committed
Merge remote-tracking branch 'origin/main' into HEAD
* origin/main: Setup v1.1.296 (code-payments#12) chore: allow screens to track own screen views on composition (code-payments#8)
2 parents b56b177 + bd1440f commit 9276ea1

24 files changed

+318
-191
lines changed

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,40 @@ import timber.log.Timber
1414
import javax.inject.Inject
1515
import javax.inject.Singleton
1616

17+
interface AnalyticsService {
18+
fun open(screen: AnalyticsManager.Screen)
19+
fun login(ownerPublicKey: String, autoCompleteCount: Int, inputChangeCount: Int)
20+
fun logout()
21+
fun track(event: AnalyticsManager.Name, vararg properties: Pair<AnalyticsManager.Property, String>)
22+
}
23+
24+
class AnalyticsServiceNull : AnalyticsService {
25+
override fun open(screen: AnalyticsManager.Screen) = Unit
26+
27+
override fun login(ownerPublicKey: String, autoCompleteCount: Int, inputChangeCount: Int) = Unit
28+
29+
override fun logout() = Unit
30+
31+
override fun track(
32+
event: AnalyticsManager.Name,
33+
vararg properties: Pair<AnalyticsManager.Property, String>
34+
) = Unit
35+
}
36+
1737
@Singleton
18-
class AnalyticsManager @Inject constructor(private val mixpanelAPI: MixpanelAPI) {
38+
class AnalyticsManager @Inject constructor(private val mixpanelAPI: MixpanelAPI): AnalyticsService {
1939
private var grabStartMillis: Long = 0L
2040
private var cashLinkGrabStartMillis: Long = 0L
2141

22-
fun open(screen: Screen) {
42+
override fun open(screen: Screen) {
2343
track(Name.Open, Pair(Property.Screen, screen.value))
2444
}
2545

26-
fun logout() {
46+
override fun logout() {
2747
track(Name.Logout)
2848
}
2949

30-
fun login(ownerPublicKey: String, autoCompleteCount: Int, inputChangeCount: Int) {
50+
override fun login(ownerPublicKey: String, autoCompleteCount: Int, inputChangeCount: Int) {
3151
track(
3252
Name.Login,
3353
Pair(Property.OwnerPublicKey, ownerPublicKey),
@@ -151,8 +171,11 @@ class AnalyticsManager @Inject constructor(private val mixpanelAPI: MixpanelAPI)
151171
Timber.i("Bill scanned. From start: " + (System.currentTimeMillis() - (timeAppInit ?: 0)))
152172
}
153173

154-
private fun track(event: Name, vararg properties: Pair<Property, String>) {
155-
if (BuildConfig.DEBUG) return //no logging in debug
174+
override fun track(event: Name, vararg properties: Pair<Property, String>) {
175+
if (BuildConfig.DEBUG) {
176+
Timber.d("debug track $event, ${properties.map { "${it.first.name}, ${it.second}" }}")
177+
return
178+
} //no logging in debug
156179

157180
val jsonObject = JSONObject()
158181
properties.forEach { property ->

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ android {
2020

2121
defaultConfig {
2222
applicationId = "com.getcode"
23-
versionCode = 295
23+
versionCode = 296
2424
versionName = "1.1.$versionCode"
2525

2626
minSdk = Android.minSdkVersion
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.getcode
2+
3+
import androidx.compose.runtime.ProvidableCompositionLocal
4+
import androidx.compose.runtime.staticCompositionLocalOf
5+
import com.getcode.manager.AnalyticsService
6+
import com.getcode.manager.AnalyticsServiceNull
7+
8+
val LocalAnalytics: ProvidableCompositionLocal<AnalyticsService> = staticCompositionLocalOf { AnalyticsServiceNull() }
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.getcode.analytics
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.lifecycle.LifecycleOwner
5+
import com.getcode.manager.AnalyticsManager
6+
7+
@Composable
8+
fun AnalyticsScreenWatcher(
9+
lifecycleOwner: LifecycleOwner,
10+
event: AnalyticsManager.Screen,
11+
) {
12+
AnalyticsWatcher(lifecycleOwner = lifecycleOwner, onEvent = { analytics, context ->
13+
analytics.open(event)
14+
})
15+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.getcode.analytics
2+
3+
import android.content.Context
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.getValue
6+
import androidx.compose.runtime.rememberUpdatedState
7+
import androidx.compose.ui.platform.LocalContext
8+
import androidx.lifecycle.Lifecycle
9+
import androidx.lifecycle.LifecycleOwner
10+
import com.getcode.LocalAnalytics
11+
import com.getcode.manager.AnalyticsService
12+
import com.getcode.util.RepeatOnLifecycle
13+
14+
@Composable
15+
fun AnalyticsWatcher(
16+
lifecycleOwner: LifecycleOwner,
17+
onEvent: (AnalyticsService, Context) -> Unit,
18+
onDispose: (AnalyticsService, Context) -> Unit = { _, _ -> },
19+
) {
20+
val context = LocalContext.current
21+
val analyticsService = LocalAnalytics.current
22+
23+
val updatedOnEvent by rememberUpdatedState(newValue = onEvent)
24+
val updatedOnDispose by rememberUpdatedState(newValue = onDispose)
25+
26+
RepeatOnLifecycle(
27+
lifecycleOwner = lifecycleOwner,
28+
targetState = Lifecycle.State.RESUMED,
29+
doOnDispose = {
30+
updatedOnDispose(analyticsService, context)
31+
},
32+
) {
33+
updatedOnEvent(analyticsService, context)
34+
}
35+
}

app/src/main/java/com/getcode/navigation/BottomSheetNavigator.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,19 @@ fun BottomSheetNavigator(
102102
}
103103

104104
@OptIn(ExperimentalMaterialApi::class)
105-
public class BottomSheetNavigator @InternalVoyagerApi constructor(
105+
class BottomSheetNavigator @InternalVoyagerApi constructor(
106106
private val navigator: Navigator,
107107
private val sheetState: ModalBottomSheetState,
108108
private val coroutineScope: CoroutineScope
109109
) : Stack<Screen> by navigator {
110110

111-
public val isVisible: Boolean
111+
val isVisible: Boolean
112112
get() = sheetState.isVisible
113113

114-
public fun show(screen: Screen) {
114+
val progress: Float
115+
get() = sheetState.progress
116+
117+
fun show(screen: Screen) {
115118
coroutineScope.launch {
116119
replaceAll(screen)
117120
sheetState.show()

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ package com.getcode.navigation
33
import androidx.compose.foundation.layout.Column
44
import androidx.compose.foundation.layout.fillMaxHeight
55
import androidx.compose.foundation.layout.fillMaxWidth
6+
import androidx.compose.foundation.layout.padding
67
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.derivedStateOf
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.runtime.remember
711
import androidx.compose.ui.Modifier
812
import androidx.compose.ui.res.stringResource
13+
import androidx.compose.ui.unit.dp
914
import cafe.adriel.voyager.core.screen.Screen
1015
import cafe.adriel.voyager.core.screen.ScreenKey
1116
import cafe.adriel.voyager.core.screen.uniqueScreenKey
@@ -41,13 +46,23 @@ data object AccountModal: MainGraph, ModalRoot {
4146
override fun Content() {
4247
val navigator = LocalCodeNavigator.current
4348
val viewModel = getViewModel<AccountSheetViewModel>()
49+
50+
val showClose by remember(navigator.progress, navigator.lastItem) {
51+
derivedStateOf {
52+
// show if navigating open
53+
if (navigator.progress > 0f && !navigator.isVisible) return@derivedStateOf true
54+
// otherwise only show if actively on screen
55+
navigator.lastItem is ModalRoot
56+
}
57+
}
4458
Column(
4559
modifier = Modifier
4660
.fillMaxWidth()
4761
.fillMaxHeight(sheetHeight)
4862
) {
4963
SheetTitle(
50-
closeButton = navigator.lastItem is AccountModal,
64+
modifier = Modifier.padding(horizontal = 20.dp),
65+
closeButton = showClose,
5166
onCloseIconClicked = { navigator.hide() })
5267
AccountHome(viewModel = viewModel)
5368
}
@@ -69,6 +84,7 @@ data object FaqScreen: MainGraph, NamedScreen {
6984
.fillMaxHeight(sheetHeight)
7085
) {
7186
SheetTitle(
87+
modifier = Modifier.padding(horizontal = 20.dp),
7288
title = name,
7389
// hide while transitioning to/from other destinations
7490
backButton = navigator.lastItem is FaqScreen,

app/src/main/java/com/getcode/navigation/Navigator.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ val LocalCodeNavigator: ProvidableCompositionLocal<CodeNavigator> =
1515
class NavigatorNull : CodeNavigator {
1616
override val lastItem: Screen? = null
1717
override val isVisible: Boolean = false
18+
override val progress: Float = 0f
1819

1920
override var screensNavigator: Navigator? = null
2021

@@ -43,6 +44,7 @@ class NavigatorNull : CodeNavigator {
4344
interface CodeNavigator {
4445
val lastItem: Screen?
4546
val isVisible: Boolean
47+
val progress: Float
4648
var screensNavigator: Navigator?
4749
fun show(screen: Screen)
4850
fun hide()
@@ -68,12 +70,16 @@ class CombinedNavigator(
6870
) : CodeNavigator {
6971
override var screensNavigator: Navigator? = null
7072

73+
7174
override val lastItem: Screen?
7275
get() = if (isVisible) sheetNavigator.lastItemOrNull else screensNavigator?.lastItemOrNull
7376

7477
override val isVisible: Boolean
7578
get() = sheetNavigator.isVisible
7679

80+
override val progress: Float
81+
get() = sheetNavigator.progress
82+
7783

7884
override fun show(screen: Screen) {
7985
sheetNavigator.show(screen)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.getcode.util
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.DisposableEffect
5+
import androidx.compose.ui.platform.LocalLifecycleOwner
6+
import androidx.lifecycle.Lifecycle
7+
import androidx.lifecycle.LifecycleOwner
8+
import androidx.lifecycle.lifecycleScope
9+
import androidx.lifecycle.repeatOnLifecycle
10+
import kotlinx.coroutines.launch
11+
12+
@Composable
13+
fun RepeatOnLifecycle(
14+
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
15+
targetState: Lifecycle.State,
16+
doOnDispose: () -> Unit = {},
17+
action: suspend () -> Unit,
18+
) {
19+
DisposableEffect(lifecycleOwner) {
20+
val job = lifecycleOwner.lifecycleScope.launch {
21+
lifecycleOwner.repeatOnLifecycle(targetState) {
22+
action()
23+
}
24+
}
25+
onDispose {
26+
job.cancel()
27+
doOnDispose()
28+
}
29+
}
30+
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.os.Bundle
66
import android.os.Debug
77
import androidx.activity.ComponentActivity
88
import androidx.activity.compose.setContent
9+
import androidx.compose.runtime.CompositionLocalProvider
910
import androidx.compose.runtime.DisposableEffect
1011
import androidx.compose.runtime.rememberUpdatedState
1112
import androidx.core.util.Consumer
@@ -15,6 +16,7 @@ import androidx.navigation.NavController
1516
import androidx.navigation.NavHostController
1617
import androidx.navigation.findNavController
1718
import com.getcode.CodeApp
19+
import com.getcode.LocalAnalytics
1820
import com.getcode.manager.AnalyticsManager
1921
import com.getcode.manager.AuthManager
2022
import com.getcode.manager.SessionManager
@@ -76,8 +78,10 @@ class MainActivity : FragmentActivity() {
7678
authManager.init(this)
7779
setFullscreen()
7880
setContent {
79-
// currentNavHostController = appState.navController
80-
CodeApp()
81+
// currentNavHostController = appState.navController
82+
CompositionLocalProvider(LocalAnalytics provides analyticsManager) {
83+
CodeApp()
84+
}
8185
}
8286
}
8387

app/src/main/java/com/getcode/view/main/account/AccountAccessKey.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
1313
import androidx.compose.ui.graphics.asImageBitmap
1414
import androidx.compose.ui.layout.ContentScale
1515
import androidx.compose.ui.platform.LocalContext
16+
import androidx.compose.ui.platform.LocalLifecycleOwner
1617
import androidx.compose.ui.res.stringResource
1718
import androidx.compose.ui.text.style.TextAlign
1819
import androidx.compose.ui.unit.dp
@@ -22,6 +23,8 @@ import androidx.hilt.navigation.compose.hiltViewModel
2223
import androidx.navigation.NavController
2324
import com.getcode.App
2425
import com.getcode.R
26+
import com.getcode.analytics.AnalyticsScreenWatcher
27+
import com.getcode.manager.AnalyticsManager
2528
import com.getcode.manager.TopBarManager
2629
import com.getcode.theme.BrandLight
2730
import com.getcode.util.IntentUtils
@@ -76,6 +79,11 @@ fun AccountAccessKey(navController: NavController) {
7679
}
7780
}
7881

82+
AnalyticsScreenWatcher(
83+
lifecycleOwner = LocalLifecycleOwner.current,
84+
event = AnalyticsManager.Screen.Backup
85+
)
86+
7987
ConstraintLayout(
8088
modifier = Modifier
8189
.fillMaxSize()

app/src/main/java/com/getcode/view/main/account/AccountDeposit.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ import androidx.compose.ui.Alignment
1010
import androidx.compose.ui.Modifier
1111
import androidx.compose.ui.draw.clip
1212
import androidx.compose.ui.platform.LocalClipboardManager
13+
import androidx.compose.ui.platform.LocalLifecycleOwner
1314
import androidx.compose.ui.res.stringResource
1415
import androidx.compose.ui.text.AnnotatedString
1516
import androidx.compose.ui.text.style.TextAlign
1617
import androidx.compose.ui.unit.dp
1718
import com.getcode.R
19+
import com.getcode.analytics.AnalyticsScreenWatcher
20+
import com.getcode.manager.AnalyticsManager
1821
import com.getcode.manager.SessionManager
1922
import com.getcode.solana.organizer.AccountType
2023
import com.getcode.theme.*
@@ -32,6 +35,11 @@ fun AccountDeposit() {
3235
val localClipboardManager = LocalClipboardManager.current
3336
var isCopied by remember { mutableStateOf(false) }
3437

38+
AnalyticsScreenWatcher(
39+
lifecycleOwner = LocalLifecycleOwner.current,
40+
event = AnalyticsManager.Screen.Deposit
41+
)
42+
3543
Column(
3644
modifier = Modifier
3745
.background(Brand)

app/src/main/java/com/getcode/view/main/account/AccountDetails.kt

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,6 @@ fun AccountDetails(
2222
viewModel: AccountSheetViewModel = hiltViewModel(),
2323
) {
2424
val dataState by viewModel.stateFlow.collectAsState()
25-
val context = LocalContext.current
26-
27-
fun onPage(page: AccountPage) {
28-
onPageSelected(page)
29-
viewModel.dispatchEvent(AccountSheetViewModel.Event.Navigate(page))
30-
}
3125

3226
Box(modifier = Modifier.fillMaxHeight()) {
3327
Column(
@@ -48,7 +42,7 @@ fun AccountDetails(
4842
positiveText = App.getInstance()
4943
.getString(R.string.action_viewAccessKey),
5044
negativeText = App.getInstance().getString(R.string.action_cancel),
51-
onPositive = { onPage(AccountPage.ACCESS_KEY) },
45+
onPositive = { onPageSelected(AccountPage.ACCESS_KEY) },
5246
onNegative = {}
5347
)
5448
)
@@ -58,11 +52,11 @@ fun AccountDetails(
5852
name = R.string.title_phoneNumber,
5953
icon = R.drawable.ic_menu_phone,
6054
isPhoneLinked = dataState.isPhoneLinked,
61-
) { onPage(AccountPage.PHONE) },
55+
) { onPageSelected(AccountPage.PHONE) },
6256
AccountMainItem(
6357
name = R.string.action_deleteAccount,
6458
icon = R.drawable.ic_delete
65-
) { onPage(AccountPage.DELETE_ACCOUNT) },
59+
) { onPageSelected(AccountPage.DELETE_ACCOUNT) },
6660
)
6761

6862
for (action in actions) {

0 commit comments

Comments
 (0)