From 659599ced191fdf1bf2643bc4edc728d8443f94f Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 18 Jul 2024 11:10:17 -0400 Subject: [PATCH 01/33] feat: share tweet to tip Signed-off-by: Brandon McAnsh --- app/src/main/AndroidManifest.xml | 6 +++ .../java/com/getcode/models/PaymentRequest.kt | 17 +++--- .../java/com/getcode/util/DeeplinkHandler.kt | 52 ++++++++++++++++++- 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2f5209785..a68d0d6be 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,6 +138,12 @@ android:scheme="codewallet" /> + + + + + + ("mode") ?: return null Timber.d("mode=$mode") - val secret = container.decode("clientSecret") ?: return null - val clientSecret = Base58.decode(secret) - if (clientSecret.size != 11) { - Timber.e("Invalid client secret") - return null - } + val secret = container.decode("clientSecret") + val clientSecret = secret?.let { Base58.decode(it) } val (successUrl, cancelUrl) = container.decode("confirmParams") ?: ConfirmParams(null, null) @@ -57,7 +54,7 @@ data class DeepLinkRequest( val baseRequest = DeepLinkRequest( mode = mode, - clientSecret = clientSecret.toList(), + clientSecret = clientSecret?.toList().orEmpty(), successUrl = successUrl?.url, cancelUrl = cancelUrl?.url, ) @@ -154,6 +151,10 @@ private inline fun JsonObject.decode(key: String): T? { } } +inline fun encode(data: T): String { + return runCatching { Json.encodeToString(data) }.getOrNull().orEmpty() +} + private inline fun JsonObject.decode(key: String, map: (T) -> R): R? { return decode(key)?.let { map(it) } } @@ -166,7 +167,7 @@ private data class LoginKeys( ) @Serializable -private data class PlatformKeys( +data class PlatformKeys( val name: String, val username: String, ) diff --git a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt index 813209b9e..6ebbeda17 100644 --- a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt +++ b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt @@ -2,13 +2,24 @@ package com.getcode.util import android.content.Intent import android.net.Uri +import androidx.core.net.toUri import cafe.adriel.voyager.core.screen.Screen +import com.getcode.models.DeepLinkRequest +import com.getcode.models.PlatformKeys +import com.getcode.models.encode import com.getcode.navigation.screens.HomeScreen import com.getcode.navigation.screens.LoginScreen +import com.getcode.network.repository.encodeBase64 import com.getcode.network.repository.urlDecode import com.getcode.utils.TraceType +import com.getcode.utils.base64EncodedData import com.getcode.utils.trace +import com.getcode.vendor.Base58 import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -42,7 +53,17 @@ class DeeplinkHandler @Inject constructor() { val intent = MutableStateFlow(debounceIntent) fun handle(intent: Intent? = debounceIntent): DeeplinkResult? { - val uri = intent?.data ?: return null + val uri = when { + intent?.data != null -> intent.data + intent?.getStringExtra(Intent.EXTRA_TEXT) != null -> { + val sharedLink = intent.getStringExtra(Intent.EXTRA_TEXT)?.toUri() ?: return null + sharedLink.resolveSharedEntity + } + + else -> null + } ?: return null + + println("resolved uri=$uri") return when (val type = uri.deeplinkType) { is Type.Login -> { DeeplinkResult( @@ -71,6 +92,35 @@ class DeeplinkHandler @Inject constructor() { } } + /** + * Handles converting inbound shared content with possible deeplinks + * e.g sharing a tweet to trigger a tipcard flow + */ + private val Uri.resolveSharedEntity: Uri + get() { + // https://x.com//status/ + when (this.host) { + "x.com", + "twitter.com" -> { + // convert shared tweets to owner's tip card + val username = pathSegments.firstOrNull() ?: return this + val payload = buildJsonObject { + put("mode", "tip") + put( + "platform", + buildJsonObject { + put("name", "Twitter") + put("username", username) + } + ) + } + val encodedPayload = encode(payload).toByteArray().encodeBase64() + return "codewallet://sdk.getcode.com/v1/elements/tip-request-page-mobile/#/p=$encodedPayload".toUri() + } + else -> return this + } + } + private val Uri.deeplinkType: Type get() = when (val segment = lastPathSegment) { "login" -> { From b4e5b2d4bce8817cd00b37be83261a30f48c2779 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 8 Aug 2024 11:48:38 -0400 Subject: [PATCH 02/33] build: prep next development cycle Signed-off-by: Brandon McAnsh --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 65c1b8d78..15feed7ce 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -13,7 +13,7 @@ object Packaging { private const val majorVersion = 2 private const val minorVersion = 1 private const val patchVersion = 7 - private const val buildNumber = 412 + private const val buildNumber = 413 const val versionCode = buildNumber const val versionName = "$majorVersion.$minorVersion.$patchVersion" From 34b395f9c52afcaf521bad139e0d5611f88aa5b0 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 8 Aug 2024 11:49:32 -0400 Subject: [PATCH 03/33] fix: make tip card dismissal more deterministic Signed-off-by: Brandon McAnsh --- .../java/com/getcode/navigation/screens/MainScreens.kt | 5 +++++ .../com/getcode/navigation/screens/ModalScreens.kt | 8 ++++++++ .../main/java/com/getcode/view/main/home/HomeScan.kt | 10 +--------- .../com/getcode/view/main/tip/ConnectAccountScreen.kt | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt b/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt index 2d65674fa..680888dc4 100644 --- a/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt +++ b/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt @@ -31,6 +31,7 @@ sealed interface HomeResult { data class Request(val amount: KinAmount) : HomeResult data class ConfirmTip(val amount: KinAmount) : HomeResult data object ShowTipCard : HomeResult + data object CancelTipEntry: HomeResult } @Parcelize @@ -66,6 +67,10 @@ data class HomeScreen( is HomeResult.ShowTipCard -> { vm.presentShareableTipCard() } + + is HomeResult.CancelTipEntry -> { + vm.cancelTipEntry() + } } } } diff --git a/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt b/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt index ad4a11eb4..7546a41fd 100644 --- a/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt +++ b/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt @@ -1,6 +1,7 @@ package com.getcode.navigation.screens import android.webkit.JavascriptInterface +import androidx.activity.compose.BackHandler import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.imePadding @@ -436,6 +437,9 @@ data class EnterTipModal(val isInChat: Boolean = false) : MainGraph, ModalRoot { } else { navigator.progress > 0f } + }, + onCloseClicked = { + navigator.hideWithResult(HomeResult.CancelTipEntry) } ) { EnterTipScreen(getViewModel()) { result -> @@ -443,6 +447,10 @@ data class EnterTipModal(val isInChat: Boolean = false) : MainGraph, ModalRoot { } } } + + BackHandler { + navigator.hideWithResult(HomeResult.CancelTipEntry) + } } } diff --git a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt index 5ca35885e..455cc3c9b 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle +import cafe.adriel.voyager.core.stack.StackEvent import com.getcode.LocalBiometricsState import com.getcode.R import com.getcode.manager.TopBarManager @@ -127,15 +128,6 @@ fun HomeScreen( } .launchIn(this) } - - LaunchedEffect(navigator) { - // reset tip entry state when tip entry is manually dismissed - // without advancing next - snapshotFlow { navigator.progress } - .filter { it == 0f && navigator.lastModalItem is EnterTipModal } - .onEach { homeViewModel.cancelTipEntry() } - .launchIn(this) - } } } } diff --git a/app/src/main/java/com/getcode/view/main/tip/ConnectAccountScreen.kt b/app/src/main/java/com/getcode/view/main/tip/ConnectAccountScreen.kt index de11719c9..4d09eb4f1 100644 --- a/app/src/main/java/com/getcode/view/main/tip/ConnectAccountScreen.kt +++ b/app/src/main/java/com/getcode/view/main/tip/ConnectAccountScreen.kt @@ -145,7 +145,7 @@ private fun TweetPreview( modifier = Modifier.weight(1f), text = xMessage, color = Color.White, - style = CodeTheme.typography.textSmall + style = CodeTheme.typography.textSmall, ) } } From 73827fd96734581d9d3447e16b680ffe163ef3c2 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 8 Aug 2024 22:36:32 -0400 Subject: [PATCH 04/33] chore: support directly handling tipcard deeplinks Signed-off-by: Brandon McAnsh --- app/src/main/AndroidManifest.xml | 12 +++++ .../java/com/getcode/models/PaymentRequest.kt | 13 ++++++ .../getcode/navigation/screens/MainScreens.kt | 7 ++- .../com/getcode/ui/components/AuthCheck.kt | 1 + .../java/com/getcode/util/DeeplinkHandler.kt | 45 ++++++++++++++----- .../com/getcode/view/main/home/HomeScan.kt | 36 +++++++-------- .../getcode/view/main/home/HomeViewModel.kt | 4 +- 7 files changed, 82 insertions(+), 36 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2f5209785..31e85cf5f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,6 +138,18 @@ android:scheme="codewallet" /> + + + + + + + + + () trace("home rendered") - HomeScreen(vm, cashLink, requestPayload) + HomeScreen(vm, cashLink, request) OnScreenResult { result -> when (result) { diff --git a/app/src/main/java/com/getcode/ui/components/AuthCheck.kt b/app/src/main/java/com/getcode/ui/components/AuthCheck.kt index 4b3821814..6734bbe67 100644 --- a/app/src/main/java/com/getcode/ui/components/AuthCheck.kt +++ b/app/src/main/java/com/getcode/ui/components/AuthCheck.kt @@ -85,6 +85,7 @@ fun AuthCheck( when (result.type) { is DeeplinkHandler.Type.Login -> true is DeeplinkHandler.Type.Cash, + is DeeplinkHandler.Type.Tip, is DeeplinkHandler.Type.Sdk -> { val hasAuth = state.isAuthenticated == true if (!hasAuth) { diff --git a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt index 813209b9e..db994f286 100644 --- a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt +++ b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt @@ -3,10 +3,12 @@ package com.getcode.util import android.content.Intent import android.net.Uri import cafe.adriel.voyager.core.screen.Screen +import com.getcode.models.DeepLinkRequest import com.getcode.navigation.screens.HomeScreen import com.getcode.navigation.screens.LoginScreen import com.getcode.network.repository.urlDecode import com.getcode.utils.TraceType +import com.getcode.utils.base64EncodedData import com.getcode.utils.trace import kotlinx.coroutines.flow.MutableStateFlow import timber.log.Timber @@ -61,9 +63,18 @@ class DeeplinkHandler @Inject constructor() { is Type.Sdk -> { Timber.d("sdk=${type.payload}") + val request = type.payload?.base64EncodedData()?.let { DeepLinkRequest.from(it) } DeeplinkResult( type, - listOf(HomeScreen(requestPayload = type.payload)), + listOf(HomeScreen(request = request)), + ) + } + + is Type.Tip -> { + Timber.d("tipcard for ${type.username} on ${type.platform}") + DeeplinkResult( + type, + listOf(HomeScreen(request = DeepLinkRequest.fromTipCardUsername(type.platform, type.username))), ) } @@ -72,20 +83,29 @@ class DeeplinkHandler @Inject constructor() { } private val Uri.deeplinkType: Type - get() = when (val segment = lastPathSegment) { - "login" -> { - var entropy = fragments[Key.entropy] - if (entropy == null) { - entropy = this.getQueryParameter("data") + get() { + // check for tipcard URLs + val components = pathSegments + if (components.count() == 2 && components[0] == "x" && components[1].isNotEmpty()) { + return Type.Tip(components[0], components[1]) + } + + return when (val segment = lastPathSegment) { + "login" -> { + var entropy = fragments[Key.entropy] + if (entropy == null) { + entropy = this.getQueryParameter("data") + } + + Type.Login(entropy.also { Timber.d("entropy=$it") }) } - Type.Login(entropy.also { Timber.d("entropy=$it") }) - } + "cash", "c" -> Type.Cash(fragments[Key.entropy]) - "cash", "c" -> Type.Cash(fragments[Key.entropy]) - // support all variations of SDK request triggers - in Type.Sdk.regex -> Type.Sdk(fragments[Key.payload]?.urlDecode()) - else -> Type.Unknown(path = segment) + // support all variations of SDK request triggers + in Type.Sdk.regex -> Type.Sdk(fragments[Key.payload]?.urlDecode()) + else -> Type.Unknown(path = segment) + } } private val Uri.fragments: Map @@ -104,6 +124,7 @@ class DeeplinkHandler @Inject constructor() { sealed interface Type { data class Login(val link: String?) : Type data class Cash(val link: String?) : Type + data class Tip(val platform: String, val username: String): Type data class Sdk(val payload: String?) : Type { companion object { val regex = Regex("^(login|payment|tip)?-?request-(modal|page)-(mobile|desktop)\$") diff --git a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt index 455cc3c9b..89d20399d 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt @@ -31,18 +31,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment.Companion.BottomCenter import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle -import cafe.adriel.voyager.core.stack.StackEvent import com.getcode.LocalBiometricsState import com.getcode.R import com.getcode.manager.TopBarManager import com.getcode.models.Bill +import com.getcode.models.DeepLinkRequest import com.getcode.navigation.core.CodeNavigator import com.getcode.navigation.core.LocalCodeNavigator import com.getcode.navigation.screens.AccountModal @@ -69,7 +68,6 @@ import com.getcode.view.main.home.components.PermissionsBlockingView import com.getcode.view.main.home.components.ReceivedKinConfirmation import com.getcode.view.main.home.components.TipConfirmation import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -89,8 +87,8 @@ enum class HomeBottomSheet { @Composable fun HomeScreen( homeViewModel: HomeViewModel, - deepLink: String? = null, - requestPayload: String? = null, + cashLink: String? = null, + request: DeepLinkRequest? = null, ) { val navigator = LocalCodeNavigator.current val dataState by homeViewModel.uiFlow.collectAsState() @@ -108,8 +106,8 @@ fun HomeScreen( HomeScan( homeViewModel = homeViewModel, dataState = dataState, - deepLink = deepLink, - requestPayload = requestPayload, + cashLink = cashLink, + request = request, ) val context = LocalContext.current @@ -136,8 +134,8 @@ fun HomeScreen( private fun HomeScan( homeViewModel: HomeViewModel, dataState: HomeUiModel, - deepLink: String?, - requestPayload: String?, + cashLink: String?, + request: DeepLinkRequest?, ) { val navigator = LocalCodeNavigator.current val scope = rememberCoroutineScope() @@ -159,28 +157,28 @@ private fun HomeScan( val focusManager = LocalFocusManager.current - var deepLinkSaved by remember(deepLink) { - mutableStateOf(deepLink) + var cashLinkSaved by remember(cashLink) { + mutableStateOf(cashLink) } - var requestPayloadSaved by remember(requestPayload) { - mutableStateOf(requestPayload) + var requestPayloadSaved by remember(request) { + mutableStateOf(request) } val biometricsState = LocalBiometricsState.current - LaunchedEffect(biometricsState, previewing, dataState.balance, deepLinkSaved, requestPayloadSaved) { + LaunchedEffect(biometricsState, previewing, dataState.balance, cashLinkSaved, requestPayloadSaved) { if (previewing) { focusManager.clearFocus() } - if (biometricsState.passed && !deepLinkSaved.isNullOrBlank()) { - homeViewModel.openCashLink(deepLink) - deepLinkSaved = null + if (biometricsState.passed && !cashLinkSaved.isNullOrBlank()) { + homeViewModel.openCashLink(cashLink) + cashLinkSaved = null } - if (biometricsState.passed && !requestPayloadSaved.isNullOrBlank() && dataState.balance != null) { + if (biometricsState.passed && requestPayloadSaved != null && dataState.balance != null) { delay(500.milliseconds) - homeViewModel.handleRequest(requestPayload) + homeViewModel.handleRequest(request) requestPayloadSaved = null } } diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index 9d13dfb94..0586737b0 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -1430,9 +1430,7 @@ class HomeViewModel @Inject constructor( } } - fun handleRequest(bytes: String?) { - val data = bytes?.base64EncodedData() ?: return - val request = DeepLinkRequest.from(data) + fun handleRequest(request: DeepLinkRequest?) { if (request != null) { if (request.paymentRequest != null) { viewModelScope.launch { From f386b196b85462094d3fcc8225aed17435a59799 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 9 Aug 2024 12:19:38 -0400 Subject: [PATCH 05/33] chore: move share tweet to tip behind beta flag Signed-off-by: Brandon McAnsh --- .../com/getcode/analytics/AnalyticsManager.kt | 2 +- .../main/java/com/getcode/model/PrefBool.kt | 3 +- .../network/repository/BetaFlagsRepository.kt | 10 +++- app/build.gradle.kts | 2 +- app/src/main/AndroidManifest.xml | 10 +++- .../java/com/getcode/models/PaymentRequest.kt | 17 +++---- app/src/main/java/com/getcode/util/Context.kt | 7 --- .../java/com/getcode/util/DeeplinkHandler.kt | 32 ++++++++---- .../main/java/com/getcode/util/IntentUtils.kt | 2 + app/src/main/java/com/getcode/util/Pacman.kt | 30 +++++++++++ .../view/main/account/BetaFlagsScreen.kt | 15 ++++-- .../view/main/account/BetaFlagsViewModel.kt | 50 ++++++------------- app/src/main/res/values/strings-universal.xml | 2 + buildSrc/src/main/java/Dependencies.kt | 3 ++ 14 files changed, 111 insertions(+), 74 deletions(-) create mode 100644 app/src/main/java/com/getcode/util/Pacman.kt diff --git a/api/src/main/java/com/getcode/analytics/AnalyticsManager.kt b/api/src/main/java/com/getcode/analytics/AnalyticsManager.kt index 833c1bf6c..1fccc04f1 100644 --- a/api/src/main/java/com/getcode/analytics/AnalyticsManager.kt +++ b/api/src/main/java/com/getcode/analytics/AnalyticsManager.kt @@ -323,7 +323,7 @@ class AnalyticsManager @Inject constructor( //Bill Bill("Bill"), Request("Request Card"), - TipCard("TIp Card"), + TipCard("Tip Card"), //Transfer Transfer("Transfer"), diff --git a/api/src/main/java/com/getcode/model/PrefBool.kt b/api/src/main/java/com/getcode/model/PrefBool.kt index 0b0f44bff..590c172e8 100644 --- a/api/src/main/java/com/getcode/model/PrefBool.kt +++ b/api/src/main/java/com/getcode/model/PrefBool.kt @@ -43,14 +43,13 @@ sealed class PrefsBool(val value: String) { data object SHOW_CONNECTIVITY_STATUS: PrefsBool("debug_no_network"), BetaFlag data object GIVE_REQUESTS_ENABLED: PrefsBool("give_requests_enabled"), BetaFlag data object BUY_MODULE_ENABLED : PrefsBool("buy_kin_enabled"), BetaFlag - - data object CHAT_UNSUB_ENABLED: PrefsBool("chat_unsub_enabled"), BetaFlag data object TIPS_ENABLED : PrefsBool("tips_enabled"), BetaFlag data object TIPS_CHAT_ENABLED: PrefsBool("tips_chat_enabled"), BetaFlag data object TIPS_CHAT_CASH_ENABLED: PrefsBool("tips_chat_cash_enabled"), BetaFlag data object BALANCE_CURRENCY_SELECTION_ENABLED: PrefsBool("balance_currency_enabled"), BetaFlag data object KADO_WEBVIEW_ENABLED : PrefsBool("kado_inapp_enabled"), BetaFlag + data object SHARE_TWEET_TO_TIP : PrefsBool("share_tweet_to_tip"), BetaFlag } val APP_SETTINGS: List = listOf(PrefsBool.CAMERA_START_BY_DEFAULT, PrefsBool.REQUIRE_BIOMETRICS) \ No newline at end of file diff --git a/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt b/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt index d42ded921..5d798f63b 100644 --- a/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt @@ -19,6 +19,7 @@ data class BetaOptions( val tipsChatCashEnabled: Boolean, val balanceCurrencySelectionEnabled: Boolean, val kadoWebViewEnabled: Boolean, + val shareTweetToTip: Boolean, ) { companion object { // Default states for various beta flags in app. @@ -36,6 +37,7 @@ data class BetaOptions( tipsChatCashEnabled = false, balanceCurrencySelectionEnabled = true, kadoWebViewEnabled = false, + shareTweetToTip = false ) } } @@ -70,7 +72,8 @@ class BetaFlagsRepository @Inject constructor( observeBetaFlag(PrefsBool.TIPS_CHAT_CASH_ENABLED, default = defaults.tipsChatCashEnabled), observeBetaFlag(PrefsBool.BALANCE_CURRENCY_SELECTION_ENABLED, defaults.balanceCurrencySelectionEnabled), observeBetaFlag(PrefsBool.DISPLAY_ERRORS, default = defaults.displayErrors), - observeBetaFlag(PrefsBool.KADO_WEBVIEW_ENABLED, default = defaults.kadoWebViewEnabled) + observeBetaFlag(PrefsBool.KADO_WEBVIEW_ENABLED, default = defaults.kadoWebViewEnabled), + observeBetaFlag(PrefsBool.SHARE_TWEET_TO_TIP, default = defaults.shareTweetToTip) ) { BetaOptions( showNetworkDropOff = it[0], @@ -86,6 +89,7 @@ class BetaFlagsRepository @Inject constructor( balanceCurrencySelectionEnabled = it[10], displayErrors = it[11], kadoWebViewEnabled = it[12], + shareTweetToTip = it[13] ) } } @@ -98,4 +102,8 @@ class BetaFlagsRepository @Inject constructor( b.takeIf { a } ?: default } } + + suspend fun isEnabled(flag: PrefsBool): Boolean { + return prefRepository.get(flag, false) + } } diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 572299105..6027ef141 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -223,5 +223,5 @@ dependencies { implementation(Libs.timber) implementation(Libs.bugsnag) - implementation("dev.chrisbanes.haze:haze:0.7.3") + implementation(Libs.haze) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4e0e30cfa..093d4174e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -150,13 +150,19 @@ android:scheme="https" /> + + + - - + ("mode") ?: return null Timber.d("mode=$mode") - val secret = container.decode("clientSecret") - val clientSecret = secret?.let { Base58.decode(it) } + val secret = container.decode("clientSecret") ?: return null + val clientSecret = Base58.decode(secret) + if (clientSecret.size != 11) { + Timber.e("Invalid client secret") + return null + } val (successUrl, cancelUrl) = container.decode("confirmParams") ?: ConfirmParams(null, null) @@ -67,7 +70,7 @@ data class DeepLinkRequest( val baseRequest = DeepLinkRequest( mode = mode, - clientSecret = clientSecret?.toList().orEmpty(), + clientSecret = clientSecret.toList(), successUrl = successUrl?.url, cancelUrl = cancelUrl?.url, ) @@ -164,10 +167,6 @@ private inline fun JsonObject.decode(key: String): T? { } } -inline fun encode(data: T): String { - return runCatching { Json.encodeToString(data) }.getOrNull().orEmpty() -} - private inline fun JsonObject.decode(key: String, map: (T) -> R): R? { return decode(key)?.let { map(it) } } @@ -180,7 +179,7 @@ private data class LoginKeys( ) @Serializable -data class PlatformKeys( +private data class PlatformKeys( val name: String, val username: String, ) diff --git a/app/src/main/java/com/getcode/util/Context.kt b/app/src/main/java/com/getcode/util/Context.kt index 1060a721a..867d5a51f 100644 --- a/app/src/main/java/com/getcode/util/Context.kt +++ b/app/src/main/java/com/getcode/util/Context.kt @@ -1,15 +1,8 @@ package com.getcode.util import android.content.Context -import androidx.biometric.BiometricManager -import androidx.biometric.BiometricPrompt -import androidx.biometric.BiometricPrompt.AuthenticationError import androidx.core.content.ContextCompat -import androidx.fragment.app.FragmentActivity import com.getcode.R -import com.getcode.network.repository.TransactionRepository.ErrorSubmitIntent -import timber.log.Timber -import java.util.concurrent.Executors fun Context.launchAppSettings() { val intent = IntentUtils.appSettings() diff --git a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt index 2b30fa6bc..cddc002c1 100644 --- a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt +++ b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt @@ -1,18 +1,24 @@ package com.getcode.util +import android.content.Context import android.content.Intent import android.net.Uri import androidx.core.net.toUri import cafe.adriel.voyager.core.screen.Screen +import com.getcode.model.BetaFlag +import com.getcode.model.PrefsBool import com.getcode.models.DeepLinkRequest import com.getcode.models.encode import com.getcode.navigation.screens.HomeScreen import com.getcode.navigation.screens.LoginScreen +import com.getcode.network.repository.BetaFlagsRepository import com.getcode.network.repository.encodeBase64 import com.getcode.network.repository.urlDecode +import com.getcode.ui.utils.getActivity import com.getcode.utils.TraceType import com.getcode.utils.base64EncodedData import com.getcode.utils.trace +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put @@ -39,7 +45,10 @@ data class DeeplinkResult( * in favour of the latest request in the navigation graph. */ @Singleton -class DeeplinkHandler @Inject constructor() { +class DeeplinkHandler @Inject constructor( + @ApplicationContext private val context: Context, + private val betaFlags: BetaFlagsRepository +) { var debounceIntent: Intent? = null set(value) { intent.value = value @@ -49,12 +58,13 @@ class DeeplinkHandler @Inject constructor() { val intent = MutableStateFlow(debounceIntent) - fun handle(intent: Intent? = debounceIntent): DeeplinkResult? { + suspend fun handle(intent: Intent? = debounceIntent): DeeplinkResult? { + println(intent) val uri = when { intent?.data != null -> intent.data intent?.getStringExtra(Intent.EXTRA_TEXT) != null -> { val sharedLink = intent.getStringExtra(Intent.EXTRA_TEXT)?.toUri() ?: return null - sharedLink.resolveSharedEntity + sharedLink.resolveSharedEntity() } else -> null @@ -108,19 +118,19 @@ class DeeplinkHandler @Inject constructor() { * Handles converting inbound shared content with possible deeplinks * e.g sharing a tweet to trigger a tipcard flow */ - private val Uri.resolveSharedEntity: Uri - get() { - // https://x.com//status/ - return when { - this.host == "x.com" || this.host == "twitter.com" -> { + private suspend fun Uri.resolveSharedEntity(): Uri { + when { + this.host == "x.com" || this.host == "twitter.com" -> { + // https://x.com//status/ + if (betaFlags.isEnabled(PrefsBool.SHARE_TWEET_TO_TIP)) { // convert shared tweets to owner's tip card val username = pathSegments.firstOrNull() ?: return this - Uri.parse(Linkify.tipCard(username, "x")) + return Uri.parse(Linkify.tipCard(username, "x")) } - - else -> this } } + return this + } private val Uri.deeplinkType: Type get() { diff --git a/app/src/main/java/com/getcode/util/IntentUtils.kt b/app/src/main/java/com/getcode/util/IntentUtils.kt index 8a61cbd30..ada046265 100644 --- a/app/src/main/java/com/getcode/util/IntentUtils.kt +++ b/app/src/main/java/com/getcode/util/IntentUtils.kt @@ -1,11 +1,13 @@ package com.getcode.util +import android.content.Context import android.content.Intent import android.net.Uri import android.provider.Settings import com.getcode.BuildConfig import com.getcode.utils.makeE164 + object IntentUtils { fun appSettings() = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { diff --git a/app/src/main/java/com/getcode/util/Pacman.kt b/app/src/main/java/com/getcode/util/Pacman.kt new file mode 100644 index 000000000..db2500529 --- /dev/null +++ b/app/src/main/java/com/getcode/util/Pacman.kt @@ -0,0 +1,30 @@ +package com.getcode.util + +import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageManager +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + + +class Pacman @Inject constructor( + @ApplicationContext private val context: Context +) { + private val packageManager = context.packageManager + + fun enableTweetShare(enable: Boolean) { + val component = ComponentName(context.packageName, "com.getcode.view.TweetShareHandler") + if (enable) { + packageManager.setComponentEnabledSetting( + component, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP + ) + } else { + packageManager.setComponentEnabledSetting( + component, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP + ) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt b/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt index 7cd07999b..30201acf8 100644 --- a/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt +++ b/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import com.getcode.R import com.getcode.model.PrefsBool +import com.getcode.network.repository.BetaOptions import com.getcode.theme.CodeTheme import com.getcode.ui.components.ButtonState import com.getcode.ui.components.CodeButton @@ -44,7 +45,7 @@ fun BetaFlagsScreen( PrefsBool.VIBRATE_ON_SCAN, R.string.beta_vibrate_on_scan, stringResource(R.string.beta_vibrate_on_scan_description), - state.isVibrateOnScan + state.tickOnScan ), BetaFeature( PrefsBool.SHOW_CONNECTIVITY_STATUS, @@ -62,7 +63,7 @@ fun BetaFlagsScreen( PrefsBool.BALANCE_CURRENCY_SELECTION_ENABLED, R.string.beta_balance_currency, stringResource(R.string.beta_balance_currency_description), - state.currencySelectionBalanceEnabled + state.balanceCurrencySelectionEnabled ), BetaFeature( PrefsBool.GIVE_REQUESTS_ENABLED, @@ -74,7 +75,7 @@ fun BetaFlagsScreen( PrefsBool.BUY_MODULE_ENABLED, R.string.beta_buy_kin, stringResource(id = R.string.beta_buy_kin_description), - state.buyKinEnabled + state.buyModuleEnabled ), BetaFeature( PrefsBool.CHAT_UNSUB_ENABLED, @@ -106,6 +107,12 @@ fun BetaFlagsScreen( stringResource(id = R.string.beta_kado_webview_description), state.kadoWebViewEnabled, ), + BetaFeature( + PrefsBool.SHARE_TWEET_TO_TIP, + R.string.beta_share_tweet_tip, + stringResource(id = R.string.beta_share_tweet_tip_description), + state.shareTweetToTip, + ), BetaFeature( PrefsBool.DISPLAY_ERRORS, R.string.beta_display_errors, @@ -143,7 +150,7 @@ fun BetaFlagsScreen( } } -private fun BetaFlagsViewModel.State.canMutate(flag: PrefsBool): Boolean { +private fun BetaOptions.canMutate(flag: PrefsBool): Boolean { return when (flag) { PrefsBool.BUY_MODULE_ENABLED -> false PrefsBool.BALANCE_CURRENCY_SELECTION_ENABLED -> false diff --git a/app/src/main/java/com/getcode/view/main/account/BetaFlagsViewModel.kt b/app/src/main/java/com/getcode/view/main/account/BetaFlagsViewModel.kt index d212ecaab..a518f71a5 100644 --- a/app/src/main/java/com/getcode/view/main/account/BetaFlagsViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/account/BetaFlagsViewModel.kt @@ -5,6 +5,7 @@ import com.getcode.model.PrefsBool import com.getcode.network.repository.BetaFlagsRepository import com.getcode.network.repository.BetaOptions import com.getcode.network.repository.PrefRepository +import com.getcode.util.Pacman import com.getcode.utils.ErrorUtils import com.getcode.view.BaseViewModel2 import dagger.hilt.android.lifecycle.HiltViewModel @@ -18,26 +19,11 @@ import javax.inject.Inject class BetaFlagsViewModel @Inject constructor( betaFlags: BetaFlagsRepository, prefRepository: PrefRepository, -) : BaseViewModel2( - initialState = State(), + pacman: Pacman, +) : BaseViewModel2( + initialState = BetaOptions.Defaults, updateStateForEvent = updateStateForEvent ) { - data class State( - val showNetworkDropOff: Boolean = false, - val canViewBuckets: Boolean = false, - val isVibrateOnScan: Boolean = false, - val currencySelectionBalanceEnabled: Boolean = false, - val displayErrors: Boolean = false, - val giveRequestsEnabled: Boolean = false, - val buyKinEnabled: Boolean = false, - val establishCodeRelationship: Boolean = false, - val chatUnsubEnabled: Boolean = false, - val tipsEnabled: Boolean = false, - val tipsChatEnabled: Boolean = false, - val tipsChatCashEnabled: Boolean = false, - val kadoWebViewEnabled: Boolean = false, - ) - sealed interface Event { data class UpdateSettings(val settings: BetaOptions) : Event data class Toggle(val setting: PrefsBool, val state: Boolean): Event @@ -58,29 +44,21 @@ class BetaFlagsViewModel @Inject constructor( it.setting, it.state ) + + when (it.setting) { + PrefsBool.SHARE_TWEET_TO_TIP -> { + pacman.enableTweetShare(it.state) + } + else -> Unit + } }.launchIn(viewModelScope) } companion object { - val updateStateForEvent: (Event) -> ((State) -> State) = { event -> + val updateStateForEvent: (Event) -> ((BetaOptions) -> BetaOptions) = { event -> when (event) { - is Event.UpdateSettings -> { state -> - with(event.settings) { - state.copy( - showNetworkDropOff = showNetworkDropOff, - canViewBuckets = canViewBuckets, - isVibrateOnScan = tickOnScan, - currencySelectionBalanceEnabled = balanceCurrencySelectionEnabled, - displayErrors = displayErrors, - giveRequestsEnabled = giveRequestsEnabled, - buyKinEnabled = buyModuleEnabled, - chatUnsubEnabled = chatUnsubEnabled, - tipsEnabled = tipsEnabled, - tipsChatEnabled = tipsChatEnabled, - tipsChatCashEnabled = tipsChatCashEnabled, - kadoWebViewEnabled = kadoWebViewEnabled, - ) - } + is Event.UpdateSettings -> { _ -> + event.settings } is Event.Toggle -> { state -> state } diff --git a/app/src/main/res/values/strings-universal.xml b/app/src/main/res/values/strings-universal.xml index a04e8d6a5..55d4736d5 100644 --- a/app/src/main/res/values/strings-universal.xml +++ b/app/src/main/res/values/strings-universal.xml @@ -24,6 +24,7 @@ Tip Chats Cash Currency Selection in Balance Buy Kin Internally + Share Tweets to Tip Show Errors If enabled, you\'ll gain the ability to tap the balance on the Balance screen to inspect individual bucket balances. If enabled, a \"No Connection\" badge will show on the scan screen when no internet is detected. @@ -37,6 +38,7 @@ If enabled, you\'ll gain the ability to chat with tippers. If enabled, you\'ll gain the ability to send Kin in Tip Chats. If enabled, the Buy Kin flow will open in an internal WebView. + If enabled, you\'ll gain the ability to share tweets directly from Twitter to Code to tip the author. %1$s %2$s Reset Tooltips diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 15feed7ce..6820b088a 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -260,4 +260,7 @@ object Libs { const val sodium_bindings = "com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings-android:${Versions.sodium_bindings}" const val fingerprint_pro = "com.fingerprint.android:pro:2.4.0" + + const val haze = "dev.chrisbanes.haze:haze:0.7.3" + const val process_phoenix = "com.jakewharton:process-phoenix:3.0.0" } From 8b949c09c2757562fdb9c223eb4b88596c61c01a Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 12 Aug 2024 12:36:25 -0400 Subject: [PATCH 06/33] chore(slideconfirm): make loading state visibility tied directly to loading Signed-off-by: Brandon McAnsh --- .../com/getcode/network/HistoryController.kt | 1 + .../network/repository/FeatureRepository.kt | 6 +- .../network/repository/MessagingRepository.kt | 1 + .../network/repository/PaymentRepository.kt | 10 +++- .../getcode/ui/components/SlideToConfirm.kt | 14 ++--- .../java/com/getcode/util/DeeplinkHandler.kt | 6 -- .../main/balance/BalanceSheetViewModel.kt | 3 + .../getcode/view/main/home/HomeViewModel.kt | 57 +++++++++---------- 8 files changed, 50 insertions(+), 48 deletions(-) diff --git a/api/src/main/java/com/getcode/network/HistoryController.kt b/api/src/main/java/com/getcode/network/HistoryController.kt index 0e1eee91f..5fde54aca 100644 --- a/api/src/main/java/com/getcode/network/HistoryController.kt +++ b/api/src/main/java/com/getcode/network/HistoryController.kt @@ -34,6 +34,7 @@ import com.getcode.network.repository.encodeBase64 import com.getcode.network.source.ChatMessagePagingSource import com.getcode.util.resources.ResourceHelper import com.getcode.util.resources.ResourceType +import com.getcode.utils.ErrorUtils import com.getcode.utils.TraceType import com.getcode.utils.trace import kotlinx.coroutines.CoroutineScope diff --git a/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt b/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt index 34bfd80c7..f68bd22cb 100644 --- a/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt @@ -1,7 +1,9 @@ package com.getcode.network.repository import com.getcode.model.BalanceCurrencyFeature +import com.getcode.model.BetaFlag import com.getcode.model.BuyModuleFeature +import com.getcode.model.Feature import com.getcode.model.PrefsBool import com.getcode.model.RequestKinFeature import com.getcode.model.TipCardFeature @@ -15,7 +17,7 @@ import javax.inject.Inject * Collates [BetaOptions] with server availability (stored in [PrefRepository]). */ class FeatureRepository @Inject constructor( - betaFlags: BetaFlagsRepository, + private val betaFlags: BetaFlagsRepository, prefRepository: PrefRepository, ) { val buyModule = combine( @@ -31,4 +33,6 @@ class FeatureRepository @Inject constructor( val requestKin = betaFlags.observe().map { RequestKinFeature(it.giveRequestsEnabled) } val balanceCurrencySelection = betaFlags.observe().map { BalanceCurrencyFeature(it.balanceCurrencySelectionEnabled) } + + suspend fun isEnabled(feature: PrefsBool): Boolean = betaFlags.isEnabled(feature) } \ No newline at end of file diff --git a/api/src/main/java/com/getcode/network/repository/MessagingRepository.kt b/api/src/main/java/com/getcode/network/repository/MessagingRepository.kt index b2d256782..e4217e4bc 100644 --- a/api/src/main/java/com/getcode/network/repository/MessagingRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/MessagingRepository.kt @@ -171,6 +171,7 @@ class MessagingRepository @Inject constructor( .build() return networkOracle.managedRequest(messagingApi.pollMessages(request)) + .observeOn(Schedulers.io()) .map { response -> Timber.d("response=${response.messagesList}") response.messagesList.mapNotNull { m -> StreamMessage.getInstance(m) } diff --git a/api/src/main/java/com/getcode/network/repository/PaymentRepository.kt b/api/src/main/java/com/getcode/network/repository/PaymentRepository.kt index 795aad651..7ad1f295c 100644 --- a/api/src/main/java/com/getcode/network/repository/PaymentRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/PaymentRepository.kt @@ -18,10 +18,12 @@ import com.getcode.network.exchange.Exchange import com.getcode.solana.keys.PublicKey import com.getcode.utils.ErrorUtils import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject import kotlin.coroutines.resume @@ -90,7 +92,9 @@ class PaymentRepository @Inject constructor( @SuppressLint("CheckResult") suspend fun completePayment(amount: KinAmount, rendezvousKey: KeyPair) { // 1. ensure we have exchange rates and compute the fees for this transaction - exchange.fetchRatesIfNeeded() + withContext(Dispatchers.IO) { + exchange.fetchRatesIfNeeded() + } var paymentAmount = amount return suspendCancellableCoroutine { cont -> @@ -156,7 +160,7 @@ class PaymentRepository @Inject constructor( Completable.concatArray( balanceController.fetchBalance(), client.fetchLimits(isForce = true) - ).doOnComplete { + ).observeOn(Schedulers.io()).doOnComplete { analytics.transfer( amount = paymentAmount, successful = true, @@ -209,7 +213,7 @@ class PaymentRepository @Inject constructor( Completable.concatArray( balanceController.fetchBalance(), client.fetchLimits(isForce = true) - ).doOnComplete { + ).observeOn(Schedulers.io()).doOnComplete { analytics.transferForTip(amount = amount, successful = true) cont.resume(Unit) }.subscribe() diff --git a/app/src/main/java/com/getcode/ui/components/SlideToConfirm.kt b/app/src/main/java/com/getcode/ui/components/SlideToConfirm.kt index 5ee5e40a7..c9f048093 100644 --- a/app/src/main/java/com/getcode/ui/components/SlideToConfirm.kt +++ b/app/src/main/java/com/getcode/ui/components/SlideToConfirm.kt @@ -3,6 +3,7 @@ package com.getcode.ui.components import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState @@ -178,7 +179,7 @@ fun SlideToConfirm( val hapticFeedback = LocalHapticFeedback.current val swipeState = rememberSwipeableState( - initialValue = if (loading) Anchor.End else Anchor.Start, + initialValue = Anchor.Start, ) val composeScope = rememberCoroutineScope() @@ -217,7 +218,6 @@ fun SlideToConfirm( hint(swipeFraction, PaddingValues(horizontal = Thumb.Size + CodeTheme.dimens.grid.x2), label) } - if (isSuccess) { Image( painter = painterResource(id = R.drawable.ic_check), @@ -227,9 +227,13 @@ fun SlideToConfirm( .align(Alignment.Center), ) } else { + val loadingColor by animateColorAsState( + targetValue = if (loading) Color.White else Color.Transparent + ) + CodeCircularProgressIndicator( strokeWidth = CodeTheme.dimens.thickBorder, - color = calculateLoadingColor(swipeFraction), + color = loadingColor, modifier = Modifier .size(CodeTheme.dimens.grid.x4) .align(Alignment.Center), @@ -362,10 +366,6 @@ private fun calculateHintTextColor(swipeFraction: Float): Color { return lerp(Color.White, Color.White.copy(alpha = 0f), fraction) } -private fun calculateLoadingColor(swipeFraction: Float): Color { - if (swipeFraction < 0.1f) return Color.White.copy(0f) - return Color.White -} @Preview @Composable diff --git a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt index cddc002c1..b9c2e44a5 100644 --- a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt +++ b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt @@ -5,23 +5,17 @@ import android.content.Intent import android.net.Uri import androidx.core.net.toUri import cafe.adriel.voyager.core.screen.Screen -import com.getcode.model.BetaFlag import com.getcode.model.PrefsBool import com.getcode.models.DeepLinkRequest -import com.getcode.models.encode import com.getcode.navigation.screens.HomeScreen import com.getcode.navigation.screens.LoginScreen import com.getcode.network.repository.BetaFlagsRepository -import com.getcode.network.repository.encodeBase64 import com.getcode.network.repository.urlDecode -import com.getcode.ui.utils.getActivity import com.getcode.utils.TraceType import com.getcode.utils.base64EncodedData import com.getcode.utils.trace import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/com/getcode/view/main/balance/BalanceSheetViewModel.kt b/app/src/main/java/com/getcode/view/main/balance/BalanceSheetViewModel.kt index b6afbdaa1..20eafc7a2 100644 --- a/app/src/main/java/com/getcode/view/main/balance/BalanceSheetViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/balance/BalanceSheetViewModel.kt @@ -10,6 +10,7 @@ import com.getcode.model.PrefsBool import com.getcode.model.Rate import com.getcode.network.BalanceController import com.getcode.network.HistoryController +import com.getcode.network.repository.BetaFlagsRepository import com.getcode.network.repository.FeatureRepository import com.getcode.network.repository.PrefRepository import com.getcode.util.Kin @@ -18,6 +19,7 @@ import com.getcode.view.BaseViewModel2 import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn @@ -124,6 +126,7 @@ class BalanceSheetViewModel @Inject constructor( eventFlow .filterIsInstance() + .filter { features.isEnabled(PrefsBool.TIPS_CHAT_ENABLED) } .onEach { historyController.fetchChats(true) } .launchIn(viewModelScope) } diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index b1f95b52b..1c6739607 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -883,30 +883,28 @@ class HomeViewModel @Inject constructor( val isReceived = payload != null val presentationStyle = if (isReceived) PresentationStyle.Pop else PresentationStyle.Slide - withContext(Dispatchers.Main) { - uiFlow.update { - var billState = it.billState.copy( - bill = Bill.Payment(amount, code, request), - valuation = PaymentValuation(amount), - primaryAction = null, - ) - - if (isReceived) { - billState = billState.copy( - paymentConfirmation = PaymentConfirmation( - state = ConfirmationState.AwaitingConfirmation, - payload = code, - requestedAmount = amount, - localAmount = amount.replacing(exchange.localRate) - ), - ) - } + uiFlow.update { + var billState = it.billState.copy( + bill = Bill.Payment(amount, code, request), + valuation = PaymentValuation(amount), + primaryAction = null, + ) - it.copy( - presentationStyle = presentationStyle, - billState = billState, + if (isReceived) { + billState = billState.copy( + paymentConfirmation = PaymentConfirmation( + state = ConfirmationState.AwaitingConfirmation, + payload = code, + requestedAmount = amount, + localAmount = amount.replacing(exchange.localRate) + ), ) } + + it.copy( + presentationStyle = presentationStyle, + billState = billState, + ) } analytics.requestShown(amount = amount) @@ -929,17 +927,14 @@ class HomeViewModel @Inject constructor( cashLinkManager.cancelBillTimeout() val paymentConfirmation = uiFlow.value.billState.paymentConfirmation ?: return@launch - withContext(Dispatchers.Main) { - uiFlow.update { - val billState = it.billState - it.copy( - billState = billState.copy( - paymentConfirmation = paymentConfirmation.copy(state = ConfirmationState.Sending) - ), - ) - } + uiFlow.update { + val billState = it.billState + it.copy( + billState = billState.copy( + paymentConfirmation = paymentConfirmation.copy(state = ConfirmationState.Sending) + ), + ) } - runCatching { paymentRepository.completePayment( paymentConfirmation.requestedAmount, From cce747a00436ea5e33d187f57d29eac63a61fe59 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 12 Aug 2024 14:03:01 -0400 Subject: [PATCH 07/33] chore: track tip card linkage Signed-off-by: Brandon McAnsh --- .../main/java/com/getcode/analytics/AnalyticsManager.kt | 5 +++++ .../main/java/com/getcode/analytics/AnalyticsService.kt | 2 ++ api/src/main/java/com/getcode/network/TipController.kt | 7 ++++++- app/src/main/java/com/getcode/util/DeeplinkHandler.kt | 6 ------ .../main/java/com/getcode/view/main/home/HomeViewModel.kt | 1 + 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/com/getcode/analytics/AnalyticsManager.kt b/api/src/main/java/com/getcode/analytics/AnalyticsManager.kt index 1fccc04f1..8033b9c18 100644 --- a/api/src/main/java/com/getcode/analytics/AnalyticsManager.kt +++ b/api/src/main/java/com/getcode/analytics/AnalyticsManager.kt @@ -269,6 +269,10 @@ class AnalyticsManager @Inject constructor( ) } + override fun tipCardLinked() { + track(Name.TipCardLinked) + } + override fun backgroundSwapInitiated() { track(Name.BackgroundSwap) } @@ -319,6 +323,7 @@ class AnalyticsManager @Inject constructor( Login("Login"), CreateAccount("Create Account"), UnintentionalLogout("Unintentional Logout"), + TipCardLinked("Tip Card Linked"), //Bill Bill("Bill"), diff --git a/api/src/main/java/com/getcode/analytics/AnalyticsService.kt b/api/src/main/java/com/getcode/analytics/AnalyticsService.kt index 2f00587c8..fea464b54 100644 --- a/api/src/main/java/com/getcode/analytics/AnalyticsService.kt +++ b/api/src/main/java/com/getcode/analytics/AnalyticsService.kt @@ -45,6 +45,7 @@ interface AnalyticsService { fun withdrawal(amount: KinAmount, successful: Boolean) fun tipCardShown(username: String) + fun tipCardLinked() fun backgroundSwapInitiated() fun unintentionalLogout() @@ -93,6 +94,7 @@ class AnalyticsServiceNull : AnalyticsService { override fun upgradePrivacy(successful: Boolean, intentId: PublicKey, actionCount: Int) = Unit override fun onBillReceived() = Unit override fun tipCardShown(username: String) = Unit + override fun tipCardLinked() = Unit override fun backgroundSwapInitiated() = Unit override fun unintentionalLogout() = Unit override fun appSettingToggled(setting: AppSetting, value: Boolean) = Unit diff --git a/api/src/main/java/com/getcode/network/TipController.kt b/api/src/main/java/com/getcode/network/TipController.kt index 0cbfb92fa..d693913a5 100644 --- a/api/src/main/java/com/getcode/network/TipController.kt +++ b/api/src/main/java/com/getcode/network/TipController.kt @@ -3,6 +3,7 @@ package com.getcode.network import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner +import com.getcode.analytics.AnalyticsService import com.getcode.manager.SessionManager import com.getcode.model.CodePayload import com.getcode.model.PrefsBool @@ -11,6 +12,8 @@ import com.getcode.model.TipMetadata import com.getcode.model.TwitterUser import com.getcode.network.client.Client import com.getcode.network.client.fetchTwitterUser +import com.getcode.network.repository.BetaFlagsRepository +import com.getcode.network.repository.BetaOptions import com.getcode.network.repository.PrefRepository import com.getcode.network.repository.TwitterUserFetchError import com.getcode.network.repository.base58 @@ -24,6 +27,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -44,6 +48,7 @@ typealias TipUser = Pair @Singleton class TipController @Inject constructor( private val client: Client, + betaFlags: BetaFlagsRepository, private val prefRepository: PrefRepository, ): LifecycleEventObserver { @@ -70,7 +75,7 @@ class TipController @Inject constructor( val showTwitterSplat: Flow = combine( connectedAccount, - prefRepository.observeOrDefault(PrefsBool.TIPS_ENABLED, false), + betaFlags.observe().map { it.tipsEnabled }, prefRepository.observeOrDefault(PrefsBool.SEEN_TIP_CARD, false) ) { connected, tipsEnabled, seen -> connected != null && !seen && tipsEnabled diff --git a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt index cddc002c1..b9c2e44a5 100644 --- a/app/src/main/java/com/getcode/util/DeeplinkHandler.kt +++ b/app/src/main/java/com/getcode/util/DeeplinkHandler.kt @@ -5,23 +5,17 @@ import android.content.Intent import android.net.Uri import androidx.core.net.toUri import cafe.adriel.voyager.core.screen.Screen -import com.getcode.model.BetaFlag import com.getcode.model.PrefsBool import com.getcode.models.DeepLinkRequest -import com.getcode.models.encode import com.getcode.navigation.screens.HomeScreen import com.getcode.navigation.screens.LoginScreen import com.getcode.network.repository.BetaFlagsRepository -import com.getcode.network.repository.encodeBase64 import com.getcode.network.repository.urlDecode -import com.getcode.ui.utils.getActivity import com.getcode.utils.TraceType import com.getcode.utils.base64EncodedData import com.getcode.utils.trace import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index b1f95b52b..f7a113060 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -242,6 +242,7 @@ class HomeViewModel @Inject constructor( .onEach { when (it) { is TwitterUser -> { + analytics.tipCardLinked() TopBarManager.showMessage( topBarMessage = TopBarManager.TopBarMessage( type = TopBarManager.TopBarMessageType.SUCCESS, From cfec920e28b39caef34a2945b14a63312ff0d47a Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 12 Aug 2024 14:17:04 -0400 Subject: [PATCH 08/33] build: prep next development cycle --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 6820b088a..755779c44 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -13,7 +13,7 @@ object Packaging { private const val majorVersion = 2 private const val minorVersion = 1 private const val patchVersion = 7 - private const val buildNumber = 413 + private const val buildNumber = 414 const val versionCode = buildNumber const val versionName = "$majorVersion.$minorVersion.$patchVersion" From 567c9a38cb5457e857918ef851aab9990c7d8967 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 13 Aug 2024 12:22:04 -0400 Subject: [PATCH 09/33] chore: open app from push notification Signed-off-by: Brandon McAnsh --- .../notifications/CodePushMessagingService.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt b/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt index 992378e30..24220f7af 100644 --- a/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt +++ b/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt @@ -3,7 +3,9 @@ package com.getcode.notifications import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.content.Context +import android.content.Intent import android.media.RingtoneManager import android.os.Build import androidx.core.app.NotificationCompat @@ -26,6 +28,7 @@ import com.getcode.util.resources.ResourceHelper import com.getcode.util.resources.ResourceType import com.getcode.utils.ErrorUtils import com.getcode.utils.installationId +import com.getcode.view.MainActivity import com.google.firebase.Firebase import com.google.firebase.installations.installations import com.google.firebase.messaging.FirebaseMessagingService @@ -183,6 +186,7 @@ class CodePushMessagingService : FirebaseMessagingService(), .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) .setSmallIcon(R.drawable.ic_code_logo_outline) .setAutoCancel(true) + .setContentIntent(buildContentIntent(type)) notificationManager.notify(title.hashCode(), notificationBuilder.build()) } @@ -207,6 +211,19 @@ class CodePushMessagingService : FirebaseMessagingService(), } } +private fun Context.buildContentIntent(type: NotificationType): PendingIntent { + val launchIntent = Intent(this, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + + return PendingIntent.getActivity( + this, + type.ordinal, + launchIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) +} + private fun NotificationManager.getActiveNotification(notificationId: Int): Notification? { val barNotifications = getActiveNotifications() From 554c2646d700fc6c8d7eb311b2176ef0ad225fc8 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 13 Aug 2024 13:42:47 -0400 Subject: [PATCH 10/33] chore: add a retry mechanism to retrieving account from AccountManager Signed-off-by: Brandon McAnsh --- .../com/getcode/network/client/Client_Chat.kt | 4 +- .../network/client/Client_Transaction.kt | 5 +- .../network/repository/IdentityRepository.kt | 1 - .../repository/TransactionRepository.kt | 148 +++++++++--------- .../repository/TransactionRepository_Swap.kt | 2 - .../main/java/com/getcode/utils/ErrorUtils.kt | 4 +- app/proguard-rules.pro | 5 + .../java/com/getcode/util/AccountUtils.kt | 46 +++++- .../com/getcode/util/AuthenticatorService.kt | 5 +- .../view/login/BaseAccessKeyViewModel.kt | 8 +- .../view/main/account/BetaFlagsViewModel.kt | 3 + 11 files changed, 139 insertions(+), 92 deletions(-) diff --git a/api/src/main/java/com/getcode/network/client/Client_Chat.kt b/api/src/main/java/com/getcode/network/client/Client_Chat.kt index 74628a913..783a63ead 100644 --- a/api/src/main/java/com/getcode/network/client/Client_Chat.kt +++ b/api/src/main/java/com/getcode/network/client/Client_Chat.kt @@ -30,14 +30,14 @@ suspend fun Client.fetchChats(owner: KeyPair): Result> { .onSuccess { Timber.d("v2 chats fetched=${it.count()}") }.onFailure { - trace("Failed fetching chats from V2", error = it, type = TraceType.Error) + trace("Failed fetching chats from V2", type = TraceType.Error) } val v1Chats = chatServiceV1.fetchChats(owner) .onSuccess { Timber.d("v1 chats fetched=${it.count()}") }.onFailure { - trace("Failed fetching chats from V1", error = it, type = TraceType.Error) + trace("Failed fetching chats from V1", type = TraceType.Error) } if (v2Chats.isSuccess || v1Chats.isSuccess) { diff --git a/api/src/main/java/com/getcode/network/client/Client_Transaction.kt b/api/src/main/java/com/getcode/network/client/Client_Transaction.kt index 3c3b8053e..6c1f4a565 100644 --- a/api/src/main/java/com/getcode/network/client/Client_Transaction.kt +++ b/api/src/main/java/com/getcode/network/client/Client_Transaction.kt @@ -24,6 +24,7 @@ import com.getcode.model.intents.IntentPublicTransfer import com.getcode.model.intents.IntentRemoteSend import com.getcode.model.intents.SwapIntent import com.getcode.network.repository.TransactionRepository +import com.getcode.network.repository.WithdrawException import com.getcode.network.repository.initiateSwap import com.getcode.solana.keys.PublicKey import com.getcode.solana.keys.base58 @@ -257,11 +258,11 @@ fun Client.withdrawExternally( destination: PublicKey ): Completable { if (amount.kin.fractionalQuarks().quarks != 0L) { - throw TransactionRepository.WithdrawException.InvalidFractionalKinAmountException() + throw WithdrawException.InvalidFractionalKinAmountException() } if (amount.kin > organizer.availableBalance) { - throw TransactionRepository.WithdrawException.InsufficientFundsException() + throw WithdrawException.InsufficientFundsException() } val intent = PublicKey.generate() diff --git a/api/src/main/java/com/getcode/network/repository/IdentityRepository.kt b/api/src/main/java/com/getcode/network/repository/IdentityRepository.kt index 6b0b0a225..644b6d912 100644 --- a/api/src/main/java/com/getcode/network/repository/IdentityRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/IdentityRepository.kt @@ -389,7 +389,6 @@ class IdentityRepository @Inject constructor( } }.first() } catch (e: Exception) { - e.printStackTrace() ErrorUtils.handleError(e) Result.failure(e) } diff --git a/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt b/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt index 7c748e7cf..00561753c 100644 --- a/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt @@ -42,6 +42,7 @@ import com.getcode.solana.organizer.GiftCardAccount import com.getcode.solana.organizer.Organizer import com.getcode.solana.organizer.Relationship import com.getcode.utils.ErrorUtils +import com.getcode.utils.TraceType import com.getcode.utils.bytes import com.getcode.utils.trace import com.google.protobuf.Timestamp @@ -303,8 +304,8 @@ class TransactionRepository @Inject constructor( val subject = SingleSubject.create() var serverMessageStream: StreamObserver? = null - val serverResponse = object : StreamObserver { - override fun onNext(value: TransactionService.SubmitIntentResponse?) { + val serverResponse = object : StreamObserver { + override fun onNext(value: SubmitIntentResponse?) { Timber.i("Received: " + value?.responseCase?.name.orEmpty()) when (value?.responseCase) { @@ -373,8 +374,9 @@ class TransactionRepository @Inject constructor( else -> Unit } - Timber.e( - "Error: ${errors.joinToString("\n")}" + trace( + "Error: ${errors.joinToString("\n")}", + type = TraceType.Error ) } @@ -696,90 +698,90 @@ class TransactionRepository @Inject constructor( } } } +} - class ErrorSubmitIntentException( - val errorSubmitIntent: ErrorSubmitIntent, - cause: Throwable? = null, - val messageString: String = "" - ) : Exception(cause) { - override val message: String - get() = "${errorSubmitIntent.javaClass.simpleName} $messageString" - } +class ErrorSubmitIntentException( + val errorSubmitIntent: ErrorSubmitIntent, + cause: Throwable? = null, + val messageString: String = "" +) : Exception(cause) { + override val message: String + get() = "${errorSubmitIntent.javaClass.simpleName} $messageString" +} - enum class DeniedReason { - Unspecified, - TooManyFreeAccountsForPhoneNumber, - TooManyFreeAccountsForDevice, - UnsupportedCountry, - UnsupportedDevice; +enum class DeniedReason { + Unspecified, + TooManyFreeAccountsForPhoneNumber, + TooManyFreeAccountsForDevice, + UnsupportedCountry, + UnsupportedDevice; - companion object { - fun fromValue(value: Int): DeniedReason { - return entries.firstOrNull { it.ordinal == value } ?: Unspecified - } + companion object { + fun fromValue(value: Int): DeniedReason { + return entries.firstOrNull { it.ordinal == value } ?: Unspecified } } +} - sealed class ErrorSubmitIntent(val value: Int) { - data class Denied(val reasons: List = emptyList()): ErrorSubmitIntent(0) - data class InvalidIntent(val reasons: List): ErrorSubmitIntent(1) - data object SignatureError: ErrorSubmitIntent(2) - data class StaleState(val reasons: List): ErrorSubmitIntent(3) - data object Unknown: ErrorSubmitIntent(-1) - data object DeviceTokenUnavailable: ErrorSubmitIntent(-2) - - override fun toString(): String { - return when (this) { - is Denied -> "denied(${reasons.joinToString()})" - DeviceTokenUnavailable -> "deviceTokenUnavailable" - is InvalidIntent -> "invalidIntent(${reasons.joinToString()})" - SignatureError -> "signatureError" - is StaleState -> "staleState(${reasons.joinToString()})" - Unknown -> "unknown" - } +sealed class ErrorSubmitIntent(val value: Int) { + data class Denied(val reasons: List = emptyList()): ErrorSubmitIntent(0) + data class InvalidIntent(val reasons: List): ErrorSubmitIntent(1) + data object SignatureError: ErrorSubmitIntent(2) + data class StaleState(val reasons: List): ErrorSubmitIntent(3) + data object Unknown: ErrorSubmitIntent(-1) + data object DeviceTokenUnavailable: ErrorSubmitIntent(-2) + + override fun toString(): String { + return when (this) { + is Denied -> "denied(${reasons.joinToString()})" + DeviceTokenUnavailable -> "deviceTokenUnavailable" + is InvalidIntent -> "invalidIntent(${reasons.joinToString()})" + SignatureError -> "signatureError" + is StaleState -> "staleState(${reasons.joinToString()})" + Unknown -> "unknown" } + } - companion object { - operator fun invoke(proto: SubmitIntentResponse.Error): ErrorSubmitIntent { - val reasonStrings = proto.errorDetailsList.mapNotNull { - when (it.typeCase) { - TransactionService.ErrorDetails.TypeCase.REASON_STRING -> - it.reasonString.reason.takeIf { reason -> reason.isNotEmpty() } - else -> null - } + companion object { + operator fun invoke(proto: SubmitIntentResponse.Error): ErrorSubmitIntent { + val reasonStrings = proto.errorDetailsList.mapNotNull { + when (it.typeCase) { + TransactionService.ErrorDetails.TypeCase.REASON_STRING -> + it.reasonString.reason.takeIf { reason -> reason.isNotEmpty() } + else -> null } - return when (proto.code) { - SubmitIntentResponse.Error.Code.DENIED -> { - val reasons = proto.errorDetailsList.mapNotNull { - if (!it.hasIntentDenied()) return@mapNotNull null - DeniedReason.fromValue(it.intentDenied.reasonValue) - } - - Denied(reasons) + } + return when (proto.code) { + SubmitIntentResponse.Error.Code.DENIED -> { + val reasons = proto.errorDetailsList.mapNotNull { + if (!it.hasIntentDenied()) return@mapNotNull null + DeniedReason.fromValue(it.intentDenied.reasonValue) } - SubmitIntentResponse.Error.Code.INVALID_INTENT -> InvalidIntent(reasonStrings) - SubmitIntentResponse.Error.Code.SIGNATURE_ERROR -> SignatureError - SubmitIntentResponse.Error.Code.STALE_STATE -> StaleState(reasonStrings) - SubmitIntentResponse.Error.Code.UNRECOGNIZED -> Unknown - else -> return Unknown + + Denied(reasons) } + SubmitIntentResponse.Error.Code.INVALID_INTENT -> InvalidIntent(reasonStrings) + SubmitIntentResponse.Error.Code.SIGNATURE_ERROR -> SignatureError + SubmitIntentResponse.Error.Code.STALE_STATE -> StaleState(reasonStrings) + SubmitIntentResponse.Error.Code.UNRECOGNIZED -> Unknown + else -> return Unknown } } } +} - sealed class WithdrawException : Exception() { - class InvalidFractionalKinAmountException : WithdrawException() - class InsufficientFundsException : WithdrawException() - } +sealed class WithdrawException : Exception() { + class InvalidFractionalKinAmountException : WithdrawException() + class InsufficientFundsException : WithdrawException() +} - sealed class FetchUpgradeableIntentsException : Exception() { - class DeserializationException : FetchUpgradeableIntentsException() - class UnknownException : FetchUpgradeableIntentsException() - } +sealed class FetchUpgradeableIntentsException : Exception() { + class DeserializationException : FetchUpgradeableIntentsException() + class UnknownException : FetchUpgradeableIntentsException() +} - sealed class AirdropException : Exception() { - class AlreadyClaimedException : AirdropException() - class UnavailableException : AirdropException() - class UnknownException : AirdropException() - } +sealed class AirdropException : Exception() { + class AlreadyClaimedException : AirdropException() + class UnavailableException : AirdropException() + class UnknownException : AirdropException() } diff --git a/api/src/main/java/com/getcode/network/repository/TransactionRepository_Swap.kt b/api/src/main/java/com/getcode/network/repository/TransactionRepository_Swap.kt index ec384d551..dc5d8f177 100644 --- a/api/src/main/java/com/getcode/network/repository/TransactionRepository_Swap.kt +++ b/api/src/main/java/com/getcode/network/repository/TransactionRepository_Swap.kt @@ -8,7 +8,6 @@ import com.getcode.model.intents.SwapConfigParameters import com.getcode.model.intents.SwapIntent import com.getcode.model.intents.requestToSubmitSignatures import com.getcode.network.core.BidirectionalStreamReference -import com.getcode.network.repository.TransactionRepository.ErrorSubmitIntent import com.getcode.solana.SolanaTransaction import com.getcode.solana.diff import com.getcode.solana.keys.Signature @@ -17,7 +16,6 @@ import com.getcode.solana.organizer.Organizer import com.getcode.utils.ErrorUtils import io.grpc.stub.StreamObserver import kotlinx.coroutines.suspendCancellableCoroutine -import org.kin.sdk.base.tools.Base58 import timber.log.Timber import kotlin.coroutines.resume diff --git a/api/src/main/java/com/getcode/utils/ErrorUtils.kt b/api/src/main/java/com/getcode/utils/ErrorUtils.kt index f2e98b319..a6577cd1d 100644 --- a/api/src/main/java/com/getcode/utils/ErrorUtils.kt +++ b/api/src/main/java/com/getcode/utils/ErrorUtils.kt @@ -8,6 +8,7 @@ import com.getcode.manager.TopBarManager import io.grpc.StatusRuntimeException import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException import io.reactivex.rxjava3.exceptions.UndeliverableException +import kotlinx.coroutines.TimeoutCancellationException import timber.log.Timber import java.net.ConnectException import java.net.UnknownHostException @@ -42,6 +43,7 @@ object ErrorUtils { BuildConfig.NOTIFY_ERRORS && throwable !is UnknownHostException && throwable !is TimeoutException && + throwable !is TimeoutCancellationException && throwable !is ConnectException ) { FirebaseCrashlytics.getInstance().recordException(throwable) @@ -62,7 +64,7 @@ object ErrorUtils { throwable.cause is StatusRuntimeException private fun isSuppressibleError(throwable: Throwable): Boolean = - throwable is SQLException || throwable is net.sqlcipher.SQLException || throwable is SuppressibleException + throwable is SQLException || throwable is net.sqlcipher.SQLException || throwable is SuppressibleException || throwable is TimeoutCancellationException } data class SuppressibleException(override val message: String): Throwable(message) \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 8482a2b34..fb05d0053 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -56,6 +56,11 @@ } -keep public class * extends java.lang.Exception +-keep public class * extends com.getcode.network.repository.ErrorSubmitIntent +-keep public class * extends com.getcode.network.repository.ErrorSubmitIntentException +-keep public class * extends com.getcode.network.repository.WithdrawException +-keep public class * extends com.getcode.network.repository.FetchUpgradeableIntentsException +-keep public class * extends com.getcode.network.repository.AirdropException # https://github.com/firebase/firebase-android-sdk/issues/3688 -keep class org.json.** { *; } diff --git a/app/src/main/java/com/getcode/util/AccountUtils.kt b/app/src/main/java/com/getcode/util/AccountUtils.kt index d3621c846..92feaa779 100644 --- a/app/src/main/java/com/getcode/util/AccountUtils.kt +++ b/app/src/main/java/com/getcode/util/AccountUtils.kt @@ -15,10 +15,14 @@ import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.subjects.SingleSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.datetime.Clock import kotlin.coroutines.resume +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.TimeSource object AccountUtils { @@ -51,7 +55,7 @@ object AccountUtils { val subject = SingleSubject.create>() return subject.doOnSubscribe { CoroutineScope(Dispatchers.IO).launch { - val result = getAccountNoActivity(context) + val result = getAccountInternal(context) subject.onSuccess(result ?: (null to null)) } } @@ -66,15 +70,46 @@ object AccountUtils { } } + private suspend fun getAccountInternal( + context: Context, + maxRetries: Int = 3, + delayDuration: Duration = 2.seconds + ): Pair? { + var currentAttempt = 0 + val startTime = TimeSource.Monotonic.markNow() + + while (currentAttempt < maxRetries) { + val result = try { + getAccountNoActivity(context) + } catch (e: Exception) { + trace(message = "Attempt $currentAttempt failed with exception: ${e.message}", error = e, type = TraceType.Error) + null + } + + if (result != null) { + return result + } else { + currentAttempt++ + if (currentAttempt < maxRetries) { + trace("Retrying after ${delayDuration.inWholeMilliseconds} ms...", type = TraceType.Log) + delay(delayDuration.inWholeMilliseconds) + } + } + } + + trace("Failed to get account after $maxRetries attempts in ${startTime.elapsedNow().inWholeMilliseconds} ms", type = TraceType.Error) + return null + } + private suspend fun getAccountNoActivity( context: Context ): Pair? = suspendCancellableCoroutine { cont -> trace("getAuthToken", type = TraceType.Silent) val am: AccountManager = AccountManager.get(context) - val account = am.accounts.getOrNull(0) + val account = am.getAccountsByType(ACCOUNT_TYPE).firstOrNull() if (account == null) { trace("no associated account found", type = TraceType.Error) - cont.resume(null to null) + cont.resume(null) return@suspendCancellableCoroutine } val start = Clock.System.now() @@ -90,9 +125,8 @@ object AccountUtils { cont.resume(authToken.orEmpty() to account) } catch (e: AuthenticatorException) { - e.printStackTrace() trace(message = "failed to read account", error = e, type = TraceType.Error) - cont.resume(null to null) + cont.resume(null) } }, handler ) @@ -110,6 +144,6 @@ object AccountUtils { } } - return getAccountNoActivity(context)?.first + return getAccountInternal(context)?.first } } \ No newline at end of file diff --git a/app/src/main/java/com/getcode/util/AuthenticatorService.kt b/app/src/main/java/com/getcode/util/AuthenticatorService.kt index fc45de3b1..902394748 100644 --- a/app/src/main/java/com/getcode/util/AuthenticatorService.kt +++ b/app/src/main/java/com/getcode/util/AuthenticatorService.kt @@ -10,8 +10,9 @@ import javax.inject.Inject @AndroidEntryPoint class AuthenticatorService : Service() { - @Inject - lateinit var accountAuthenticator: AccountAuthenticator + private val accountAuthenticator: AccountAuthenticator by lazy { + AccountAuthenticator(this) + } override fun onBind(intent: Intent): IBinder? { var binder: IBinder? = null diff --git a/app/src/main/java/com/getcode/view/login/BaseAccessKeyViewModel.kt b/app/src/main/java/com/getcode/view/login/BaseAccessKeyViewModel.kt index 62ebb0f17..3ff0831a3 100644 --- a/app/src/main/java/com/getcode/view/login/BaseAccessKeyViewModel.kt +++ b/app/src/main/java/com/getcode/view/login/BaseAccessKeyViewModel.kt @@ -14,7 +14,9 @@ import com.getcode.manager.SessionManager import com.getcode.manager.TopBarManager import com.getcode.media.MediaScanner import com.getcode.network.repository.TransactionRepository -import com.getcode.network.repository.TransactionRepository.DeniedReason +import com.getcode.network.repository.DeniedReason +import com.getcode.network.repository.ErrorSubmitIntent +import com.getcode.network.repository.ErrorSubmitIntentException import com.getcode.network.repository.decodeBase64 import com.getcode.theme.Alert import com.getcode.theme.Brand @@ -299,9 +301,9 @@ abstract class BaseAccessKeyViewModel( internal fun onSubmitError(e: Throwable) { when (e) { - is TransactionRepository.ErrorSubmitIntentException -> { + is ErrorSubmitIntentException -> { when (val intent = e.errorSubmitIntent) { - is TransactionRepository.ErrorSubmitIntent.Denied -> { + is ErrorSubmitIntent.Denied -> { if (intent.reasons.isEmpty() || intent.reasons.first() == DeniedReason.Unspecified) { getSomethingWentWrongError() } else { diff --git a/app/src/main/java/com/getcode/view/main/account/BetaFlagsViewModel.kt b/app/src/main/java/com/getcode/view/main/account/BetaFlagsViewModel.kt index a518f71a5..ad4aabecd 100644 --- a/app/src/main/java/com/getcode/view/main/account/BetaFlagsViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/account/BetaFlagsViewModel.kt @@ -49,6 +49,9 @@ class BetaFlagsViewModel @Inject constructor( PrefsBool.SHARE_TWEET_TO_TIP -> { pacman.enableTweetShare(it.state) } + PrefsBool.DISPLAY_ERRORS -> { + ErrorUtils.setDisplayErrors(it.state) + } else -> Unit } }.launchIn(viewModelScope) From ff7f1065d7372dc2ac9e2903b096cb48aa2cf8f1 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 14 Aug 2024 12:14:49 -0400 Subject: [PATCH 11/33] build: prep next development cycle Signed-off-by: Brandon McAnsh --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 755779c44..0a09bf04d 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -13,7 +13,7 @@ object Packaging { private const val majorVersion = 2 private const val minorVersion = 1 private const val patchVersion = 7 - private const val buildNumber = 414 + private const val buildNumber = 415 const val versionCode = buildNumber const val versionName = "$majorVersion.$minorVersion.$patchVersion" From 412bcebc783efb2b1977be803e026de8d9505a02 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 14 Aug 2024 12:15:28 -0400 Subject: [PATCH 12/33] fix(chat): use localized title for mute banner Signed-off-by: Brandon McAnsh --- app/src/main/java/com/getcode/view/main/chat/ChatScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/getcode/view/main/chat/ChatScreen.kt b/app/src/main/java/com/getcode/view/main/chat/ChatScreen.kt index ce26aaeae..19c1a44e4 100644 --- a/app/src/main/java/com/getcode/view/main/chat/ChatScreen.kt +++ b/app/src/main/java/com/getcode/view/main/chat/ChatScreen.kt @@ -22,6 +22,7 @@ import com.getcode.ui.components.VerticalDivider import com.getcode.ui.components.chat.MessageList import com.getcode.ui.components.chat.MessageListEvent import com.getcode.ui.components.chat.utils.ChatItem +import com.getcode.ui.components.chat.utils.localized import com.getcode.ui.utils.withTopBorder @Composable @@ -33,7 +34,7 @@ fun ChatScreen( val listState = rememberLazyListState() val context = LocalContext.current - val title = state.title + val title = state.title.localized Column(modifier = Modifier.fillMaxSize()) { MessageList( From bc7ad47979af827bc0295bea2bb8f6e04a626973 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 14 Aug 2024 12:49:55 -0400 Subject: [PATCH 13/33] chore(accounthome): move version info to BottomBar; draw with gradient Signed-off-by: Brandon McAnsh --- .../java/com/getcode/ui/utils/Modifier.kt | 130 ++++++++++++++++++ .../getcode/view/main/account/AccountHome.kt | 34 +++-- 2 files changed, 154 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/getcode/ui/utils/Modifier.kt b/app/src/main/java/com/getcode/ui/utils/Modifier.kt index c453337df..07e5dd865 100644 --- a/app/src/main/java/com/getcode/ui/utils/Modifier.kt +++ b/app/src/main/java/com/getcode/ui/utils/Modifier.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -27,6 +29,7 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onPlaced @@ -37,6 +40,7 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.offset import com.getcode.theme.BrandLight +import com.getcode.theme.CodeTheme inline fun Modifier.addIf( predicate: Boolean, @@ -235,4 +239,130 @@ fun Modifier.drawWithGradient( blendMode = blendMode ) } +} + +private val gradientSize + @Composable get() = CodeTheme.dimens.staticGrid.x8 + +fun Modifier.verticalScrollStateGradient( + scrollState: LazyListState, + color: Color = Color.Unspecified, + showAtStart: Boolean = true, + showAtEnd: Boolean = true, + isLongGradient: Boolean = false, +): Modifier = composed { + val backgroundColor = color.takeOrElse { CodeTheme.colors.background } + val gradientSizePx = + with(LocalDensity.current) { gradientSize.toPx() } * if (isLongGradient) 1.5f else 1f + this + .addIf(showAtStart && !scrollState.isScrolledToStart()) { + Modifier.drawWithGradient( + color = backgroundColor, + startY = { gradientSizePx }, + endY = { 0f }, + ) + } + .addIf(showAtEnd && !scrollState.isScrolledToEnd()) { + Modifier.drawWithGradient( + color = backgroundColor, + startY = { size.height - gradientSizePx }, + ) + } +} + +fun Modifier.horizontalScrollStateGradient( + scrollState: LazyListState, + color: Color, + showAtStart: Boolean = true, + showAtEnd: Boolean = true, +): Modifier = composed { + val gradientSizePx = + with(LocalDensity.current) { gradientSize.toPx() } + this + .addIf(showAtStart && !scrollState.isScrolledToStart()) { + Modifier.drawWithContent { + drawContent() + drawRect( + brush = Brush.horizontalGradient( + colors = listOf( + color, + Color.Transparent, + ), + startX = 0f, + endX = gradientSizePx, + ), + ) + } + } + .addIf(showAtEnd && !scrollState.isScrolledToEnd()) { + Modifier.drawWithContent { + drawContent() + drawRect( + brush = Brush.horizontalGradient( + colors = listOf( + Color.Transparent, + color, + ), + startX = size.width - gradientSizePx, + endX = size.width, + ), + ) + } + } +} + +fun Modifier.verticalScrollStateGradient( + scrollState: LazyGridState, + color: Color, + showAtStart: Boolean = true, + showAtEnd: Boolean = true, +): Modifier = composed { + val gradientSizePx = + with(LocalDensity.current) { gradientSize.toPx() } + this + .addIf(showAtStart && !scrollState.isVerticallyScrolledToStart()) { + Modifier.drawWithGradient( + color = color, + startY = { gradientSizePx }, + endY = { 0f }, + ) + } + .addIf(showAtEnd && !scrollState.isVerticallyScrolledToEnd()) { + Modifier.drawWithGradient( + color = color, + startY = { size.height - gradientSizePx }, + ) + } +} + +fun LazyListState.isScrolledToEnd(): Boolean { + val lastItem = layoutInfo.visibleItemsInfo.lastOrNull() + return lastItem == null || + (lastItem.index == layoutInfo.totalItemsCount - 1 && lastItem.size + lastItem.offset <= layoutInfo.viewportEndOffset) +} + +fun LazyListState.isScrolledToStart(): Boolean { + val firstItem = layoutInfo.visibleItemsInfo.firstOrNull() + return firstItem == null || firstItem.offset == 0 +} + +fun LazyGridState.isVerticallyScrolledToEnd(): Boolean { + val lastItem = layoutInfo.visibleItemsInfo.lastOrNull() + return lastItem == null || + (lastItem.index == layoutInfo.totalItemsCount - 1 && lastItem.size.height + lastItem.offset.y <= layoutInfo.viewportEndOffset) +} + +fun LazyGridState.isVerticallyScrolledToStart(): Boolean { + val firstItem = layoutInfo.visibleItemsInfo.firstOrNull() + return firstItem == null || firstItem.offset.y == 0 +} + +fun Modifier.footerShadow() = this.drawWithContent { + drawContent() + drawRect( + brush = Brush.verticalGradient( + endY = size.height * 0.12f, + colors = listOf(Color(0x10000000), Color.Transparent), + ), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/getcode/view/main/account/AccountHome.kt b/app/src/main/java/com/getcode/view/main/account/AccountHome.kt index e4784a550..70c69390d 100644 --- a/app/src/main/java/com/getcode/view/main/account/AccountHome.kt +++ b/app/src/main/java/com/getcode/view/main/account/AccountHome.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.material.Text @@ -47,11 +48,12 @@ import com.getcode.navigation.screens.BuySellScreen import com.getcode.navigation.screens.DepositKinScreen import com.getcode.navigation.screens.FaqScreen import com.getcode.navigation.screens.WithdrawalAmountScreen -import com.getcode.theme.BrandLight import com.getcode.theme.CodeTheme import com.getcode.theme.White10 +import com.getcode.ui.components.CodeScaffold import com.getcode.ui.utils.getActivity import com.getcode.ui.utils.rememberedClickable +import com.getcode.ui.utils.verticalScrollStateGradient import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -86,6 +88,7 @@ fun AccountHome( navigator.push(BuySellScreen) } } + AccountPage.DEPOSIT -> navigator.push(DepositKinScreen) AccountPage.WITHDRAW -> navigator.push(WithdrawalAmountScreen) AccountPage.FAQ -> navigator.push(FaqScreen) @@ -120,18 +123,11 @@ fun AccountHome( } } - LazyColumn(modifier = Modifier.fillMaxSize()) { - items(dataState.items, key = { it.type }, contentType = { it }) { item -> - ListItem(item = item) { - handleItemClicked(item.type) - } - } - - item { + CodeScaffold( + bottomBar = { Box(modifier = Modifier.fillMaxWidth()) { Text( modifier = Modifier - .padding(top = CodeTheme.dimens.grid.x7) .fillMaxWidth() .align(Alignment.Center), text = "Version ${BuildConfig.VERSION_NAME} • Build ${BuildConfig.VERSION_CODE}", @@ -142,6 +138,24 @@ fun AccountHome( ) } } + ) { padding -> + val listState = rememberLazyListState() + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .verticalScrollStateGradient( + scrollState = listState, + isLongGradient = true, + ), + state = listState, + ) { + items(dataState.items, key = { it.type }, contentType = { it }) { item -> + ListItem(item = item) { + handleItemClicked(item.type) + } + } + } } } From 880808d5dd26645b01d2891b525cb5646075fd67 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 14 Aug 2024 17:24:51 -0400 Subject: [PATCH 14/33] build: prep next development cycle --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 0a09bf04d..b03ce9817 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -13,7 +13,7 @@ object Packaging { private const val majorVersion = 2 private const val minorVersion = 1 private const val patchVersion = 7 - private const val buildNumber = 415 + private const val buildNumber = 416 const val versionCode = buildNumber const val versionName = "$majorVersion.$minorVersion.$patchVersion" From e5275363de0881a2f9b35c2314f93f52b3225b9e Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 16 Aug 2024 08:18:40 -0400 Subject: [PATCH 15/33] build: bump build number --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index b03ce9817..ee6cf58e5 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -13,7 +13,7 @@ object Packaging { private const val majorVersion = 2 private const val minorVersion = 1 private const val patchVersion = 7 - private const val buildNumber = 416 + private const val buildNumber = 417 const val versionCode = buildNumber const val versionName = "$majorVersion.$minorVersion.$patchVersion" From 3c064201a63055a226357c8bfc0376e220540527 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 16 Aug 2024 08:17:10 -0400 Subject: [PATCH 16/33] feat: add tip card to home screen behind beta flag Signed-off-by: Brandon McAnsh --- .../main/java/com/getcode/model/Feature.kt | 5 +++ .../main/java/com/getcode/model/PrefBool.kt | 1 + .../network/repository/BetaFlagsRepository.kt | 10 ++++-- .../network/repository/FeatureRepository.kt | 3 +- .../navigation/screens/ModalScreens.kt | 34 ++++++++++++++----- .../view/main/account/BetaFlagsScreen.kt | 6 ++++ .../com/getcode/view/main/home/HomeScan.kt | 12 ++++++- .../getcode/view/main/home/HomeViewModel.kt | 9 +++++ .../view/main/home/components/HomeBottom.kt | 27 ++++++++++++--- app/src/main/res/drawable/ic_tip_card.xml | 20 +++++++++++ app/src/main/res/values/strings-universal.xml | 2 ++ app/src/main/res/values/strings.xml | 1 + 12 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 app/src/main/res/drawable/ic_tip_card.xml diff --git a/api/src/main/java/com/getcode/model/Feature.kt b/api/src/main/java/com/getcode/model/Feature.kt index ebcc72bf0..b62461377 100644 --- a/api/src/main/java/com/getcode/model/Feature.kt +++ b/api/src/main/java/com/getcode/model/Feature.kt @@ -17,6 +17,11 @@ data class TipCardFeature( override val available: Boolean = true, // always available ): Feature +data class TipCardOnHomeScreenFeature( + override val enabled: Boolean = BetaOptions.Defaults.tipCardOnHomeScreen, + override val available: Boolean = true, // always available +): Feature + data class TipChatFeature( override val enabled: Boolean = BetaOptions.Defaults.tipsChatEnabled, override val available: Boolean = true, // always available diff --git a/api/src/main/java/com/getcode/model/PrefBool.kt b/api/src/main/java/com/getcode/model/PrefBool.kt index 590c172e8..3c6b41b71 100644 --- a/api/src/main/java/com/getcode/model/PrefBool.kt +++ b/api/src/main/java/com/getcode/model/PrefBool.kt @@ -50,6 +50,7 @@ sealed class PrefsBool(val value: String) { data object BALANCE_CURRENCY_SELECTION_ENABLED: PrefsBool("balance_currency_enabled"), BetaFlag data object KADO_WEBVIEW_ENABLED : PrefsBool("kado_inapp_enabled"), BetaFlag data object SHARE_TWEET_TO_TIP : PrefsBool("share_tweet_to_tip"), BetaFlag + data object TIP_CARD_ON_HOMESCREEN: PrefsBool("tip_card_on_home_screen"), BetaFlag } val APP_SETTINGS: List = listOf(PrefsBool.CAMERA_START_BY_DEFAULT, PrefsBool.REQUIRE_BIOMETRICS) \ No newline at end of file diff --git a/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt b/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt index 5d798f63b..ba9c121ef 100644 --- a/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt @@ -20,6 +20,7 @@ data class BetaOptions( val balanceCurrencySelectionEnabled: Boolean, val kadoWebViewEnabled: Boolean, val shareTweetToTip: Boolean, + val tipCardOnHomeScreen: Boolean, ) { companion object { // Default states for various beta flags in app. @@ -37,7 +38,8 @@ data class BetaOptions( tipsChatCashEnabled = false, balanceCurrencySelectionEnabled = true, kadoWebViewEnabled = false, - shareTweetToTip = false + shareTweetToTip = false, + tipCardOnHomeScreen = true, ) } } @@ -73,7 +75,8 @@ class BetaFlagsRepository @Inject constructor( observeBetaFlag(PrefsBool.BALANCE_CURRENCY_SELECTION_ENABLED, defaults.balanceCurrencySelectionEnabled), observeBetaFlag(PrefsBool.DISPLAY_ERRORS, default = defaults.displayErrors), observeBetaFlag(PrefsBool.KADO_WEBVIEW_ENABLED, default = defaults.kadoWebViewEnabled), - observeBetaFlag(PrefsBool.SHARE_TWEET_TO_TIP, default = defaults.shareTweetToTip) + observeBetaFlag(PrefsBool.SHARE_TWEET_TO_TIP, default = defaults.shareTweetToTip), + observeBetaFlag(PrefsBool.TIP_CARD_ON_HOMESCREEN, defaults.tipCardOnHomeScreen) ) { BetaOptions( showNetworkDropOff = it[0], @@ -89,7 +92,8 @@ class BetaFlagsRepository @Inject constructor( balanceCurrencySelectionEnabled = it[10], displayErrors = it[11], kadoWebViewEnabled = it[12], - shareTweetToTip = it[13] + shareTweetToTip = it[13], + tipCardOnHomeScreen = it[14] ) } } diff --git a/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt b/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt index f68bd22cb..b6a0e3af7 100644 --- a/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt @@ -7,6 +7,7 @@ import com.getcode.model.Feature import com.getcode.model.PrefsBool import com.getcode.model.RequestKinFeature import com.getcode.model.TipCardFeature +import com.getcode.model.TipCardOnHomeScreenFeature import com.getcode.model.TipChatCashFeature import com.getcode.model.TipChatFeature import kotlinx.coroutines.flow.combine @@ -26,7 +27,7 @@ class FeatureRepository @Inject constructor( ) { enabled, available -> BuyModuleFeature(enabled, available) } val tipCards = betaFlags.observe().map { TipCardFeature(it.tipsEnabled) } - + val tipCardOnHomeScreen = betaFlags.observe().map { TipCardOnHomeScreenFeature(it.tipCardOnHomeScreen) } val tipChat = betaFlags.observe().map { TipChatFeature(it.tipsChatEnabled) } val tipChatCash = betaFlags.observe().map { TipChatCashFeature(it.tipsChatCashEnabled) } diff --git a/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt b/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt index 7546a41fd..a31f7c54d 100644 --- a/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt +++ b/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt @@ -466,19 +466,35 @@ data class ConnectAccount( override fun Content() { val navigator = LocalCodeNavigator.current val viewModel = getViewModel() - ModalContainer( - backButtonEnabled = { - if (navigator.isVisible) { - it is ConnectAccount - } else { - navigator.progress > 0f + when (reason) { + IdentityConnectionReason.TipCard -> { + ModalContainer( + closeButtonEnabled = { + if (navigator.isVisible) { + it is ConnectAccount + } else { + navigator.progress > 0f + } + } + ) { + ConnectAccountScreen(viewModel) + } + } + IdentityConnectionReason.IdentityReveal -> { + ModalContainer( + backButtonEnabled = { + if (navigator.isVisible) { + it is ConnectAccount + } else { + navigator.progress > 0f + } + } + ) { + ConnectAccountScreen(viewModel) } } - ) { - ConnectAccountScreen(viewModel) } - LaunchedEffect(viewModel, reason) { viewModel.dispatchEvent(TipConnectViewModel.Event.OnReasonChanged(reason)) } diff --git a/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt b/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt index 30201acf8..544a079de 100644 --- a/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt +++ b/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt @@ -89,6 +89,12 @@ fun BetaFlagsScreen( stringResource(id = R.string.beta_tipcard_description), state.tipsEnabled, ), + BetaFeature( + PrefsBool.TIP_CARD_ON_HOMESCREEN, + R.string.beta_tipcard_on_homescreen, + stringResource(id = R.string.beta_tipcard_on_homescreen_description), + state.tipCardOnHomeScreen, + ), BetaFeature( PrefsBool.TIPS_CHAT_ENABLED, R.string.beta_tipchats, diff --git a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt index 89d20399d..8dc82ceba 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt @@ -48,9 +48,11 @@ import com.getcode.navigation.screens.AccountModal import com.getcode.navigation.screens.BalanceModal import com.getcode.navigation.screens.BuyMoreKinModal import com.getcode.navigation.screens.BuySellScreen +import com.getcode.navigation.screens.ConnectAccount import com.getcode.navigation.screens.EnterTipModal import com.getcode.navigation.screens.GetKinModal import com.getcode.navigation.screens.GiveKinModal +import com.getcode.navigation.screens.HomeResult import com.getcode.navigation.screens.ShareDownloadLinkModal import com.getcode.ui.components.OnLifecycleEvent import com.getcode.ui.components.PermissionCheck @@ -81,7 +83,8 @@ enum class HomeBottomSheet { GIVE_KIN, GET_KIN, BALANCE, - SHARE_DOWNLOAD + SHARE_DOWNLOAD, + TIP_CARD } @Composable @@ -191,6 +194,13 @@ private fun HomeScan( HomeBottomSheet.GET_KIN -> navigator.show(GetKinModal) HomeBottomSheet.BALANCE -> navigator.show(BalanceModal) HomeBottomSheet.SHARE_DOWNLOAD -> navigator.show(ShareDownloadLinkModal) + HomeBottomSheet.TIP_CARD -> { + if (dataState.tipCardConnected) { + homeViewModel.presentShareableTipCard() + } else { + navigator.push(ConnectAccount()) + } + } HomeBottomSheet.NONE -> Unit } } diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index f229a50d3..c7471481e 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -32,6 +32,7 @@ import com.getcode.model.PrefsBool import com.getcode.model.Rate import com.getcode.model.RequestKinFeature import com.getcode.model.TipCardFeature +import com.getcode.model.TipCardOnHomeScreenFeature import com.getcode.model.TwitterUser import com.getcode.model.Username import com.getcode.models.Bill @@ -147,6 +148,7 @@ data class HomeUiModel( val buyModule: Feature = BuyModuleFeature(), val requestKin: Feature = RequestKinFeature(), val tips: Feature = TipCardFeature(), + val tipCardOnHomeScreen: Feature = TipCardOnHomeScreenFeature(), val tipCardConnected: Boolean = false, ) @@ -225,6 +227,13 @@ class HomeViewModel @Inject constructor( } }.launchIn(viewModelScope) + features.tipCardOnHomeScreen + .onEach { module -> + uiFlow.update { + it.copy(tipCardOnHomeScreen = module) + } + }.launchIn(viewModelScope) + features.requestKin .onEach { module -> uiFlow.update { diff --git a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt index 0f9419543..769b2a620 100644 --- a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt +++ b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt @@ -50,7 +50,11 @@ internal fun HomeBottom( contentPadding = PaddingValues(horizontal = CodeTheme.dimens.grid.x3), ) { BottomBarAction( - label = stringResource(R.string.title_getKin), + label = if (state.tipCardOnHomeScreen.enabled) { + stringResource(R.string.title_tipCard) + } else { + stringResource(R.string.title_getKin) + }, contentPadding = PaddingValues( start = CodeTheme.dimens.grid.x3, end = CodeTheme.dimens.grid.x3, @@ -58,8 +62,18 @@ internal fun HomeBottom( bottom = CodeTheme.dimens.grid.x2, ), imageSize = CodeTheme.dimens.grid.x7, - painter = painterResource(R.drawable.ic_wallet), - onClick = { onPress(HomeBottomSheet.GET_KIN) }, + painter = if (state.tipCardOnHomeScreen.enabled) { + painterResource(R.drawable.ic_tip_card) + } else { + painterResource(R.drawable.ic_wallet) + }, + onClick = { + if (state.tipCardOnHomeScreen.enabled) { + onPress(HomeBottomSheet.TIP_CARD) + } else { + onPress(HomeBottomSheet.GET_KIN) + } + }, ) Spacer(modifier = Modifier.weight(1f)) BottomBarAction( @@ -86,7 +100,12 @@ internal fun HomeBottom( modifier = Modifier.padding(top = 2.dp, end = 2.dp), count = state.chatUnreadCount, color = ChatNodeDefaults.UnreadIndicator, - enterTransition = scaleIn(animationSpec = tween(durationMillis = 300, delayMillis = 1000)) + fadeIn() + enterTransition = scaleIn( + animationSpec = tween( + durationMillis = 300, + delayMillis = 1000 + ) + ) + fadeIn() ) } ) diff --git a/app/src/main/res/drawable/ic_tip_card.xml b/app/src/main/res/drawable/ic_tip_card.xml new file mode 100644 index 000000000..f9edb10ab --- /dev/null +++ b/app/src/main/res/drawable/ic_tip_card.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/app/src/main/res/values/strings-universal.xml b/app/src/main/res/values/strings-universal.xml index 55d4736d5..328c3d16c 100644 --- a/app/src/main/res/values/strings-universal.xml +++ b/app/src/main/res/values/strings-universal.xml @@ -20,6 +20,7 @@ Vibrate on Scan Show Connectivity Status Tip Card + Tip Card on Home Screen Tip Chats Tip Chats Cash Currency Selection in Balance @@ -35,6 +36,7 @@ If enabled, a relationship account will be established with getcode.com if it doesn\'t yet exist. If enabled, an option to unsubscribe from a chat will appear for supported chats. If enabled, you\'ll gain the ability to share a tip card. + If enabled, your tip card will replace Get Cash on the home screen. If enabled, you\'ll gain the ability to chat with tippers. If enabled, you\'ll gain the ability to send Kin in Tip Chats. If enabled, the Buy Kin flow will open in an internal WebView. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a52bd9cbd..4e6cb7013 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,4 +49,5 @@ Connect Account Connecting your X account allows you to reveal your identity to the people you tip. To connect your account post to X. Use Kado Sandbox + Tip Card From 7baaf2de602c60eebeb838623edd78461c8838a5 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 16 Aug 2024 17:03:47 -0400 Subject: [PATCH 17/33] chore: switch to generated versionCode Signed-off-by: Brandon McAnsh --- app/build.gradle.kts | 3 ++- build.gradle.kts | 2 ++ buildSrc/src/main/java/Dependencies.kt | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6027ef141..119d991b3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,6 +14,7 @@ plugins { id(Plugins.firebase_perf) id(Plugins.bugsnag) id(Plugins.secrets_gradle_plugin) + id(Plugins.versioning_gradle_plugin) } val contributorsSigningConfig = ContributorsSignatory(rootProject) @@ -24,7 +25,7 @@ android { defaultConfig { applicationId = Android.namespace - versionCode = Packaging.versionCode + versionCode = versioning.getVersionCode() versionName = Packaging.versionName minSdk = Android.minSdkVersion diff --git a/build.gradle.kts b/build.gradle.kts index 8570b51bd..96e44c946 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ buildscript { repositories { + gradlePluginPortal() google() mavenCentral() maven(url = "https://plugins.gradle.org/m2/") @@ -18,6 +19,7 @@ buildscript { classpath(Classpath.secrets_gradle_plugin) classpath(Classpath.kotlin_serialization_plugin) classpath(Classpath.protobuf_plugin) + classpath(Classpath.versioning_gradle_plugin) } } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index ee6cf58e5..c76503a27 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -13,9 +13,7 @@ object Packaging { private const val majorVersion = 2 private const val minorVersion = 1 private const val patchVersion = 7 - private const val buildNumber = 417 - const val versionCode = buildNumber const val versionName = "$majorVersion.$minorVersion.$patchVersion" } @@ -104,6 +102,7 @@ object Classpath { const val bugsnag = "com.bugsnag:bugsnag-android-gradle-plugin:8.+" const val firebase_perf = "com.google.firebase:perf-plugin:1.4.2" const val secrets_gradle_plugin = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1" + const val versioning_gradle_plugin = "de.nanogiants:android-versioning:2.4.0" } object Plugins { @@ -120,6 +119,7 @@ object Plugins { const val firebase_perf = "com.google.firebase.firebase-perf" const val bugsnag = "com.bugsnag.android.gradle" const val secrets_gradle_plugin = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" + const val versioning_gradle_plugin = "de.nanogiants.android-versioning" } object Libs { From ca9eef1baf50930244c4588bd4751acaff4b01a1 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 16 Aug 2024 17:04:10 -0400 Subject: [PATCH 18/33] chore: show instead of push ConnectAccountScreen from home screen Signed-off-by: Brandon McAnsh --- app/src/main/java/com/getcode/view/main/home/HomeScan.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt index 8dc82ceba..672bd4c33 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt @@ -198,7 +198,7 @@ private fun HomeScan( if (dataState.tipCardConnected) { homeViewModel.presentShareableTipCard() } else { - navigator.push(ConnectAccount()) + navigator.show(ConnectAccount()) } } HomeBottomSheet.NONE -> Unit From 4e4569406d769e5037865508e07933a987a49212 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 16 Aug 2024 17:05:12 -0400 Subject: [PATCH 19/33] build: prep next development cycle Signed-off-by: Brandon McAnsh --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index c76503a27..ee33e6682 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -12,7 +12,7 @@ object Android { object Packaging { private const val majorVersion = 2 private const val minorVersion = 1 - private const val patchVersion = 7 + private const val patchVersion = 8 const val versionName = "$majorVersion.$minorVersion.$patchVersion" } From 275308c7c7c34447810b912b287edb9687fc55a5 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 16 Aug 2024 17:24:20 -0400 Subject: [PATCH 20/33] chore: update translations Signed-off-by: Brandon McAnsh --- .../navigation/screens/ModalScreens.kt | 4 +-- .../main/account/AccountSheetViewModel.kt | 2 +- .../getcode/view/main/balance/BalanceSheet.kt | 2 +- .../getcode/view/main/getKin/GetKinSheet.kt | 2 +- .../view/main/home/components/HomeBottom.kt | 2 +- .../main/res/values-ar/strings-localized.xml | 3 ++ .../main/res/values-bg/strings-localized.xml | 3 ++ .../main/res/values-cs/strings-localized.xml | 3 ++ .../main/res/values-da/strings-localized.xml | 3 ++ .../main/res/values-de/strings-localized.xml | 3 ++ .../main/res/values-el/strings-localized.xml | 3 ++ .../res/values-en-rGB/strings-localized.xml | 29 ++++++++------- .../main/res/values-es/strings-localized.xml | 3 ++ .../main/res/values-fi/strings-localized.xml | 3 ++ .../res/values-fr-rCA/strings-localized.xml | 3 ++ .../main/res/values-fr/strings-localized.xml | 3 ++ .../main/res/values-he/strings-localized.xml | 3 ++ .../res/values-hi-rIN/strings-localized.xml | 3 ++ .../main/res/values-hu/strings-localized.xml | 3 ++ .../main/res/values-id/strings-localized.xml | 3 ++ .../main/res/values-it/strings-localized.xml | 3 ++ .../main/res/values-ja/strings-localized.xml | 3 ++ .../main/res/values-ko/strings-localized.xml | 3 ++ .../main/res/values-ms/strings-localized.xml | 3 ++ .../main/res/values-nl/strings-localized.xml | 3 ++ .../main/res/values-no/strings-localized.xml | 3 ++ .../main/res/values-pl/strings-localized.xml | 3 ++ .../main/res/values-pt/strings-localized.xml | 3 ++ .../main/res/values-ro/strings-localized.xml | 3 ++ .../main/res/values-ru/strings-localized.xml | 3 ++ .../main/res/values-sk/strings-localized.xml | 3 ++ .../main/res/values-sr/strings-localized.xml | 3 ++ .../main/res/values-sv/strings-localized.xml | 3 ++ .../main/res/values-th/strings-localized.xml | 3 ++ .../main/res/values-tr/strings-localized.xml | 3 ++ .../main/res/values-uk/strings-localized.xml | 3 ++ .../main/res/values-vi/strings-localized.xml | 3 ++ .../res/values-zh-rCN/strings-localized.xml | 3 ++ .../res/values-zh-rTW/strings-localized.xml | 3 ++ app/src/main/res/values/strings-localized.xml | 36 ++++++++++--------- 40 files changed, 141 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt b/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt index a31f7c54d..51997739c 100644 --- a/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt +++ b/app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt @@ -318,7 +318,7 @@ data class BuyMoreKinModal( override val key: ScreenKey = uniqueScreenKey override val name: String - @Composable get() = stringResource(id = R.string.action_buyMoreKin) + @Composable get() = stringResource(id = R.string.action_addCash) @Composable override fun Content() { @@ -367,7 +367,7 @@ data class KadoWebScreen(val url: String) : MainGraph, ModalContent { override val key: ScreenKey = uniqueScreenKey override val name: String - @Composable get() = stringResource(id = R.string.action_buyMoreKin) + @Composable get() = stringResource(id = R.string.action_addCash) @Composable override fun Content() { diff --git a/app/src/main/java/com/getcode/view/main/account/AccountSheetViewModel.kt b/app/src/main/java/com/getcode/view/main/account/AccountSheetViewModel.kt index cd26e9eaf..fb4ad7653 100644 --- a/app/src/main/java/com/getcode/view/main/account/AccountSheetViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/account/AccountSheetViewModel.kt @@ -170,7 +170,7 @@ class AccountSheetViewModel @Inject constructor( when (it.type) { AccountPage.BUY_KIN -> { if (buyModuleEnabled) { - it.copy(name = R.string.action_buyMoreKin) + it.copy(name = R.string.action_addCash) } else { it.copy(name = R.string.title_buySellKin) } diff --git a/app/src/main/java/com/getcode/view/main/balance/BalanceSheet.kt b/app/src/main/java/com/getcode/view/main/balance/BalanceSheet.kt index 4ee9e802b..e94c9a1fa 100644 --- a/app/src/main/java/com/getcode/view/main/balance/BalanceSheet.kt +++ b/app/src/main/java/com/getcode/view/main/balance/BalanceSheet.kt @@ -186,7 +186,7 @@ fun BalanceContent( ) } }, - text = stringResource(id = R.string.action_buyMoreKin) + text = stringResource(id = R.string.action_addCash) ) } } diff --git a/app/src/main/java/com/getcode/view/main/getKin/GetKinSheet.kt b/app/src/main/java/com/getcode/view/main/getKin/GetKinSheet.kt index f7167f5b0..d1397c9b4 100644 --- a/app/src/main/java/com/getcode/view/main/getKin/GetKinSheet.kt +++ b/app/src/main/java/com/getcode/view/main/getKin/GetKinSheet.kt @@ -190,7 +190,7 @@ private fun Header() { modifier = Modifier.padding(vertical = CodeTheme.dimens.grid.x2), ) Text( - text = stringResource(R.string.title_getKin), + text = stringResource(R.string.title_getCash), style = CodeTheme.typography.displayMedium.bolded(), modifier = Modifier.padding(vertical = CodeTheme.dimens.grid.x3), ) diff --git a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt index 769b2a620..526fc950c 100644 --- a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt +++ b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt @@ -53,7 +53,7 @@ internal fun HomeBottom( label = if (state.tipCardOnHomeScreen.enabled) { stringResource(R.string.title_tipCard) } else { - stringResource(R.string.title_getKin) + stringResource(R.string.title_getCash) }, contentPadding = PaddingValues( start = CodeTheme.dimens.grid.x3, diff --git a/app/src/main/res/values-ar/strings-localized.xml b/app/src/main/res/values-ar/strings-localized.xml index 834714080..e8e45350a 100644 --- a/app/src/main/res/values-ar/strings-localized.xml +++ b/app/src/main/res/values-ar/strings-localized.xml @@ -1,5 +1,6 @@ + أضف نقودًا عن طريق استخدام بطاقة الخصم السماح بإتاحة الوصول إلى الكاميرا السماح بإتاحة الوصول إلى جهات الاتصال السماح بالإشعارات اللحظية @@ -322,6 +323,7 @@ فشلت العملية الأسئلة الشائعة الممنوحة + احصل غلى نقود احصل على صديق بدأ في Code احصل على Kin احصل على المزيد من Kin @@ -344,6 +346,7 @@ العملات الأخيرة ارسل طلب إحالة لصديق, واحصل على 5 دولارات مكافأة الإحالة + طلب نقود اطلب Kin اطلب إكرامية مطلوب Face ID\n\n diff --git a/app/src/main/res/values-bg/strings-localized.xml b/app/src/main/res/values-bg/strings-localized.xml index 9755ba5fd..5eda7027b 100644 --- a/app/src/main/res/values-bg/strings-localized.xml +++ b/app/src/main/res/values-bg/strings-localized.xml @@ -1,5 +1,6 @@ + Добавете парична сума с дебитна карта Разреши достъп до камерата Разреши достъп до контактите Разреши насочени известия @@ -322,6 +323,7 @@ Неуспешно Често задавани въпроси Даде + Вземете парична сума Вземете \"Приятел се присъедини\" към Code Получаване на средства в Kin Получавайте повече Kin @@ -344,6 +346,7 @@ Последно използвани валути Препоръчай на приятел, вземи $5 Бонус за препоръка + Направете заявка за парична сума Поискай Kin Поискай бакшиш Изискване на Face ID diff --git a/app/src/main/res/values-cs/strings-localized.xml b/app/src/main/res/values-cs/strings-localized.xml index 1b5017e4f..25fe802f6 100644 --- a/app/src/main/res/values-cs/strings-localized.xml +++ b/app/src/main/res/values-cs/strings-localized.xml @@ -1,5 +1,6 @@ + Přidání hotovosti pomocí debetní karty Umožnit přístup fotoaparátu Povolit přístup ke kontaktům Povolit zprávy push @@ -322,6 +323,7 @@ Selhalo Časté dotazy Darováno + Získat hotovost Začněte kamarádem používat Code Získat Kin Získejte další Kin @@ -344,6 +346,7 @@ Poslední měny Doporučte kamaráda a získejte 5 USD Bonus za doporučení + Žádost o hotovost Požádat o Kin Požádat o spropitné Vyžaduje Face ID diff --git a/app/src/main/res/values-da/strings-localized.xml b/app/src/main/res/values-da/strings-localized.xml index cb7e5fd4a..990116288 100644 --- a/app/src/main/res/values-da/strings-localized.xml +++ b/app/src/main/res/values-da/strings-localized.xml @@ -1,5 +1,6 @@ + Tilføj kontanter med et betalingskort Tillad kameraadgang Tillad adgang til kontakter Tillad pushnotifikationer @@ -322,6 +323,7 @@ Mislykkedes FAQ Gav + Få kontanter Få en ven i gang med Code Få Kin Få flere Kin @@ -344,6 +346,7 @@ Seneste valutaer Henvis en ven, få $5 Henvisningsbonus + Anmod om kontanter Anmod om Kin Anmod om drikkepenge Kræv Face ID diff --git a/app/src/main/res/values-de/strings-localized.xml b/app/src/main/res/values-de/strings-localized.xml index 37b5c6613..f6c22ef78 100644 --- a/app/src/main/res/values-de/strings-localized.xml +++ b/app/src/main/res/values-de/strings-localized.xml @@ -1,5 +1,6 @@ + Bargeld mit einer Debitkarte hinzufügen\n Kamera-Zugriff erlauben Zugriff auf Kontakte zulassen Push-Benachrichtigungen zulassen @@ -322,6 +323,7 @@ Fehlgeschlagen FAQ Überwiesen + Bargeld erhalten\n Gewinne einen deiner Freunde für Code Kin erhalten Mehr Kin erhalten\n @@ -344,6 +346,7 @@ Letzte Währungen Empfehle Sie uns an einen Freund oder eine Freundin weiter und erhalten Sie 5 $ dafür Empfehlungsbonus + Bargeld anfordern Kin anfordern Trinkgeld anfordern Face ID erforderlich diff --git a/app/src/main/res/values-el/strings-localized.xml b/app/src/main/res/values-el/strings-localized.xml index 911192c75..eff24f0fa 100644 --- a/app/src/main/res/values-el/strings-localized.xml +++ b/app/src/main/res/values-el/strings-localized.xml @@ -1,5 +1,6 @@ + Προσθέστε μετρητά με χρεωστική κάρτα Να Επιτρέπεται η Πρόσβαση στην Κάμερα Να Επιτρέπεται η Πρόσβαση στις Επαφές Να Επιτρέπονται οι Ειδοποιήσεις Push @@ -322,6 +323,7 @@ Απέτυχε Αριθμός Τηλεφώνου Δόθηκε + Λάβετε μετρητά Βρες ένα Φίλο να Ξεκινήσει στο Code Λάβετε Kin Αποκτήστε Περισσότερα Kin @@ -344,6 +346,7 @@ Πρόσφατα Νομίσματα Σύστησε ένα Φίλο, Λάβε $5 Δώρο Σύστασης Φίλου + Ζητήστε μετρητά Ζητήστε Kin Ζητήστε Φιλοδώρημα Απαιτείται Αναγνώριση Προσώπου diff --git a/app/src/main/res/values-en-rGB/strings-localized.xml b/app/src/main/res/values-en-rGB/strings-localized.xml index bdfac5686..9d41538b3 100644 --- a/app/src/main/res/values-en-rGB/strings-localized.xml +++ b/app/src/main/res/values-en-rGB/strings-localized.xml @@ -1,11 +1,12 @@ + Add Cash with a Debit Card Allow Camera Access Allow Access to Contacts Allow Push Notifications Balance - Add Cash with a Debit Card - Add Cash with a Debit Card + Buy Kin + Buy More Kin Cancel Cancel Send Collect This Cash @@ -23,7 +24,7 @@ Enable Face ID Enable Touch ID Exit - Give Cash + Give Kin Invite Invites Join the Waitlist @@ -68,10 +69,10 @@ Unsubscribe Update View Access Key - Withdraw Cash + Withdraw Kin Wrote the 12 Words Down Instead? Yes - Yes, Withdraw Cash + Yes, Withdraw Kin Yes, I Wrote Them Down and Kin @@ -213,9 +214,9 @@ Tap the Google Lens icon to open the QR code to log into Code. Alternatively you can log in manually by entering the 12 words in the Code Log In screen. Advarsel! Dette billede giver adgang til alle de funds, du har i Code. Del ikke dette billede med andre. Hold det sikkert og beskyttet. Code enables you to receive Kin by pointing your camera at the digital bill on another user\'s phone - You need to turn on Camera in Settings to scan Codes + You need to allow camera access to be able to receive Kin Authenticate to access Code. - Add Cash with a Debit Card + Buy Kin Buy Kin (Coming Soon) Buying and selling Kin is currently a complex process. These processes will become simpler over time. If you would like to learn how to buy and sell Kin today, you can watch the walkthrough videos below. You can only give up to %1$s @@ -322,10 +323,11 @@ Failed FAQ Gave + Get Cash Get a Friend Started on Code - Get Cash - Get more cash - Give Cash + Get Kin + Get more Kin + Give Kin Insufficient funds Invite a Friend Limited Time Offer @@ -344,7 +346,8 @@ Recent Currencies Refer a Friend and Get $5 Referral Bonus - Request Cash + Request Cash + Request Kin Request a Tip Require Face ID Require Passcode @@ -358,12 +361,12 @@ Spent Switch Accounts Terms of Service - Tip Cash + Tip Kin Unknown Update Required Verify Phone Number Welcome Bonus - Withdraw Cash + Withdraw Kin Withdrew Your Access Key Tap the logo to share the app download link diff --git a/app/src/main/res/values-es/strings-localized.xml b/app/src/main/res/values-es/strings-localized.xml index 0287edda8..04b91ffbd 100644 --- a/app/src/main/res/values-es/strings-localized.xml +++ b/app/src/main/res/values-es/strings-localized.xml @@ -1,5 +1,6 @@ + Añadir efectivo con una tarjeta de débito Permitir acceso a cámara Permitir acceso a contactos Permitir notificaciones Push @@ -322,6 +323,7 @@ Fallido FAQ Dio + Obtener efectivo Inicia a un amigo en Code Obtener Kin Obtenga más Kin @@ -344,6 +346,7 @@ Divisas recientes Recomienda a un amigo y consigue 5 $ Bono por recomendación + Solicitar efectivo Solicitar Kin Solicitar una propina Requerir Face ID diff --git a/app/src/main/res/values-fi/strings-localized.xml b/app/src/main/res/values-fi/strings-localized.xml index 7c77251f0..ed5ba9226 100644 --- a/app/src/main/res/values-fi/strings-localized.xml +++ b/app/src/main/res/values-fi/strings-localized.xml @@ -1,5 +1,6 @@ + Lisää rahaa pankkikortilla Salli kameran käyttöoikeudet Salli yhteystietojen käyttöoikeudet Salli palveluilmoitukset @@ -322,6 +323,7 @@ Epäonnistui UKK Antoi + Nosta rahaa Auta kaveri alkuun Coden parissa Hae Kin Hanki lisää kinejä @@ -344,6 +346,7 @@ Viimeaikaiset valuutat Suosita ystävää, saat 5 $ Suositusbonus + Pyydä rahaa Pyydä kinejä Pyydä tippi Vaadi kasvotunnistus diff --git a/app/src/main/res/values-fr-rCA/strings-localized.xml b/app/src/main/res/values-fr-rCA/strings-localized.xml index 7054916d7..dd9edbd07 100644 --- a/app/src/main/res/values-fr-rCA/strings-localized.xml +++ b/app/src/main/res/values-fr-rCA/strings-localized.xml @@ -1,5 +1,6 @@ + Ajouter des liquidités avec une carte de débit Autoriser l\'accès à la caméra Autoriser l\'accès aux contacts Autoriser les notifications poussées @@ -322,6 +323,7 @@ Échoué FAQ A donné + Obtenir des liquidités Faire démarrer un ami sur Code Obtenez Kin Obtenez plus de Kin @@ -344,6 +346,7 @@ Devises récentes Parrainez un ami, recevez 5 $ Prime de parrainage + Demander des liquidités Demander des Kin Demander un pourboire Exiger Face ID diff --git a/app/src/main/res/values-fr/strings-localized.xml b/app/src/main/res/values-fr/strings-localized.xml index 186f8078a..fefa04c65 100644 --- a/app/src/main/res/values-fr/strings-localized.xml +++ b/app/src/main/res/values-fr/strings-localized.xml @@ -1,5 +1,6 @@ + Ajouter des liquidités avec une carte de débit Autoriser l\'accès à la caméra Autoriser l\'accès aux contacts Autoriser les notifications push @@ -322,6 +323,7 @@ Échoué FAQ Donné + Obtenir des liquidités Invitez un ami à s\'inscrire sur Code Obtenir des Kin Obtenir plus de Kin @@ -344,6 +346,7 @@ Devises récentes Parrainez un ami, recevez 5 $ Bonus de parrainage + Demander des liquidités Demander des Kin Demander un pourboire Nécessite Face ID diff --git a/app/src/main/res/values-he/strings-localized.xml b/app/src/main/res/values-he/strings-localized.xml index d92d38f62..79a5c3c04 100644 --- a/app/src/main/res/values-he/strings-localized.xml +++ b/app/src/main/res/values-he/strings-localized.xml @@ -1,5 +1,6 @@ + הוספת כסף באמצעות כרטיס חיוב אפשר גישה למצלמה אפשר גישה לאנשי הקשר אפשר הודעות דחיפה @@ -322,6 +323,7 @@ נכשל שאלות נפוצות העברות שבוצעו + קבלת כסף עודד חבר להתחיל להשתמש ב-Code השגת Kin השיגו עוד Kin @@ -344,6 +346,7 @@ סוגי מטבע בשימוש לאחרונה הפנה חבר, קבל 5$ בונוס הפניה + בקשת כסף בקשת Kin בקשת טיפ נדרש Face ID diff --git a/app/src/main/res/values-hi-rIN/strings-localized.xml b/app/src/main/res/values-hi-rIN/strings-localized.xml index fa261100e..9d268fec4 100644 --- a/app/src/main/res/values-hi-rIN/strings-localized.xml +++ b/app/src/main/res/values-hi-rIN/strings-localized.xml @@ -1,5 +1,6 @@ + डेबिट कार्ड से कैश जोड़ें कैमरा एक्सेस की अनुमति दें संपर्कों को एक्सेस करने दें पुश नोटिफिकेशन की अनुमति दें @@ -322,6 +323,7 @@ असफल अक्सर पूछे जाने वाले प्रश्न दिया + कैश पाएं किसी दोस्त से Code इस्तेमाल करना शुरू करवाएं Kin पाएं अधिक Kin पाएं @@ -344,6 +346,7 @@ हाल की मुद्राएं किसी दोस्त को रेफ़र करें, $5 प्राप्त करें रैफरल बोनस + कैश के लिए रिक्वेस्ट करें Kin के लिए अनुरोध करें एक टिप का अनुरोध करें फेस ID की ज़रूरत है diff --git a/app/src/main/res/values-hu/strings-localized.xml b/app/src/main/res/values-hu/strings-localized.xml index f5307151c..8b5363caa 100644 --- a/app/src/main/res/values-hu/strings-localized.xml +++ b/app/src/main/res/values-hu/strings-localized.xml @@ -1,5 +1,6 @@ + Adj hozzá készpént egy bankkártyával Kamera-hozzáférés engedélyezése Hozzáférés engedélyezése a névjegyekhez Push értesítések engedélyezése @@ -322,6 +323,7 @@ Sikertelen GYIK Adott + Kapj készpénzt Szerezz egy barátot elkezdte használni a Code-ot Szerezz Kint Szerezzen több Kin-t @@ -344,6 +346,7 @@ Legutóbbi pénznemek Ajánlj egy barátot, kapsz 5 dollárt Ajánlói bónusz + Kérj készpénzt Kin kérése Kérj borravalót Face ID-t igényel diff --git a/app/src/main/res/values-id/strings-localized.xml b/app/src/main/res/values-id/strings-localized.xml index 3d4237fb2..a5044786c 100644 --- a/app/src/main/res/values-id/strings-localized.xml +++ b/app/src/main/res/values-id/strings-localized.xml @@ -1,5 +1,6 @@ + Tambahkan Uang Tunai dengan Kartu Debit Izinkan Akses Kamera Izinkan Akses ke Kontak Izinkan Pemberitahuan Push @@ -322,6 +323,7 @@ Gagal Tanya-Jawab Memberi + Dapatkan Uang Tunai Ajak Teman Memulai Code Dapatkan Kin Dapatkan Lebih Banyak Kin @@ -344,6 +346,7 @@ Mata Uang Terbaru Ajak Teman, Dapatkan $5 Bonus Referensi + Minta Uang Tunai Minta Kin Minta Tip Memerlukan Face ID diff --git a/app/src/main/res/values-it/strings-localized.xml b/app/src/main/res/values-it/strings-localized.xml index ed6cee185..eebe0d3e1 100644 --- a/app/src/main/res/values-it/strings-localized.xml +++ b/app/src/main/res/values-it/strings-localized.xml @@ -1,5 +1,6 @@ + Aggiungi Contanti con una Carta di Debito Consenti l\'accesso alla fotocamera Consenti l\'accesso ai contatti Consenti le notifiche push @@ -322,6 +323,7 @@ Non riuscito Domande frequenti Donati + Ottieni Contanti Fai iniziare un amico su Code Ottieni Kin Ottieni più Kin @@ -344,6 +346,7 @@ Valute recenti Invita un amico, ricevi 5 $ Bonus referral + Richiedi Contanti Richiedi Kin Richiedi una mancia Richiedi Face ID diff --git a/app/src/main/res/values-ja/strings-localized.xml b/app/src/main/res/values-ja/strings-localized.xml index 926f44196..9b46506d8 100644 --- a/app/src/main/res/values-ja/strings-localized.xml +++ b/app/src/main/res/values-ja/strings-localized.xml @@ -1,5 +1,6 @@ + デビットカードで現金を追加する カメラへのアクセスを許可する 連絡先へのアクセスを許可する プッシュ通知を許可する @@ -322,6 +323,7 @@ 失敗しました よくある質問 与えた + 現金を得る 友だちにCodeを始めさせる Kinを入手 Kinをもっと入手する @@ -344,6 +346,7 @@ 最近の通貨 友だちを紹介して5ドルもらおう 紹介ボーナス + 現金を要求する Kinをリクエスト チップをリクエストする Face IDが必要 diff --git a/app/src/main/res/values-ko/strings-localized.xml b/app/src/main/res/values-ko/strings-localized.xml index 3ca14b6b0..781c227ad 100644 --- a/app/src/main/res/values-ko/strings-localized.xml +++ b/app/src/main/res/values-ko/strings-localized.xml @@ -1,5 +1,6 @@ + 직불 카드로 현금 추가 카메라 접근 허용 연락처 접근 허용 푸쉬 알림 허용 @@ -322,6 +323,7 @@ 실패함 자주 묻는 질문 보낸 내역 + 현금 받기 친구가 Code를 시작하게 만들기 Kin 받기 킨 더 확보하기 @@ -344,6 +346,7 @@ 최근 통화 친구 추천하고 $5 받으세요 추천 보너스 + 현금 요청 킨 요청 팁 요청 Face ID 필요 diff --git a/app/src/main/res/values-ms/strings-localized.xml b/app/src/main/res/values-ms/strings-localized.xml index 2f9cdb93d..43a9d30a6 100644 --- a/app/src/main/res/values-ms/strings-localized.xml +++ b/app/src/main/res/values-ms/strings-localized.xml @@ -1,5 +1,6 @@ + Tambah Wang Tunai dengan Kad Debit Benarkan Akses Kamera Benarkan Akses kepada Kenalan Benarkan Notifikasi Tolak @@ -322,6 +323,7 @@ Gagal FAQ Beri + Dapatkan Wang Tunai Dapatkan Rakan untuk Bermula menggunakan Code Dapatkan Kin Dapatkan Lebih Banyak Kin @@ -344,6 +346,7 @@ Mata Wang Baru Rujuk Rakan, Dapat $5 Bonus Rujukan + Minta Wang Tunai Minta Kin Minta Tip Perlukan ID Muka diff --git a/app/src/main/res/values-nl/strings-localized.xml b/app/src/main/res/values-nl/strings-localized.xml index e5a9729ba..e681ced06 100644 --- a/app/src/main/res/values-nl/strings-localized.xml +++ b/app/src/main/res/values-nl/strings-localized.xml @@ -1,5 +1,6 @@ + Voeg contant geld toe met een bankpas Toegang tot camera toestaan Toegang tot contacten toestaan Pushmeldingen toestaan @@ -322,6 +323,7 @@ Mislukt Veelgestelde Vragen Geschonken + Ontvang contant geld Laat een vriend beginnen met Code Krijg Kin Verkrijg meer Kin @@ -344,6 +346,7 @@ Recente valuta Verwijs een vriend en ontvang $5 Verwijzingsbonus + Vraag contant geld aan Kin aanvragen Een fooi aanvragen Vereist gezichts-ID diff --git a/app/src/main/res/values-no/strings-localized.xml b/app/src/main/res/values-no/strings-localized.xml index 2517b2a28..f78dc43ff 100644 --- a/app/src/main/res/values-no/strings-localized.xml +++ b/app/src/main/res/values-no/strings-localized.xml @@ -1,5 +1,6 @@ + Sett inn penger med et debetkort Tillat kameratilgang Gi tilgang til kontakter Tillat push-varsler @@ -322,6 +323,7 @@ Mislyktes Vanlige spørsmål Ga + Få penger Få en venn i gang med Code Skaff deg Kin Få flere Kin @@ -344,6 +346,7 @@ Nylige valutaer Henvis en venn, få USD 5 Henvisningsbonus + Be om penger Be om Kin Be om tips Krev ansikts-ID diff --git a/app/src/main/res/values-pl/strings-localized.xml b/app/src/main/res/values-pl/strings-localized.xml index d04d92e1c..abea190b3 100644 --- a/app/src/main/res/values-pl/strings-localized.xml +++ b/app/src/main/res/values-pl/strings-localized.xml @@ -1,5 +1,6 @@ + Dodaj Cash z użyciem karty debetowej Zezwól na dostęp do kamery Zezwól na dostęp do kontaktów Zezwól na powiadomienia typu push @@ -322,6 +323,7 @@ NIe powiodło się Często zadawane pytania Dał + Otrzymaj Cash Poleć znajomemu aplikację Code Uzyskaj kin Zdobądź więcej Kin @@ -344,6 +346,7 @@ Niedawne waluty Poleć znajomemu i otrzymaj 5 $ Bonus za polecenie + Poproś o Cash Poproś o Kin Poproś o napiwek Wymagaj Face ID diff --git a/app/src/main/res/values-pt/strings-localized.xml b/app/src/main/res/values-pt/strings-localized.xml index ff81a7648..f836224c3 100644 --- a/app/src/main/res/values-pt/strings-localized.xml +++ b/app/src/main/res/values-pt/strings-localized.xml @@ -1,5 +1,6 @@ + Adicionar dinheiro com um cartão de débito Permitir Acesso à Câmara Permitir Acesso aos Contactos Permitir Notificações Push @@ -322,6 +323,7 @@ Falhou Guiar FAQ Deu + Receber dinheiro Iniciar um Amigo na Code Obter Kin Obter mais Kin @@ -344,6 +346,7 @@ Moedas Recentes Refira a uma amigo, ganhe 5€ Bónus de Referência + Solicitar dinheiro Solicitar Kin Solicitar uma recompensa Pedir Face ID diff --git a/app/src/main/res/values-ro/strings-localized.xml b/app/src/main/res/values-ro/strings-localized.xml index 978700d63..7df37deef 100644 --- a/app/src/main/res/values-ro/strings-localized.xml +++ b/app/src/main/res/values-ro/strings-localized.xml @@ -1,5 +1,6 @@ + Adăugați numerar cu un card de debit Permite accesul la fotocameră Permite accesul la contacte Permite notificările push @@ -322,6 +323,7 @@ Eșuat Întrebări frecvente Trimis + Obțineți numerar Ajutați un prieten să înceapă cu Code Obține kin Obține mai mulţi Kin @@ -344,6 +346,7 @@ Valute folosite recent Recomandați un prieten, primiți 5 dolari Bonus de recomandare + Solicitați numerar Solicită monede Kin Solicită un bacșiș Solicită Face ID diff --git a/app/src/main/res/values-ru/strings-localized.xml b/app/src/main/res/values-ru/strings-localized.xml index bcd48da52..c146e3494 100644 --- a/app/src/main/res/values-ru/strings-localized.xml +++ b/app/src/main/res/values-ru/strings-localized.xml @@ -1,5 +1,6 @@ + Добавить наличные с помощью дебетовой карты Разрешить доступ к камере Разрешить доступ к контактам Разрешить push-уведомления @@ -322,6 +323,7 @@ Сбой Частые вопросы Дал(-а) + Получить наличные Пригласите друга начать пользоваться Code Получить Kin Получить больше Kin @@ -344,6 +346,7 @@ Недавние валюты Приведите друга, получите 5 долл. США Реферальный бонус + Потребовать наличные Запросить кин Запросить чаевые Запрашивать Face ID diff --git a/app/src/main/res/values-sk/strings-localized.xml b/app/src/main/res/values-sk/strings-localized.xml index 859e5883f..2ac203891 100644 --- a/app/src/main/res/values-sk/strings-localized.xml +++ b/app/src/main/res/values-sk/strings-localized.xml @@ -1,5 +1,6 @@ + Navýšiť hotovosť z debetnej karty Povoliť prístup k fotoaparátu Povoliť prístup ku kontaktom Povoliť upozornenia push @@ -322,6 +323,7 @@ Zlyhalo Často kladené otázky Dal + Získať hotovosť Začnite používať kód priateľa Získajte Kin Získajte viac Kinov @@ -344,6 +346,7 @@ Najnovšie meny Odporučte priateľa a získajte 5 dolárov Bonus za odporúčanie + Požiadať o hotovosť Požiadať o kin Požiadať o tip Vyžaduje Face ID diff --git a/app/src/main/res/values-sr/strings-localized.xml b/app/src/main/res/values-sr/strings-localized.xml index 99dc958fc..38918478d 100644 --- a/app/src/main/res/values-sr/strings-localized.xml +++ b/app/src/main/res/values-sr/strings-localized.xml @@ -1,5 +1,6 @@ + Додај готовину помоћу дебитне картице Dozvoli pristup kameri Dozvoli pristup kontaktima Dozvoli prosleđena obaveštenja @@ -322,6 +323,7 @@ Nije uspelo Najčešća pitanja Dato + Узми готовину Нека пријатељ започне са Кодом Прибави Кин Набавите још кинова @@ -344,6 +346,7 @@ Nedavne valute Препоручите пријатеља, узмите 5$ Бонус за препоруке + Захтевај готовину Затражи Кин Затражи напојницу Захтевање Face ID-ја diff --git a/app/src/main/res/values-sv/strings-localized.xml b/app/src/main/res/values-sv/strings-localized.xml index 6c1809cda..5f038cf71 100644 --- a/app/src/main/res/values-sv/strings-localized.xml +++ b/app/src/main/res/values-sv/strings-localized.xml @@ -1,5 +1,6 @@ + Lägg till kontanter med ett betalkort Tillåt åtkomst till kameran Tillåt åtkomst till kontakter Tillåt push-notiser @@ -322,6 +323,7 @@ Misslyckad VANLIGA FRÅGOR Gav + Få kontanter Få en vän att börja med Code Få Kin Skaffa mer Kin @@ -344,6 +346,7 @@ Senaste valutorna Värva en vän, få 5 USD Värvningsbonus + Begär kontanter Begär Kin Be om dricks Kräv Face ID diff --git a/app/src/main/res/values-th/strings-localized.xml b/app/src/main/res/values-th/strings-localized.xml index 98c4f7091..ff7508b93 100644 --- a/app/src/main/res/values-th/strings-localized.xml +++ b/app/src/main/res/values-th/strings-localized.xml @@ -1,5 +1,6 @@ + เติมเงินสดด้วยบัตรเดบิต อนุญาตให้เข้าถึงกล้อง อนุญาตให้เข้าถึงรายชื่อติดต่อ อนุญาตให้แจ้งเตือนแบบพุช @@ -322,6 +323,7 @@ ล้มเหลว คำถามที่พบบ่อย ให้ + รับเงินสด ชวนเพื่อนมาเริ่มต้นใช้งาน Code รับ Kin รับ Kin เพิ่มเติม @@ -344,6 +346,7 @@ สกุลเงินล่าสุด แนะนำเพื่อน รับ $5 โบนัสการแนะนำ + ขอรับเงินสด ขอรับ Kin ขอรับทิป ต้องใช้ Face ID diff --git a/app/src/main/res/values-tr/strings-localized.xml b/app/src/main/res/values-tr/strings-localized.xml index 62f3a38d7..6631f9123 100644 --- a/app/src/main/res/values-tr/strings-localized.xml +++ b/app/src/main/res/values-tr/strings-localized.xml @@ -1,5 +1,6 @@ + Banka Kartıyla Nakit Ekle Kamera Erişimine İzin Ver Kişiler\'e Erişime İzin Ver Anlık Bildirimlere İzin Ver @@ -322,6 +323,7 @@ Başarısız SSS Verdi + Nakıt Al Bir Arkadaşınızı Code\'a başlatın Kin Al Daha Fazla Kin Al @@ -344,6 +346,7 @@ Son Para Birimleri Bir Arkadaş Yönlendir, $5\'ı Kazan Yönlendirme Bonusu + Nakit Talep Et Kin Talep Et Bahşiş İste Face ID gerekir diff --git a/app/src/main/res/values-uk/strings-localized.xml b/app/src/main/res/values-uk/strings-localized.xml index 3caf97073..bb3e678a1 100644 --- a/app/src/main/res/values-uk/strings-localized.xml +++ b/app/src/main/res/values-uk/strings-localized.xml @@ -1,5 +1,6 @@ + Внести гроші з дебетової картки Дозволити доступ до камери Дозволити доступ до контактів Дозволити push-сповіщення @@ -322,6 +323,7 @@ Не вдалося Часто задавані питання Надано + Отримати гроші. Запросіть друга в Code Отримати Kin Отримати більше Kin @@ -344,6 +346,7 @@ Останні валюти Порекомендуйте другу, отримайте 5 дол. Реферальний бонус + Запросити грошовий переказ Запит на Kin Запросити чайові Вимагати Face ID diff --git a/app/src/main/res/values-vi/strings-localized.xml b/app/src/main/res/values-vi/strings-localized.xml index 248c92691..cbb45dba7 100644 --- a/app/src/main/res/values-vi/strings-localized.xml +++ b/app/src/main/res/values-vi/strings-localized.xml @@ -1,5 +1,6 @@ + Thêm Tiền mặt Bằng Thẻ Ghi nợ Cho phép Truy cập Camera Cho phép Truy cập vào Danh bạ Cho phép Thông báo Đẩy @@ -322,6 +323,7 @@ Không thành công Hỏi Đáp Đã tặng + Nhận Tiền mặt Mời một người bạn bắt đầu dùng Code Nhận Kin Nhận Thêm Kin @@ -344,6 +346,7 @@ Đơn vị tiền tệ Gần đây Giới thiệu một người bạn - Nhận 5$ Tiền thưởng Giới thiệu + Yêu cầu Tiền mặt Yêu cầu Kin Yêu cầu Tiền boa Cần có Face ID diff --git a/app/src/main/res/values-zh-rCN/strings-localized.xml b/app/src/main/res/values-zh-rCN/strings-localized.xml index 26f5c4c5d..e84a11231 100644 --- a/app/src/main/res/values-zh-rCN/strings-localized.xml +++ b/app/src/main/res/values-zh-rCN/strings-localized.xml @@ -1,5 +1,6 @@ + 使用借记卡添加现金 允许相机访问 允许访问通讯录 允许推送通知 @@ -322,6 +323,7 @@ 失败 常见问题 已给 + 获取现金 让好友开始使用 Code 获取 Kin 币 获取更多 Kin 币 @@ -344,6 +346,7 @@ 最近的货币 推荐好友,获得 $5 推荐奖金 + 申请现金 请求 Kin 请求小费 需要Face ID diff --git a/app/src/main/res/values-zh-rTW/strings-localized.xml b/app/src/main/res/values-zh-rTW/strings-localized.xml index 0fe3c7b48..4af5c14df 100644 --- a/app/src/main/res/values-zh-rTW/strings-localized.xml +++ b/app/src/main/res/values-zh-rTW/strings-localized.xml @@ -1,5 +1,6 @@ + 使用簽帳金融卡添加現金 允許相機存取權 允許存取聯絡人 允許推播通知 @@ -322,6 +323,7 @@ 失敗 常見問題集 已給予 + 獲取現金 邀請朋友開始使用 Code 錢包 取得 Kin 獲得更多 Kin @@ -344,6 +346,7 @@ 最近使用的貨幣 推薦一位朋友,可獲 5 美元獎勵 推薦獎勵 + 請求現金 要求 Kin 要求小費 需要 Face ID diff --git a/app/src/main/res/values/strings-localized.xml b/app/src/main/res/values/strings-localized.xml index 722631b40..ecdddf550 100644 --- a/app/src/main/res/values/strings-localized.xml +++ b/app/src/main/res/values/strings-localized.xml @@ -1,11 +1,12 @@ + Add Cash with a Debit Card Allow Camera Access Allow Access to Contacts Allow Push Notifications Balance - Add Cash with a Debit Card - Add Cash with a Debit Card + Buy Kin + Buy More Kin Cancel Cancel Send Collect This Cash @@ -23,7 +24,7 @@ Enable Face ID Enable Touch ID Exit - Give Cash + Give Kin Invite Invites Join the Waitlist @@ -53,6 +54,7 @@ Send Send Verification Code Share + Share as a URL Share Download Link Share This Video Show My Tip Card @@ -68,10 +70,10 @@ Unsubscribe Update View Access Key - Withdraw Cash + Withdraw Kin Wrote the 12 Words Down Instead? Yes - Yes, Withdraw Cash + Yes, Withdraw Kin Yes, I Wrote Them Down and Kin @@ -159,7 +161,7 @@ We believe payments should be simple, powerful, and global. By building with advanced blockchain technology Code offers features that traditional payments apps can\'t, like global peer to peer transfers, micropayments that unlock individual articles online, and zero fee tips for your favorite creators. Kin is a cryptocurrency like Bitcoin, but is also designed for fast, inexpensive payments. Like Bitcoin there is only a limited amount of Kin available. If more people buy Kin the value goes up, and if more people sell Kin the value goes down. This dynamic allows everyone who holds Kin to share in the value creation if adoption of Kin grows. - You can buy Kin with your debit card. This is accessible in the Get Kin tab. + You can buy Kin with your debit card. This is accessible in the Get Cash tab. Yes you can. Selling Kin is supported on a number of cryptocurrency exchanges. There are three main ways you can help: talk about your Code experience on social media, encourage your friends to try out Code for themselves, and encourage your favorite websites to integrate Code payments by asking them to check out [getcode.com](https://getcode.com). What is Code? @@ -203,7 +205,7 @@ You received %1$s of Kin for sending someone their first Kin. You can now request tips. Deposit Received - Cash Returned + Kin Returned New on Code Referral Bonus Received X Account Connected @@ -215,7 +217,7 @@ Code enables you to receive Kin by pointing your camera at the digital bill on another user\'s phone You need to turn on Camera in Settings to scan Codes Authenticate to access Code. - Add Cash with a Debit Card + Buy Kin Buy Kin (Coming Soon) Buying and selling Kin is currently a complex process. These processes will get simpler over time. If you want to learn how to buy and sell Kin today you can watch the walk through videos below. You can only give up to %1$s @@ -231,10 +233,10 @@ Make sure you have your Secret Recovery Phrase saved, and then enter “Delete” to delete your Code account. This action is irreversible. Didn\'t get an SMS at %1$s? Didn\'t get an SMS? Resend - Disabling Face ID requires you to verify your identity. + Disabling Face ID requires you to authenticate. Don\'t have the Code Wallet app? You don\'t have any Kin yet. - Enable Face ID to further enhance the security of transaction in Code. + Enable Face ID to require authentication when opening Code. Enter destination address Enter up to %1$s Get $5 of Kin for free when you get a friend to sign up for Code and you send them their first Kin. @@ -322,10 +324,11 @@ Failed FAQ Gave + Get Cash Get a Friend Started on Code - Get Cash - Get More Cash - Give Cash + Get Kin + Get More Kin + Give Kin Insufficient Funds Invite a Friend Limited Time Offer @@ -344,6 +347,7 @@ Recent Currencies Refer a Friend, Get $5 Referral Bonus + Request Cash Request Kin Request a Tip Require Face ID @@ -358,13 +362,13 @@ Spent Switch Accounts Terms of Service - Tip Cash + Tip Kin Unknown Update Required Verify Phone Number Welcome Bonus - Withdraw Cash + Withdraw Kin Withdrew Your Access Key - Tap the logo to share the app download link + Tap the Code logo to share the app download link From 35aaf8efa47895c645ab424707f6590d20de7a93 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 16 Aug 2024 18:05:01 -0400 Subject: [PATCH 21/33] chore: instrument breadcrumbs for core flows Signed-off-by: Brandon McAnsh --- api/build.gradle.kts | 1 + api/src/main/java/com/getcode/model/Kin.kt | 7 ++- .../network/client/Client_Transaction.kt | 28 ++++++--- .../network/client/TransactionReceiver.kt | 16 +++++ .../com/getcode/network/exchange/Exchange.kt | 10 ++++ .../repository/TransactionRepository.kt | 11 ++++ .../com/getcode/solana/organizer/Organizer.kt | 11 ++++ .../java/com/getcode/solana/organizer/Tray.kt | 28 ++++++++- .../main/java/com/getcode/utils}/Instant.kt | 14 ++++- .../main/java/com/getcode/utils/Logging.kt | 34 ++++++++--- api/src/main/java/com/getcode/utils/String.kt | 10 ++++ .../notifications/CodePushMessagingService.kt | 11 ++++ .../java/com/getcode/util/AccountUtils.kt | 8 +++ .../main/java/com/getcode/util/DateUtils.kt | 2 + .../getcode/view/main/home/HomeViewModel.kt | 58 +++++++++++++++++-- 15 files changed, 225 insertions(+), 24 deletions(-) rename {app/src/main/java/com/getcode/util => api/src/main/java/com/getcode/utils}/Instant.kt (69%) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index dac4b0039..8d33b5c93 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -74,6 +74,7 @@ dependencies { implementation(Libs.rxjava) implementation(Libs.kotlinx_coroutines_core) implementation(Libs.kotlinx_serialization_json) + implementation(Libs.kotlinx_datetime) implementation(Libs.inject) implementation(Libs.grpc_okhttp) diff --git a/api/src/main/java/com/getcode/model/Kin.kt b/api/src/main/java/com/getcode/model/Kin.kt index c5f3e3d6f..cfd3f0a3f 100644 --- a/api/src/main/java/com/getcode/model/Kin.kt +++ b/api/src/main/java/com/getcode/model/Kin.kt @@ -63,10 +63,13 @@ data class Kin(val quarks: Long): Value { } } -fun min(a: Kin, b: Kin): Kin { +private fun min(a: Kin, b: Kin): Kin { if (a.quarks > b.quarks) { return b } return a -} \ No newline at end of file +} + +val Kin.description: String + get() = "K ${toKinTruncating().quarks} ${fractionalQuarks()}" \ No newline at end of file diff --git a/api/src/main/java/com/getcode/network/client/Client_Transaction.kt b/api/src/main/java/com/getcode/network/client/Client_Transaction.kt index 6c1f4a565..637c0af1c 100644 --- a/api/src/main/java/com/getcode/network/client/Client_Transaction.kt +++ b/api/src/main/java/com/getcode/network/client/Client_Transaction.kt @@ -31,7 +31,9 @@ import com.getcode.solana.keys.base58 import com.getcode.solana.organizer.GiftCardAccount import com.getcode.solana.organizer.Organizer import com.getcode.solana.organizer.Relationship +import com.getcode.utils.TraceType import com.getcode.utils.flowInterval +import com.getcode.utils.trace import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers @@ -337,15 +339,20 @@ fun Client.withdrawExternally( Completable.complete() }.doOnComplete { Timber.d(steps.joinToString("\n")) - } + }.concatWith( // 6. Execute withdrawal - .concatWith( - withdraw( - amount = amount, - organizer = organizer, - destination = destination - ) + withdraw( + amount = amount, + organizer = organizer, + destination = destination ) + ).doOnComplete { + trace( + tag = "Trx", + message = "Withdraw completed", + type = TraceType.Process + ) + } } private fun Client.withdraw( @@ -517,6 +524,13 @@ fun Client.receiveFromPrimaryIfWithinLimits(organizer: Organizer): Completable { } } .ignoreElement() + .andThen { + trace( + tag = "Trx", + message = "Received from primary", + type = TraceType.Process + ) + } .andThen { fetchLimits(true) } } diff --git a/api/src/main/java/com/getcode/network/client/TransactionReceiver.kt b/api/src/main/java/com/getcode/network/client/TransactionReceiver.kt index 5050fcd9c..48764a281 100644 --- a/api/src/main/java/com/getcode/network/client/TransactionReceiver.kt +++ b/api/src/main/java/com/getcode/network/client/TransactionReceiver.kt @@ -76,8 +76,24 @@ class TransactionReceiver @Inject constructor( organizer = organizer ).blockingGet() + trace( + tag = "Trx", + message = "Received from relationship", + type = TraceType.Process, + metadata = { + "domain" to relationship.domain.relationshipHost + "kin" to relationship.partialBalance + } + ) + receivedTotal += relationship.partialBalance + trace( + tag = "Trx", + message = "Received from incoming", + type = TraceType.Process + ) + if (intent is IntentPublicTransfer) { setTray(organizer, intent.resultTray) } diff --git a/api/src/main/java/com/getcode/network/exchange/Exchange.kt b/api/src/main/java/com/getcode/network/exchange/Exchange.kt index f9283db33..604c813ee 100644 --- a/api/src/main/java/com/getcode/network/exchange/Exchange.kt +++ b/api/src/main/java/com/getcode/network/exchange/Exchange.kt @@ -11,6 +11,7 @@ import com.getcode.network.core.NetworkOracle import com.getcode.network.repository.PrefRepository import com.getcode.utils.ErrorUtils import com.getcode.utils.TraceType +import com.getcode.utils.format import com.getcode.utils.trace import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.datetime.Instant import timber.log.Timber import java.util.Date import javax.inject.Inject @@ -249,6 +251,14 @@ class CodeExchange @Inject constructor( rates.rateForUsd()!! } + trace(tag = "Background", + message = "Updated rates", + type = TraceType.Process, + metadata = { + "date" to Instant.fromEpochMilliseconds(rates.dateMillis).format("yyyy-MM-dd HH:mm:ss") + } + ) + } @OptIn(ExperimentalTime::class) diff --git a/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt b/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt index 00561753c..9a65b20d7 100644 --- a/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt @@ -584,6 +584,17 @@ class TransactionRepository @Inject constructor( .doOnSuccess { setLimits(it) setMaximumDeposit(it.maxDeposit) + trace( + tag = "Trx", + message = "Fetched limits", + type = TraceType.Process, + metadata = { + val sendLimit = it.sendLimitFor(CurrencyCode.USD) + if (sendLimit != null) { + "limitNextTx" to sendLimit + } + } + ) } .doOnError(ErrorUtils::handleError) .toFlowable() diff --git a/api/src/main/java/com/getcode/solana/organizer/Organizer.kt b/api/src/main/java/com/getcode/solana/organizer/Organizer.kt index a17f0c733..ecc5fe89b 100644 --- a/api/src/main/java/com/getcode/solana/organizer/Organizer.kt +++ b/api/src/main/java/com/getcode/solana/organizer/Organizer.kt @@ -8,7 +8,9 @@ import com.getcode.model.Kin import com.getcode.model.unusable import com.getcode.network.repository.getPublicKeyBase58 import com.getcode.solana.keys.* +import com.getcode.utils.TraceType import com.getcode.utils.timedTrace +import com.getcode.utils.trace import timber.log.Timber class Organizer( @@ -59,6 +61,15 @@ class Organizer( this.accountInfos = infos tray.createRelationships(infos) propagateBalances() + + trace( + tag = "Organizer", + message = "Fetched account infos", + type = TraceType.Process, + metadata = { + "tray" to tray.reportableRepresentation() + } + ) } fun getAccountInfo() = accountInfos diff --git a/api/src/main/java/com/getcode/solana/organizer/Tray.kt b/api/src/main/java/com/getcode/solana/organizer/Tray.kt index bbda8f243..2a2888509 100644 --- a/api/src/main/java/com/getcode/solana/organizer/Tray.kt +++ b/api/src/main/java/com/getcode/solana/organizer/Tray.kt @@ -1,6 +1,5 @@ package com.getcode.solana.organizer -import android.content.Context import com.getcode.crypt.DerivePath import com.getcode.crypt.DerivedKey import com.getcode.crypt.MnemonicPhrase @@ -8,9 +7,11 @@ import com.getcode.model.AccountInfo import com.getcode.model.Domain import com.getcode.model.Kin import com.getcode.model.RelationshipBox +import com.getcode.model.description import com.getcode.solana.keys.PublicKey +import com.getcode.solana.keys.base58 import com.getcode.utils.TraceType -import com.getcode.utils.timedTrace +import com.getcode.utils.padded import com.getcode.utils.trace import kotlin.math.min @@ -866,6 +867,29 @@ class Tray( return container } + fun reportableRepresentation(): List { + return listOf( + string(named = "Primary ", partialAccount = owner), + string(named = "Incoming ", partialAccount = incoming), + string(named = "Outgoing ", partialAccount = outgoing), + string("1 ", slot = slot(SlotType.Bucket1)), + string("10 ", slot = slot(SlotType.Bucket10)), + string("100 ", slot = slot(SlotType.Bucket100)), + string("1k ", slot = slot(SlotType.Bucket1k)), + string("10k ", slot = slot(SlotType.Bucket10k)), + string("100k ", slot = slot(SlotType.Bucket100k)), + string("1m ", slot = slot(SlotType.Bucket1m)), + ) + } + + private fun string(named: String, partialAccount: PartialAccount): String { + return "$named ${partialAccount.getCluster().vaultPublicKey.base58().padded(44)}) ${partialAccount.partialBalance.description}" + } + + private fun string(named: String, slot: Slot): String { + return "$named ${slot.getCluster().vaultPublicKey.base58().padded(44)}) ${slot.partialBalance.description}" + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/java/com/getcode/util/Instant.kt b/api/src/main/java/com/getcode/utils/Instant.kt similarity index 69% rename from app/src/main/java/com/getcode/util/Instant.kt rename to api/src/main/java/com/getcode/utils/Instant.kt index d31c36213..bb4a80e9a 100644 --- a/app/src/main/java/com/getcode/util/Instant.kt +++ b/api/src/main/java/com/getcode/utils/Instant.kt @@ -1,4 +1,4 @@ -package com.getcode.util +package com.getcode.utils import kotlinx.datetime.DatePeriod import kotlinx.datetime.DateTimeUnit @@ -9,6 +9,9 @@ import kotlinx.datetime.atStartOfDayIn import kotlinx.datetime.minus import kotlinx.datetime.plus import kotlinx.datetime.toLocalDateTime +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale fun Instant.toLocalDate(timeZone: TimeZone = TimeZone.currentSystemDefault()) = toLocalDateTime(timeZone).date @@ -18,4 +21,13 @@ fun LocalDate.atStartOfDay(tz: TimeZone = TimeZone.currentSystemDefault()) = atS fun LocalDate.atEndOfDay(tz: TimeZone = TimeZone.currentSystemDefault()): Instant { val tomorrowAtMidnight = ((this + DatePeriod(days = 1)).atStartOfDayIn(tz)) return tomorrowAtMidnight.minus(value = 1, unit = DateTimeUnit.NANOSECOND) +} + +fun Instant.format(format: String = "yyyy-MM-dd"): String { + val epoch = this.toEpochMilliseconds() + + val formatter = SimpleDateFormat(format, Locale.getDefault()) + val date = Date(epoch) + + return formatter.format(date) } \ No newline at end of file diff --git a/api/src/main/java/com/getcode/utils/Logging.kt b/api/src/main/java/com/getcode/utils/Logging.kt index be475b31d..f73a90a45 100644 --- a/api/src/main/java/com/getcode/utils/Logging.kt +++ b/api/src/main/java/com/getcode/utils/Logging.kt @@ -67,25 +67,28 @@ fun trace( message: String, tag: String? = null, type: TraceType = TraceType.Log, + metadata: MetadataBuilder.() -> Unit = {}, error: Throwable? = null ) { - val tree = if (tag == null) Timber else Timber.tag(tag) - val traceMessage = if (tag == null) message else "trace : $message" + val tagBlock = tag?.let { "[$it] " } + val tree = if (tagBlock == null) Timber else Timber.tag(tagBlock) - tree.d(traceMessage) + tree.d(message) + + val metadataMap = metadata { metadata() } if (Bugsnag.isStarted()) { val breadcrumb = if (tag != null) { - "$tag | $traceMessage" + "$tagBlock $message" } else { - traceMessage + message } val breadcrumbType = type.toBugsnagBreadcrumbType() if (breadcrumbType != null) { Bugsnag.leaveBreadcrumb( breadcrumb, - emptyMap(), + metadataMap, breadcrumbType ) } @@ -98,6 +101,7 @@ fun timedTrace( message: String, tag: String? = null, type: TraceType = TraceType.Log, + metadata: MetadataBuilder.() -> Unit = {}, error: Throwable? = null, block: () -> T ): T { @@ -107,7 +111,23 @@ fun timedTrace( } val newMessage = "$message took ${time.inWholeMilliseconds}ms" - trace(newMessage, tag, type, error) + trace(newMessage, tag, type, metadata, error) return result +} + +class MetadataBuilder { + private val map = mutableMapOf() + + infix fun String.to(value: Any) { + map[this] = value + } + + fun build(): Map = map +} + +fun metadata(block: MetadataBuilder.() -> Unit): Map { + val builder = MetadataBuilder() + builder.block() + return builder.build() } \ No newline at end of file diff --git a/api/src/main/java/com/getcode/utils/String.kt b/api/src/main/java/com/getcode/utils/String.kt index eb9404360..1180ef6f4 100644 --- a/api/src/main/java/com/getcode/utils/String.kt +++ b/api/src/main/java/com/getcode/utils/String.kt @@ -30,5 +30,15 @@ fun String.base64EncodedData(): ByteArray { return data } +fun String.padded(minCount: Int): String { + return if (this.length < minCount) { + val toInsert = minCount - this.length + val padding = " ".repeat(toInsert) + this + padding + } else { + this + } +} + typealias Base64String = String typealias Base58String = String \ No newline at end of file diff --git a/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt b/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt index 24220f7af..2c8973560 100644 --- a/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt +++ b/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt @@ -27,7 +27,9 @@ import com.getcode.util.CurrencyUtils import com.getcode.util.resources.ResourceHelper import com.getcode.util.resources.ResourceType import com.getcode.utils.ErrorUtils +import com.getcode.utils.TraceType import com.getcode.utils.installationId +import com.getcode.utils.trace import com.getcode.view.MainActivity import com.google.firebase.Firebase import com.google.firebase.installations.installations @@ -189,6 +191,15 @@ class CodePushMessagingService : FirebaseMessagingService(), .setContentIntent(buildContentIntent(type)) notificationManager.notify(title.hashCode(), notificationBuilder.build()) + + trace( + tag = "Push", + message = "Push notification shown", + metadata = { + "category" to type.name + }, + type = TraceType.Process + ) } private fun updateOrganizerAndSwap() = launch { diff --git a/app/src/main/java/com/getcode/util/AccountUtils.kt b/app/src/main/java/com/getcode/util/AccountUtils.kt index 92feaa779..7c4c3a079 100644 --- a/app/src/main/java/com/getcode/util/AccountUtils.kt +++ b/app/src/main/java/com/getcode/util/AccountUtils.kt @@ -91,6 +91,14 @@ object AccountUtils { } else { currentAttempt++ if (currentAttempt < maxRetries) { + trace( + tag = "Account", + message = "Retrying login", + metadata = { + "count" to currentAttempt + }, + type = TraceType.Process, + ) trace("Retrying after ${delayDuration.inWholeMilliseconds} ms...", type = TraceType.Log) delay(delayDuration.inWholeMilliseconds) } diff --git a/app/src/main/java/com/getcode/util/DateUtils.kt b/app/src/main/java/com/getcode/util/DateUtils.kt index 91fa913d4..bcce855dd 100644 --- a/app/src/main/java/com/getcode/util/DateUtils.kt +++ b/app/src/main/java/com/getcode/util/DateUtils.kt @@ -5,6 +5,8 @@ import android.text.format.DateFormat import android.text.format.DateUtils import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext +import com.getcode.utils.atStartOfDay +import com.getcode.utils.toLocalDate import kotlinx.datetime.Clock import kotlinx.datetime.Instant import java.util.Calendar diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index c7471481e..fbadad2aa 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -81,10 +81,12 @@ import com.getcode.util.resources.ResourceHelper import com.getcode.util.showNetworkError import com.getcode.util.vibration.Vibrator import com.getcode.utils.ErrorUtils +import com.getcode.utils.TraceType import com.getcode.utils.base64EncodedData import com.getcode.utils.catchSafely import com.getcode.utils.network.NetworkConnectivityListener import com.getcode.utils.nonce +import com.getcode.utils.trace import com.getcode.vendor.Base58 import com.getcode.view.BaseViewModel import com.kik.kikx.models.ScannableKikCode @@ -456,7 +458,19 @@ class HomeViewModel @Inject constructor( ) }, onError = { cancelSend(style = PresentationStyle.Slide) }, - present = { data -> presentSend(data, bill, vibrate) } + present = { data -> + if (!bill.didReceive) { + trace( + tag = "Bill", + message = "Pull out cash", + metadata = { + "amount" to bill.amount + }, + type = TraceType.User, + ) + } + presentSend(data, bill, vibrate) + } ) } @@ -653,13 +667,41 @@ class HomeViewModel @Inject constructor( when (codePayload.kind) { Kind.Cash, - Kind.GiftCard -> attemptReceive(organizer, codePayload) + Kind.GiftCard -> { + trace( + tag = "Bill", + message = "Scanned cash", + type = TraceType.User, + ) + attemptReceive(organizer, codePayload) + } Kind.RequestPayment, - Kind.RequestPaymentV2 -> attemptPayment(codePayload) + Kind.RequestPaymentV2 -> { + trace( + tag = "Bill", + message = "Scanned request card", + type = TraceType.User, + ) + attemptPayment(codePayload) + } - Kind.Login -> attemptLogin(codePayload) - Kind.Tip -> attemptTip(codePayload) + Kind.Login -> { + trace( + tag = "Bill", + message = "Scanned login card", + type = TraceType.User, + ) + attemptLogin(codePayload) + } + Kind.Tip -> { + trace( + tag = "Bill", + message = "Scanned tip card", + type = TraceType.User, + ) + attemptTip(codePayload) + } } } @@ -695,6 +737,12 @@ class HomeViewModel @Inject constructor( tipController.clearTwitterSplat() + trace( + tag = "Bill", + message = "Show my tip card", + type = TraceType.User, + ) + withContext(Dispatchers.Main) { uiFlow.update { val billState = it.billState.copy( From e645db75cfc3cefbfcae2b2a3d5050c4d513d040 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 16 Aug 2024 18:27:13 -0400 Subject: [PATCH 22/33] feat: update share action for tip cards Signed-off-by: Brandon McAnsh --- .../main/java/com/getcode/models/BillState.kt | 4 +-- .../java/com/getcode/ui/components/Pill.kt | 2 +- .../getcode/view/main/home/HomeViewModel.kt | 2 +- .../home/components/BillManagementOptions.kt | 35 ++++++++++--------- .../main/kotlin/com/getcode/theme/Color.kt | 2 +- .../main/kotlin/com/getcode/theme/Theme.kt | 11 ++++++ 6 files changed, 35 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/getcode/models/BillState.kt b/app/src/main/java/com/getcode/models/BillState.kt index d3ca32229..75e7c509e 100644 --- a/app/src/main/java/com/getcode/models/BillState.kt +++ b/app/src/main/java/com/getcode/models/BillState.kt @@ -47,7 +47,7 @@ data class BillState( sealed interface Action { - val label: String + val label: String? @Composable get val asset: Painter @Composable get @@ -63,7 +63,7 @@ data class BillState( data class Share(override val action: () -> Unit): Action { override val label: String - @Composable get() = stringResource(R.string.action_share) + @Composable get() = stringResource(R.string.action_shareAsURL) override val asset: Painter @Composable get() = painterResource(id = R.drawable.ic_remote_send) diff --git a/app/src/main/java/com/getcode/ui/components/Pill.kt b/app/src/main/java/com/getcode/ui/components/Pill.kt index 897cebb44..7842cf6c8 100644 --- a/app/src/main/java/com/getcode/ui/components/Pill.kt +++ b/app/src/main/java/com/getcode/ui/components/Pill.kt @@ -51,7 +51,7 @@ fun Pill( fun Pill( modifier: Modifier = Modifier, backgroundColor: Color = Black50, - contentColor: Color = Color.White, + contentColor: Color = CodeTheme.colors.onAction, shape: CornerBasedShape = CircleShape, contentPadding: PaddingValues = PaddingValues( horizontal = CodeTheme.dimens.grid.x2, diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index fbadad2aa..e767a9713 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -748,7 +748,7 @@ class HomeViewModel @Inject constructor( val billState = it.billState.copy( bill = Bill.Tip(code), primaryAction = BillState.Action.Share { onRemoteSend() }, - secondaryAction = BillState.Action.Done(::cancelSend) + secondaryAction = BillState.Action.Cancel(::cancelSend) ) it.copy( diff --git a/app/src/main/java/com/getcode/view/main/home/components/BillManagementOptions.kt b/app/src/main/java/com/getcode/view/main/home/components/BillManagementOptions.kt index e28b133fd..6ff73b582 100644 --- a/app/src/main/java/com/getcode/view/main/home/components/BillManagementOptions.kt +++ b/app/src/main/java/com/getcode/view/main/home/components/BillManagementOptions.kt @@ -21,6 +21,7 @@ import com.getcode.theme.Gray50 import com.getcode.theme.White import com.getcode.ui.components.CodeCircularProgressIndicator import com.getcode.ui.components.Pill +import com.getcode.ui.utils.addIf import com.getcode.ui.utils.rememberedClickable @Composable @@ -45,10 +46,9 @@ internal fun BillManagementOptions( if (primaryAction != null) { Pill( modifier = Modifier - .rememberedClickable(enabled = !isSending) { primaryAction.action() } - .padding(vertical = 15.dp, horizontal = 20.dp), - contentPadding = PaddingValues(), - backgroundColor = Gray50, + .rememberedClickable(enabled = !isSending) { primaryAction.action() }, + contentPadding = PaddingValues(15.dp), + backgroundColor = CodeTheme.colors.action, ) { Box { Row( @@ -59,10 +59,12 @@ internal fun BillManagementOptions( contentDescription = "", modifier = Modifier.width(22.dp) ) - Text( - modifier = Modifier.padding(start = 10.dp), - text = primaryAction.label - ) + primaryAction.label?.let { label -> + Text( + modifier = Modifier.padding(start = 10.dp), + text = label + ) + } } if (isSending) { @@ -80,20 +82,21 @@ internal fun BillManagementOptions( if (secondaryAction != null) { Pill( modifier = Modifier - .rememberedClickable(enabled = isInteractable) { secondaryAction.action() } - .padding(vertical = 15.dp, horizontal = 20.dp), - contentPadding = PaddingValues(), - backgroundColor = Gray50, + .rememberedClickable(enabled = isInteractable) { secondaryAction.action() }, + contentPadding = PaddingValues(15.dp), + backgroundColor = CodeTheme.colors.action, ) { Image( painter = secondaryAction.asset, contentDescription = "", modifier = Modifier.size(18.dp) ) - Text( - modifier = Modifier.padding(start = 10.dp), - text = secondaryAction.label - ) + secondaryAction.label?.let { label -> + Text( + modifier = Modifier.padding(start = 10.dp), + text = label + ) + } } } } diff --git a/common/theme/src/main/kotlin/com/getcode/theme/Color.kt b/common/theme/src/main/kotlin/com/getcode/theme/Color.kt index 1879d653b..951c78b8e 100644 --- a/common/theme/src/main/kotlin/com/getcode/theme/Color.kt +++ b/common/theme/src/main/kotlin/com/getcode/theme/Color.kt @@ -8,7 +8,7 @@ val BrandLight = Color(0xFF7379A0) val BrandSubtle = Color(0xFF565C86) val BrandMuted = Color(0xFF45464E) val BrandDark = Color(0xFF1F1A34) -val BrandOverlay = Color(0xBF1E1E1E) +val BrandAction = Color(0xFF212121) val Brand01 = Color(0xFF130F27) val White = Color(0xffffffff) diff --git a/common/theme/src/main/kotlin/com/getcode/theme/Theme.kt b/common/theme/src/main/kotlin/com/getcode/theme/Theme.kt index 71aa9583e..cabc2784f 100644 --- a/common/theme/src/main/kotlin/com/getcode/theme/Theme.kt +++ b/common/theme/src/main/kotlin/com/getcode/theme/Theme.kt @@ -23,6 +23,8 @@ private val DarkColorPalette = CodeColors( brandLight = BrandLight, brandSubtle = BrandSubtle, brandMuted = BrandMuted, + action = Gray50, + onAction = White, background = Brand, onBackground = White, surface = Brand, @@ -82,6 +84,8 @@ class CodeColors( brandLight: Color, brandSubtle: Color, brandMuted: Color, + action: Color, + onAction: Color, background: Color, onBackground: Color, surface: Color, @@ -99,6 +103,10 @@ class CodeColors( private set var brandMuted by mutableStateOf(brandMuted) private set + var action by mutableStateOf(action) + private set + var onAction by mutableStateOf(onAction) + private set var background by mutableStateOf(background) private set var onBackground by mutableStateOf(onBackground) @@ -121,6 +129,7 @@ class CodeColors( brandLight = other.brandLight brandSubtle = other.brandSubtle brandMuted = other.brandMuted + action = other.action background = other.background onBackground = other.onBackground surface = other.surface @@ -136,6 +145,8 @@ class CodeColors( brandLight = brandLight, brandSubtle = brandSubtle, brandMuted = brandMuted, + action = action, + onAction = onAction, background = background, onBackground = onBackground, surface = surface, From 29d5020c63991c85dc544e66a35876bcb4955058 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 16 Aug 2024 18:57:29 -0400 Subject: [PATCH 23/33] build: fetch all commits in CI Signed-off-by: Brandon McAnsh --- .github/workflows/build-upload-android-alpha.yml | 2 ++ .github/workflows/build-upload-android-internal.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build-upload-android-alpha.yml b/.github/workflows/build-upload-android-alpha.yml index 078585de7..3c239aee6 100644 --- a/.github/workflows/build-upload-android-alpha.yml +++ b/.github/workflows/build-upload-android-alpha.yml @@ -21,6 +21,8 @@ jobs: steps: - uses: actions/checkout@master + with: + fetch-depth: 0 - name: Setup Java env uses: actions/setup-java@v3 diff --git a/.github/workflows/build-upload-android-internal.yml b/.github/workflows/build-upload-android-internal.yml index f9232b94b..abbbeed5b 100644 --- a/.github/workflows/build-upload-android-internal.yml +++ b/.github/workflows/build-upload-android-internal.yml @@ -17,6 +17,8 @@ jobs: steps: - uses: actions/checkout@master + with: + fetch-depth: 0 - name: Setup Java env uses: actions/setup-java@v3 From a10ae968a76a238a5f019b5bd6220b124cbabfee Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 19 Aug 2024 12:35:38 -0400 Subject: [PATCH 24/33] chore(tipcontroller): reduce polling frequency to 5s from 20; don't poll forever Signed-off-by: Brandon McAnsh --- .../java/com/getcode/network/TipController.kt | 35 ++++++++++--------- .../java/com/getcode/view/MainActivity.kt | 6 ++++ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/api/src/main/java/com/getcode/network/TipController.kt b/api/src/main/java/com/getcode/network/TipController.kt index d693913a5..e7b0579d9 100644 --- a/api/src/main/java/com/getcode/network/TipController.kt +++ b/api/src/main/java/com/getcode/network/TipController.kt @@ -1,9 +1,5 @@ package com.getcode.network -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleOwner -import com.getcode.analytics.AnalyticsService import com.getcode.manager.SessionManager import com.getcode.model.CodePayload import com.getcode.model.PrefsBool @@ -13,7 +9,6 @@ import com.getcode.model.TwitterUser import com.getcode.network.client.Client import com.getcode.network.client.fetchTwitterUser import com.getcode.network.repository.BetaFlagsRepository -import com.getcode.network.repository.BetaOptions import com.getcode.network.repository.PrefRepository import com.getcode.network.repository.TwitterUserFetchError import com.getcode.network.repository.base58 @@ -27,7 +22,6 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -50,7 +44,11 @@ class TipController @Inject constructor( private val client: Client, betaFlags: BetaFlagsRepository, private val prefRepository: PrefRepository, -): LifecycleEventObserver { +) { + + companion object { + private const val POLL_FREQUENCY_SECS = 5L + } private var pollTimer: Timer? = null private var lastPoll: Long = 0L @@ -63,6 +61,8 @@ class TipController @Inject constructor( var userMetadata: TwitterUser? = null private set + private var confirmedConnection = false + val connectedAccount: StateFlow = prefRepository.observeOrDefault(PrefsString.KEY_TIP_ACCOUNT, "") .map { runCatching { Json.decodeFromString(it) }.getOrNull() } .distinctUntilChanged() @@ -84,10 +84,10 @@ class TipController @Inject constructor( private fun startPollTimer() { Timber.d("twitter poll start") pollTimer?.cancel() - pollTimer = fixedRateTimer("twitterPollTimer", false, 0, 1000 * 20) { + pollTimer = fixedRateTimer("twitterPollTimer", false, 0, 1000 * POLL_FREQUENCY_SECS) { scope.launch { val time = System.currentTimeMillis() - val isPastThrottle = time - lastPoll > 1000 * 10 || lastPoll == 0L + val isPastThrottle = time - lastPoll > 1000 * (POLL_FREQUENCY_SECS / 2.0) || lastPoll == 0L if (isPastThrottle) { callForConnectedUser() @@ -105,6 +105,8 @@ class TipController @Inject constructor( .onSuccess { Timber.d("current user twitter connected @ ${it.username}") prefRepository.set(PrefsString.KEY_TIP_ACCOUNT, Json.encodeToString(it)) + confirmedConnection = true + stopTimerInternal() } .onFailure { when (it) { @@ -125,6 +127,7 @@ class TipController @Inject constructor( } fun checkForConnection() { + if (confirmedConnection) return startPollTimer() } @@ -172,15 +175,13 @@ class TipController @Inject constructor( return null } - private fun stopTimer() { - pollTimer?.cancel() + fun stopTimer() { + confirmedConnection = false + stopTimerInternal() } - override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - when (event) { - Lifecycle.Event.ON_RESUME -> checkForConnection() - Lifecycle.Event.ON_STOP -> stopTimer() - else -> Unit - } + private fun stopTimerInternal() { + Timber.d("twitter poll stop") + pollTimer?.cancel() } } \ No newline at end of file diff --git a/app/src/main/java/com/getcode/view/MainActivity.kt b/app/src/main/java/com/getcode/view/MainActivity.kt index 68302a2c1..b8c76f03e 100644 --- a/app/src/main/java/com/getcode/view/MainActivity.kt +++ b/app/src/main/java/com/getcode/view/MainActivity.kt @@ -19,6 +19,7 @@ import com.getcode.LocalNetworkObserver import com.getcode.LocalPhoneFormatter import com.getcode.R import com.getcode.analytics.AnalyticsService +import com.getcode.network.TipController import com.getcode.network.client.Client import com.getcode.network.exchange.Exchange import com.getcode.ui.tips.DefinedTips @@ -64,6 +65,9 @@ class MainActivity : FragmentActivity() { @Inject lateinit var exchange: Exchange + @Inject + lateinit var tipController: TipController + @Inject lateinit var tipEngine: TipsEngine @@ -135,11 +139,13 @@ class MainActivity : FragmentActivity() { override fun onResume() { super.onResume() client.startTimer() + tipController.checkForConnection() } override fun onStop() { super.onStop() client.stopTimer() + tipController.stopTimer() } } From 5d6ce83eac202a0e2044263a0a6d0f59ea2ce01f Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 19 Aug 2024 12:37:19 -0400 Subject: [PATCH 25/33] chore: remove label for cancel bill action Signed-off-by: Brandon McAnsh --- app/src/main/java/com/getcode/models/BillState.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/getcode/models/BillState.kt b/app/src/main/java/com/getcode/models/BillState.kt index 75e7c509e..9fe31ab82 100644 --- a/app/src/main/java/com/getcode/models/BillState.kt +++ b/app/src/main/java/com/getcode/models/BillState.kt @@ -70,8 +70,8 @@ data class BillState( } data class Cancel(override val action: () -> Unit): Action { - override val label: String - @Composable get() = stringResource(R.string.action_cancel) + override val label: String? + @Composable get() = null override val asset: Painter @Composable get() = painterResource(R.drawable.ic_bill_close) From 87edfa63e3bf176ca01d3fb98892f55f912230f1 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 19 Aug 2024 12:38:10 -0400 Subject: [PATCH 26/33] feat: simplify home bottom bar Signed-off-by: Brandon McAnsh --- .../com/getcode/view/main/home/DecorView.kt | 8 +- .../com/getcode/view/main/home/HomeScan.kt | 27 ++-- .../getcode/view/main/home/HomeViewModel.kt | 33 +++-- .../view/main/home/components/HomeBottom.kt | 131 +++++++++--------- app/src/main/res/drawable/ic_balance.xml | 21 +++ .../main/res/drawable/ic_kin_white_small.xml | 11 ++ app/src/main/res/drawable/ic_tip_card.xml | 24 ++-- app/src/main/res/values/strings.xml | 2 + 8 files changed, 141 insertions(+), 116 deletions(-) create mode 100644 app/src/main/res/drawable/ic_balance.xml create mode 100644 app/src/main/res/drawable/ic_kin_white_small.xml diff --git a/app/src/main/java/com/getcode/view/main/home/DecorView.kt b/app/src/main/java/com/getcode/view/main/home/DecorView.kt index 763c55829..e34ba1db1 100644 --- a/app/src/main/java/com/getcode/view/main/home/DecorView.kt +++ b/app/src/main/java/com/getcode/view/main/home/DecorView.kt @@ -59,7 +59,7 @@ internal fun DecorView( isCameraReady: Boolean, isPaused: Boolean, modifier: Modifier = Modifier, - showBottomSheet: (HomeBottomSheet) -> Unit, + onAction: (HomeAction) -> Unit, ) { val tips = LocalTipsEngine.current!!.tips as DefinedTips val tipProvider = LocalTipProvider.current @@ -74,7 +74,7 @@ internal fun DecorView( val scope = rememberCoroutineScope() val openDownloadModal = { - showBottomSheet(HomeBottomSheet.SHARE_DOWNLOAD) + onAction(HomeAction.SHARE_DOWNLOAD) scope.launch { delay(300) tipProvider.dismiss() @@ -113,7 +113,7 @@ internal fun DecorView( .align(Alignment.TopEnd) .clip(CircleShape) .rememberedClickable { - showBottomSheet(HomeBottomSheet.ACCOUNT) + onAction(HomeAction.ACCOUNT) }, painter = painterResource( R.drawable.ic_home_options @@ -186,7 +186,7 @@ internal fun DecorView( .padding(bottom = CodeTheme.dimens.grid.x3), state = dataState, onPress = { - showBottomSheet(it) + onAction(it) }, ) } diff --git a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt index 672bd4c33..ba6a17283 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt @@ -52,7 +52,6 @@ import com.getcode.navigation.screens.ConnectAccount import com.getcode.navigation.screens.EnterTipModal import com.getcode.navigation.screens.GetKinModal import com.getcode.navigation.screens.GiveKinModal -import com.getcode.navigation.screens.HomeResult import com.getcode.navigation.screens.ShareDownloadLinkModal import com.getcode.ui.components.OnLifecycleEvent import com.getcode.ui.components.PermissionCheck @@ -77,7 +76,7 @@ import timber.log.Timber import kotlin.time.Duration.Companion.milliseconds -enum class HomeBottomSheet { +enum class HomeAction { NONE, ACCOUNT, GIVE_KIN, @@ -186,22 +185,22 @@ private fun HomeScan( } } - fun showBottomSheet(bottomSheet: HomeBottomSheet) { + fun handleAction(action: HomeAction) { scope.launch { - when (bottomSheet) { - HomeBottomSheet.GIVE_KIN -> navigator.show(GiveKinModal) - HomeBottomSheet.ACCOUNT -> navigator.show(AccountModal) - HomeBottomSheet.GET_KIN -> navigator.show(GetKinModal) - HomeBottomSheet.BALANCE -> navigator.show(BalanceModal) - HomeBottomSheet.SHARE_DOWNLOAD -> navigator.show(ShareDownloadLinkModal) - HomeBottomSheet.TIP_CARD -> { + when (action) { + HomeAction.GIVE_KIN -> navigator.show(GiveKinModal) + HomeAction.ACCOUNT -> navigator.show(AccountModal) + HomeAction.GET_KIN -> navigator.show(GetKinModal) + HomeAction.BALANCE -> navigator.show(BalanceModal) + HomeAction.SHARE_DOWNLOAD -> navigator.show(ShareDownloadLinkModal) + HomeAction.TIP_CARD -> { if (dataState.tipCardConnected) { homeViewModel.presentShareableTipCard() } else { navigator.show(ConnectAccount()) } } - HomeBottomSheet.NONE -> Unit + HomeAction.NONE -> Unit } } } @@ -225,7 +224,7 @@ private fun HomeScan( } ) }, - showBottomSheet = { showBottomSheet(it) }, + onAction = { handleAction(it) }, ) OnLifecycleEvent { _, event -> @@ -291,7 +290,7 @@ private fun BillContainer( homeViewModel: HomeViewModel, scannerView: @Composable () -> Unit, onStartCamera: () -> Unit, - showBottomSheet: (HomeBottomSheet) -> Unit, + onAction: (HomeAction) -> Unit, ) { val onPermissionResult = { isGranted: Boolean -> @@ -377,7 +376,7 @@ private fun BillContainer( exit = fadeOut(), modifier = Modifier.fillMaxSize() ) { - DecorView(updatedState, isCameraReady, isPaused) { showBottomSheet(it) } + DecorView(updatedState, isCameraReady, isPaused) { onAction(it) } } var managementHeight by remember { diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index e767a9713..915f091af 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -150,7 +150,7 @@ data class HomeUiModel( val buyModule: Feature = BuyModuleFeature(), val requestKin: Feature = RequestKinFeature(), val tips: Feature = TipCardFeature(), - val tipCardOnHomeScreen: Feature = TipCardOnHomeScreenFeature(), + val actions: List = listOf(HomeAction.GIVE_KIN, HomeAction.TIP_CARD, HomeAction.BALANCE), val tipCardConnected: Boolean = false, ) @@ -186,7 +186,6 @@ class HomeViewModel @Inject constructor( private val mnemonicManager: MnemonicManager, private val cashLinkManager: CashLinkManager, appSettings: AppSettingsRepository, - betaFlags: BetaFlagsRepository, features: FeatureRepository, ) : BaseViewModel(resources), ScreenModel { val uiFlow = MutableStateFlow(HomeUiModel()) @@ -208,20 +207,6 @@ class HomeViewModel @Inject constructor( } }.launchIn(viewModelScope) -// betaFlags.observe() -// .distinctUntilChanged() -// .onEach { beta -> -// ErrorUtils.setDisplayErrors(beta.displayErrors) -// -// if (beta.establishCodeRelationship) { -// val organizer = SessionManager.getOrganizer() ?: return@onEach -// val domain = Domain.from("getcode.com") ?: return@onEach -// if (organizer.relationshipFor(domain) == null) { -// client.awaitEstablishRelationship(organizer, domain) -// } -// } -// }.launchIn(viewModelScope) - features.buyModule .onEach { module -> uiFlow.update { @@ -232,7 +217,7 @@ class HomeViewModel @Inject constructor( features.tipCardOnHomeScreen .onEach { module -> uiFlow.update { - it.copy(tipCardOnHomeScreen = module) + it.copy(actions = buildActions(module.enabled)) } }.launchIn(viewModelScope) @@ -388,6 +373,20 @@ class HomeViewModel @Inject constructor( } } + private fun buildActions( + tipCardOnHomeScreen: Boolean, + ): List { + return listOf( + HomeAction.GIVE_KIN, + if (tipCardOnHomeScreen) { + HomeAction.TIP_CARD + } else { + HomeAction.GET_KIN + }, + HomeAction.BALANCE + ) + } + fun onCameraScanning(scanning: Boolean) { uiFlow.update { it.copy(isCameraScanEnabled = scanning) } } diff --git a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt index 526fc950c..6b3381ca5 100644 --- a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt +++ b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt @@ -4,6 +4,7 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.scaleIn import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -15,7 +16,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId @@ -24,15 +24,16 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach import com.getcode.R import com.getcode.theme.CodeTheme import com.getcode.ui.components.Badge import com.getcode.ui.components.Row import com.getcode.ui.components.chat.ChatNodeDefaults import com.getcode.ui.utils.heightOrZero -import com.getcode.ui.utils.rememberedClickable +import com.getcode.ui.utils.unboundedClickable import com.getcode.ui.utils.widthOrZero -import com.getcode.view.main.home.HomeBottomSheet +import com.getcode.view.main.home.HomeAction import com.getcode.view.main.home.HomeUiModel @Preview @@ -40,75 +41,70 @@ import com.getcode.view.main.home.HomeUiModel internal fun HomeBottom( modifier: Modifier = Modifier, state: HomeUiModel = HomeUiModel(), - onPress: (homeBottomSheet: HomeBottomSheet) -> Unit = {}, + onPress: (homeBottomSheet: HomeAction) -> Unit = {}, ) { Row( modifier = Modifier .fillMaxWidth() .then(modifier), verticalAlignment = Alignment.Bottom, - contentPadding = PaddingValues(horizontal = CodeTheme.dimens.grid.x3), + horizontalArrangement = Arrangement.SpaceAround, ) { - BottomBarAction( - label = if (state.tipCardOnHomeScreen.enabled) { - stringResource(R.string.title_tipCard) - } else { - stringResource(R.string.title_getCash) - }, - contentPadding = PaddingValues( - start = CodeTheme.dimens.grid.x3, - end = CodeTheme.dimens.grid.x3, - top = CodeTheme.dimens.grid.x1, - bottom = CodeTheme.dimens.grid.x2, - ), - imageSize = CodeTheme.dimens.grid.x7, - painter = if (state.tipCardOnHomeScreen.enabled) { - painterResource(R.drawable.ic_tip_card) - } else { - painterResource(R.drawable.ic_wallet) - }, - onClick = { - if (state.tipCardOnHomeScreen.enabled) { - onPress(HomeBottomSheet.TIP_CARD) - } else { - onPress(HomeBottomSheet.GET_KIN) + state.actions.fastForEach { action -> + when (action) { + HomeAction.GIVE_KIN -> { + BottomBarAction( + modifier = Modifier.weight(1f), + label = stringResource(R.string.action_give), + painter = painterResource(R.drawable.ic_kin_white_small), + onClick = { onPress(action) } + ) } - }, - ) - Spacer(modifier = Modifier.weight(1f)) - BottomBarAction( - label = stringResource(R.string.action_giveKin), - contentPadding = PaddingValues( - horizontal = CodeTheme.dimens.grid.x3, - vertical = CodeTheme.dimens.grid.x2 - ), - imageSize = CodeTheme.dimens.grid.x10, - painter = painterResource(R.drawable.ic_kin_white), - onClick = { onPress(HomeBottomSheet.GIVE_KIN) } - ) - Spacer(modifier = Modifier.weight(1f)) - BottomBarAction( - label = stringResource(R.string.action_balance), - contentPadding = PaddingValues( - horizontal = CodeTheme.dimens.grid.x2, - ), - imageSize = CodeTheme.dimens.grid.x9, - painter = painterResource(R.drawable.ic_history), - onClick = { onPress(HomeBottomSheet.BALANCE) }, - badge = { - Badge( - modifier = Modifier.padding(top = 2.dp, end = 2.dp), - count = state.chatUnreadCount, - color = ChatNodeDefaults.UnreadIndicator, - enterTransition = scaleIn( - animationSpec = tween( - durationMillis = 300, - delayMillis = 1000 - ) - ) + fadeIn() - ) + HomeAction.GET_KIN -> { + BottomBarAction( + modifier = Modifier.weight(1f), + label = stringResource(R.string.action_receive), + painter = painterResource(R.drawable.ic_wallet), + onClick = { onPress(action) }, + ) + } + HomeAction.BALANCE -> { + BottomBarAction( + modifier = Modifier.weight(1f), + label = stringResource(R.string.action_balance), + painter = painterResource(R.drawable.ic_balance), + contentPadding = PaddingValues( + top = CodeTheme.dimens.grid.x2, + bottom = CodeTheme.dimens.grid.x3 + ), + imageSize = CodeTheme.dimens.staticGrid.x6, + onClick = { onPress(HomeAction.BALANCE) }, + badge = { + Badge( + modifier = Modifier.padding(top = 2.dp, end = 2.dp), + count = state.chatUnreadCount, + color = ChatNodeDefaults.UnreadIndicator, + enterTransition = scaleIn( + animationSpec = tween( + durationMillis = 300, + delayMillis = 1000 + ) + ) + fadeIn() + ) + } + ) + } + HomeAction.TIP_CARD -> { + BottomBarAction( + modifier = Modifier.weight(1f), + label = stringResource(R.string.action_receive), + painter = painterResource(R.drawable.ic_tip_card), + onClick = { onPress(action) }, + ) + } + else -> Unit } - ) + } } } @@ -116,9 +112,11 @@ internal fun HomeBottom( private fun BottomBarAction( modifier: Modifier = Modifier, label: String, - contentPadding: PaddingValues = PaddingValues(), + contentPadding: PaddingValues = PaddingValues( + vertical = CodeTheme.dimens.grid.x2 + ), painter: Painter, - imageSize: Dp, + imageSize: Dp = CodeTheme.dimens.staticGrid.x8, badge: @Composable () -> Unit = { }, onClick: () -> Unit, ) { @@ -127,8 +125,7 @@ private fun BottomBarAction( content = { Column( modifier = Modifier - .clip(CodeTheme.shapes.medium) - .rememberedClickable { onClick() } + .unboundedClickable { onClick() } .layoutId("action"), horizontalAlignment = Alignment.CenterHorizontally ) { diff --git a/app/src/main/res/drawable/ic_balance.xml b/app/src/main/res/drawable/ic_balance.xml new file mode 100644 index 000000000..0af9ca841 --- /dev/null +++ b/app/src/main/res/drawable/ic_balance.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_kin_white_small.xml b/app/src/main/res/drawable/ic_kin_white_small.xml new file mode 100644 index 000000000..5c6421d5d --- /dev/null +++ b/app/src/main/res/drawable/ic_kin_white_small.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_tip_card.xml b/app/src/main/res/drawable/ic_tip_card.xml index f9edb10ab..ee23ff720 100644 --- a/app/src/main/res/drawable/ic_tip_card.xml +++ b/app/src/main/res/drawable/ic_tip_card.xml @@ -1,19 +1,15 @@ - - - - + android:width="28dp" + android:height="40dp" + android:viewportWidth="28" + android:viewportHeight="40"> + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e6cb7013..fa349717f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,4 +50,6 @@ Connecting your X account allows you to reveal your identity to the people you tip. To connect your account post to X. Use Kado Sandbox Tip Card + Give + Receive From 01b557f1fc57d785526378f9e5e1eba5a9b936ed Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 19 Aug 2024 13:07:48 -0400 Subject: [PATCH 27/33] chore: create a tip verification state Signed-off-by: Brandon McAnsh --- .../main/java/com/getcode/model/PrefBool.kt | 1 + .../java/com/getcode/network/TipController.kt | 52 +++++++++++++------ .../getcode/view/main/home/HomeViewModel.kt | 1 + .../view/main/tip/TipConnectViewModel.kt | 1 + 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/api/src/main/java/com/getcode/model/PrefBool.kt b/api/src/main/java/com/getcode/model/PrefBool.kt index 3c6b41b71..7645b92c7 100644 --- a/api/src/main/java/com/getcode/model/PrefBool.kt +++ b/api/src/main/java/com/getcode/model/PrefBool.kt @@ -25,6 +25,7 @@ sealed class PrefsBool(val value: String) { data object IS_ELIGIBLE_GIVE_FIRST_KIN_AIRDROP: PrefsBool("is_eligible_give_first_kin_airdrop"), InternalRouting data object HAS_REMOVED_LOCAL_CURRENCY: PrefsBool("removed_local_currency"), InternalRouting data object SEEN_TIP_CARD : PrefsBool("seen_tip_card"), InternalRouting + data object STARTED_TIP_CONNECT: PrefsBool("started_tip_connect"), InternalRouting data object BUY_MODULE_AVAILABLE : PrefsBool("buy_module_available"), InternalRouting diff --git a/api/src/main/java/com/getcode/network/TipController.kt b/api/src/main/java/com/getcode/network/TipController.kt index e7b0579d9..fa8735d51 100644 --- a/api/src/main/java/com/getcode/network/TipController.kt +++ b/api/src/main/java/com/getcode/network/TipController.kt @@ -47,7 +47,7 @@ class TipController @Inject constructor( ) { companion object { - private const val POLL_FREQUENCY_SECS = 5L + private const val POLL_FREQUENCY_LOOKING_SECS = 5L } private var pollTimer: Timer? = null @@ -61,8 +61,6 @@ class TipController @Inject constructor( var userMetadata: TwitterUser? = null private set - private var confirmedConnection = false - val connectedAccount: StateFlow = prefRepository.observeOrDefault(PrefsString.KEY_TIP_ACCOUNT, "") .map { runCatching { Json.decodeFromString(it) }.getOrNull() } .distinctUntilChanged() @@ -72,6 +70,14 @@ class TipController @Inject constructor( initialValue = null ) + val verificationInProgress: StateFlow = prefRepository.observeOrDefault(PrefsBool.STARTED_TIP_CONNECT, false) + .distinctUntilChanged() + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = false + ) + val showTwitterSplat: Flow = combine( connectedAccount, @@ -82,18 +88,22 @@ class TipController @Inject constructor( } private fun startPollTimer() { + if (connectedAccount.value != null) return + Timber.d("twitter poll start") pollTimer?.cancel() - pollTimer = fixedRateTimer("twitterPollTimer", false, 0, 1000 * POLL_FREQUENCY_SECS) { - scope.launch { - val time = System.currentTimeMillis() - val isPastThrottle = time - lastPoll > 1000 * (POLL_FREQUENCY_SECS / 2.0) || lastPoll == 0L - - if (isPastThrottle) { - callForConnectedUser() + pollTimer = + fixedRateTimer("twitterPollTimer", false, 0, 1000 * POLL_FREQUENCY_LOOKING_SECS) { + scope.launch { + val time = System.currentTimeMillis() + val isPastThrottle = + time - lastPoll > 1000 * (POLL_FREQUENCY_LOOKING_SECS / 2.0) || lastPoll == 0L + + if (isPastThrottle) { + callForConnectedUser() + } } } - } } private suspend fun callForConnectedUser() { @@ -105,8 +115,7 @@ class TipController @Inject constructor( .onSuccess { Timber.d("current user twitter connected @ ${it.username}") prefRepository.set(PrefsString.KEY_TIP_ACCOUNT, Json.encodeToString(it)) - confirmedConnection = true - stopTimerInternal() + stopTimer() } .onFailure { when (it) { @@ -127,7 +136,12 @@ class TipController @Inject constructor( } fun checkForConnection() { - if (confirmedConnection) return + if (!verificationInProgress.value) { + scope.launch { + callForConnectedUser() + } + return + } startPollTimer() } @@ -152,6 +166,7 @@ class TipController @Inject constructor( fun clearTwitterSplat() { prefRepository.set(PrefsBool.SEEN_TIP_CARD, true) + endVerification() } fun generateTipVerification(): String? { @@ -175,8 +190,15 @@ class TipController @Inject constructor( return null } + fun startVerification() { + prefRepository.set(PrefsBool.STARTED_TIP_CONNECT, true) + } + + private fun endVerification() { + prefRepository.set(PrefsBool.STARTED_TIP_CONNECT, false) + } + fun stopTimer() { - confirmedConnection = false stopTimerInternal() } diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index 915f091af..c629b9906 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -232,6 +232,7 @@ class HomeViewModel @Inject constructor( .filter { it } .onEach { delay(500) } .flatMapLatest { tipController.connectedAccount } + .filter { tipController.verificationInProgress.value } .filterNotNull() .distinctUntilChanged() .filter { uiFlow.value.isCameraScanEnabled } diff --git a/app/src/main/java/com/getcode/view/main/tip/TipConnectViewModel.kt b/app/src/main/java/com/getcode/view/main/tip/TipConnectViewModel.kt index bc32fe3d2..852e1a916 100644 --- a/app/src/main/java/com/getcode/view/main/tip/TipConnectViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/tip/TipConnectViewModel.kt @@ -69,6 +69,7 @@ class TipConnectViewModel @Inject constructor( .map { IntentUtils.tweet(it) } .onEach { dispatchEvent(Event.OpenX(it)) + tipController.startVerification() }.launchIn(viewModelScope) } From 18ce59391bb9cd9242389109fe5ad3bf95c67617 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 19 Aug 2024 16:33:23 -0400 Subject: [PATCH 28/33] chore: update Give Kin title to Give Cash Signed-off-by: Brandon McAnsh --- app/src/main/java/com/getcode/navigation/screens/MainScreens.kt | 2 +- app/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt b/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt index f19cad36b..e57ad689d 100644 --- a/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt +++ b/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt @@ -86,7 +86,7 @@ data object GiveKinModal : AppScreen(), MainGraph, ModalRoot { override val name: String - @Composable get() = stringResource(id = R.string.title_giveKin) + @Composable get() = stringResource(id = R.string.title_giveCash) @Composable override fun Content() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa349717f..9c4034d66 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,7 @@ Connecting your X account allows you to reveal your identity to the people you tip. To connect your account post to X. Use Kado Sandbox Tip Card + Give Cash Give Receive From 6c88beb1ae3f51548dec5fe89361a2d0dadafc12 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 20 Aug 2024 12:46:13 -0400 Subject: [PATCH 29/33] chore: update localizations Signed-off-by: Brandon McAnsh --- app/src/main/res/values-ar/strings-localized.xml | 5 +++++ app/src/main/res/values-bg/strings-localized.xml | 5 +++++ app/src/main/res/values-cs/strings-localized.xml | 5 +++++ app/src/main/res/values-da/strings-localized.xml | 5 +++++ app/src/main/res/values-de/strings-localized.xml | 5 +++++ app/src/main/res/values-el/strings-localized.xml | 5 +++++ app/src/main/res/values-en-rGB/strings-localized.xml | 5 +++++ app/src/main/res/values-es/strings-localized.xml | 5 +++++ app/src/main/res/values-fi/strings-localized.xml | 5 +++++ app/src/main/res/values-fr-rCA/strings-localized.xml | 5 +++++ app/src/main/res/values-fr/strings-localized.xml | 5 +++++ app/src/main/res/values-he/strings-localized.xml | 5 +++++ app/src/main/res/values-hi-rIN/strings-localized.xml | 5 +++++ app/src/main/res/values-hu/strings-localized.xml | 5 +++++ app/src/main/res/values-id/strings-localized.xml | 5 +++++ app/src/main/res/values-it/strings-localized.xml | 5 +++++ app/src/main/res/values-ja/strings-localized.xml | 5 +++++ app/src/main/res/values-ko/strings-localized.xml | 5 +++++ app/src/main/res/values-ms/strings-localized.xml | 5 +++++ app/src/main/res/values-nl/strings-localized.xml | 5 +++++ app/src/main/res/values-no/strings-localized.xml | 5 +++++ app/src/main/res/values-pl/strings-localized.xml | 5 +++++ app/src/main/res/values-pt/strings-localized.xml | 5 +++++ app/src/main/res/values-ro/strings-localized.xml | 5 +++++ app/src/main/res/values-ru/strings-localized.xml | 5 +++++ app/src/main/res/values-sk/strings-localized.xml | 5 +++++ app/src/main/res/values-sr/strings-localized.xml | 5 +++++ app/src/main/res/values-sv/strings-localized.xml | 5 +++++ app/src/main/res/values-th/strings-localized.xml | 5 +++++ app/src/main/res/values-tr/strings-localized.xml | 5 +++++ app/src/main/res/values-uk/strings-localized.xml | 5 +++++ app/src/main/res/values-vi/strings-localized.xml | 5 +++++ app/src/main/res/values-zh-rCN/strings-localized.xml | 5 +++++ app/src/main/res/values-zh-rTW/strings-localized.xml | 5 +++++ app/src/main/res/values/strings-localized.xml | 4 ++++ app/src/main/res/values/strings.xml | 4 ---- 36 files changed, 174 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-ar/strings-localized.xml b/app/src/main/res/values-ar/strings-localized.xml index e8e45350a..f934b233f 100644 --- a/app/src/main/res/values-ar/strings-localized.xml +++ b/app/src/main/res/values-ar/strings-localized.xml @@ -24,6 +24,7 @@ تفعيل خاصية التعرّف على الوجه تفعيل خاصية التعرّف على بصمة الإصبع الخروج + أعط منح الأموال الدعوة الدعوات @@ -44,6 +45,7 @@ اللصق من الحافظة انشر لربط حساب الوضع في المحفظة + استقبل استعادة الحساب الموجود بالفعل التذكير حذف رقم الهاتف @@ -54,6 +56,7 @@ تم الإرسال إرسال رمز التحقق مشاركة + مشاركة كعنوان URL شارك رابط التنزيل شارك مقطع الفيديو هذا عرض بطاقة الإكرامية الخاصة بي @@ -327,6 +330,7 @@ احصل على صديق بدأ في Code احصل على Kin احصل على المزيد من Kin + أعط النقود منح الأموال الرصيد غير كافٍ دعوة صديق @@ -361,6 +365,7 @@ النقود المصروفة تبديل الحسابات شروط الخدمة + بطاقة Tip إكرامية Kin غير معروف التحديث مطلوب diff --git a/app/src/main/res/values-bg/strings-localized.xml b/app/src/main/res/values-bg/strings-localized.xml index 5eda7027b..02a8a7c78 100644 --- a/app/src/main/res/values-bg/strings-localized.xml +++ b/app/src/main/res/values-bg/strings-localized.xml @@ -24,6 +24,7 @@ Разрешете лицево разпознаване Разрешете разпознаване чрез докосване Изход + Дайте Задай член на семейството Покани Покани @@ -44,6 +45,7 @@ Постави от клипборда Публикувайте за да свържете акаунт Постави в портфейл + Получете Възстанови съществуващ профил Напомняне Премахване на телефонен номер @@ -54,6 +56,7 @@ Изпращане Изпрати код за потвърждение Сподели + Споделете като URL Споделете линк за сваляне Споделете този видео клип Покажи моята карта за бакшиши @@ -327,6 +330,7 @@ Вземете \"Приятел се присъедини\" към Code Получаване на средства в Kin Получавайте повече Kin + Дайте кеш Посочете член на семейството Недостатъчна наличност Покани приятел @@ -361,6 +365,7 @@ Похарчено Смени акаунти Условия на ползване + Карта за бакшиши Бакшиш Kin Неизвестен Необходимо е обновяване diff --git a/app/src/main/res/values-cs/strings-localized.xml b/app/src/main/res/values-cs/strings-localized.xml index 25fe802f6..9f5882a99 100644 --- a/app/src/main/res/values-cs/strings-localized.xml +++ b/app/src/main/res/values-cs/strings-localized.xml @@ -24,6 +24,7 @@ Povolit Face ID Povolit Touch ID Odejít + Dát Darovat kin Přizvat Pozvánky @@ -44,6 +45,7 @@ Vložit z vývěsky Zveřejnit příspěvek pro připojení účtu Vložit do peněženky + Přijmout Obnovit stávající účet Připomenout Odstranit telefonní číslo @@ -54,6 +56,7 @@ Odeslat Zaslat ověřovací kód Sdílet + Sdílet jako adresu URL Sdílet odkaz stažení Sdílet toto video Ukázat kartu spropitného @@ -327,6 +330,7 @@ Začněte kamarádem používat Code Získat Kin Získejte další Kin + Dát hotovost Darovat kin Nedostatek prostředků Přizvat přítele @@ -361,6 +365,7 @@ Utraceno Přepnout účet Všeobecné obchodní podmínky + Karta tipu Dát Kin spropitné Neznámé Je nutná aktualizace diff --git a/app/src/main/res/values-da/strings-localized.xml b/app/src/main/res/values-da/strings-localized.xml index 990116288..7a5e106f3 100644 --- a/app/src/main/res/values-da/strings-localized.xml +++ b/app/src/main/res/values-da/strings-localized.xml @@ -24,6 +24,7 @@ Aktiver Face ID Aktiver Touch ID Forlad + Giv Giv Kin Inviter Invitationer @@ -44,6 +45,7 @@ Indsæt fra udklipsholder Slå noget op for at forbinde konto Læg i Wallet + Modtag Genskab eksisterende konto Husk Fjern telefonnummer @@ -54,6 +56,7 @@ Send Send verifikationskode Del + Del som en URL Del downloadlink Del denne video Vis mit drikkepengekort @@ -327,6 +330,7 @@ Få en ven i gang med Code Få Kin Få flere Kin + Giv kontanter Giv Kin Ikke nok penge Inviter en ven @@ -361,6 +365,7 @@ Brugt Skift konti Servicebetingelser + Tipkort Giv drikkepenge i Kin Ukendt Opdatering nødvendig diff --git a/app/src/main/res/values-de/strings-localized.xml b/app/src/main/res/values-de/strings-localized.xml index f6c22ef78..1a84216c3 100644 --- a/app/src/main/res/values-de/strings-localized.xml +++ b/app/src/main/res/values-de/strings-localized.xml @@ -24,6 +24,7 @@ Face ID aktivieren Touch ID aktivieren Beenden + Geben Kin überweisen Einladen Einladen @@ -44,6 +45,7 @@ Aus Zwischenablage einfügen Posten, um das Konto zu verbinden In Wallet legen + Erhalten Bestehendes Konto wiederherstellen Erinnern Telefonnummer entfernen @@ -54,6 +56,7 @@ Senden Verifizierungscode senden Teilen + Als URL teilen Download-Link teilen Dieses Video teilen\n Meine Trinkgeldkarte anzeigen @@ -327,6 +330,7 @@ Gewinne einen deiner Freunde für Code Kin erhalten Mehr Kin erhalten\n + Geld geben Kin überweisen Unzureichende Mittel\n Einen Freund einladen @@ -361,6 +365,7 @@ Ausgegeben Konten wechseln Servicebedingungen + Tippkarte Kin-Trinkgeld geben Unbekannt Aktualisierung erforderlich diff --git a/app/src/main/res/values-el/strings-localized.xml b/app/src/main/res/values-el/strings-localized.xml index eff24f0fa..32d87ad02 100644 --- a/app/src/main/res/values-el/strings-localized.xml +++ b/app/src/main/res/values-el/strings-localized.xml @@ -24,6 +24,7 @@ Ενεργοποιήστε το Face ID Ενεργοποιήστε το Touch ID Έξοδος + Δώσε \n Δώστε Kin Πρόσκληση Προσκλήσεις @@ -44,6 +45,7 @@ Επικόλληση από Πρόχειρο Στείλε Μήνυμα για Σύνδεση του Λογαριασμού Τοποθέτηση σε Πορτοφόλι + Λάβε Ανάκτηση Υπάρχοντος Λογαριασμού Υπενθύμιση Κατάργηση Αριθμού Τηλεφώνου @@ -54,6 +56,7 @@ Αποστολή Αποστολή Κωδικού Επαλήθευσης Κοινοποίηση + Κοινή χρήση ως URL(Ενιαίος Εντοπιστής Πόρων) Μοιράσου το Σύνδεσμο Λήψης Μοιραστείτε Αυτό το Βίντεο Κοινοποίηση της Κάρτας Φιλοδωρημάτων μου @@ -327,6 +330,7 @@ Βρες ένα Φίλο να Ξεκινήσει στο Code Λάβετε Kin Αποκτήστε Περισσότερα Kin + Δώσε Μετρητά Δώστε Kin Ανεπαρκή Κεφάλαια Προσκαλέστε έναν Φίλο @@ -361,6 +365,7 @@ Δαπανημένα Εναλλαγή Λογαριασμών Όροι Χρήσης + Συμβουλή Φιλοδώρημα Kin Άγνωστο Απαιτείται Ενημέρωση diff --git a/app/src/main/res/values-en-rGB/strings-localized.xml b/app/src/main/res/values-en-rGB/strings-localized.xml index 9d41538b3..2daec51b1 100644 --- a/app/src/main/res/values-en-rGB/strings-localized.xml +++ b/app/src/main/res/values-en-rGB/strings-localized.xml @@ -24,6 +24,7 @@ Enable Face ID Enable Touch ID Exit + Give Give Kin Invite Invites @@ -44,6 +45,7 @@ Paste From Clipboard Post to Connect Account Put in Wallet + Receive Recover Existing Account Remind Remove Phone Number @@ -54,6 +56,7 @@ Send Send Verification Code Share + Share as a URL Share Download Link Share this video Show My Tip Card @@ -327,6 +330,7 @@ Get a Friend Started on Code Get Kin Get more Kin + Give Cash Give Kin Insufficient funds Invite a Friend @@ -361,6 +365,7 @@ Spent Switch Accounts Terms of Service + Tip Card Tip Kin Unknown Update Required diff --git a/app/src/main/res/values-es/strings-localized.xml b/app/src/main/res/values-es/strings-localized.xml index 04b91ffbd..d67476c18 100644 --- a/app/src/main/res/values-es/strings-localized.xml +++ b/app/src/main/res/values-es/strings-localized.xml @@ -24,6 +24,7 @@ Habilitar identificación facial Habilitar identificación táctil Salir + Dar Dar Kin Invitar Invitaciones @@ -44,6 +45,7 @@ Pegar desde el portapapeles Publica para conectar la cuenta Guardar en el monedero + Recibir Recuperar cuenta existente Recordar Eliminar número de teléfono @@ -54,6 +56,7 @@ Enviar Enviar código de verificación Compartir + Compartir como URL Compartir enlace de descarga Compartir este vídeo Mostrar mi tarjeta de propinas @@ -327,6 +330,7 @@ Inicia a un amigo en Code Obtener Kin Obtenga más Kin + Dar efectivo Dar Kin Fondos insuficientes Invitar a un amigo @@ -361,6 +365,7 @@ Gastado Cambiar de cuenta Términos y condiciones + Tarjeta de propina Dar Kin de propina Desconocido Actualización requerida diff --git a/app/src/main/res/values-fi/strings-localized.xml b/app/src/main/res/values-fi/strings-localized.xml index ed5ba9226..3bdb4ccf3 100644 --- a/app/src/main/res/values-fi/strings-localized.xml +++ b/app/src/main/res/values-fi/strings-localized.xml @@ -24,6 +24,7 @@ Ota kasvotunnistus käyttöön Ota kosketustunnistus käyttöön Poistu + Anna Anna kinejä Kutsu Kutsut @@ -44,6 +45,7 @@ Liitä leikepöydältä Julkaise yhdistettävälle tilille Pane lompakkoon + Ota vastaan Palauta olemassa oleva tili Muistuta Poista puhelinnumero @@ -54,6 +56,7 @@ Lähetä Lähetä vahvistuskoodi Jaa + Jaa URL-osoitteena Jaa latauslinkki Jaa tämä video Näytä tippikorttini @@ -327,6 +330,7 @@ Auta kaveri alkuun Coden parissa Hae Kin Hanki lisää kinejä + Anna rahaa Antoi kinejä Riittämättömästi varoja Kutsu ystävä @@ -361,6 +365,7 @@ Käytetty Vaihda tilejä Palveluehdot + Tippikortti Anna kinejä tippinä Tuntematon Päivitys vaaditaan diff --git a/app/src/main/res/values-fr-rCA/strings-localized.xml b/app/src/main/res/values-fr-rCA/strings-localized.xml index dd9edbd07..3b873ba65 100644 --- a/app/src/main/res/values-fr-rCA/strings-localized.xml +++ b/app/src/main/res/values-fr-rCA/strings-localized.xml @@ -24,6 +24,7 @@ Activer Face ID Activer Touch ID Sortir + Donner Donner des Kin Inviter Invitations @@ -44,6 +45,7 @@ Coller depuis le presse-papiers Envoyez un message à Connecter le compte Mettre dans le portefeuille + Recevoir Récupérer un compte existant Rappeler Supprimer le numéro de téléphone @@ -54,6 +56,7 @@ Envoyer Envoyer le code de vérification Partager + Partager comme adresse URL Partager le lien de téléchargement Partagez cette vidéo Montrer ma carte de pourboire @@ -327,6 +330,7 @@ Faire démarrer un ami sur Code Obtenez Kin Obtenez plus de Kin + Donner de l\'argent Donner Kin Fonds insuffisants Inviter un ami @@ -361,6 +365,7 @@ Dépensé Changer de compte Conditions d\'utilisation + Carte à puce Donner un pourboire en Kin Inconnu Mise à jour requise diff --git a/app/src/main/res/values-fr/strings-localized.xml b/app/src/main/res/values-fr/strings-localized.xml index fefa04c65..f6f5e1835 100644 --- a/app/src/main/res/values-fr/strings-localized.xml +++ b/app/src/main/res/values-fr/strings-localized.xml @@ -24,6 +24,7 @@ Activez Face ID Activez Touch ID Quitter + Donner Donner Kin Inviter Invitations @@ -44,6 +45,7 @@ Coller depuis le presse-papiers Publier sur le compte connecté Mettre dans le portefeuille + Recevoir Récupérer le compte existant Rappeler Supprimer le numéro de téléphone @@ -54,6 +56,7 @@ Envoyer Envoyer le code de vérification Partager + Partager comme URL Partagez le lien de téléchargement Partager cette vidéo Afficher ma carte de pourboires @@ -327,6 +330,7 @@ Invitez un ami à s\'inscrire sur Code Obtenir des Kin Obtenir plus de Kin + Donner de l\'argent Donner Kin Fonds insuffisants Inviter un ami @@ -361,6 +365,7 @@ Dépensé Échanger des comptes Conditions d\'utilisation + Carte conseil Donner un pourboire en Kin Inconnu Mise à jour requise diff --git a/app/src/main/res/values-he/strings-localized.xml b/app/src/main/res/values-he/strings-localized.xml index 79a5c3c04..5e64d72d2 100644 --- a/app/src/main/res/values-he/strings-localized.xml +++ b/app/src/main/res/values-he/strings-localized.xml @@ -24,6 +24,7 @@ הפוך Face ID לזמין הפוך Touch ID לזמין יציאה + תן תן קין הזמן הזמנות @@ -44,6 +45,7 @@ הדבק מהלוח יש להעלות פוסט כדי לחבר את החשבון שים בארנק + קבל שחזר חשבון קיים הזכר הסר מספר טלפון @@ -54,6 +56,7 @@ שליחה שלח קוד אימות שיתוף + שיתוף כ-URL שתף קישור להורדה שתפו סרטון זה הצג את כרטיס הטיפים שלי @@ -327,6 +330,7 @@ עודד חבר להתחיל להשתמש ב-Code השגת Kin השיגו עוד Kin + תן מזומן העבר קין אין מספיק כספים הזמן חבר @@ -361,6 +365,7 @@ הוצאו החלף חשבון תנאי שימוש + כרטיס טיפ מתן Kin כטיפ לא ידוע נדרש עדכון diff --git a/app/src/main/res/values-hi-rIN/strings-localized.xml b/app/src/main/res/values-hi-rIN/strings-localized.xml index 9d268fec4..684c79da1 100644 --- a/app/src/main/res/values-hi-rIN/strings-localized.xml +++ b/app/src/main/res/values-hi-rIN/strings-localized.xml @@ -24,6 +24,7 @@ Face ID चालू करें Touch ID चालू करें बाहर निकलें + दें Kin दे आमंत्रित करें आमंत्रण @@ -44,6 +45,7 @@ क्लिपबोर्ड से पेस्ट करें अकाउंट कनेक्ट करने के लिए पोस्ट करें वॉलेट में डालें + पाएँ वर्तमान अकाउंट को पुनर्प्राप्त करें\n याद दिलाएं फ़ोन नंबर हटाएं @@ -54,6 +56,7 @@ सेंड वेरिफिकेशन कोड भेजें शेयर करें + URL के रूप में शेयर करें डाउनलोड लिंक शेयर करें इस वीडियो को शेयर करें मेरा टिप कार्ड दिखाएँ @@ -327,6 +330,7 @@ किसी दोस्त से Code इस्तेमाल करना शुरू करवाएं Kin पाएं अधिक Kin पाएं + कैश दें Kin दे अपर्याप्त फंड एक दोस्त को आमंत्रित करें @@ -361,6 +365,7 @@ खर्च किया अकाउंट स्विच करें सेवा की शर्तें + टिप कार्ड Kin को टिप दें अज्ञात अपडेट की आवश्यकता है diff --git a/app/src/main/res/values-hu/strings-localized.xml b/app/src/main/res/values-hu/strings-localized.xml index 8b5363caa..7ae1e607a 100644 --- a/app/src/main/res/values-hu/strings-localized.xml +++ b/app/src/main/res/values-hu/strings-localized.xml @@ -24,6 +24,7 @@ Arc azonosítás bekapcsolása Érintéses azonosítás bekapcsolása Kilépés + Adj Kin adása Meghív Meghívottak @@ -44,6 +45,7 @@ Beillesztés a vágólapról Írj bejegyzést, hogy csatlakoztasd a fiókodat Walletba helyez + Kapj Meglévő fiók helyreállítása Emlékeztet Telefonszám eltávolítása @@ -54,6 +56,7 @@ Küldés Ellenőrző kód küldése Megosztás + Megosztás URL-ként Letöltési hivatkozás megosztása Ossza meg ezt a videót Borravalós kártyám mutatása @@ -327,6 +330,7 @@ Szerezz egy barátot elkezdte használni a Code-ot Szerezz Kint Szerezzen több Kin-t + Adj készpénzt Kin felvétele Nincs elég tőke Hívd meg egy barátod @@ -361,6 +365,7 @@ Elköltött Fiókcsere Szolgáltatási feltételek + Borravalós kártya Kin borravaló Ismeretlen Frissítés szükséges diff --git a/app/src/main/res/values-id/strings-localized.xml b/app/src/main/res/values-id/strings-localized.xml index a5044786c..8e7f45348 100644 --- a/app/src/main/res/values-id/strings-localized.xml +++ b/app/src/main/res/values-id/strings-localized.xml @@ -24,6 +24,7 @@ Aktifkan Face ID Aktifkan Touch ID Keluar + Berikan Berikan Kin Undang Undangan @@ -44,6 +45,7 @@ Tempel Dari Papan Klip Post untuk Menghubungkan Akun Masukkan ke Dompet + Terima Pulihkan Akun Yang Ada Ingatkan Hapus Nomor Telepon @@ -54,6 +56,7 @@ Kirim Kirim Kode Verifikasi Bagikan + Bagikan sebagai URL Bagikan Tautan Unduhan Bagikan Video Ini Tampilkan Kartu Tip Saya @@ -327,6 +330,7 @@ Ajak Teman Memulai Code Dapatkan Kin Dapatkan Lebih Banyak Kin + Berikan Uang Berikan Kin Dana Tidak Cukup Undang Teman @@ -361,6 +365,7 @@ Terpakai Beralih Akun Ketentuan Layanan + Kartu Tip Beri Tip Kin Tak Dikenal Memerlukan Pembaruan diff --git a/app/src/main/res/values-it/strings-localized.xml b/app/src/main/res/values-it/strings-localized.xml index eebe0d3e1..5d56eaf59 100644 --- a/app/src/main/res/values-it/strings-localized.xml +++ b/app/src/main/res/values-it/strings-localized.xml @@ -24,6 +24,7 @@ Abilita Face ID Abilita Touch ID Esci + Dare Dona Kin Invita Inviti @@ -44,6 +45,7 @@ Incolla dagli appunti Pubblica per Connettere l\'Account Metti nel portafoglio + Ricevere Recupera un account esistente Ricorda Rimuovi numero di telefono @@ -54,6 +56,7 @@ Invia Invia codice di verifica Condividi + Condividi come URL Condividi link per il download Condividi questo video Moatra la mia Tip Card @@ -327,6 +330,7 @@ Fai iniziare un amico su Code Ottieni Kin Ottieni più Kin + Dare denaro Dona Kin Fondi insufficienti Invita un amico @@ -361,6 +365,7 @@ Spesi Cambia account Termini di servizio + Carta suggerimento Mancia Kin Sconosciuto Aggiornamento richiesto diff --git a/app/src/main/res/values-ja/strings-localized.xml b/app/src/main/res/values-ja/strings-localized.xml index 9b46506d8..e954c977d 100644 --- a/app/src/main/res/values-ja/strings-localized.xml +++ b/app/src/main/res/values-ja/strings-localized.xml @@ -24,6 +24,7 @@ Face IDを有効にする Touch IDを有効にする 終了する + 渡す Kinを与える 招待 招待 @@ -44,6 +45,7 @@ クリップボードから貼り付ける 投稿してアカウントを接続 ウォレットに入れる + 受け取る 既存のアカウントを復元する リマインド 電話番号を削除する @@ -54,6 +56,7 @@ 送信 確認コードを送信する 共有 + URLとして共有する ダウンロードリンクを共有 この動画をシェアする チップカードを表示する @@ -327,6 +330,7 @@ 友だちにCodeを始めさせる Kinを入手 Kinをもっと入手する + 現金を渡す Kinを与える 資金不足 友達を招待する @@ -361,6 +365,7 @@ 使用済み アカウントを切り替える 利用規約 + チップカード Kinにチップを支払う 不明 更新が必要です diff --git a/app/src/main/res/values-ko/strings-localized.xml b/app/src/main/res/values-ko/strings-localized.xml index 781c227ad..401affeed 100644 --- a/app/src/main/res/values-ko/strings-localized.xml +++ b/app/src/main/res/values-ko/strings-localized.xml @@ -24,6 +24,7 @@ Face ID 활성화 Touch ID 활성화 나가기 + 주기 Kin 보내기 초대 초대 @@ -44,6 +45,7 @@ 클립보드에서 붙여넣기 계정을 연결하려면 게시하기 지갑에 넣기 + 받기 기존 계정 복구하기 알림 전화번호 삭제 @@ -54,6 +56,7 @@ 보내기 인증 코드 보내기 공유 + URL로 공유 다운로드 링크 공유 이 동영상 공유 내 팁 카드 표시 @@ -327,6 +330,7 @@ 친구가 Code를 시작하게 만들기 Kin 받기 킨 더 확보하기 + 현금 주기 Kin 보내기 자금 부족 친구 초대하기 @@ -361,6 +365,7 @@ 지출 계정 바꾸기 서비스 약관 + 팁 카드 팁 킨 알 수 없음 업데이트 요구됨 diff --git a/app/src/main/res/values-ms/strings-localized.xml b/app/src/main/res/values-ms/strings-localized.xml index 43a9d30a6..462978cad 100644 --- a/app/src/main/res/values-ms/strings-localized.xml +++ b/app/src/main/res/values-ms/strings-localized.xml @@ -24,6 +24,7 @@ Mengaktifkan ID Wajah Mengaktifkan ID Sentuhan Keluar + Beri Beri Kin Ajak Ajakan @@ -44,6 +45,7 @@ Tampal Dari Papan Keratan Siar untuk Menghubungkan Akaun Masukkan dalam Dompet + Terima Pulihkan Akaun Sedia Ada Ingatkan Singkirkan Nombor Telefon @@ -54,6 +56,7 @@ Hantar Hantar Kod Pengesahan Kongsi + Kongsikan sebagai URL Kongsi Pautan Muat Turun Kongsi Video Ini Tunjuk Kad Tip Saya @@ -327,6 +330,7 @@ Dapatkan Rakan untuk Bermula menggunakan Code Dapatkan Kin Dapatkan Lebih Banyak Kin + Beri Wang Beri Kin Dana Tidak Mencukupi Ajak Kawan @@ -361,6 +365,7 @@ Perbelanjaan Tukar Akaun Syarat Perkhidmatan + Kad Petua Tip Kin Tidak diketahui Kemas Kini Diperlukan diff --git a/app/src/main/res/values-nl/strings-localized.xml b/app/src/main/res/values-nl/strings-localized.xml index e681ced06..d9422b44e 100644 --- a/app/src/main/res/values-nl/strings-localized.xml +++ b/app/src/main/res/values-nl/strings-localized.xml @@ -24,6 +24,7 @@ Gezichts-ID inschakelen Touch-ID inschakelen Afsluiten + Geven KIN schenken Uitnodigen Uitnodigingen @@ -44,6 +45,7 @@ Plakken van klembord Post om het account te verbinden In wallet plaatsen + Ontvangen Bestaand account herstellen Herinneren Telefoonnummer verwijderen @@ -54,6 +56,7 @@ Verzenden Verificatiecode sturen Delen + Delen als URL Downloadlink delen Deel deze video Toon mijn fooikaart @@ -327,6 +330,7 @@ Laat een vriend beginnen met Code Krijg Kin Verkrijg meer Kin + Contant geven KIN schenken Onvoldoende fondsen Een vriend uitnodigen @@ -361,6 +365,7 @@ Besteed Wisselen van account Servicevoorwaarden + Fooikaart Fooi-kin Onbekend Update vereist diff --git a/app/src/main/res/values-no/strings-localized.xml b/app/src/main/res/values-no/strings-localized.xml index f78dc43ff..b3ba5d8ac 100644 --- a/app/src/main/res/values-no/strings-localized.xml +++ b/app/src/main/res/values-no/strings-localized.xml @@ -24,6 +24,7 @@ Aktiver Face ID Aktiver Touch ID Avslutt + Gi Gi Kin Inviter Invitasjoner @@ -44,6 +45,7 @@ Lim inn fra utklippstavlen Skriv et innlegg for å koble til kontoen Legg i lommeboken + Motta Gjenopprett eksisterende konto Påminn Fjern telefonnummer @@ -54,6 +56,7 @@ Send Send bekreftelseskode Del + Del som nettadresse Del nedlastingslenke Del denne videoen Vis tipskortet mitt @@ -327,6 +330,7 @@ Få en venn i gang med Code Skaff deg Kin Få flere Kin + Gi kontanter Gi Kin Utilstrekkelige midler Inviter en venn @@ -361,6 +365,7 @@ Brukt Bytt konto Brukervilkår + Tipskort Tips Kin Ukjent Oppdatering kreves diff --git a/app/src/main/res/values-pl/strings-localized.xml b/app/src/main/res/values-pl/strings-localized.xml index abea190b3..c9f26eeb0 100644 --- a/app/src/main/res/values-pl/strings-localized.xml +++ b/app/src/main/res/values-pl/strings-localized.xml @@ -24,6 +24,7 @@ Włącz funkcję Face ID Włącz funkcję Touch ID Wyjdź + Podaruj Daj Kin Zaproś Zaproszenia @@ -44,6 +45,7 @@ Wklej ze schowka Wyślij post, aby podłączyć konto Umieść w portfelu + Otrzymaj Odzyskaj istniejące konto Przypomnij Usuń numer telefonu @@ -54,6 +56,7 @@ Wyślij Wyślij kod weryfikacyjny Udostępnij + Udostępnij URL Udostępnij odnośnik do pobrania Udostępnij ten film Pokaż moją kartę napiwków @@ -327,6 +330,7 @@ Poleć znajomemu aplikację Code Uzyskaj kin Zdobądź więcej Kin + Podaruj gotówkę Daj Kin Niewystarczające środki Zaproś znajomego @@ -361,6 +365,7 @@ Wydano Przełącz konta Warunki świadczenia usług + Karta napiwkowa Napiwek Kin Nieznane Wymagana aktualizacja diff --git a/app/src/main/res/values-pt/strings-localized.xml b/app/src/main/res/values-pt/strings-localized.xml index f836224c3..b36c448fd 100644 --- a/app/src/main/res/values-pt/strings-localized.xml +++ b/app/src/main/res/values-pt/strings-localized.xml @@ -24,6 +24,7 @@ Ativar a identificação facial Ativar a identificação por toque Sair + Dar Dar Kin Convidar Convites @@ -44,6 +45,7 @@ Colar a Partir da Área de Transferência Faça uma publicação para associar conta Por na Carteira + Receber Recuperar Conta Existente Relembrar Remover Número de Telefone @@ -54,6 +56,7 @@ Enviar Enviar Código de Verificação Partilhar + Partilhar como URL Partilhar Ligação de Download Partilhar este vídeo Exibir o meu Cartão de Recompensas @@ -327,6 +330,7 @@ Iniciar um Amigo na Code Obter Kin Obter mais Kin + Dar dinheiro Dar Kin Fundos insuficientes Convidar um Amigo @@ -361,6 +365,7 @@ Gastou Mudar de Conta Termos de Serviço + Cartão de Dica Dar recompensa em Kin Desconhecido Atualização Necessária diff --git a/app/src/main/res/values-ro/strings-localized.xml b/app/src/main/res/values-ro/strings-localized.xml index 7df37deef..31da11d35 100644 --- a/app/src/main/res/values-ro/strings-localized.xml +++ b/app/src/main/res/values-ro/strings-localized.xml @@ -24,6 +24,7 @@ Activează Face ID Activează Touch ID Ieșire + Trimite Dă Kin Invită Invitații @@ -44,6 +45,7 @@ Lipește din clipboard Postează pentru a conecta contul Pune în portofel + Primește Recuperează un cont existent Reamintește Elimină numărul de telefon @@ -54,6 +56,7 @@ Trimite Trimite verificarea pentru Code Distribuie + Distribuie ca adresă URL Partajați link-ul de descărcare Partajează acest videoclip Arată cardul meu de bacșiș @@ -327,6 +330,7 @@ Ajutați un prieten să înceapă cu Code Obține kin Obține mai mulţi Kin + Trimite cash Trimite Kin Fonduri insuficiente Invită un prieten @@ -361,6 +365,7 @@ Cheltuiți Folosește alt cont Termeni de utilizare + Card de bacșiș Trimite bacșiș monede Kin Necunoscut Este necesară actualizarea diff --git a/app/src/main/res/values-ru/strings-localized.xml b/app/src/main/res/values-ru/strings-localized.xml index c146e3494..e1fdf7474 100644 --- a/app/src/main/res/values-ru/strings-localized.xml +++ b/app/src/main/res/values-ru/strings-localized.xml @@ -24,6 +24,7 @@ Включить Face ID Включить Touch ID Выйти + Дать Дать Kin Пригласить Приглашения @@ -44,6 +45,7 @@ Вставить из буфера обмена Опубликуйте пост, чтобы подключить аккаунт Положить в кошелек + Получить Восстановить существующую учетную запись Напомнить Удалить номер телефона @@ -54,6 +56,7 @@ Отправить Отправить код проверки Поделиться + Поделиться в виде URL Поделитесь ссылкой для загрузки Поделитесь этим видео Показать мою карту чаевых @@ -327,6 +330,7 @@ Пригласите друга начать пользоваться Code Получить Kin Получить больше Kin + Дать деньги Дал(-а) Kin Недостаточно средств Пригласить друга @@ -361,6 +365,7 @@ Потрачено Переключить учетные записи Условия пользования + Карточка чаевых Дать чаевые в кин Неизвестный Требуется обновление diff --git a/app/src/main/res/values-sk/strings-localized.xml b/app/src/main/res/values-sk/strings-localized.xml index 2ac203891..bc89b18a4 100644 --- a/app/src/main/res/values-sk/strings-localized.xml +++ b/app/src/main/res/values-sk/strings-localized.xml @@ -24,6 +24,7 @@ Povoliť Face ID Povoliť Touch ID Odísť + Dať Dať Kin Pozvať Pozýva @@ -44,6 +45,7 @@ Prilepiť zo schránky Uverejniť pre prepojenie účtu Vložiť do peňaženky + Dostať Obnoviť existujúci účet Pripomenúť Odstrániť telefónne číslo @@ -54,6 +56,7 @@ Odoslať Poslať overovací kód Zdieľať + Zdieľať ako adresu URL Zdieľať odkaz na stiahnutie Zdieľajte toto video Zobraziť moju tipovaciu kartu @@ -327,6 +330,7 @@ Začnite používať kód priateľa Získajte Kin Získajte viac Kinov + Dať hotovosť Dať Kin Nedostatok prostriedkov Pozvať priateľa @@ -361,6 +365,7 @@ Použité Prepnúť účty Podmienky služby + Karta tringeltov Tipovať kin Neznámy Vyžaduje sa aktualizácia diff --git a/app/src/main/res/values-sr/strings-localized.xml b/app/src/main/res/values-sr/strings-localized.xml index 38918478d..7e134fbc1 100644 --- a/app/src/main/res/values-sr/strings-localized.xml +++ b/app/src/main/res/values-sr/strings-localized.xml @@ -24,6 +24,7 @@ Омогући Face ID Омогући Touch ID Izađi + Дај Daj Kin Pozovi Pozivi @@ -44,6 +45,7 @@ Nalepi iz ostave Поставите објаву да повежете налог Stavi u novčanik + Прими Oporavi postojeći nalog Podseti Ukloni broj telefona @@ -54,6 +56,7 @@ Пошаљи Pošalji verifikacioni kod Подели + Дели као URL адресу Подели везу за преузимање Дели овај видео-запис Покажи моју картицу за напојнице @@ -327,6 +330,7 @@ Нека пријатељ започне са Кодом Прибави Кин Набавите још кинова + Давање готовине Daj Kin Нема довољно средстава Pozovi prijatelja @@ -361,6 +365,7 @@ Потрошено Promeni naloge Uslovi korišcenja usluge + Картица са саветом Дај напојницу у виду Кина Непознато Potrebno je ažuriranje diff --git a/app/src/main/res/values-sv/strings-localized.xml b/app/src/main/res/values-sv/strings-localized.xml index 5f038cf71..714330066 100644 --- a/app/src/main/res/values-sv/strings-localized.xml +++ b/app/src/main/res/values-sv/strings-localized.xml @@ -24,6 +24,7 @@ Aktivera Face ID Aktivera Touch ID Avsluta + Ge Ge Kin Bjud in Bjuder in @@ -44,6 +45,7 @@ Klistra in från Urklipp Posta för att koppla kontot Lägg i plånboken + Ta emot Återskapa befintligt konto Påminn Ta bort telefonnummer @@ -54,6 +56,7 @@ Skicka Skicka verifieringskod Dela + Dela som webbadress Dela nedladdningslänk Dela den här videon Visa mitt drickskort @@ -327,6 +330,7 @@ Få en vän att börja med Code Få Kin Skaffa mer Kin + Ge pengar Ge Kin Otillräckliga medel Bjud in en vän @@ -361,6 +365,7 @@ Spenderat Byt konto Villkor för tjänsten + Drickskort Ge Kin i dricks Okänt Uppdatering krävs diff --git a/app/src/main/res/values-th/strings-localized.xml b/app/src/main/res/values-th/strings-localized.xml index ff7508b93..e3647e40d 100644 --- a/app/src/main/res/values-th/strings-localized.xml +++ b/app/src/main/res/values-th/strings-localized.xml @@ -24,6 +24,7 @@ เปิดใช้งาน Face ID เปิดใช้งาน Touch ID ออก + ให้ ให้ Kin เชิญ เชิญ @@ -44,6 +45,7 @@ วางจากคลิปบอร์ด โพสต์เพื่อเชื่อมโยงบัญชี\n ใส่ในกระเป๋าเงิน + รับ กู้คืนบัญชีที่มีอยู่ เตือน ลบหมายเลขโทรศัพท์ @@ -54,6 +56,7 @@ ส่ง ส่งรหัสยืนยัน แชร์ + แชร์เป็น URL แชร์ลิงก์ดาวน์โหลด แชร์วิดีโอนี้ แสดงบัตรทิปของฉัน @@ -327,6 +330,7 @@ ชวนเพื่อนมาเริ่มต้นใช้งาน Code รับ Kin รับ Kin เพิ่มเติม + ให้เงินสด ให้ Kin เงินทุนไม่เพียงพอ ชวนเพื่อน @@ -361,6 +365,7 @@ ใช้จ่าย สลับบัญชี เงื่อนไขการให้บริการ + บัตรทิป ให้ทิปเป็น Kin ไม่ทราบ จำเป็นต้องอัปเดต diff --git a/app/src/main/res/values-tr/strings-localized.xml b/app/src/main/res/values-tr/strings-localized.xml index 6631f9123..5e0677be5 100644 --- a/app/src/main/res/values-tr/strings-localized.xml +++ b/app/src/main/res/values-tr/strings-localized.xml @@ -24,6 +24,7 @@ Face ID\'yi etkinleştir Touch ID\'yi etkinleştir Çıkış + Ver Kin Ver Davet Et Davetler @@ -44,6 +45,7 @@ Panodan Yapıştır Hesabı Bağlamak için Gönder Cüzdana Yerleştir + Al Mevcut Hesabı Kurtar Hatırlat Telefon Numarasını Kaldır @@ -54,6 +56,7 @@ Gönder Doğrulama Kodu Gönder Paylaş + URL olarak paylaş İndirme Bağlantısı Paylaş Bu Videoyu Paylaş Bahşiş Kartımı Göster @@ -327,6 +330,7 @@ Bir Arkadaşınızı Code\'a başlatın Kin Al Daha Fazla Kin Al + Nakit Ver Kin Ver Yetersiz Bakiye Arkadaşını Davet Et @@ -361,6 +365,7 @@ Harcanan Hesap Değiştir Hizmet Şartları + Bahşiş Kartı Bahşiş Olarak Kin Ver Bilinmeyen Güncelleme Gerekli diff --git a/app/src/main/res/values-uk/strings-localized.xml b/app/src/main/res/values-uk/strings-localized.xml index bb3e678a1..a31dd498c 100644 --- a/app/src/main/res/values-uk/strings-localized.xml +++ b/app/src/main/res/values-uk/strings-localized.xml @@ -24,6 +24,7 @@ Увімкнути Face ID Увімкнути Touch ID Вийти + Дати Дати Кін Запросити Запрошення @@ -44,6 +45,7 @@ Вставити з буфера обміну Зробіть публікацію, щоб прив’язати акаунт Покласти в гаманець + Отримати Відновити існуючий акаунт Нагадати Видалити номер телефону @@ -54,6 +56,7 @@ Надіслати Надіслати код підтвердження Поділитися + Поділитися як URL-адресою Поділитись посиланням на завантаження Поділитися цим відео Показати мою картку для чайових @@ -327,6 +330,7 @@ Запросіть друга в Code Отримати Kin Отримати більше Kin + Дати готівку Дати Kin Недостатньо коштів Запросити друга @@ -361,6 +365,7 @@ Витрачено Змінити акаунт Умови обслуговування + Картка з підказками Дати чайові у Kin Невідомо Потрібне оновлення diff --git a/app/src/main/res/values-vi/strings-localized.xml b/app/src/main/res/values-vi/strings-localized.xml index cbb45dba7..016ee9983 100644 --- a/app/src/main/res/values-vi/strings-localized.xml +++ b/app/src/main/res/values-vi/strings-localized.xml @@ -24,6 +24,7 @@ Bật Face ID Bật Touch ID Thoát + Cho Tặng Kin Mời Lời mời @@ -44,6 +45,7 @@ Dán Từ Khay nhớ tạm Hãy đăng bài để kết nối tài khoản Đặt trong Ví + Nhận Khôi phục Tài khoản Hiện có Nhắc nhở Xóa Số Điện thoại @@ -54,6 +56,7 @@ Gửi Gửi Mã Xác minh Chia sẻ + Chia sẻ dưới dạng URL Chia sẻ đường liên kết tải xuống Chia sẻ Video Này Hiển thị Thẻ tiền boa của tôi @@ -327,6 +330,7 @@ Mời một người bạn bắt đầu dùng Code Nhận Kin Nhận Thêm Kin + Cho Tiền mặt Tặng Kin Không đủ Tiền Mời Bạn bè @@ -361,6 +365,7 @@ Đã chi tiêu Chuyển đổi Tài khoản Điều khoản Dịch vụ + Thẻ Boa Boa Kin Không xác định Cần Cập nhật diff --git a/app/src/main/res/values-zh-rCN/strings-localized.xml b/app/src/main/res/values-zh-rCN/strings-localized.xml index e84a11231..60dfb4f30 100644 --- a/app/src/main/res/values-zh-rCN/strings-localized.xml +++ b/app/src/main/res/values-zh-rCN/strings-localized.xml @@ -24,6 +24,7 @@ 启用 Face ID 启用 Touch ID 退出 + 给出 给与 Kin 邀请 邀请 @@ -44,6 +45,7 @@ 从剪贴板粘贴 发布至连接的账户 放入钱包 + 接收 恢复现有账户 提醒 移除手机号码 @@ -54,6 +56,7 @@ 发送 发送验证码 分享 + 以 URL 形式分享 分享下载链接 分享此视频 显示我的小费卡 @@ -327,6 +330,7 @@ 让好友开始使用 Code 获取 Kin 币 获取更多 Kin 币 + 给出现金 给与 Kin 资金不足 邀请好友 @@ -361,6 +365,7 @@ 花费 切换账户 服务条款 + 小费卡 Kin 小费 未知 需要更新 diff --git a/app/src/main/res/values-zh-rTW/strings-localized.xml b/app/src/main/res/values-zh-rTW/strings-localized.xml index 4af5c14df..f5c77f818 100644 --- a/app/src/main/res/values-zh-rTW/strings-localized.xml +++ b/app/src/main/res/values-zh-rTW/strings-localized.xml @@ -24,6 +24,7 @@ 啟用 Face ID 啟用 Face ID 退出 + 給予 給予 Kin 邀請 邀請 @@ -44,6 +45,7 @@ 從剪貼簿貼上 發文以連結帳戶 放入錢包 + 接受 恢復現有帳戶 提醒 移除電話號碼 @@ -54,6 +56,7 @@ 發送 傳送驗證碼 分享 + 分享網址連結 分享下載連結 分享該影片 顯示我的 Tip Card @@ -327,6 +330,7 @@ 邀請朋友開始使用 Code 錢包 取得 Kin 獲得更多 Kin + 給予現金 給予 Kin 餘額不足 邀請朋友 @@ -361,6 +365,7 @@ 花費 切換帳戶 服務條款 + 提示卡 以 Kin 給小費 不明 需要更新 diff --git a/app/src/main/res/values/strings-localized.xml b/app/src/main/res/values/strings-localized.xml index ecdddf550..f7041861d 100644 --- a/app/src/main/res/values/strings-localized.xml +++ b/app/src/main/res/values/strings-localized.xml @@ -24,6 +24,7 @@ Enable Face ID Enable Touch ID Exit + Give Give Kin Invite Invites @@ -44,6 +45,7 @@ Paste From Clipboard Post to Connect Account Put in Wallet + Receive Recover Existing Account Remind Remove Phone Number @@ -328,6 +330,7 @@ Get a Friend Started on Code Get Kin Get More Kin + Give Cash Give Kin Insufficient Funds Invite a Friend @@ -362,6 +365,7 @@ Spent Switch Accounts Terms of Service + Tip Card Tip Kin Unknown Update Required diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9c4034d66..a52bd9cbd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,8 +49,4 @@ Connect Account Connecting your X account allows you to reveal your identity to the people you tip. To connect your account post to X. Use Kado Sandbox - Tip Card - Give Cash - Give - Receive From e57c45ac946c7d0a473eb90e22d9e8d267292f90 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 20 Aug 2024 13:21:31 -0400 Subject: [PATCH 30/33] build: update protobuf and grpc dependencies Signed-off-by: Brandon McAnsh --- buildSrc/src/main/java/Dependencies.kt | 91 ++++++++++++++++++-------- model/build.gradle | 22 +++---- 2 files changed, 70 insertions(+), 43 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index ee33e6682..6d163128a 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -39,6 +39,7 @@ object Versions { const val sqlcipher = "4.5.1@aar" const val compose = "2024.05.00" + // compose compiler is tied to [Versions.kotlin] // See compatibility mapping here: // https://developer.android.com/jetpack/androidx/releases/compose-compiler @@ -66,8 +67,10 @@ object Versions { const val play_service_auth = "20.7.0" const val play_service_auth_phone = "18.0.2" + const val grpc: String = "1.62.2" const val grpc_okhttp: String = "1.33.1" - const val grpc_kotlin: String = "1.0.0" + const val grpc_kotlin: String = "1.4.1" + const val protobuf: String = "3.25.3" const val mp_android_chart: String = "v3.1.0" const val lib_phone_number_port: String = "8.12.43" @@ -84,24 +87,30 @@ object Versions { const val markwon = "4.6.2" const val timber = "5.0.1" const val voyager = "1.0.0" - const val protobuf_plugin = "0.8.14" + const val protobuf_plugin = "0.9.4" const val sodium_bindings = "0.9.0" } object Classpath { - const val android_gradle_build_tools = "com.android.tools.build:gradle:${Versions.android_gradle_build_tools}" + const val android_gradle_build_tools = + "com.android.tools.build:gradle:${Versions.android_gradle_build_tools}" const val kotlin_hilt_plugin = "com.google.dagger:hilt-android-gradle-plugin:${Versions.hilt}" - const val androidx_navigation_safeargs = "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.androidx_navigation}" + const val androidx_navigation_safeargs = + "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.androidx_navigation}" const val kotlin_gradle_plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" - const val kotlin_serialization_plugin = "org.jetbrains.kotlin:kotlin-serialization:${Versions.kotlin}" + const val kotlin_serialization_plugin = + "org.jetbrains.kotlin:kotlin-serialization:${Versions.kotlin}" const val google_services = "com.google.gms:google-services:${Versions.google_services}" - const val protobuf_plugin = "com.google.protobuf:protobuf-gradle-plugin:${Versions.protobuf_plugin}" + const val protobuf_plugin = + "com.google.protobuf:protobuf-gradle-plugin:${Versions.protobuf_plugin}" - const val crashlytics_gradle = "com.google.firebase:firebase-crashlytics-gradle:${Versions.crashlytics_gradle}" + const val crashlytics_gradle = + "com.google.firebase:firebase-crashlytics-gradle:${Versions.crashlytics_gradle}" const val bugsnag = "com.bugsnag:bugsnag-android-gradle-plugin:8.+" const val firebase_perf = "com.google.firebase:perf-plugin:1.4.2" - const val secrets_gradle_plugin = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1" + const val secrets_gradle_plugin = + "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1" const val versioning_gradle_plugin = "de.nanogiants:android-versioning:2.4.0" } @@ -118,7 +127,8 @@ object Plugins { const val firebase_crashlytics = "com.google.firebase.crashlytics" const val firebase_perf = "com.google.firebase.firebase-perf" const val bugsnag = "com.bugsnag.android.gradle" - const val secrets_gradle_plugin = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" + const val secrets_gradle_plugin = + "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" const val versioning_gradle_plugin = "de.nanogiants.android-versioning" } @@ -128,9 +138,11 @@ object Libs { const val androidx_annotation = "androidx.annotation:annotation:${Versions.androidx_annotation}" const val androidx_biometrics = "androidx.biometric:biometric:${Versions.androidx_biometrics}" const val androidx_camerax_core = "androidx.camera:camera-core:${Versions.androidx_camerax}" - const val androidx_camerax_camera2 = "androidx.camera:camera-camera2:${Versions.androidx_camerax}" - const val androidx_camerax_lifecycle = "androidx.camera:camera-lifecycle:${Versions.androidx_camerax}" - const val androidx_camerax_view = "androidx.camera:camera-view:${Versions.androidx_camerax}" + const val androidx_camerax_camera2 = + "androidx.camera:camera-camera2:${Versions.androidx_camerax}" + const val androidx_camerax_lifecycle = + "androidx.camera:camera-lifecycle:${Versions.androidx_camerax}" + const val androidx_camerax_view = "androidx.camera:camera-view:${Versions.androidx_camerax}" const val androidx_core = "androidx.core:core-ktx:${Versions.androidx_core}" const val androidx_constraint_layout = "androidx.constraintlayout:constraintlayout:${Versions.androidx_constraint_layout}" @@ -141,7 +153,8 @@ object Libs { const val androidx_navigation_ui = "androidx.navigation:navigation-ui-ktx:${Versions.androidx_navigation}" const val androidx_browser = "androidx.browser:browser:${Versions.androidx_browser}" - const val androidx_paging_runtime = "androidx.paging:paging-runtime-ktx:${Versions.androidx_paging}" + const val androidx_paging_runtime = + "androidx.paging:paging-runtime-ktx:${Versions.androidx_paging}" const val androidx_room_runtime = "androidx.room:room-runtime:${Versions.androidx_room}" const val androidx_room_rxjava3 = "androidx.room:room-rxjava3:${Versions.androidx_room}" @@ -165,10 +178,14 @@ object Libs { "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:${Versions.kotlinx_coroutines}" const val kotlinx_coroutines_test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinx_coroutines}" - const val kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.6" - const val kotlinx_datetime = "org.jetbrains.kotlinx:kotlinx-datetime:${Versions.kotlinx_datetime}" - const val kotlinx_serialization_core = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.kotlinx_serialization}" - const val kotlinx_serialization_json = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinx_serialization}" + const val kotlinx_collections_immutable = + "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.6" + const val kotlinx_datetime = + "org.jetbrains.kotlinx:kotlinx-datetime:${Versions.kotlinx_datetime}" + const val kotlinx_serialization_core = + "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.kotlinx_serialization}" + const val kotlinx_serialization_json = + "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinx_serialization}" const val okhttp = "com.squareup.okhttp3:okhttp:${Versions.okhttp}" const val okhttp_logging_interceptor = @@ -177,7 +194,8 @@ object Libs { const val androidx_datastore = "androidx.datastore:datastore-preferences:1.1.1" const val androidx_constraint_layout_compose = "androidx.constraintlayout:constraintlayout-compose:1.0.1" - const val androidx_lifecycle_viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidx_lifecycle}" + const val androidx_lifecycle_viewmodel = + "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidx_lifecycle}" const val compose_bom = "androidx.compose:compose-bom:${Versions.compose}" const val compose_accompanist = @@ -190,7 +208,8 @@ object Libs { "androidx.compose.ui:ui-tooling-preview" const val compose_foundation = "androidx.compose.foundation:foundation" const val compose_material = "androidx.compose.material:material" - const val compose_materialIconsExtended = "androidx.compose.material:material-icons-extended-android" + const val compose_materialIconsExtended = + "androidx.compose.material:material-icons-extended-android" const val compose_activities = "androidx.activity:activity-compose:${Versions.compose_activities}" const val compose_view_models = @@ -199,10 +218,14 @@ object Libs { const val compose_navigation = "androidx.navigation:navigation-compose:${Versions.compose_navigation}" const val compose_paging = "androidx.paging:paging-compose:${Versions.compose_paging}" - const val compose_voyager_navigation = "cafe.adriel.voyager:voyager-navigator:${Versions.voyager}" - const val compose_voyager_navigation_hilt = "cafe.adriel.voyager:voyager-hilt:${Versions.voyager}" - const val compose_voyager_navigation_bottomsheet = "cafe.adriel.voyager:voyager-bottom-sheet-navigator:${Versions.voyager}" - const val compose_voyager_navigation_transitions = "cafe.adriel.voyager:voyager-transitions:${Versions.voyager}" + const val compose_voyager_navigation = + "cafe.adriel.voyager:voyager-navigator:${Versions.voyager}" + const val compose_voyager_navigation_hilt = + "cafe.adriel.voyager:voyager-hilt:${Versions.voyager}" + const val compose_voyager_navigation_bottomsheet = + "cafe.adriel.voyager:voyager-bottom-sheet-navigator:${Versions.voyager}" + const val compose_voyager_navigation_transitions = + "cafe.adriel.voyager:voyager-transitions:${Versions.voyager}" const val compose_webview = "io.github.kevinnzou:compose-webview:${Versions.compose_webview}" const val rxjava = "io.reactivex.rxjava3:rxjava:${Versions.rxjava}" @@ -220,17 +243,26 @@ object Libs { const val firebase_perf = "com.google.firebase:firebase-perf" const val play_integrity = "com.google.android.play:integrity:1.3.0" - const val play_service_auth = "com.google.android.gms:play-services-auth:${Versions.play_service_auth}" - const val play_service_auth_phone = "com.google.android.gms:play-services-auth-api-phone:${Versions.play_service_auth_phone}" + const val play_service_auth = + "com.google.android.gms:play-services-auth:${Versions.play_service_auth}" + const val play_service_auth_phone = + "com.google.android.gms:play-services-auth-api-phone:${Versions.play_service_auth_phone}" const val grpc_okhttp = "io.grpc:grpc-okhttp:${Versions.grpc_okhttp}" - const val grpc_kotlin = "io.grpc:grpc-kotlin-stub-lite:${Versions.grpc_kotlin}" + const val grpc_kotlin = "io.grpc:grpc-kotlin-stub:${Versions.grpc_kotlin}" + const val grpc_protobuf = "io.grpc:grpc-protobuf:${Versions.grpc}" + const val grpc_protobuf_lite = "io.grpc:grpc-protobuf-lite:${Versions.grpc}" + const val grpc_stub = "io.grpc:grpc-stub:${Versions.grpc}" + const val protobuf_java = "com.google.protobuf:protobuf-java:${Versions.protobuf}" + const val protobuf_kotlin_lite = "com.google.protobuf:protobuf-kotlin-lite:${Versions.protobuf}" const val inject = "javax.inject:javax.inject:1" const val mp_android_chart = "com.github.PhilJay:MPAndroidChart:${Versions.mp_android_chart}" - const val lib_phone_number_port = "io.michaelrocks:libphonenumber-android:${Versions.lib_phone_number_port}" - const val lib_phone_number_google = "com.googlecode.libphonenumber:libphonenumber:${Versions.lib_phone_number_google}" + const val lib_phone_number_port = + "io.michaelrocks:libphonenumber-android:${Versions.lib_phone_number_port}" + const val lib_phone_number_google = + "com.googlecode.libphonenumber:libphonenumber:${Versions.lib_phone_number_google}" const val hilt_nav_compose = "androidx.hilt:hilt-navigation-compose:1.1.0-alpha01" const val zxing = "com.google.zxing:core:${Versions.zxing}" @@ -257,7 +289,8 @@ object Libs { const val cloudy = "com.github.skydoves:cloudy:0.1.2" - const val sodium_bindings = "com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings-android:${Versions.sodium_bindings}" + const val sodium_bindings = + "com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings-android:${Versions.sodium_bindings}" const val fingerprint_pro = "com.fingerprint.android:pro:2.4.0" diff --git a/model/build.gradle b/model/build.gradle index a8a13e934..efc02e26c 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -11,20 +11,14 @@ def archSuffix = Os.isFamily(Os.FAMILY_MAC) ? ':osx-x86_64' : '' version = '0.0.1' group = 'com.codeinc.gen' -def grpcVersion = "1.49.0" -def protocVersion = "3.12.0" dependencies { - implementation "io.grpc:grpc-protobuf-lite:$grpcVersion" // "io.grpc:grpc-protobuf-lite:${grpcVersion}" - implementation "io.grpc:grpc-stub:$grpcVersion" -// protobuf("io.envoyproxy.protoc-gen-validate:pgv-java-stub:${validateVersion}") { -// exclude group: "com.google.protobuf", module: "protobuf-java" -// exclude group: "com.google.protobuf", module: "protobuf-java-util" -// } + implementation(Libs.grpc_protobuf_lite) + implementation(Libs.grpc_stub) // Kotlin Generation - implementation "io.grpc:grpc-kotlin-stub-lite:1.0.0" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" - implementation 'javax.annotation:javax.annotation-api:1.3.2' + implementation(Libs.grpc_kotlin) + implementation(Libs.protobuf_kotlin_lite) + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0" } @@ -46,11 +40,11 @@ compileJava { protobuf { protoc { - artifact = "com.google.protobuf:protoc:${protocVersion}$archSuffix" + artifact = "com.google.protobuf:protoc:${Versions.protobuf}$archSuffix" } plugins { grpc { - artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + artifact = "io.grpc:protoc-gen-grpc-java:${Versions.grpc}" } // javapgv { // artifact = "io.envoyproxy.protoc-gen-validate:protoc-gen-validate:${validateVersion}" @@ -101,4 +95,4 @@ artifacts { java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 -} \ No newline at end of file +} From a7effce721b5bac3e8fc2366b821330220cf4cbe Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Tue, 20 Aug 2024 15:31:38 -0400 Subject: [PATCH 31/33] chore: split protos into own module; reorganize Signed-off-by: Brandon McAnsh --- api/build.gradle.kts | 5 + app/build.gradle.kts | 1 + common/resources/build.gradle.kts | 3 - {ed25519 => crypto/ed25519}/.gitignore | 0 {ed25519 => crypto/ed25519}/CMakeLists.txt | 0 {ed25519 => crypto/ed25519}/build.gradle.kts | 0 .../ed25519}/libs/base64/CMakeLists.txt | 0 .../ed25519}/libs/base64/base64.cpp | 0 .../ed25519}/libs/base64/base64.hpp | 0 .../ed25519}/libs/codeScanner/CMakeLists.txt | 0 .../ed25519}/libs/codeScanner/src/Array.h | 0 .../ed25519}/libs/codeScanner/src/Counted.h | 0 .../libs/codeScanner/src/Exception.cpp | 0 .../ed25519}/libs/codeScanner/src/Exception.h | 0 .../libs/codeScanner/src/GenericGF.cpp | 0 .../ed25519}/libs/codeScanner/src/GenericGF.h | 0 .../libs/codeScanner/src/GenericGFPoly.cpp | 0 .../libs/codeScanner/src/GenericGFPoly.h | 0 .../src/IllegalArgumentException.cpp | 0 .../src/IllegalArgumentException.h | 0 .../codeScanner/src/IllegalStateException.h | 0 .../libs/codeScanner/src/ReaderException.h | 0 .../codeScanner/src/ReedSolomonDecoder.cpp | 0 .../libs/codeScanner/src/ReedSolomonDecoder.h | 0 .../codeScanner/src/ReedSolomonEncoder.cpp | 0 .../libs/codeScanner/src/ReedSolomonEncoder.h | 0 .../codeScanner/src/ReedSolomonException.cpp | 0 .../codeScanner/src/ReedSolomonException.h | 0 .../ed25519}/libs/codeScanner/src/ZXing.h | 0 .../libs/codeScanner/src/kikcode_constants.h | 0 .../libs/codeScanner/src/kikcode_encoding.cpp | 0 .../libs/codeScanner/src/kikcode_encoding.h | 0 .../codeScanner/src/kikcode_encoding_jni.cpp | 0 .../codeScanner/src/kikcode_encoding_jni.h | 0 .../libs/codeScanner/src/kikcodes.cpp | 0 .../ed25519}/libs/codeScanner/src/kikcodes.h | 0 .../ed25519}/libs/ed25519/CMakeLists.txt | 0 .../ed25519}/libs/ed25519/license.txt | 0 .../ed25519}/libs/ed25519/readme.md | 0 .../ed25519}/libs/ed25519/src/add_scalar.c | 0 .../ed25519}/libs/ed25519/src/ed25519.h | 0 .../ed25519}/libs/ed25519/src/fe.c | 0 .../ed25519}/libs/ed25519/src/fe.h | 0 .../ed25519}/libs/ed25519/src/fixedint.h | 0 .../ed25519}/libs/ed25519/src/ge.c | 0 .../ed25519}/libs/ed25519/src/ge.h | 0 .../ed25519}/libs/ed25519/src/key_exchange.c | 0 .../ed25519}/libs/ed25519/src/keypair.c | 0 .../ed25519}/libs/ed25519/src/precomp_data.h | 0 .../ed25519}/libs/ed25519/src/sc.c | 0 .../ed25519}/libs/ed25519/src/sc.h | 0 .../ed25519}/libs/ed25519/src/seed.c | 0 .../ed25519}/libs/ed25519/src/sha3.c | 0 .../ed25519}/libs/ed25519/src/sha3.h | 0 .../ed25519}/libs/ed25519/src/sha512.c | 0 .../ed25519}/libs/ed25519/src/sha512.h | 0 .../ed25519}/libs/ed25519/src/sign.c | 0 .../ed25519}/libs/ed25519/src/verify.c | 0 .../ed25519}/libs/ed25519/test.c | 0 .../ed25519}/proguard-rules.pro | 0 .../ed25519}/src/main/AndroidManifest.xml | 0 .../ed25519}/src/main/cpp/kik-codes.cpp | 0 .../ed25519}/src/main/cpp/native-lib.cpp | 0 .../com/getcode/codeScanner/CodeScanner.java | 0 .../java/com/getcode/ed25519/Ed25519.java | 0 {model => crypto/kin}/.gitignore | 0 crypto/kin/build.gradle.kts | 36 +++++++ model/build.gradle | 98 ------------------- model/remove_proto_validation.sh | 21 ---- service/models/.gitignore | 2 + service/models/build.gradle.kts | 77 +++++++++++++++ service/protos/.gitignore | 2 + service/protos/build.gradle.kts | 11 +++ .../proto/account/v1/account_service.proto | 0 .../main}/proto/badge/v1/badge_service.proto | 0 .../main}/proto/chat/v1/chat_service.proto | 0 .../main}/proto/chat/v2/chat_service.proto | 0 .../src/main}/proto/common/v1/model.proto | 0 .../contact/v1/contact_list_service.proto | 0 .../proto/currency/v1/currency_service.proto | 0 .../proto/device/v1/device_service.proto | 0 .../proto/invite/v2/invite_service.proto | 0 .../messaging/v1/messaging_service.proto | 0 .../v1/micro_payment_service.proto | 0 .../phone/v1/phone_verification_service.proto | 0 .../main}/proto/push/v1/push_service.proto | 0 .../transaction/v2/transaction_service.proto | 0 .../proto/user/v1/identity_service.proto | 0 settings.gradle | 6 +- 89 files changed, 138 insertions(+), 124 deletions(-) rename {ed25519 => crypto/ed25519}/.gitignore (100%) rename {ed25519 => crypto/ed25519}/CMakeLists.txt (100%) rename {ed25519 => crypto/ed25519}/build.gradle.kts (100%) rename {ed25519 => crypto/ed25519}/libs/base64/CMakeLists.txt (100%) rename {ed25519 => crypto/ed25519}/libs/base64/base64.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/base64/base64.hpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/CMakeLists.txt (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/Array.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/Counted.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/Exception.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/Exception.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/GenericGF.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/GenericGF.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/GenericGFPoly.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/GenericGFPoly.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/IllegalArgumentException.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/IllegalArgumentException.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/IllegalStateException.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/ReaderException.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/ReedSolomonDecoder.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/ReedSolomonDecoder.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/ReedSolomonEncoder.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/ReedSolomonEncoder.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/ReedSolomonException.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/ReedSolomonException.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/ZXing.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/kikcode_constants.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/kikcode_encoding.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/kikcode_encoding.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/kikcode_encoding_jni.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/kikcode_encoding_jni.h (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/kikcodes.cpp (100%) rename {ed25519 => crypto/ed25519}/libs/codeScanner/src/kikcodes.h (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/CMakeLists.txt (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/license.txt (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/readme.md (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/add_scalar.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/ed25519.h (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/fe.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/fe.h (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/fixedint.h (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/ge.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/ge.h (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/key_exchange.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/keypair.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/precomp_data.h (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/sc.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/sc.h (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/seed.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/sha3.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/sha3.h (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/sha512.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/sha512.h (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/sign.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/src/verify.c (100%) rename {ed25519 => crypto/ed25519}/libs/ed25519/test.c (100%) rename {ed25519 => crypto/ed25519}/proguard-rules.pro (100%) rename {ed25519 => crypto/ed25519}/src/main/AndroidManifest.xml (100%) rename {ed25519 => crypto/ed25519}/src/main/cpp/kik-codes.cpp (100%) rename {ed25519 => crypto/ed25519}/src/main/cpp/native-lib.cpp (100%) rename {ed25519 => crypto/ed25519}/src/main/java/com/getcode/codeScanner/CodeScanner.java (100%) rename {ed25519 => crypto/ed25519}/src/main/java/com/getcode/ed25519/Ed25519.java (100%) rename {model => crypto/kin}/.gitignore (100%) create mode 100644 crypto/kin/build.gradle.kts delete mode 100644 model/build.gradle delete mode 100755 model/remove_proto_validation.sh create mode 100644 service/models/.gitignore create mode 100644 service/models/build.gradle.kts create mode 100644 service/protos/.gitignore create mode 100644 service/protos/build.gradle.kts rename {model => service/protos/src/main}/proto/account/v1/account_service.proto (100%) rename {model => service/protos/src/main}/proto/badge/v1/badge_service.proto (100%) rename {model => service/protos/src/main}/proto/chat/v1/chat_service.proto (100%) rename {model => service/protos/src/main}/proto/chat/v2/chat_service.proto (100%) rename {model => service/protos/src/main}/proto/common/v1/model.proto (100%) rename {model => service/protos/src/main}/proto/contact/v1/contact_list_service.proto (100%) rename {model => service/protos/src/main}/proto/currency/v1/currency_service.proto (100%) rename {model => service/protos/src/main}/proto/device/v1/device_service.proto (100%) rename {model => service/protos/src/main}/proto/invite/v2/invite_service.proto (100%) rename {model => service/protos/src/main}/proto/messaging/v1/messaging_service.proto (100%) rename {model => service/protos/src/main}/proto/micropayment/v1/micro_payment_service.proto (100%) rename {model => service/protos/src/main}/proto/phone/v1/phone_verification_service.proto (100%) rename {model => service/protos/src/main}/proto/push/v1/push_service.proto (100%) rename {model => service/protos/src/main}/proto/transaction/v2/transaction_service.proto (100%) rename {model => service/protos/src/main}/proto/user/v1/identity_service.proto (100%) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 8d33b5c93..3fe8eb31c 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,3 +1,5 @@ +import com.google.protobuf.gradle.protobuf + plugins { id(Plugins.android_library) id(Plugins.kotlin_android) @@ -70,6 +72,9 @@ android { dependencies { implementation(project(":common:resources")) + api(project(":service:models")) + implementation(project(":crypto:ed25519")) + implementation(project(":crypto:kin")) implementation(Libs.rxjava) implementation(Libs.kotlinx_coroutines_core) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 119d991b3..c99c148b3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -115,6 +115,7 @@ dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) implementation(project(":api")) + implementation(project(":crypto:ed25519")) implementation(project(":common:resources")) implementation(project(":common:theme")) implementation(project(":vendor:tipkit:tipkit-m2")) diff --git a/common/resources/build.gradle.kts b/common/resources/build.gradle.kts index 200009ec8..8374f9d0d 100644 --- a/common/resources/build.gradle.kts +++ b/common/resources/build.gradle.kts @@ -32,9 +32,6 @@ android { } dependencies { - api(project(":model")) - api(project(":ed25519")) - api(Libs.androidx_annotation) api(Libs.kotlin_stdlib) api(Libs.kotlinx_coroutines_core) diff --git a/ed25519/.gitignore b/crypto/ed25519/.gitignore similarity index 100% rename from ed25519/.gitignore rename to crypto/ed25519/.gitignore diff --git a/ed25519/CMakeLists.txt b/crypto/ed25519/CMakeLists.txt similarity index 100% rename from ed25519/CMakeLists.txt rename to crypto/ed25519/CMakeLists.txt diff --git a/ed25519/build.gradle.kts b/crypto/ed25519/build.gradle.kts similarity index 100% rename from ed25519/build.gradle.kts rename to crypto/ed25519/build.gradle.kts diff --git a/ed25519/libs/base64/CMakeLists.txt b/crypto/ed25519/libs/base64/CMakeLists.txt similarity index 100% rename from ed25519/libs/base64/CMakeLists.txt rename to crypto/ed25519/libs/base64/CMakeLists.txt diff --git a/ed25519/libs/base64/base64.cpp b/crypto/ed25519/libs/base64/base64.cpp similarity index 100% rename from ed25519/libs/base64/base64.cpp rename to crypto/ed25519/libs/base64/base64.cpp diff --git a/ed25519/libs/base64/base64.hpp b/crypto/ed25519/libs/base64/base64.hpp similarity index 100% rename from ed25519/libs/base64/base64.hpp rename to crypto/ed25519/libs/base64/base64.hpp diff --git a/ed25519/libs/codeScanner/CMakeLists.txt b/crypto/ed25519/libs/codeScanner/CMakeLists.txt similarity index 100% rename from ed25519/libs/codeScanner/CMakeLists.txt rename to crypto/ed25519/libs/codeScanner/CMakeLists.txt diff --git a/ed25519/libs/codeScanner/src/Array.h b/crypto/ed25519/libs/codeScanner/src/Array.h similarity index 100% rename from ed25519/libs/codeScanner/src/Array.h rename to crypto/ed25519/libs/codeScanner/src/Array.h diff --git a/ed25519/libs/codeScanner/src/Counted.h b/crypto/ed25519/libs/codeScanner/src/Counted.h similarity index 100% rename from ed25519/libs/codeScanner/src/Counted.h rename to crypto/ed25519/libs/codeScanner/src/Counted.h diff --git a/ed25519/libs/codeScanner/src/Exception.cpp b/crypto/ed25519/libs/codeScanner/src/Exception.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/Exception.cpp rename to crypto/ed25519/libs/codeScanner/src/Exception.cpp diff --git a/ed25519/libs/codeScanner/src/Exception.h b/crypto/ed25519/libs/codeScanner/src/Exception.h similarity index 100% rename from ed25519/libs/codeScanner/src/Exception.h rename to crypto/ed25519/libs/codeScanner/src/Exception.h diff --git a/ed25519/libs/codeScanner/src/GenericGF.cpp b/crypto/ed25519/libs/codeScanner/src/GenericGF.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/GenericGF.cpp rename to crypto/ed25519/libs/codeScanner/src/GenericGF.cpp diff --git a/ed25519/libs/codeScanner/src/GenericGF.h b/crypto/ed25519/libs/codeScanner/src/GenericGF.h similarity index 100% rename from ed25519/libs/codeScanner/src/GenericGF.h rename to crypto/ed25519/libs/codeScanner/src/GenericGF.h diff --git a/ed25519/libs/codeScanner/src/GenericGFPoly.cpp b/crypto/ed25519/libs/codeScanner/src/GenericGFPoly.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/GenericGFPoly.cpp rename to crypto/ed25519/libs/codeScanner/src/GenericGFPoly.cpp diff --git a/ed25519/libs/codeScanner/src/GenericGFPoly.h b/crypto/ed25519/libs/codeScanner/src/GenericGFPoly.h similarity index 100% rename from ed25519/libs/codeScanner/src/GenericGFPoly.h rename to crypto/ed25519/libs/codeScanner/src/GenericGFPoly.h diff --git a/ed25519/libs/codeScanner/src/IllegalArgumentException.cpp b/crypto/ed25519/libs/codeScanner/src/IllegalArgumentException.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/IllegalArgumentException.cpp rename to crypto/ed25519/libs/codeScanner/src/IllegalArgumentException.cpp diff --git a/ed25519/libs/codeScanner/src/IllegalArgumentException.h b/crypto/ed25519/libs/codeScanner/src/IllegalArgumentException.h similarity index 100% rename from ed25519/libs/codeScanner/src/IllegalArgumentException.h rename to crypto/ed25519/libs/codeScanner/src/IllegalArgumentException.h diff --git a/ed25519/libs/codeScanner/src/IllegalStateException.h b/crypto/ed25519/libs/codeScanner/src/IllegalStateException.h similarity index 100% rename from ed25519/libs/codeScanner/src/IllegalStateException.h rename to crypto/ed25519/libs/codeScanner/src/IllegalStateException.h diff --git a/ed25519/libs/codeScanner/src/ReaderException.h b/crypto/ed25519/libs/codeScanner/src/ReaderException.h similarity index 100% rename from ed25519/libs/codeScanner/src/ReaderException.h rename to crypto/ed25519/libs/codeScanner/src/ReaderException.h diff --git a/ed25519/libs/codeScanner/src/ReedSolomonDecoder.cpp b/crypto/ed25519/libs/codeScanner/src/ReedSolomonDecoder.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/ReedSolomonDecoder.cpp rename to crypto/ed25519/libs/codeScanner/src/ReedSolomonDecoder.cpp diff --git a/ed25519/libs/codeScanner/src/ReedSolomonDecoder.h b/crypto/ed25519/libs/codeScanner/src/ReedSolomonDecoder.h similarity index 100% rename from ed25519/libs/codeScanner/src/ReedSolomonDecoder.h rename to crypto/ed25519/libs/codeScanner/src/ReedSolomonDecoder.h diff --git a/ed25519/libs/codeScanner/src/ReedSolomonEncoder.cpp b/crypto/ed25519/libs/codeScanner/src/ReedSolomonEncoder.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/ReedSolomonEncoder.cpp rename to crypto/ed25519/libs/codeScanner/src/ReedSolomonEncoder.cpp diff --git a/ed25519/libs/codeScanner/src/ReedSolomonEncoder.h b/crypto/ed25519/libs/codeScanner/src/ReedSolomonEncoder.h similarity index 100% rename from ed25519/libs/codeScanner/src/ReedSolomonEncoder.h rename to crypto/ed25519/libs/codeScanner/src/ReedSolomonEncoder.h diff --git a/ed25519/libs/codeScanner/src/ReedSolomonException.cpp b/crypto/ed25519/libs/codeScanner/src/ReedSolomonException.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/ReedSolomonException.cpp rename to crypto/ed25519/libs/codeScanner/src/ReedSolomonException.cpp diff --git a/ed25519/libs/codeScanner/src/ReedSolomonException.h b/crypto/ed25519/libs/codeScanner/src/ReedSolomonException.h similarity index 100% rename from ed25519/libs/codeScanner/src/ReedSolomonException.h rename to crypto/ed25519/libs/codeScanner/src/ReedSolomonException.h diff --git a/ed25519/libs/codeScanner/src/ZXing.h b/crypto/ed25519/libs/codeScanner/src/ZXing.h similarity index 100% rename from ed25519/libs/codeScanner/src/ZXing.h rename to crypto/ed25519/libs/codeScanner/src/ZXing.h diff --git a/ed25519/libs/codeScanner/src/kikcode_constants.h b/crypto/ed25519/libs/codeScanner/src/kikcode_constants.h similarity index 100% rename from ed25519/libs/codeScanner/src/kikcode_constants.h rename to crypto/ed25519/libs/codeScanner/src/kikcode_constants.h diff --git a/ed25519/libs/codeScanner/src/kikcode_encoding.cpp b/crypto/ed25519/libs/codeScanner/src/kikcode_encoding.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/kikcode_encoding.cpp rename to crypto/ed25519/libs/codeScanner/src/kikcode_encoding.cpp diff --git a/ed25519/libs/codeScanner/src/kikcode_encoding.h b/crypto/ed25519/libs/codeScanner/src/kikcode_encoding.h similarity index 100% rename from ed25519/libs/codeScanner/src/kikcode_encoding.h rename to crypto/ed25519/libs/codeScanner/src/kikcode_encoding.h diff --git a/ed25519/libs/codeScanner/src/kikcode_encoding_jni.cpp b/crypto/ed25519/libs/codeScanner/src/kikcode_encoding_jni.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/kikcode_encoding_jni.cpp rename to crypto/ed25519/libs/codeScanner/src/kikcode_encoding_jni.cpp diff --git a/ed25519/libs/codeScanner/src/kikcode_encoding_jni.h b/crypto/ed25519/libs/codeScanner/src/kikcode_encoding_jni.h similarity index 100% rename from ed25519/libs/codeScanner/src/kikcode_encoding_jni.h rename to crypto/ed25519/libs/codeScanner/src/kikcode_encoding_jni.h diff --git a/ed25519/libs/codeScanner/src/kikcodes.cpp b/crypto/ed25519/libs/codeScanner/src/kikcodes.cpp similarity index 100% rename from ed25519/libs/codeScanner/src/kikcodes.cpp rename to crypto/ed25519/libs/codeScanner/src/kikcodes.cpp diff --git a/ed25519/libs/codeScanner/src/kikcodes.h b/crypto/ed25519/libs/codeScanner/src/kikcodes.h similarity index 100% rename from ed25519/libs/codeScanner/src/kikcodes.h rename to crypto/ed25519/libs/codeScanner/src/kikcodes.h diff --git a/ed25519/libs/ed25519/CMakeLists.txt b/crypto/ed25519/libs/ed25519/CMakeLists.txt similarity index 100% rename from ed25519/libs/ed25519/CMakeLists.txt rename to crypto/ed25519/libs/ed25519/CMakeLists.txt diff --git a/ed25519/libs/ed25519/license.txt b/crypto/ed25519/libs/ed25519/license.txt similarity index 100% rename from ed25519/libs/ed25519/license.txt rename to crypto/ed25519/libs/ed25519/license.txt diff --git a/ed25519/libs/ed25519/readme.md b/crypto/ed25519/libs/ed25519/readme.md similarity index 100% rename from ed25519/libs/ed25519/readme.md rename to crypto/ed25519/libs/ed25519/readme.md diff --git a/ed25519/libs/ed25519/src/add_scalar.c b/crypto/ed25519/libs/ed25519/src/add_scalar.c similarity index 100% rename from ed25519/libs/ed25519/src/add_scalar.c rename to crypto/ed25519/libs/ed25519/src/add_scalar.c diff --git a/ed25519/libs/ed25519/src/ed25519.h b/crypto/ed25519/libs/ed25519/src/ed25519.h similarity index 100% rename from ed25519/libs/ed25519/src/ed25519.h rename to crypto/ed25519/libs/ed25519/src/ed25519.h diff --git a/ed25519/libs/ed25519/src/fe.c b/crypto/ed25519/libs/ed25519/src/fe.c similarity index 100% rename from ed25519/libs/ed25519/src/fe.c rename to crypto/ed25519/libs/ed25519/src/fe.c diff --git a/ed25519/libs/ed25519/src/fe.h b/crypto/ed25519/libs/ed25519/src/fe.h similarity index 100% rename from ed25519/libs/ed25519/src/fe.h rename to crypto/ed25519/libs/ed25519/src/fe.h diff --git a/ed25519/libs/ed25519/src/fixedint.h b/crypto/ed25519/libs/ed25519/src/fixedint.h similarity index 100% rename from ed25519/libs/ed25519/src/fixedint.h rename to crypto/ed25519/libs/ed25519/src/fixedint.h diff --git a/ed25519/libs/ed25519/src/ge.c b/crypto/ed25519/libs/ed25519/src/ge.c similarity index 100% rename from ed25519/libs/ed25519/src/ge.c rename to crypto/ed25519/libs/ed25519/src/ge.c diff --git a/ed25519/libs/ed25519/src/ge.h b/crypto/ed25519/libs/ed25519/src/ge.h similarity index 100% rename from ed25519/libs/ed25519/src/ge.h rename to crypto/ed25519/libs/ed25519/src/ge.h diff --git a/ed25519/libs/ed25519/src/key_exchange.c b/crypto/ed25519/libs/ed25519/src/key_exchange.c similarity index 100% rename from ed25519/libs/ed25519/src/key_exchange.c rename to crypto/ed25519/libs/ed25519/src/key_exchange.c diff --git a/ed25519/libs/ed25519/src/keypair.c b/crypto/ed25519/libs/ed25519/src/keypair.c similarity index 100% rename from ed25519/libs/ed25519/src/keypair.c rename to crypto/ed25519/libs/ed25519/src/keypair.c diff --git a/ed25519/libs/ed25519/src/precomp_data.h b/crypto/ed25519/libs/ed25519/src/precomp_data.h similarity index 100% rename from ed25519/libs/ed25519/src/precomp_data.h rename to crypto/ed25519/libs/ed25519/src/precomp_data.h diff --git a/ed25519/libs/ed25519/src/sc.c b/crypto/ed25519/libs/ed25519/src/sc.c similarity index 100% rename from ed25519/libs/ed25519/src/sc.c rename to crypto/ed25519/libs/ed25519/src/sc.c diff --git a/ed25519/libs/ed25519/src/sc.h b/crypto/ed25519/libs/ed25519/src/sc.h similarity index 100% rename from ed25519/libs/ed25519/src/sc.h rename to crypto/ed25519/libs/ed25519/src/sc.h diff --git a/ed25519/libs/ed25519/src/seed.c b/crypto/ed25519/libs/ed25519/src/seed.c similarity index 100% rename from ed25519/libs/ed25519/src/seed.c rename to crypto/ed25519/libs/ed25519/src/seed.c diff --git a/ed25519/libs/ed25519/src/sha3.c b/crypto/ed25519/libs/ed25519/src/sha3.c similarity index 100% rename from ed25519/libs/ed25519/src/sha3.c rename to crypto/ed25519/libs/ed25519/src/sha3.c diff --git a/ed25519/libs/ed25519/src/sha3.h b/crypto/ed25519/libs/ed25519/src/sha3.h similarity index 100% rename from ed25519/libs/ed25519/src/sha3.h rename to crypto/ed25519/libs/ed25519/src/sha3.h diff --git a/ed25519/libs/ed25519/src/sha512.c b/crypto/ed25519/libs/ed25519/src/sha512.c similarity index 100% rename from ed25519/libs/ed25519/src/sha512.c rename to crypto/ed25519/libs/ed25519/src/sha512.c diff --git a/ed25519/libs/ed25519/src/sha512.h b/crypto/ed25519/libs/ed25519/src/sha512.h similarity index 100% rename from ed25519/libs/ed25519/src/sha512.h rename to crypto/ed25519/libs/ed25519/src/sha512.h diff --git a/ed25519/libs/ed25519/src/sign.c b/crypto/ed25519/libs/ed25519/src/sign.c similarity index 100% rename from ed25519/libs/ed25519/src/sign.c rename to crypto/ed25519/libs/ed25519/src/sign.c diff --git a/ed25519/libs/ed25519/src/verify.c b/crypto/ed25519/libs/ed25519/src/verify.c similarity index 100% rename from ed25519/libs/ed25519/src/verify.c rename to crypto/ed25519/libs/ed25519/src/verify.c diff --git a/ed25519/libs/ed25519/test.c b/crypto/ed25519/libs/ed25519/test.c similarity index 100% rename from ed25519/libs/ed25519/test.c rename to crypto/ed25519/libs/ed25519/test.c diff --git a/ed25519/proguard-rules.pro b/crypto/ed25519/proguard-rules.pro similarity index 100% rename from ed25519/proguard-rules.pro rename to crypto/ed25519/proguard-rules.pro diff --git a/ed25519/src/main/AndroidManifest.xml b/crypto/ed25519/src/main/AndroidManifest.xml similarity index 100% rename from ed25519/src/main/AndroidManifest.xml rename to crypto/ed25519/src/main/AndroidManifest.xml diff --git a/ed25519/src/main/cpp/kik-codes.cpp b/crypto/ed25519/src/main/cpp/kik-codes.cpp similarity index 100% rename from ed25519/src/main/cpp/kik-codes.cpp rename to crypto/ed25519/src/main/cpp/kik-codes.cpp diff --git a/ed25519/src/main/cpp/native-lib.cpp b/crypto/ed25519/src/main/cpp/native-lib.cpp similarity index 100% rename from ed25519/src/main/cpp/native-lib.cpp rename to crypto/ed25519/src/main/cpp/native-lib.cpp diff --git a/ed25519/src/main/java/com/getcode/codeScanner/CodeScanner.java b/crypto/ed25519/src/main/java/com/getcode/codeScanner/CodeScanner.java similarity index 100% rename from ed25519/src/main/java/com/getcode/codeScanner/CodeScanner.java rename to crypto/ed25519/src/main/java/com/getcode/codeScanner/CodeScanner.java diff --git a/ed25519/src/main/java/com/getcode/ed25519/Ed25519.java b/crypto/ed25519/src/main/java/com/getcode/ed25519/Ed25519.java similarity index 100% rename from ed25519/src/main/java/com/getcode/ed25519/Ed25519.java rename to crypto/ed25519/src/main/java/com/getcode/ed25519/Ed25519.java diff --git a/model/.gitignore b/crypto/kin/.gitignore similarity index 100% rename from model/.gitignore rename to crypto/kin/.gitignore diff --git a/crypto/kin/build.gradle.kts b/crypto/kin/build.gradle.kts new file mode 100644 index 000000000..72ac956c8 --- /dev/null +++ b/crypto/kin/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + id(Plugins.android_library) + id(Plugins.kotlin_android) + id(Plugins.kotlin_serialization) +} + +android { + namespace = "${Android.namespace}.vendor.kin" + compileSdk = Android.compileSdkVersion + defaultConfig { + minSdk = Android.minSdkVersion + targetSdk = Android.targetSdkVersion + buildToolsVersion = Android.buildToolsVersion + testInstrumentationRunner = Android.testInstrumentationRunner + } + + kotlinOptions { + jvmTarget = Versions.java + freeCompilerArgs += listOf( + "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-Xuse-experimental=kotlinx.coroutines.FlowPreview", + "-opt-in=kotlin.ExperimentalUnsignedTypes", + "-opt-in=kotlin.RequiresOptIn" + ) + } + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(Versions.java)) + } + } +} + +dependencies { + api(Libs.kin_sdk) +} diff --git a/model/build.gradle b/model/build.gradle deleted file mode 100644 index efc02e26c..000000000 --- a/model/build.gradle +++ /dev/null @@ -1,98 +0,0 @@ -import org.apache.tools.ant.taskdefs.condition.Os - -plugins { - id 'java-library' - id 'com.google.protobuf' - id 'maven-publish' -} - -def archSuffix = Os.isFamily(Os.FAMILY_MAC) ? ':osx-x86_64' : '' - -version = '0.0.1' -group = 'com.codeinc.gen' - -dependencies { - implementation(Libs.grpc_protobuf_lite) - implementation(Libs.grpc_stub) - - // Kotlin Generation - implementation(Libs.grpc_kotlin) - implementation(Libs.protobuf_kotlin_lite) - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0" - -} - -def generatedSrcRoot = file("${buildDir}/generated/source/proto/main/java") -def generatedSrcRoot2 = file("${buildDir}/generated/source/proto/main/grpc") -def generatedSrcRoot3 = file("${buildDir}/generated/source/proto/main/grpcKt") -sourceSets.main { - proto { - srcDir "${rootDir}/model/proto" - } - java { - srcDirs = ["$generatedSrcRoot", "$generatedSrcRoot2", "$generatedSrcRoot3"] - } -} - -compileJava { - options.annotationProcessorGeneratedSourcesDirectory generatedSrcRoot -} - -protobuf { - protoc { - artifact = "com.google.protobuf:protoc:${Versions.protobuf}$archSuffix" - } - plugins { - grpc { - artifact = "io.grpc:protoc-gen-grpc-java:${Versions.grpc}" - } -// javapgv { -// artifact = "io.envoyproxy.protoc-gen-validate:protoc-gen-validate:${validateVersion}" -// } - grpcKt { - artifact = "io.grpc:protoc-gen-grpc-kotlin:1.0.0:jdk7@jar" - } - } - generateProtoTasks { - all().each { task -> - task.builtins { - java { - option "lite" - } - } - } - all()*.plugins { - grpc { - option 'lite' - } - grpcKt { - option 'lite' - } - } - } -} - -jar { - manifest { - attributes('code-api': project.name, - 'Version': project.version) - } - - exclude "**/*agora*/**" -} - -task sourcesJar(type: Jar) { - from sourceSets.main.allSource - archiveClassifier = 'sources' - from generatedSrcRoot, generatedSrcRoot2, generatedSrcRoot3 - exclude "**/*agora*/**" -} - -artifacts { - archives sourcesJar -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} diff --git a/model/remove_proto_validation.sh b/model/remove_proto_validation.sh deleted file mode 100755 index 783c9bfc5..000000000 --- a/model/remove_proto_validation.sh +++ /dev/null @@ -1,21 +0,0 @@ -# -# For all .proto files, strip the validation parameters & replace inline -# - -# 1. hack: first add a couple newlines after all "];" -find proto/ -name "*.proto" -type f -exec sh -c "awk '{gsub(/];/, \"];\n\n\"); print}' {} > tmp && mv tmp {}" \; - -# 2. strip everything between square brackets [...] -find proto/ -name "*.proto" -type f -exec sh -c "awk -v RS='' '{gsub(/ \[.*\]/, \"\"); print}' {} > tmp && mv tmp {}" \; - -# 3. add a newline after all trailing } brackets -find proto/ -name "*.proto" -type f -exec sh -c "awk '{gsub(/}/, \"}\n\"); print}' {} > tmp && mv tmp {}" \; - -# 4. strip validate import statement -find proto/ -name "*.proto" -type f -exec sh -c "awk -v RS='' '{gsub(/import \"validate\/validate.proto\";/, \"\"); print}' {} > tmp && mv tmp {}" \; - -# 5. strip validate required options -find proto/ -name "*.proto" -type f -exec sh -c "awk -v RS='' '{gsub(/option \(validate.required\) = true;/, \"\"); print}' {} > tmp && mv tmp {}" \; - -# 6. add a newline after all trailing } brackets -find proto/ -name "*.proto" -type f -exec sh -c "awk '{gsub(/}/, \"}\n\"); print}' {} > tmp && mv tmp {}" \; diff --git a/service/models/.gitignore b/service/models/.gitignore new file mode 100644 index 000000000..9f2a07880 --- /dev/null +++ b/service/models/.gitignore @@ -0,0 +1,2 @@ +build/ +.gradle/ diff --git a/service/models/build.gradle.kts b/service/models/build.gradle.kts new file mode 100644 index 000000000..90238dade --- /dev/null +++ b/service/models/build.gradle.kts @@ -0,0 +1,77 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +plugins { + id(Plugins.android_library) + id(Plugins.kotlin_android) + id("com.google.protobuf") +} + +val archSuffix = if (Os.isFamily(Os.FAMILY_MAC)) ":osx-x86_64" else "" + +version = "0.0.1" +group = "com.codeinc.gen" + +dependencies { + protobuf(project(":service:protos")) + + implementation(Libs.grpc_protobuf_lite) + implementation(Libs.grpc_stub) + + // Kotlin Generation + implementation(Libs.grpc_kotlin) + implementation(Libs.protobuf_kotlin_lite) + implementation(Libs.kotlinx_coroutines_core) +} + +kotlin { + jvmToolchain(17) +} + +android { + compileSdk = 34 + buildToolsVersion = "34.0.0" + namespace = "io.grpc.examples.stublite" +} + +tasks.withType().all { + kotlinOptions { + freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn") + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${Versions.protobuf}$archSuffix" + } + plugins { + create("java") { + artifact = "io.grpc:protoc-gen-grpc-java:${Versions.grpc}" + } + create("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:${Versions.grpc}" + } + create("grpckt") { + artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar" + } + } + generateProtoTasks { + all().forEach { + it.plugins { + create("java") { + option("lite") + } + create("grpc") { + option("lite") + } + create("grpckt") { + option("lite") + } + } + it.builtins { + create("kotlin") { + option("lite") + } + } + } + } +} \ No newline at end of file diff --git a/service/protos/.gitignore b/service/protos/.gitignore new file mode 100644 index 000000000..9f2a07880 --- /dev/null +++ b/service/protos/.gitignore @@ -0,0 +1,2 @@ +build/ +.gradle/ diff --git a/service/protos/build.gradle.kts b/service/protos/build.gradle.kts new file mode 100644 index 000000000..4cd4475f1 --- /dev/null +++ b/service/protos/build.gradle.kts @@ -0,0 +1,11 @@ +// todo: maybe use variants / configurations to do both stub & stub-lite here + +// Note: We use the java-library plugin to get the protos into the artifact for this subproject +// because there doesn't seem to be an better way. +plugins { + `java-library` +} + +java { + sourceSets.getByName("main").resources.srcDir("src/main/proto") +} \ No newline at end of file diff --git a/model/proto/account/v1/account_service.proto b/service/protos/src/main/proto/account/v1/account_service.proto similarity index 100% rename from model/proto/account/v1/account_service.proto rename to service/protos/src/main/proto/account/v1/account_service.proto diff --git a/model/proto/badge/v1/badge_service.proto b/service/protos/src/main/proto/badge/v1/badge_service.proto similarity index 100% rename from model/proto/badge/v1/badge_service.proto rename to service/protos/src/main/proto/badge/v1/badge_service.proto diff --git a/model/proto/chat/v1/chat_service.proto b/service/protos/src/main/proto/chat/v1/chat_service.proto similarity index 100% rename from model/proto/chat/v1/chat_service.proto rename to service/protos/src/main/proto/chat/v1/chat_service.proto diff --git a/model/proto/chat/v2/chat_service.proto b/service/protos/src/main/proto/chat/v2/chat_service.proto similarity index 100% rename from model/proto/chat/v2/chat_service.proto rename to service/protos/src/main/proto/chat/v2/chat_service.proto diff --git a/model/proto/common/v1/model.proto b/service/protos/src/main/proto/common/v1/model.proto similarity index 100% rename from model/proto/common/v1/model.proto rename to service/protos/src/main/proto/common/v1/model.proto diff --git a/model/proto/contact/v1/contact_list_service.proto b/service/protos/src/main/proto/contact/v1/contact_list_service.proto similarity index 100% rename from model/proto/contact/v1/contact_list_service.proto rename to service/protos/src/main/proto/contact/v1/contact_list_service.proto diff --git a/model/proto/currency/v1/currency_service.proto b/service/protos/src/main/proto/currency/v1/currency_service.proto similarity index 100% rename from model/proto/currency/v1/currency_service.proto rename to service/protos/src/main/proto/currency/v1/currency_service.proto diff --git a/model/proto/device/v1/device_service.proto b/service/protos/src/main/proto/device/v1/device_service.proto similarity index 100% rename from model/proto/device/v1/device_service.proto rename to service/protos/src/main/proto/device/v1/device_service.proto diff --git a/model/proto/invite/v2/invite_service.proto b/service/protos/src/main/proto/invite/v2/invite_service.proto similarity index 100% rename from model/proto/invite/v2/invite_service.proto rename to service/protos/src/main/proto/invite/v2/invite_service.proto diff --git a/model/proto/messaging/v1/messaging_service.proto b/service/protos/src/main/proto/messaging/v1/messaging_service.proto similarity index 100% rename from model/proto/messaging/v1/messaging_service.proto rename to service/protos/src/main/proto/messaging/v1/messaging_service.proto diff --git a/model/proto/micropayment/v1/micro_payment_service.proto b/service/protos/src/main/proto/micropayment/v1/micro_payment_service.proto similarity index 100% rename from model/proto/micropayment/v1/micro_payment_service.proto rename to service/protos/src/main/proto/micropayment/v1/micro_payment_service.proto diff --git a/model/proto/phone/v1/phone_verification_service.proto b/service/protos/src/main/proto/phone/v1/phone_verification_service.proto similarity index 100% rename from model/proto/phone/v1/phone_verification_service.proto rename to service/protos/src/main/proto/phone/v1/phone_verification_service.proto diff --git a/model/proto/push/v1/push_service.proto b/service/protos/src/main/proto/push/v1/push_service.proto similarity index 100% rename from model/proto/push/v1/push_service.proto rename to service/protos/src/main/proto/push/v1/push_service.proto diff --git a/model/proto/transaction/v2/transaction_service.proto b/service/protos/src/main/proto/transaction/v2/transaction_service.proto similarity index 100% rename from model/proto/transaction/v2/transaction_service.proto rename to service/protos/src/main/proto/transaction/v2/transaction_service.proto diff --git a/model/proto/user/v1/identity_service.proto b/service/protos/src/main/proto/user/v1/identity_service.proto similarity index 100% rename from model/proto/user/v1/identity_service.proto rename to service/protos/src/main/proto/user/v1/identity_service.proto diff --git a/settings.gradle b/settings.gradle index f3b37c9f1..841868ca0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,10 @@ -include ':model' include ':api' include ':app' -include ':ed25519' +include ':crypto:kin' +include ':crypto:ed25519' include ':common:resources' +include ':service:models' +include ':service:protos' include ':common:theme' include ':vendor:tipkit:tipkit' include ':vendor:tipkit:tipkit-m2' From 7a180d50258e9e7f9809ece7c9071397b8c04463 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 21 Aug 2024 11:38:52 -0400 Subject: [PATCH 32/33] style(home): update bottom bar assets Signed-off-by: Brandon McAnsh --- .../view/main/home/components/HomeBottom.kt | 9 ++------- app/src/main/res/drawable/ic_balance.xml | 14 +++++++------- app/src/main/res/drawable/ic_kin_white_small.xml | 10 +++++----- app/src/main/res/drawable/ic_tip_card.xml | 12 ++++++------ 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt index 6b3381ca5..4f2b92032 100644 --- a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt +++ b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt @@ -73,11 +73,6 @@ internal fun HomeBottom( modifier = Modifier.weight(1f), label = stringResource(R.string.action_balance), painter = painterResource(R.drawable.ic_balance), - contentPadding = PaddingValues( - top = CodeTheme.dimens.grid.x2, - bottom = CodeTheme.dimens.grid.x3 - ), - imageSize = CodeTheme.dimens.staticGrid.x6, onClick = { onPress(HomeAction.BALANCE) }, badge = { Badge( @@ -116,7 +111,7 @@ private fun BottomBarAction( vertical = CodeTheme.dimens.grid.x2 ), painter: Painter, - imageSize: Dp = CodeTheme.dimens.staticGrid.x8, + imageSize: Dp = CodeTheme.dimens.staticGrid.x10, badge: @Composable () -> Unit = { }, onClick: () -> Unit, ) { @@ -125,7 +120,7 @@ private fun BottomBarAction( content = { Column( modifier = Modifier - .unboundedClickable { onClick() } + .unboundedClickable(rippleRadius = imageSize) { onClick() } .layoutId("action"), horizontalAlignment = Alignment.CenterHorizontally ) { diff --git a/app/src/main/res/drawable/ic_balance.xml b/app/src/main/res/drawable/ic_balance.xml index 0af9ca841..134e269a1 100644 --- a/app/src/main/res/drawable/ic_balance.xml +++ b/app/src/main/res/drawable/ic_balance.xml @@ -1,20 +1,20 @@ + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> diff --git a/app/src/main/res/drawable/ic_kin_white_small.xml b/app/src/main/res/drawable/ic_kin_white_small.xml index 5c6421d5d..471856956 100644 --- a/app/src/main/res/drawable/ic_kin_white_small.xml +++ b/app/src/main/res/drawable/ic_kin_white_small.xml @@ -1,10 +1,10 @@ + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> diff --git a/app/src/main/res/drawable/ic_tip_card.xml b/app/src/main/res/drawable/ic_tip_card.xml index ee23ff720..50e4af53d 100644 --- a/app/src/main/res/drawable/ic_tip_card.xml +++ b/app/src/main/res/drawable/ic_tip_card.xml @@ -1,15 +1,15 @@ + android:width="50dp" + android:height="50dp" + android:viewportWidth="50" + android:viewportHeight="50"> From 73efc9001133d9c9ffc063ccf88346fdd92637e1 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 21 Aug 2024 16:58:22 -0400 Subject: [PATCH 33/33] feat: improve network resiliency when starting in a no network state Signed-off-by: Brandon McAnsh --- .../java/com/getcode/db/ConversationDao.kt | 6 + .../com/getcode/mapper/ConversationMapper.kt | 1 - .../main/java/com/getcode/model/Feature.kt | 8 +- .../main/java/com/getcode/model/PrefBool.kt | 4 +- .../main/java/com/getcode/model/chat/Chat.kt | 5 + .../com/getcode/model/chat/ChatMessage.kt | 4 + .../com/getcode/network/BalanceController.kt | 51 +++--- ...Controller.kt => ChatHistoryController.kt} | 45 +++--- .../getcode/network/ConversationController.kt | 2 +- .../com/getcode/network/api/CurrencyApi.kt | 10 +- .../com/getcode/network/exchange/Exchange.kt | 145 ++++++++---------- .../network/repository/BetaFlagsRepository.kt | 16 +- .../network/repository/FeatureRepository.kt | 10 +- .../network/service/CurrencyService.kt | 54 +++++++ .../java/com/getcode/utils/network/Retry.kt | 58 +++++++ app/build.gradle.kts | 1 + .../main/java/com/getcode/inject/ApiModule.kt | 7 +- .../java/com/getcode/inject/DataModule.kt | 5 +- .../java/com/getcode/manager/AuthManager.kt | 5 +- .../getcode/navigation/screens/ChatScreens.kt | 105 ++----------- .../getcode/navigation/screens/MainScreens.kt | 102 ++++++++++++ .../notifications/CodePushMessagingService.kt | 4 +- .../com/getcode/ui/components/CodeButton.kt | 4 +- .../getcode/ui/components/chat/ChatNode.kt | 11 +- .../ui/components/chat/TipChatActions.kt | 6 +- .../java/com/getcode/util/AccountUtils.kt | 73 ++++----- .../java/com/getcode/view/login/LoginHome.kt | 4 +- .../view/main/account/BetaFlagsScreen.kt | 18 +-- .../AccountWithdrawSummaryViewModel.kt | 4 +- .../main/balance/BalanceSheetViewModel.kt | 9 +- .../getcode/view/main/chat/ChatViewModel.kt | 6 +- .../conversation/ConversationViewModel.kt | 10 +- .../view/main/chat/list/ChatListScreen.kt | 39 +++++ .../view/main/chat/list/ChatListViewModel.kt | 44 ++++++ .../com/getcode/view/main/home/HomeScan.kt | 5 +- .../getcode/view/main/home/HomeViewModel.kt | 50 +++--- .../view/main/home/components/HomeBottom.kt | 35 ++++- app/src/main/res/drawable/ic_chat.xml | 9 ++ app/src/main/res/values/strings-universal.xml | 8 +- app/src/main/res/values/strings.xml | 3 + common/resources/build.gradle.kts | 12 +- .../util/resources/icons/ImageVector.kt | 77 ++++++++++ .../util/resources/icons/MessageCircle.kt | 52 +++++++ 43 files changed, 749 insertions(+), 378 deletions(-) rename api/src/main/java/com/getcode/network/{HistoryController.kt => ChatHistoryController.kt} (91%) create mode 100644 api/src/main/java/com/getcode/network/service/CurrencyService.kt create mode 100644 api/src/main/java/com/getcode/utils/network/Retry.kt create mode 100644 app/src/main/java/com/getcode/view/main/chat/list/ChatListScreen.kt create mode 100644 app/src/main/java/com/getcode/view/main/chat/list/ChatListViewModel.kt create mode 100644 app/src/main/res/drawable/ic_chat.xml create mode 100644 common/resources/src/main/java/com/getcode/util/resources/icons/ImageVector.kt create mode 100644 common/resources/src/main/java/com/getcode/util/resources/icons/MessageCircle.kt diff --git a/api/src/main/java/com/getcode/db/ConversationDao.kt b/api/src/main/java/com/getcode/db/ConversationDao.kt index dac6e0c89..ff98c92ad 100644 --- a/api/src/main/java/com/getcode/db/ConversationDao.kt +++ b/api/src/main/java/com/getcode/db/ConversationDao.kt @@ -1,5 +1,7 @@ package com.getcode.db +import androidx.paging.PagingData +import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert @@ -19,6 +21,10 @@ interface ConversationDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun upsertConversations(vararg conversation: Conversation) + @RewriteQueriesToDropUnusedColumns + @Query("SELECT * FROM conversations") + fun observeConversations(): PagingSource + @RewriteQueriesToDropUnusedColumns @Query("SELECT * FROM conversations LEFT JOIN conversation_pointers ON conversations.idBase58 = conversation_pointers.conversationIdBase58 WHERE conversations.idBase58 = :id") fun observeConversation(id: String): Flow diff --git a/api/src/main/java/com/getcode/mapper/ConversationMapper.kt b/api/src/main/java/com/getcode/mapper/ConversationMapper.kt index d814f13f8..548c58dc9 100644 --- a/api/src/main/java/com/getcode/mapper/ConversationMapper.kt +++ b/api/src/main/java/com/getcode/mapper/ConversationMapper.kt @@ -4,7 +4,6 @@ import com.getcode.model.Conversation import com.getcode.model.chat.Chat import com.getcode.model.chat.ChatType import com.getcode.model.chat.self -import com.getcode.network.TipController import com.getcode.network.localized import com.getcode.network.repository.base58 import com.getcode.util.resources.ResourceHelper diff --git a/api/src/main/java/com/getcode/model/Feature.kt b/api/src/main/java/com/getcode/model/Feature.kt index b62461377..f1102cfd0 100644 --- a/api/src/main/java/com/getcode/model/Feature.kt +++ b/api/src/main/java/com/getcode/model/Feature.kt @@ -22,13 +22,13 @@ data class TipCardOnHomeScreenFeature( override val available: Boolean = true, // always available ): Feature -data class TipChatFeature( - override val enabled: Boolean = BetaOptions.Defaults.tipsChatEnabled, +data class ConversationsFeature( + override val enabled: Boolean = BetaOptions.Defaults.conversationsEnabled, override val available: Boolean = true, // always available ): Feature -data class TipChatCashFeature( - override val enabled: Boolean = BetaOptions.Defaults.tipsChatCashEnabled, +data class ConversationCashFeature( + override val enabled: Boolean = BetaOptions.Defaults.conversationCashEnabled, override val available: Boolean = true, // always available ): Feature diff --git a/api/src/main/java/com/getcode/model/PrefBool.kt b/api/src/main/java/com/getcode/model/PrefBool.kt index 7645b92c7..185daa3ac 100644 --- a/api/src/main/java/com/getcode/model/PrefBool.kt +++ b/api/src/main/java/com/getcode/model/PrefBool.kt @@ -46,8 +46,8 @@ sealed class PrefsBool(val value: String) { data object BUY_MODULE_ENABLED : PrefsBool("buy_kin_enabled"), BetaFlag data object CHAT_UNSUB_ENABLED: PrefsBool("chat_unsub_enabled"), BetaFlag data object TIPS_ENABLED : PrefsBool("tips_enabled"), BetaFlag - data object TIPS_CHAT_ENABLED: PrefsBool("tips_chat_enabled"), BetaFlag - data object TIPS_CHAT_CASH_ENABLED: PrefsBool("tips_chat_cash_enabled"), BetaFlag + data object CONVERSATIONS_ENABLED: PrefsBool("conversations_enabled"), BetaFlag + data object CONVERSATION_CASH_ENABLED: PrefsBool("convo_cash_enabled"), BetaFlag data object BALANCE_CURRENCY_SELECTION_ENABLED: PrefsBool("balance_currency_enabled"), BetaFlag data object KADO_WEBVIEW_ENABLED : PrefsBool("kado_inapp_enabled"), BetaFlag data object SHARE_TWEET_TO_TIP : PrefsBool("share_tweet_to_tip"), BetaFlag diff --git a/api/src/main/java/com/getcode/model/chat/Chat.kt b/api/src/main/java/com/getcode/model/chat/Chat.kt index 944af211d..624fe8893 100644 --- a/api/src/main/java/com/getcode/model/chat/Chat.kt +++ b/api/src/main/java/com/getcode/model/chat/Chat.kt @@ -2,6 +2,7 @@ package com.getcode.model.chat import com.getcode.model.Cursor import com.getcode.model.ID +import kotlinx.serialization.Serializable import java.util.UUID /** @@ -18,6 +19,7 @@ import java.util.UUID * @param cursor [Cursor] value for this chat for reference in subsequent GetChatsRequest * @param messages List of messages within this chat */ +@Serializable data class Chat( val id: ID, val type: ChatType, @@ -135,6 +137,9 @@ data class Chat( val Chat.isV2: Boolean get() = members.isNotEmpty() +val Chat.isNotification: Boolean + get() = type == ChatType.Notification + val Chat.isConversation: Boolean get() = type == ChatType.TwoWay diff --git a/api/src/main/java/com/getcode/model/chat/ChatMessage.kt b/api/src/main/java/com/getcode/model/chat/ChatMessage.kt index 752d375e4..977f3db87 100644 --- a/api/src/main/java/com/getcode/model/chat/ChatMessage.kt +++ b/api/src/main/java/com/getcode/model/chat/ChatMessage.kt @@ -4,6 +4,8 @@ import com.getcode.ed25519.Ed25519.KeyPair import com.getcode.model.Cursor import com.getcode.model.ID import com.getcode.model.MessageStatus +import com.getcode.utils.serializer.UUIDSerializer +import kotlinx.serialization.Serializable import java.util.UUID /** @@ -18,8 +20,10 @@ import java.util.UUID * @param contents Ordered message content. A message may have more than one piece of content. * @param status Derived [MessageStatus] from [Pointer]'s in [ChatMember]. */ +@Serializable data class ChatMessage( val id: ID, // time based UUID in v2 + @Serializable(with = UUIDSerializer::class) val senderId: UUID?, val isFromSelf: Boolean, val cursor: Cursor, diff --git a/api/src/main/java/com/getcode/network/BalanceController.kt b/api/src/main/java/com/getcode/network/BalanceController.kt index c1aff4af5..74905e397 100644 --- a/api/src/main/java/com/getcode/network/BalanceController.kt +++ b/api/src/main/java/com/getcode/network/BalanceController.kt @@ -13,6 +13,7 @@ import com.getcode.solana.organizer.Organizer import com.getcode.solana.organizer.Tray import com.getcode.utils.FormatUtils import com.getcode.utils.network.NetworkConnectivityListener +import com.getcode.utils.network.retryable import com.getcode.utils.trace import io.reactivex.rxjava3.core.Completable import kotlinx.coroutines.CoroutineScope @@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -38,8 +40,7 @@ data class BalanceDisplay( val marketValue: Double = 0.0, val formattedValue: String = "", val currency: Currency? = null, - - ) +) open class BalanceController @Inject constructor( exchange: Exchange, @@ -65,25 +66,34 @@ open class BalanceController @Inject constructor( .stateIn(scope, SharingStarted.Eagerly, BalanceDisplay()) init { - combine( - exchange.observeLocalRate() - .flowOn(Dispatchers.IO) - .onEach { - val display = _balanceDisplay.value ?: BalanceDisplay() - _balanceDisplay.value = display.copy(currency = getCurrencyFromCode(it.currency)) + networkObserver.state + .map { it.connected } + .onEach { connected -> + if (connected) { + retryable({ fetchBalanceSuspend() }) + } + } + .flatMapLatest { + combine( + exchange.observeLocalRate() + .flowOn(Dispatchers.IO) + .onEach { + val display = _balanceDisplay.value ?: BalanceDisplay() + _balanceDisplay.value = + display.copy(currency = getCurrencyFromCode(it.currency)) + } + .onEach { exchange.fetchRatesIfNeeded() }, + balanceRepository.balanceFlow, + ) { rate, balance -> + rate to balance.coerceAtLeast(0.0) + }.map { (rate, balance) -> + refreshBalance(balance, rate) } - .onEach { exchange.fetchRatesIfNeeded() }, - balanceRepository.balanceFlow, - networkObserver.state - ) { rate, balance, _ -> - rate to balance.coerceAtLeast(0.0) - }.map { (rate, balance) -> - refreshBalance(balance, rate) - }.distinctUntilChanged().onEach { (marketValue, amountText) -> - val display = _balanceDisplay.value ?: BalanceDisplay() - _balanceDisplay.value = - display.copy(marketValue = marketValue, formattedValue = amountText) - }.launchIn(scope) + }.distinctUntilChanged().onEach { (marketValue, amountText) -> + val display = _balanceDisplay.value ?: BalanceDisplay() + _balanceDisplay.value = + display.copy(marketValue = marketValue, formattedValue = amountText) + }.launchIn(scope) } fun setTray(organizer: Organizer, tray: Tray) { @@ -155,6 +165,7 @@ open class BalanceController @Inject constructor( suspend fun fetchBalanceSuspend() { + Timber.d("fetching balance") if (SessionManager.isAuthenticated() != true) { Timber.d("FetchBalance - Not authenticated") return diff --git a/api/src/main/java/com/getcode/network/HistoryController.kt b/api/src/main/java/com/getcode/network/ChatHistoryController.kt similarity index 91% rename from api/src/main/java/com/getcode/network/HistoryController.kt rename to api/src/main/java/com/getcode/network/ChatHistoryController.kt index 5fde54aca..77d520c1d 100644 --- a/api/src/main/java/com/getcode/network/HistoryController.kt +++ b/api/src/main/java/com/getcode/network/ChatHistoryController.kt @@ -17,13 +17,12 @@ import com.getcode.model.Cursor import com.getcode.model.ID import com.getcode.model.MessageStatus import com.getcode.model.chat.ChatMember -import com.getcode.model.chat.ChatType import com.getcode.model.chat.Identity import com.getcode.model.chat.Platform import com.getcode.model.chat.Title import com.getcode.model.chat.isConversation +import com.getcode.model.chat.isNotification import com.getcode.model.chat.selfId -import com.getcode.model.description import com.getcode.network.client.Client import com.getcode.network.client.advancePointer import com.getcode.network.client.fetchChats @@ -34,7 +33,6 @@ import com.getcode.network.repository.encodeBase64 import com.getcode.network.source.ChatMessagePagingSource import com.getcode.util.resources.ResourceHelper import com.getcode.util.resources.ResourceType -import com.getcode.utils.ErrorUtils import com.getcode.utils.TraceType import com.getcode.utils.trace import kotlinx.coroutines.CoroutineScope @@ -42,31 +40,35 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import okhttp3.internal.toImmutableList import timber.log.Timber import java.util.Locale import javax.inject.Inject import javax.inject.Singleton -// TODO: See if we can merge this into [ChatController] @Singleton -class HistoryController @Inject constructor( +class ChatHistoryController @Inject constructor( private val client: Client, - private val resources: ResourceHelper, private val tipController: TipController, private val conversationMapper: ConversationMapper, private val conversationMessageMapper: ConversationMessageMapper, ) : CoroutineScope by CoroutineScope(Dispatchers.IO) { - private val _chats = MutableStateFlow?>(null) + private val chatEntries = MutableStateFlow?>(null) + val notifications: StateFlow?> + get() = chatEntries + .map { it?.filter { entry -> entry.isNotification } } + .stateIn(this, SharingStarted.Eagerly, emptyList()) + val chats: StateFlow?> - get() = _chats.asStateFlow() + get() = chatEntries + .map { it?.filter { entry -> entry.isConversation } } + .stateIn(this, SharingStarted.Eagerly, emptyList()) var loadingMessages: Boolean = false @@ -86,9 +88,9 @@ class HistoryController @Inject constructor( pagerMap[chatId] ?: ChatMessagePagingSource( client = client, owner = owner()!!, - chat = _chats.value?.find { it.id == chatId }, + chat = chatEntries.value?.find { it.id == chatId }, onMessagesFetched = { messages -> - val chat = _chats.value?.find { it.id == chatId } ?: return@ChatMessagePagingSource + val chat = chatEntries.value?.find { it.id == chatId } ?: return@ChatMessagePagingSource updateChatWithMessages(chat, messages) } ).also { @@ -99,14 +101,14 @@ class HistoryController @Inject constructor( fun updateChatWithMessages(chat: Chat, messages: List) { val updatedMessages = (chat.messages + messages).distinctBy { it.id } val updatedChat = chat.copy(messages = updatedMessages) - val chats = _chats.value?.map { + val chats = chatEntries.value?.map { if (it.id == updatedChat.id) { updatedChat } else { it } }?.sortedByDescending { it.lastMessageMillis } - _chats.update { chats } + chatEntries.update { chats } } fun chatFlow(chatId: ID) = @@ -132,7 +134,7 @@ class HistoryController @Inject constructor( if (!update) { pagerMap.clear() chatFlows.clear() - _chats.value = containers + chatEntries.value = containers loadingMessages = true } @@ -151,13 +153,13 @@ class HistoryController @Inject constructor( } loadingMessages = false - _chats.value = updatedWithMessages.sortedByDescending { it.lastMessageMillis } + chatEntries.value = updatedWithMessages.sortedByDescending { it.lastMessageMillis } } suspend fun advanceReadPointer(chatId: ID) { val owner = owner() ?: return - _chats.update { + chatEntries.update { it?.toMutableList()?.apply chats@{ indexOfFirst { chat -> chat.id == chatId } .takeIf { index -> index >= 0 } @@ -180,7 +182,7 @@ class HistoryController @Inject constructor( } fun advanceReadPointerUpTo(chatId: ID, timestamp: Long) { - _chats.update { + chatEntries.update { it?.toMutableList()?.apply chats@{ indexOfFirst { chat -> chat.id == chatId } .takeIf { index -> index >= 0 } @@ -198,7 +200,7 @@ class HistoryController @Inject constructor( suspend fun setMuted(chat: Chat, muted: Boolean): Result { val owner = owner() ?: return Result.failure(Throwable("No owner detected")) - _chats.update { + chatEntries.update { it?.toMutableList()?.apply chats@{ indexOfFirst { item -> item.id == chat.id } .takeIf { index -> index >= 0 } @@ -216,7 +218,7 @@ class HistoryController @Inject constructor( suspend fun setSubscribed(chat: Chat, subscribed: Boolean): Result { val owner = owner() ?: return Result.failure(Throwable("No owner detected")) - _chats.update { + chatEntries.update { it?.toMutableList()?.apply chats@{ indexOfFirst { item -> item.id == chat.id } .takeIf { index -> index >= 0 } @@ -250,7 +252,6 @@ class HistoryController @Inject constructor( MessageStatus.Delivered ) } - } .onFailure { Timber.e(t = it, "Failed to fetch messages for $encodedId.") diff --git a/api/src/main/java/com/getcode/network/ConversationController.kt b/api/src/main/java/com/getcode/network/ConversationController.kt index 77b866944..0e0dfe017 100644 --- a/api/src/main/java/com/getcode/network/ConversationController.kt +++ b/api/src/main/java/com/getcode/network/ConversationController.kt @@ -47,7 +47,7 @@ interface ConversationController { } class ConversationStreamController @Inject constructor( - private val historyController: HistoryController, + private val historyController: ChatHistoryController, private val exchange: Exchange, private val chatService: ChatServiceV2, private val conversationMapper: ConversationMapper, diff --git a/api/src/main/java/com/getcode/network/api/CurrencyApi.kt b/api/src/main/java/com/getcode/network/api/CurrencyApi.kt index b9ed682e8..cd433cb1d 100644 --- a/api/src/main/java/com/getcode/network/api/CurrencyApi.kt +++ b/api/src/main/java/com/getcode/network/api/CurrencyApi.kt @@ -7,16 +7,18 @@ import io.grpc.ManagedChannel import io.reactivex.rxjava3.core.Scheduler import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn import javax.inject.Inject class CurrencyApi @Inject constructor( managedChannel: ManagedChannel, - private val scheduler: Scheduler = Schedulers.io() ) : GrpcApi(managedChannel) { private val api = CurrencyGrpc.newStub(managedChannel) - fun getRates(request: CurrencyService.GetAllRatesRequest = CurrencyService.GetAllRatesRequest.getDefaultInstance()): Single = + fun getRates(request: CurrencyService.GetAllRatesRequest = CurrencyService.GetAllRatesRequest.getDefaultInstance()): Flow = api::getAllRates - .callAsSingle(request) - .subscribeOn(scheduler) + .callAsCancellableFlow(request) + .flowOn(Dispatchers.IO) } diff --git a/api/src/main/java/com/getcode/network/exchange/Exchange.kt b/api/src/main/java/com/getcode/network/exchange/Exchange.kt index 604c813ee..c2c6d4242 100644 --- a/api/src/main/java/com/getcode/network/exchange/Exchange.kt +++ b/api/src/main/java/com/getcode/network/exchange/Exchange.kt @@ -9,9 +9,12 @@ import com.getcode.model.Rate import com.getcode.network.api.CurrencyApi import com.getcode.network.core.NetworkOracle import com.getcode.network.repository.PrefRepository +import com.getcode.network.service.ApiRateResult +import com.getcode.network.service.CurrencyService import com.getcode.utils.ErrorUtils import com.getcode.utils.TraceType import com.getcode.utils.format +import com.getcode.utils.network.retryable import com.getcode.utils.trace import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -29,6 +32,7 @@ import timber.log.Timber import java.util.Date import javax.inject.Inject import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException import kotlin.time.Duration.Companion.convert import kotlin.time.Duration.Companion.minutes import kotlin.time.DurationUnit @@ -88,8 +92,7 @@ class ExchangeNull : Exchange { } class CodeExchange @Inject constructor( - private val currencyApi: CurrencyApi, - private val networkOracle: NetworkOracle, + private val currencyService: CurrencyService, prefs: PrefRepository, private val preferredCurrency: suspend () -> Currency?, private val defaultCurrency: suspend () -> Currency?, @@ -126,6 +129,7 @@ class CodeExchange @Inject constructor( private val isStale: Boolean get() { + if (rates.rates.isEmpty()) return true // Remember, the exchange rates date is the server-provided // date-of-rate and not the time the rate was fetched. It // might be reasonable for the server to return a date that @@ -145,7 +149,7 @@ class CodeExchange @Inject constructor( .mapNotNull { preferred -> preferred ?: CurrencyCode.tryValueOf(defaultCurrency()?.code.orEmpty()) }.onEach { setEntryCurrency(it) } - .launchIn(this) + .launchIn(this@CodeExchange) } launch { @@ -169,22 +173,26 @@ class CodeExchange @Inject constructor( override suspend fun fetchRatesIfNeeded() { if (isStale) { - runCatching { fetchExchangeRates() } - .onSuccess { (updatedRates, date) -> - db?.exchangeDao()?.insert(rates = updatedRates, syncedAt = date) - set(RatesBox(date, updatedRates)) - }.onFailure { - it.printStackTrace() + retryable( + call = { + currencyService.getRates() + .onSuccess { (updatedRates, date) -> + db?.exchangeDao()?.insert(rates = updatedRates, syncedAt = date) + set(RatesBox(date, updatedRates)) + } } + ) } + + updateRates() } - private suspend fun setEntryCurrency(currency: CurrencyCode) { + private fun setEntryCurrency(currency: CurrencyCode) { entryCurrency = currency updateRates() } - private suspend fun setLocalCurrency(currency: CurrencyCode) { + private fun setLocalCurrency(currency: CurrencyCode) { localCurrency = currency updateRates() } @@ -211,86 +219,67 @@ class CodeExchange @Inject constructor( override fun rateForUsd(): Rate? = rates.rateForUsd() - private suspend fun updateRates() { + private fun updateRates() { if (rates.isEmpty) { return } val localRate = localCurrency?.let { rates.rateFor(it) } - _localRate.value = if (localRate != null) { - trace( - message = "Updated the local currency: $localCurrency, " + - "Staleness ${System.currentTimeMillis() - rates.dateMillis} ms, " + - "Date: ${Date(rates.dateMillis)}", - type = TraceType.Silent - ) - localRate - } else { - trace( - message = "local:: Rate for $localCurrency not found. Defaulting to USD.", - type = TraceType.Silent - ) - rates.rateForUsd()!! + val localChanged = _localRate.value != localRate + if (localChanged) { + _localRate.value = if (localRate != null) { + trace( + tag = "Background", + message = "Updated the local currency: $localCurrency, " + + "Staleness ${System.currentTimeMillis() - rates.dateMillis} ms, " + + "Date: ${Date(rates.dateMillis)}", + type = TraceType.Process + ) + localRate + } else { + trace( + tag = "Background", + message = "local:: Rate for $localCurrency not found. Defaulting to USD.", + type = TraceType.Process + ) + rates.rateForUsd()!! + } } val entryRate = entryCurrency?.let { rates.rateFor(it) } - _entryRate.value = if (entryRate != null) { - trace( - message = "Updated the entry currency: $entryCurrency, " + - "Staleness ${System.currentTimeMillis() - rates.dateMillis} ms, " + - "Date: ${Date(rates.dateMillis)}", - type = TraceType.Silent - ) - entryRate - } else { - trace( - message = "entry:: Rate for $entryCurrency not found. Defaulting to USD.", - type = TraceType.Silent - ) - rates.rateForUsd()!! - } - - trace(tag = "Background", - message = "Updated rates", - type = TraceType.Process, - metadata = { - "date" to Instant.fromEpochMilliseconds(rates.dateMillis).format("yyyy-MM-dd HH:mm:ss") + val entryChanged = _entryRate.value != entryRate + if (entryChanged) { + _entryRate.value = if (entryRate != null) { + trace( + tag = "Background", + message = "Updated the entry currency: $entryCurrency, " + + "Staleness ${System.currentTimeMillis() - rates.dateMillis} ms, " + + "Date: ${Date(rates.dateMillis)}", + type = TraceType.Process + ) + entryRate + } else { + trace( + tag = "Background", + message = "entry:: Rate for $entryCurrency not found. Defaulting to USD.", + type = TraceType.Process + ) + rates.rateForUsd()!! } - ) - - } + } - @OptIn(ExperimentalTime::class) - @SuppressLint("CheckResult") - private suspend fun fetchExchangeRates() = suspendCancellableCoroutine { cont -> - Timber.d("fetching rates") - currencyApi.getRates() - .let { networkOracle.managedRequest(it) } - .subscribe({ response -> - val rates = response.ratesMap.mapNotNull { (key, value) -> - val currency = CurrencyCode.tryValueOf(key) ?: return@mapNotNull null - Rate(fx = value, currency = currency) - }.toMutableList() - - if (rates.none { it.currency == CurrencyCode.KIN }) { - rates.add(Rate(fx = 1.0, currency = CurrencyCode.KIN)) + if (localChanged || entryChanged) { + trace(tag = "Background", + message = "Updated rates", + type = TraceType.Process, + metadata = { + "date" to Instant.fromEpochMilliseconds(rates.dateMillis) + .format("yyyy-MM-dd HH:mm:ss") } - - cont.resume( - rates.toList() to convert( - value = response.asOf.seconds.toDouble(), - sourceUnit = DurationUnit.SECONDS, - targetUnit = DurationUnit.MILLISECONDS - ).toLong() - ) - }, { - ErrorUtils.handleError(it) - cont.resume(emptyList() to System.currentTimeMillis()) - }) + ) + } } - - } private data class RatesBox(val dateMillis: Long, val rates: Map) { diff --git a/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt b/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt index ba9c121ef..7d6a9f0da 100644 --- a/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/BetaFlagsRepository.kt @@ -15,8 +15,8 @@ data class BetaOptions( val buyModuleEnabled: Boolean, val chatUnsubEnabled: Boolean, val tipsEnabled: Boolean, - val tipsChatEnabled: Boolean, - val tipsChatCashEnabled: Boolean, + val conversationsEnabled: Boolean, + val conversationCashEnabled: Boolean, val balanceCurrencySelectionEnabled: Boolean, val kadoWebViewEnabled: Boolean, val shareTweetToTip: Boolean, @@ -34,8 +34,8 @@ data class BetaOptions( buyModuleEnabled = true, chatUnsubEnabled = false, tipsEnabled = true, - tipsChatEnabled = false, - tipsChatCashEnabled = false, + conversationsEnabled = false, + conversationCashEnabled = false, balanceCurrencySelectionEnabled = true, kadoWebViewEnabled = false, shareTweetToTip = false, @@ -70,8 +70,8 @@ class BetaFlagsRepository @Inject constructor( observeBetaFlag(PrefsBool.BUY_MODULE_ENABLED, default = defaults.buyModuleEnabled), observeBetaFlag(PrefsBool.CHAT_UNSUB_ENABLED, default = defaults.chatUnsubEnabled), observeBetaFlag(PrefsBool.TIPS_ENABLED, default = defaults.tipsEnabled), - observeBetaFlag(PrefsBool.TIPS_CHAT_ENABLED, default = defaults.tipsChatEnabled), - observeBetaFlag(PrefsBool.TIPS_CHAT_CASH_ENABLED, default = defaults.tipsChatCashEnabled), + observeBetaFlag(PrefsBool.CONVERSATIONS_ENABLED, default = defaults.conversationsEnabled), + observeBetaFlag(PrefsBool.CONVERSATION_CASH_ENABLED, default = defaults.conversationCashEnabled), observeBetaFlag(PrefsBool.BALANCE_CURRENCY_SELECTION_ENABLED, defaults.balanceCurrencySelectionEnabled), observeBetaFlag(PrefsBool.DISPLAY_ERRORS, default = defaults.displayErrors), observeBetaFlag(PrefsBool.KADO_WEBVIEW_ENABLED, default = defaults.kadoWebViewEnabled), @@ -87,8 +87,8 @@ class BetaFlagsRepository @Inject constructor( buyModuleEnabled = it[5], chatUnsubEnabled = it[6], tipsEnabled = it[7], - tipsChatEnabled = it[8], - tipsChatCashEnabled = it[9], + conversationsEnabled = it[8], + conversationCashEnabled = it[9], balanceCurrencySelectionEnabled = it[10], displayErrors = it[11], kadoWebViewEnabled = it[12], diff --git a/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt b/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt index b6a0e3af7..680b157fc 100644 --- a/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/FeatureRepository.kt @@ -1,15 +1,13 @@ package com.getcode.network.repository import com.getcode.model.BalanceCurrencyFeature -import com.getcode.model.BetaFlag import com.getcode.model.BuyModuleFeature -import com.getcode.model.Feature import com.getcode.model.PrefsBool import com.getcode.model.RequestKinFeature import com.getcode.model.TipCardFeature import com.getcode.model.TipCardOnHomeScreenFeature -import com.getcode.model.TipChatCashFeature -import com.getcode.model.TipChatFeature +import com.getcode.model.ConversationCashFeature +import com.getcode.model.ConversationsFeature import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -28,8 +26,8 @@ class FeatureRepository @Inject constructor( val tipCards = betaFlags.observe().map { TipCardFeature(it.tipsEnabled) } val tipCardOnHomeScreen = betaFlags.observe().map { TipCardOnHomeScreenFeature(it.tipCardOnHomeScreen) } - val tipChat = betaFlags.observe().map { TipChatFeature(it.tipsChatEnabled) } - val tipChatCash = betaFlags.observe().map { TipChatCashFeature(it.tipsChatCashEnabled) } + val conversations = betaFlags.observe().map { ConversationsFeature(it.conversationsEnabled) } + val conversationsCash = betaFlags.observe().map { ConversationCashFeature(it.conversationCashEnabled) } val requestKin = betaFlags.observe().map { RequestKinFeature(it.giveRequestsEnabled) } diff --git a/api/src/main/java/com/getcode/network/service/CurrencyService.kt b/api/src/main/java/com/getcode/network/service/CurrencyService.kt new file mode 100644 index 000000000..fd99cda8e --- /dev/null +++ b/api/src/main/java/com/getcode/network/service/CurrencyService.kt @@ -0,0 +1,54 @@ +package com.getcode.network.service + +import com.getcode.model.CurrencyCode +import com.getcode.model.Rate +import com.getcode.network.api.CurrencyApi +import com.getcode.network.core.NetworkOracle +import com.getcode.utils.ErrorUtils +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import timber.log.Timber +import javax.inject.Inject +import kotlin.time.Duration.Companion.convert +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime + +data class ApiRateResult( + val rates: List, + val dateMillis: Long, +) + +class CurrencyService @Inject constructor( + private val api: CurrencyApi, + private val networkOracle: NetworkOracle, +) { + @OptIn(ExperimentalTime::class) + suspend fun getRates(): Result { + Timber.d("fetching rates") + return try { + networkOracle.managedRequest(api.getRates()) + .map { response -> + val rates = response.ratesMap.mapNotNull { (key, value) -> + val currency = CurrencyCode.tryValueOf(key) ?: return@mapNotNull null + Rate(fx = value, currency = currency) + }.toMutableList() + + if (rates.none { it.currency == CurrencyCode.KIN }) { + rates.add(Rate(fx = 1.0, currency = CurrencyCode.KIN)) + } + + Result.success(ApiRateResult( + rates = rates.toList(), + dateMillis = convert( + value = response.asOf.seconds.toDouble(), + sourceUnit = DurationUnit.SECONDS, + targetUnit = DurationUnit.MILLISECONDS + ).toLong() + )) + }.first() + } catch (e: Exception) { + ErrorUtils.handleError(e) + Result.failure(e) + } + } +} \ No newline at end of file diff --git a/api/src/main/java/com/getcode/utils/network/Retry.kt b/api/src/main/java/com/getcode/utils/network/Retry.kt new file mode 100644 index 000000000..e5c6128e0 --- /dev/null +++ b/api/src/main/java/com/getcode/utils/network/Retry.kt @@ -0,0 +1,58 @@ +package com.getcode.utils.network + +import com.getcode.utils.TraceType +import com.getcode.utils.trace +import kotlinx.coroutines.delay +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.TimeSource + +suspend fun retryable( + call: suspend () -> T, + maxRetries: Int = 3, + delayDuration: Duration = 2.seconds, + onRetry: (Int) -> Unit = { currentAttempt -> + trace( + message = "Retrying call", + metadata = { + "count" to currentAttempt + }, + type = TraceType.Process, + ) + }, + onError: (startTime: TimeSource.Monotonic.ValueTimeMark) -> Unit = { startTime -> + trace( + "Failed to get a success after $maxRetries attempts in ${startTime.elapsedNow().inWholeMilliseconds} ms", + type = TraceType.Error + ) + }, +): T? { + var currentAttempt = 0 + val startTime = TimeSource.Monotonic.markNow() + + while (currentAttempt < maxRetries) { + val result = try { + call() + } catch (e: Exception) { + trace( + message = "Attempt $currentAttempt failed with exception: ${e.message}", + error = e, + type = TraceType.Error + ) + null + } + + if (result != null) { + return result + } else { + currentAttempt++ + if (currentAttempt < maxRetries) { + onRetry(currentAttempt) + delay(delayDuration.inWholeMilliseconds) + } + } + } + + onError(startTime) + return null +} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c99c148b3..f6196b92d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -116,6 +116,7 @@ dependencies { implementation(project(":api")) implementation(project(":crypto:ed25519")) + implementation(project(":crypto:kin")) implementation(project(":common:resources")) implementation(project(":common:theme")) implementation(project(":vendor:tipkit:tipkit-m2")) diff --git a/app/src/main/java/com/getcode/inject/ApiModule.kt b/app/src/main/java/com/getcode/inject/ApiModule.kt index 9a7648902..f503bba84 100644 --- a/app/src/main/java/com/getcode/inject/ApiModule.kt +++ b/app/src/main/java/com/getcode/inject/ApiModule.kt @@ -31,6 +31,7 @@ import com.getcode.util.AccountAuthenticator import com.getcode.util.locale.LocaleHelper import com.getcode.utils.network.NetworkConnectivityListener import com.getcode.network.service.ChatServiceV2 +import com.getcode.network.service.CurrencyService import com.getcode.network.service.DeviceService import com.getcode.util.CurrencyUtils import com.getcode.util.resources.ResourceHelper @@ -191,14 +192,12 @@ object ApiModule { @Singleton @Provides fun providesExchange( - currencyApi: CurrencyApi, - networkOracle: NetworkOracle, + currencyService: CurrencyService, locale: LocaleHelper, currencyUtils: CurrencyUtils, prefRepository: PrefRepository, ): Exchange = CodeExchange( - currencyApi = currencyApi, - networkOracle = networkOracle, + currencyService = currencyService, prefs = prefRepository, preferredCurrency = { val preferredCurrencyCode = prefRepository.get( diff --git a/app/src/main/java/com/getcode/inject/DataModule.kt b/app/src/main/java/com/getcode/inject/DataModule.kt index e22594d62..b02ccb473 100644 --- a/app/src/main/java/com/getcode/inject/DataModule.kt +++ b/app/src/main/java/com/getcode/inject/DataModule.kt @@ -4,8 +4,7 @@ import com.getcode.mapper.ConversationMapper import com.getcode.mapper.ConversationMessageWithContentMapper import com.getcode.network.ConversationController import com.getcode.network.ConversationStreamController -import com.getcode.network.HistoryController -import com.getcode.network.client.Client +import com.getcode.network.ChatHistoryController import com.getcode.network.exchange.Exchange import com.getcode.network.service.ChatServiceV2 import dagger.Module @@ -21,7 +20,7 @@ object DataModule { @Provides @Singleton fun providesConversationController( - historyController: HistoryController, + historyController: ChatHistoryController, chatServiceV2: ChatServiceV2, exchange: Exchange, conversationMapper: ConversationMapper, diff --git a/app/src/main/java/com/getcode/manager/AuthManager.kt b/app/src/main/java/com/getcode/manager/AuthManager.kt index 65ac31228..bdbec8c76 100644 --- a/app/src/main/java/com/getcode/manager/AuthManager.kt +++ b/app/src/main/java/com/getcode/manager/AuthManager.kt @@ -1,7 +1,6 @@ package com.getcode.manager import android.annotation.SuppressLint -import android.app.Activity import android.content.Context import com.bugsnag.android.Bugsnag import com.getcode.BuildConfig @@ -13,7 +12,7 @@ import com.getcode.model.AirdropType import com.getcode.model.PrefsBool import com.getcode.model.PrefsString import com.getcode.network.BalanceController -import com.getcode.network.HistoryController +import com.getcode.network.ChatHistoryController import com.getcode.network.exchange.Exchange import com.getcode.network.repository.BetaFlagsRepository import com.getcode.network.repository.IdentityRepository @@ -56,7 +55,7 @@ class AuthManager @Inject constructor( private val betaFlags: BetaFlagsRepository, private val exchange: Exchange, private val balanceController: BalanceController, - private val historyController: HistoryController, + private val historyController: ChatHistoryController, private val inMemoryDao: InMemoryDao, private val analytics: AnalyticsService, private val mnemonicManager: MnemonicManager, diff --git a/app/src/main/java/com/getcode/navigation/screens/ChatScreens.kt b/app/src/main/java/com/getcode/navigation/screens/ChatScreens.kt index e275e4be9..2f7959bb9 100644 --- a/app/src/main/java/com/getcode/navigation/screens/ChatScreens.kt +++ b/app/src/main/java/com/getcode/navigation/screens/ChatScreens.kt @@ -4,57 +4,40 @@ import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Icon import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.BubbleChart import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.zIndex import androidx.paging.compose.collectAsLazyPagingItems -import cafe.adriel.voyager.core.lifecycle.LifecycleEffect import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.screen.uniqueScreenKey import cafe.adriel.voyager.hilt.getViewModel -import coil3.compose.AsyncImage -import coil3.compose.LocalPlatformContext -import coil3.request.ImageRequest -import coil3.request.error import com.getcode.R import com.getcode.model.ID import com.getcode.model.chat.Reference import com.getcode.navigation.core.LocalCodeNavigator import com.getcode.theme.CodeTheme -import com.getcode.ui.components.SheetTitleDefaults -import com.getcode.ui.components.SheetTitleText -import com.getcode.ui.components.chat.AnonymousAvatar import com.getcode.ui.components.chat.UserAvatar import com.getcode.ui.components.chat.utils.localized -import com.getcode.ui.utils.getActivityScopedViewModel import com.getcode.util.formatDateRelatively -import com.getcode.view.main.balance.BalanceScreen -import com.getcode.view.main.balance.BalanceSheetViewModel import com.getcode.view.main.chat.ChatScreen import com.getcode.view.main.chat.ChatViewModel import com.getcode.view.main.chat.conversation.ChatConversationScreen import com.getcode.view.main.chat.conversation.ConversationViewModel -import com.getcode.view.main.home.HomeViewModel +import com.getcode.view.main.chat.list.ChatListScreen +import com.getcode.view.main.chat.list.ChatListViewModel import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -63,92 +46,23 @@ import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize -data object BalanceModal : ChatGraph, ModalRoot { +data object ChatListModal: ChatGraph, ModalRoot { + @IgnoredOnParcel override val key: ScreenKey = uniqueScreenKey - override val name: String - @Composable get() = stringResource(id = R.string.title_balance) + @Composable get() = stringResource(id = R.string.title_chat) @Composable override fun Content() { - val navigator = LocalCodeNavigator.current - - val viewModel = getActivityScopedViewModel() - val state by viewModel.stateFlow.collectAsState() - val isViewingBuckets by remember(state.isBucketDebuggerVisible) { - derivedStateOf { state.isBucketDebuggerVisible } - } - - val backButton = @Composable { - when { - isViewingBuckets -> SheetTitleDefaults.BackButton() - !isViewingBuckets && state.isBucketDebuggerEnabled -> { - Icon( - imageVector = Icons.Rounded.BubbleChart, - contentDescription = "", - tint = Color.White, - ) - } - - else -> Unit - } - } - ModalContainer( - navigator = navigator, - onLogoClicked = {}, - backButton = backButton, - backButtonEnabled = { isViewingBuckets || state.isBucketDebuggerEnabled }, - onBackClicked = when { - isViewingBuckets -> { - { - viewModel.dispatchEvent( - BalanceSheetViewModel.Event.OnDebugBucketsVisible(false) - ) - } - } - - state.isBucketDebuggerEnabled -> { - { - viewModel.dispatchEvent( - BalanceSheetViewModel.Event.OnDebugBucketsVisible(true) - ) - } - } - - else -> null - }, - closeButtonEnabled = close@{ - if (viewModel.stateFlow.value.isBucketDebuggerVisible) return@close false - if (navigator.isVisible) { - it is BalanceModal - } else { - navigator.progress > 0f - } - }, - onCloseClicked = null, + closeButtonEnabled = { it is ChatListModal }, ) { - BalanceScreen(state = state, dispatch = viewModel::dispatchEvent) + val viewModel = getViewModel() +// val conversations = viewModel.conversations.collectAsLazyPagingItems() + ChatListScreen(dispatch = {}) } - - LifecycleEffect( - onStarted = { - val disposedScreen = navigator.lastItem - if (disposedScreen !is BalanceModal) { - viewModel.dispatchEvent(BalanceSheetViewModel.Event.OnOpened) - } - }, - onDisposed = { - val disposedScreen = navigator.lastItem - if (disposedScreen !is BalanceModal) { - viewModel.dispatchEvent( - BalanceSheetViewModel.Event.OnDebugBucketsVisible(false) - ) - } - } - ) } } @@ -198,7 +112,6 @@ data class ChatMessageConversationScreen( @Composable override fun Content() { val navigator = LocalCodeNavigator.current - val homeViewModel = getViewModel() val vm = getViewModel() val state by vm.stateFlow.collectAsState() diff --git a/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt b/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt index e57ad689d..e3e5ab27d 100644 --- a/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt +++ b/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt @@ -1,8 +1,17 @@ package com.getcode.navigation.screens +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.BubbleChart import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.lifecycle.Lifecycle +import cafe.adriel.voyager.core.lifecycle.LifecycleEffect import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.screen.uniqueScreenKey import cafe.adriel.voyager.hilt.getViewModel @@ -10,12 +19,15 @@ import com.getcode.R import com.getcode.model.KinAmount import com.getcode.models.DeepLinkRequest import com.getcode.navigation.core.LocalCodeNavigator +import com.getcode.ui.components.SheetTitleDefaults import com.getcode.ui.utils.RepeatOnLifecycle import com.getcode.ui.utils.getActivityScopedViewModel import com.getcode.utils.trace import com.getcode.view.download.ShareDownloadScreen import com.getcode.view.main.account.AccountHome import com.getcode.view.main.account.AccountSheetViewModel +import com.getcode.view.main.balance.BalanceScreen +import com.getcode.view.main.balance.BalanceSheetViewModel import com.getcode.view.main.giveKin.GiveKinScreen import com.getcode.view.main.home.HomeScreen import com.getcode.view.main.home.HomeViewModel @@ -192,6 +204,96 @@ data object ShareDownloadLinkModal : MainGraph, ModalRoot { } } +@Parcelize +data object BalanceModal : ChatGraph, ModalRoot { + @IgnoredOnParcel + override val key: ScreenKey = uniqueScreenKey + + + override val name: String + @Composable get() = stringResource(id = R.string.title_balance) + + @Composable + override fun Content() { + val navigator = LocalCodeNavigator.current + + val viewModel = getActivityScopedViewModel() + val state by viewModel.stateFlow.collectAsState() + val isViewingBuckets by remember(state.isBucketDebuggerVisible) { + derivedStateOf { state.isBucketDebuggerVisible } + } + + val backButton = @Composable { + when { + isViewingBuckets -> SheetTitleDefaults.BackButton() + !isViewingBuckets && state.isBucketDebuggerEnabled -> { + Icon( + imageVector = Icons.Rounded.BubbleChart, + contentDescription = "", + tint = Color.White, + ) + } + + else -> Unit + } + } + + ModalContainer( + navigator = navigator, + onLogoClicked = {}, + backButton = backButton, + backButtonEnabled = { isViewingBuckets || state.isBucketDebuggerEnabled }, + onBackClicked = when { + isViewingBuckets -> { + { + viewModel.dispatchEvent( + BalanceSheetViewModel.Event.OnDebugBucketsVisible(false) + ) + } + } + + state.isBucketDebuggerEnabled -> { + { + viewModel.dispatchEvent( + BalanceSheetViewModel.Event.OnDebugBucketsVisible(true) + ) + } + } + + else -> null + }, + closeButtonEnabled = close@{ + if (viewModel.stateFlow.value.isBucketDebuggerVisible) return@close false + if (navigator.isVisible) { + it is BalanceModal + } else { + navigator.progress > 0f + } + }, + onCloseClicked = null, + ) { + BalanceScreen(state = state, dispatch = viewModel::dispatchEvent) + } + + LifecycleEffect( + onStarted = { + val disposedScreen = navigator.lastItem + if (disposedScreen !is BalanceModal) { + viewModel.dispatchEvent(BalanceSheetViewModel.Event.OnOpened) + } + }, + onDisposed = { + val disposedScreen = navigator.lastItem + if (disposedScreen !is BalanceModal) { + viewModel.dispatchEvent( + BalanceSheetViewModel.Event.OnDebugBucketsVisible(false) + ) + } + } + ) + } +} + @Composable fun AppScreen.OnScreenResult(block: (T) -> Unit) { diff --git a/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt b/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt index 2c8973560..f6c9c7303 100644 --- a/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt +++ b/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt @@ -17,7 +17,7 @@ import com.getcode.manager.SessionManager import com.getcode.model.notifications.NotificationType import com.getcode.model.notifications.parse import com.getcode.network.BalanceController -import com.getcode.network.HistoryController +import com.getcode.network.ChatHistoryController import com.getcode.network.TipController import com.getcode.network.repository.AccountRepository import com.getcode.network.repository.PushRepository @@ -73,7 +73,7 @@ class CodePushMessagingService : FirebaseMessagingService(), lateinit var balanceController: BalanceController @Inject - lateinit var historyController: HistoryController + lateinit var historyController: ChatHistoryController @Inject lateinit var tipController: TipController diff --git a/app/src/main/java/com/getcode/ui/components/CodeButton.kt b/app/src/main/java/com/getcode/ui/components/CodeButton.kt index 6bc5c515a..ea1c93525 100644 --- a/app/src/main/java/com/getcode/ui/components/CodeButton.kt +++ b/app/src/main/java/com/getcode/ui/components/CodeButton.kt @@ -38,9 +38,8 @@ enum class ButtonState { @Composable fun CodeButton( - modifier: Modifier = Modifier, - onClick: () -> Unit, text: String, + modifier: Modifier = Modifier, isLoading: Boolean = false, isSuccess: Boolean = false, enabled: Boolean = true, @@ -51,6 +50,7 @@ fun CodeButton( buttonState: ButtonState = ButtonState.Bordered, textColor: Color = Color.Unspecified, shape: Shape = CodeTheme.shapes.small, + onClick: () -> Unit, ) { CodeButton( modifier = modifier, diff --git a/app/src/main/java/com/getcode/ui/components/chat/ChatNode.kt b/app/src/main/java/com/getcode/ui/components/chat/ChatNode.kt index ee9f79a62..19e7de5ad 100644 --- a/app/src/main/java/com/getcode/ui/components/chat/ChatNode.kt +++ b/app/src/main/java/com/getcode/ui/components/chat/ChatNode.kt @@ -20,14 +20,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex -import coil3.compose.AsyncImage -import coil3.compose.LocalPlatformContext -import coil3.request.ImageRequest -import coil3.request.error import com.getcode.LocalBetaFlags -import com.getcode.R +import com.getcode.model.Conversation import com.getcode.model.chat.Chat import com.getcode.model.chat.MessageContent import com.getcode.theme.BrandLight @@ -38,7 +32,6 @@ import com.getcode.ui.components.chat.utils.localizedText import com.getcode.ui.utils.rememberedClickable import com.getcode.util.DateUtils import com.getcode.util.formatTimeRelatively -import java.util.UUID object ChatNodeDefaults { val UnreadIndicator: Color = Color(0xFF31BB00) @@ -62,7 +55,7 @@ fun ChatNode( horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x3), verticalAlignment = Alignment.CenterVertically ) { - if (betaFlags.tipsChatEnabled) { + if (betaFlags.conversationsEnabled) { val imageModifier = Modifier .size(CodeTheme.dimens.staticGrid.x10) .clip(CircleShape) diff --git a/app/src/main/java/com/getcode/ui/components/chat/TipChatActions.kt b/app/src/main/java/com/getcode/ui/components/chat/TipChatActions.kt index d79b196ef..6a1412f07 100644 --- a/app/src/main/java/com/getcode/ui/components/chat/TipChatActions.kt +++ b/app/src/main/java/com/getcode/ui/components/chat/TipChatActions.kt @@ -3,10 +3,6 @@ package com.getcode.ui.components.chat import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -25,7 +21,7 @@ internal fun TipChatActions( showTipActions: Boolean, openMessageChat: () -> Unit ) { - val tipChatsEnabled = LocalBetaFlags.current.tipsChatEnabled + val tipChatsEnabled = LocalBetaFlags.current.conversationsEnabled if (showTipActions) { if (tipChatsEnabled && contents.verb is Verb.ReceivedTip) { diff --git a/app/src/main/java/com/getcode/util/AccountUtils.kt b/app/src/main/java/com/getcode/util/AccountUtils.kt index 7c4c3a079..0d1c2b1d9 100644 --- a/app/src/main/java/com/getcode/util/AccountUtils.kt +++ b/app/src/main/java/com/getcode/util/AccountUtils.kt @@ -9,20 +9,17 @@ import android.os.HandlerThread import androidx.core.os.bundleOf import com.getcode.BuildConfig import com.getcode.utils.TraceType +import com.getcode.utils.network.retryable import com.getcode.utils.trace import io.reactivex.rxjava3.annotations.NonNull import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.subjects.SingleSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.datetime.Clock import kotlin.coroutines.resume -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -import kotlin.time.TimeSource object AccountUtils { @@ -55,7 +52,19 @@ object AccountUtils { val subject = SingleSubject.create>() return subject.doOnSubscribe { CoroutineScope(Dispatchers.IO).launch { - val result = getAccountInternal(context) + val result = retryable( + call = { getAccountNoActivity(context) }, + onRetry = { currentAttempt -> + trace( + tag = "Account", + message = "Retrying call", + metadata = { + "count" to currentAttempt + }, + type = TraceType.Process, + ) + } + ) subject.onSuccess(result ?: (null to null)) } } @@ -70,45 +79,6 @@ object AccountUtils { } } - private suspend fun getAccountInternal( - context: Context, - maxRetries: Int = 3, - delayDuration: Duration = 2.seconds - ): Pair? { - var currentAttempt = 0 - val startTime = TimeSource.Monotonic.markNow() - - while (currentAttempt < maxRetries) { - val result = try { - getAccountNoActivity(context) - } catch (e: Exception) { - trace(message = "Attempt $currentAttempt failed with exception: ${e.message}", error = e, type = TraceType.Error) - null - } - - if (result != null) { - return result - } else { - currentAttempt++ - if (currentAttempt < maxRetries) { - trace( - tag = "Account", - message = "Retrying login", - metadata = { - "count" to currentAttempt - }, - type = TraceType.Process, - ) - trace("Retrying after ${delayDuration.inWholeMilliseconds} ms...", type = TraceType.Log) - delay(delayDuration.inWholeMilliseconds) - } - } - } - - trace("Failed to get account after $maxRetries attempts in ${startTime.elapsedNow().inWholeMilliseconds} ms", type = TraceType.Error) - return null - } - private suspend fun getAccountNoActivity( context: Context ): Pair? = suspendCancellableCoroutine { cont -> @@ -152,6 +122,19 @@ object AccountUtils { } } - return getAccountInternal(context)?.first + return retryable( + call = { getAccountNoActivity(context) }, + onRetry = { currentAttempt -> + trace( + tag = "Account", + message = "Retrying call", + metadata = { + "count" to currentAttempt + }, + type = TraceType.Process, + ) + } + + )?.first } } \ No newline at end of file diff --git a/app/src/main/java/com/getcode/view/login/LoginHome.kt b/app/src/main/java/com/getcode/view/login/LoginHome.kt index 360665e9b..08933b03c 100644 --- a/app/src/main/java/com/getcode/view/login/LoginHome.kt +++ b/app/src/main/java/com/getcode/view/login/LoginHome.kt @@ -87,7 +87,7 @@ fun LoginHome( ) CodeButton( - Modifier + modifier = Modifier .fillMaxWidth() .constrainAs(buttonCreate) { top.linkTo(logo.bottom) //possibly remove!! @@ -99,7 +99,7 @@ fun LoginHome( buttonState = ButtonState.Filled, ) CodeButton( - Modifier + modifier = Modifier .fillMaxWidth() .constrainAs(buttonLogin) { top.linkTo(buttonCreate.bottom) diff --git a/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt b/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt index 544a079de..2e52c383c 100644 --- a/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt +++ b/app/src/main/java/com/getcode/view/main/account/BetaFlagsScreen.kt @@ -96,16 +96,16 @@ fun BetaFlagsScreen( state.tipCardOnHomeScreen, ), BetaFeature( - PrefsBool.TIPS_CHAT_ENABLED, - R.string.beta_tipchats, - stringResource(id = R.string.beta_tipchats_description), - state.tipsChatEnabled, + PrefsBool.CONVERSATIONS_ENABLED, + R.string.beta_conversations, + stringResource(id = R.string.beta_conversations_description), + state.conversationsEnabled, ), BetaFeature( - PrefsBool.TIPS_CHAT_CASH_ENABLED, - R.string.beta_tipchats_cash, - stringResource(id = R.string.beta_tipchats_cash_description), - state.tipsChatCashEnabled, + PrefsBool.CONVERSATION_CASH_ENABLED, + R.string.beta_conversations_cash, + stringResource(id = R.string.beta_conversations_cash_description), + state.conversationCashEnabled, ), BetaFeature( PrefsBool.KADO_WEBVIEW_ENABLED, @@ -161,7 +161,7 @@ private fun BetaOptions.canMutate(flag: PrefsBool): Boolean { PrefsBool.BUY_MODULE_ENABLED -> false PrefsBool.BALANCE_CURRENCY_SELECTION_ENABLED -> false PrefsBool.TIPS_ENABLED -> false - PrefsBool.TIPS_CHAT_CASH_ENABLED -> tipsChatEnabled + PrefsBool.CONVERSATION_CASH_ENABLED -> conversationsEnabled else -> true } } diff --git a/app/src/main/java/com/getcode/view/main/account/withdraw/AccountWithdrawSummaryViewModel.kt b/app/src/main/java/com/getcode/view/main/account/withdraw/AccountWithdrawSummaryViewModel.kt index 13ad7d0be..ba0f21248 100644 --- a/app/src/main/java/com/getcode/view/main/account/withdraw/AccountWithdrawSummaryViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/account/withdraw/AccountWithdrawSummaryViewModel.kt @@ -15,7 +15,7 @@ import com.getcode.model.Rate import com.getcode.navigation.core.CodeNavigator import com.getcode.navigation.screens.HomeScreen import com.getcode.navigation.screens.WithdrawalArgs -import com.getcode.network.HistoryController +import com.getcode.network.ChatHistoryController import com.getcode.network.client.* import com.getcode.util.resources.ResourceHelper import com.getcode.utils.ErrorUtils @@ -44,7 +44,7 @@ data class AccountWithdrawSummaryUiModel( class AccountWithdrawSummaryViewModel @Inject constructor( private val analytics: AnalyticsService, private val client: Client, - private val historyController: HistoryController, + private val historyController: ChatHistoryController, private val resources: ResourceHelper, ) : BaseViewModel(resources) { val uiFlow = MutableStateFlow(AccountWithdrawSummaryUiModel()) diff --git a/app/src/main/java/com/getcode/view/main/balance/BalanceSheetViewModel.kt b/app/src/main/java/com/getcode/view/main/balance/BalanceSheetViewModel.kt index 20eafc7a2..03f43098f 100644 --- a/app/src/main/java/com/getcode/view/main/balance/BalanceSheetViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/balance/BalanceSheetViewModel.kt @@ -9,8 +9,7 @@ import com.getcode.model.Feature import com.getcode.model.PrefsBool import com.getcode.model.Rate import com.getcode.network.BalanceController -import com.getcode.network.HistoryController -import com.getcode.network.repository.BetaFlagsRepository +import com.getcode.network.ChatHistoryController import com.getcode.network.repository.FeatureRepository import com.getcode.network.repository.PrefRepository import com.getcode.util.Kin @@ -31,7 +30,7 @@ import javax.inject.Inject @HiltViewModel class BalanceSheetViewModel @Inject constructor( balanceController: BalanceController, - historyController: HistoryController, + historyController: ChatHistoryController, prefsRepository: PrefRepository, features: FeatureRepository, networkObserver: NetworkConnectivityListener, @@ -103,7 +102,7 @@ class BalanceSheetViewModel @Inject constructor( } .launchIn(viewModelScope) - historyController.chats + historyController.notifications .onEach { if (it == null || (it.isEmpty() && !networkObserver.isConnected)) { dispatchEvent(Dispatchers.Main, Event.OnChatsLoading(true)) @@ -126,7 +125,7 @@ class BalanceSheetViewModel @Inject constructor( eventFlow .filterIsInstance() - .filter { features.isEnabled(PrefsBool.TIPS_CHAT_ENABLED) } + .filter { features.isEnabled(PrefsBool.CONVERSATIONS_ENABLED) } .onEach { historyController.fetchChats(true) } .launchIn(viewModelScope) } diff --git a/app/src/main/java/com/getcode/view/main/chat/ChatViewModel.kt b/app/src/main/java/com/getcode/view/main/chat/ChatViewModel.kt index 673101e2f..1630fbd94 100644 --- a/app/src/main/java/com/getcode/view/main/chat/ChatViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/chat/ChatViewModel.kt @@ -12,7 +12,7 @@ import com.getcode.model.chat.Reference import com.getcode.model.chat.Title import com.getcode.model.chat.Verb import com.getcode.network.ConversationController -import com.getcode.network.HistoryController +import com.getcode.network.ChatHistoryController import com.getcode.network.repository.BetaFlagsRepository import com.getcode.network.repository.base58 import com.getcode.ui.components.chat.utils.ChatItem @@ -40,7 +40,7 @@ import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel class ChatViewModel @Inject constructor( - historyController: HistoryController, + historyController: ChatHistoryController, conversationController: ConversationController, betaFlags: BetaFlagsRepository, ) : BaseViewModel2( @@ -87,7 +87,7 @@ class ChatViewModel @Inject constructor( .onEach { Timber.d("chatid=${it?.base58}") } .filterNotNull() .onEach { historyController.advanceReadPointer(it) } - .flatMapLatest { historyController.chats } + .flatMapLatest { historyController.notifications } .flowOn(Dispatchers.IO) .filterNotNull() .mapNotNull { chats -> chats.firstOrNull { it.id == stateFlow.value.chatId } } diff --git a/app/src/main/java/com/getcode/view/main/chat/conversation/ConversationViewModel.kt b/app/src/main/java/com/getcode/view/main/chat/conversation/ConversationViewModel.kt index a7b67569e..f96c74a29 100644 --- a/app/src/main/java/com/getcode/view/main/chat/conversation/ConversationViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/chat/conversation/ConversationViewModel.kt @@ -12,12 +12,11 @@ import androidx.paging.map import com.getcode.BuildConfig import com.getcode.R import com.getcode.manager.BottomBarManager -import com.getcode.manager.TopBarManager import com.getcode.model.ConversationWithLastPointers import com.getcode.model.Feature import com.getcode.model.ID import com.getcode.model.MessageStatus -import com.getcode.model.TipChatCashFeature +import com.getcode.model.ConversationCashFeature import com.getcode.model.TwitterUser import com.getcode.model.chat.ChatType import com.getcode.model.chat.Platform @@ -35,16 +34,13 @@ import com.getcode.utils.timestamp import com.getcode.view.BaseViewModel2 import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -91,7 +87,7 @@ class ConversationViewModel @Inject constructor( val Default = State( conversationId = null, reference = null, - tipChatCash = TipChatCashFeature(), + tipChatCash = ConversationCashFeature(), title = "Anonymous Tipper", textFieldState = TextFieldState(), identityAvailable = false, @@ -186,7 +182,7 @@ class ConversationViewModel @Inject constructor( .onEach { dispatchEvent(Event.OnConversationChanged(it)) } .launchIn(viewModelScope) - features.tipChatCash + features.conversationsCash .onEach { dispatchEvent(Event.OnTipsChatCashChanged(it)) } .launchIn(viewModelScope) diff --git a/app/src/main/java/com/getcode/view/main/chat/list/ChatListScreen.kt b/app/src/main/java/com/getcode/view/main/chat/list/ChatListScreen.kt new file mode 100644 index 000000000..01c2210bb --- /dev/null +++ b/app/src/main/java/com/getcode/view/main/chat/list/ChatListScreen.kt @@ -0,0 +1,39 @@ +package com.getcode.view.main.chat.list + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.getcode.theme.CodeTheme +import com.getcode.ui.components.ButtonState +import com.getcode.ui.components.CodeButton +import com.getcode.ui.components.CodeScaffold + +@Composable +fun ChatListScreen( + dispatch: (ChatListViewModel.Event) -> Unit, +) { + CodeScaffold( + bottomBar = { + Box(modifier = Modifier.fillMaxWidth()) { + CodeButton( + modifier = Modifier.fillMaxWidth().padding(horizontal = CodeTheme.dimens.inset), + buttonState = ButtonState.Filled, + text = "Start a New Chat" + ) { + + } + } + } + ) { padding -> + LazyColumn(modifier = Modifier.padding(padding)) { +// items(conversations.itemCount) { index -> +// conversations[index]?.let { chat -> +// ChatNode(chat = chat) { } +// } +// } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/getcode/view/main/chat/list/ChatListViewModel.kt b/app/src/main/java/com/getcode/view/main/chat/list/ChatListViewModel.kt new file mode 100644 index 000000000..7be3733e6 --- /dev/null +++ b/app/src/main/java/com/getcode/view/main/chat/list/ChatListViewModel.kt @@ -0,0 +1,44 @@ +package com.getcode.view.main.chat.list + +import androidx.paging.PagingData +import androidx.paging.map +import com.getcode.model.Conversation +import com.getcode.network.ConversationController +import com.getcode.network.TipController +import com.getcode.view.BaseViewModel2 +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +@HiltViewModel +class ChatListViewModel @Inject constructor( + conversationController: ConversationController, +): BaseViewModel2( + initialState = State(), + updateStateForEvent = updateStateForEvent +) { + data class State( + val x: String = "" + ) + + sealed interface Event { + data object Noop: Event + } + + + companion object { + val updateStateForEvent: (Event) -> ((State) -> State) = { event -> + when (event) { + Event.Noop -> { state -> state } + } + } + } +} + +data class ConversationWithMetadata( + val conversation: Conversation, + val image: String?, + val latestMessage: String?, + val latestMessageMillis: Long? +) \ No newline at end of file diff --git a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt index ba6a17283..977e07c67 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt @@ -48,6 +48,7 @@ import com.getcode.navigation.screens.AccountModal import com.getcode.navigation.screens.BalanceModal import com.getcode.navigation.screens.BuyMoreKinModal import com.getcode.navigation.screens.BuySellScreen +import com.getcode.navigation.screens.ChatListModal import com.getcode.navigation.screens.ConnectAccount import com.getcode.navigation.screens.EnterTipModal import com.getcode.navigation.screens.GetKinModal @@ -83,7 +84,8 @@ enum class HomeAction { GET_KIN, BALANCE, SHARE_DOWNLOAD, - TIP_CARD + TIP_CARD, + CHAT } @Composable @@ -201,6 +203,7 @@ private fun HomeScan( } } HomeAction.NONE -> Unit + HomeAction.CHAT -> navigator.show(ChatListModal) } } } diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index c629b9906..e38d03990 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -31,8 +31,6 @@ import com.getcode.model.Kind import com.getcode.model.PrefsBool import com.getcode.model.Rate import com.getcode.model.RequestKinFeature -import com.getcode.model.TipCardFeature -import com.getcode.model.TipCardOnHomeScreenFeature import com.getcode.model.TwitterUser import com.getcode.model.Username import com.getcode.models.Bill @@ -46,7 +44,7 @@ import com.getcode.models.PaymentValuation import com.getcode.models.TipConfirmation import com.getcode.models.amountFloored import com.getcode.network.BalanceController -import com.getcode.network.HistoryController +import com.getcode.network.ChatHistoryController import com.getcode.network.TipController import com.getcode.network.client.Client import com.getcode.network.client.RemoteSendException @@ -64,6 +62,7 @@ import com.getcode.network.client.sendRequestToReceiveBill import com.getcode.network.exchange.Exchange import com.getcode.network.repository.AppSettingsRepository import com.getcode.network.repository.BetaFlagsRepository +import com.getcode.network.repository.BetaOptions import com.getcode.network.repository.FeatureRepository import com.getcode.network.repository.PaymentRepository import com.getcode.network.repository.PrefRepository @@ -82,7 +81,6 @@ import com.getcode.util.showNetworkError import com.getcode.util.vibration.Vibrator import com.getcode.utils.ErrorUtils import com.getcode.utils.TraceType -import com.getcode.utils.base64EncodedData import com.getcode.utils.catchSafely import com.getcode.utils.network.NetworkConnectivityListener import com.getcode.utils.nonce @@ -149,7 +147,6 @@ data class HomeUiModel( val chatUnreadCount: Int = 0, val buyModule: Feature = BuyModuleFeature(), val requestKin: Feature = RequestKinFeature(), - val tips: Feature = TipCardFeature(), val actions: List = listOf(HomeAction.GIVE_KIN, HomeAction.TIP_CARD, HomeAction.BALANCE), val tipCardConnected: Boolean = false, ) @@ -172,7 +169,7 @@ class HomeViewModel @Inject constructor( private val receiveTransactionRepository: ReceiveTransactionRepository, private val paymentRepository: PaymentRepository, private val balanceController: BalanceController, - private val historyController: HistoryController, + private val historyController: ChatHistoryController, private val tipController: TipController, private val prefRepository: PrefRepository, private val analytics: AnalyticsService, @@ -186,6 +183,7 @@ class HomeViewModel @Inject constructor( private val mnemonicManager: MnemonicManager, private val cashLinkManager: CashLinkManager, appSettings: AppSettingsRepository, + betaFlagsRepository: BetaFlagsRepository, features: FeatureRepository, ) : BaseViewModel(resources), ScreenModel { val uiFlow = MutableStateFlow(HomeUiModel()) @@ -208,26 +206,27 @@ class HomeViewModel @Inject constructor( }.launchIn(viewModelScope) features.buyModule + .distinctUntilChanged() .onEach { module -> uiFlow.update { it.copy(buyModule = module) } }.launchIn(viewModelScope) - features.tipCardOnHomeScreen - .onEach { module -> - uiFlow.update { - it.copy(actions = buildActions(module.enabled)) - } - }.launchIn(viewModelScope) - features.requestKin + .distinctUntilChanged() .onEach { module -> uiFlow.update { it.copy(requestKin = module) } }.launchIn(viewModelScope) + betaFlagsRepository.observe() + .distinctUntilChanged() + .onEach { betaFlags -> + uiFlow.update { it.copy(actions = buildActions(betaFlags)) } + }.launchIn(viewModelScope) + tipController.showTwitterSplat .filter { it } .onEach { delay(500) } @@ -375,17 +374,22 @@ class HomeViewModel @Inject constructor( } private fun buildActions( - tipCardOnHomeScreen: Boolean, + betaOptions: BetaOptions, ): List { - return listOf( - HomeAction.GIVE_KIN, - if (tipCardOnHomeScreen) { - HomeAction.TIP_CARD - } else { - HomeAction.GET_KIN - }, - HomeAction.BALANCE - ) + val actions = mutableListOf(HomeAction.GIVE_KIN) + actions += if (betaOptions.tipCardOnHomeScreen) { + HomeAction.TIP_CARD + } else { + HomeAction.GET_KIN + } + + if (betaOptions.conversationsEnabled) { + actions += HomeAction.CHAT + } + + actions += HomeAction.BALANCE + + return actions } fun onCameraScanning(scanning: Boolean) { diff --git a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt index 4f2b92032..25e7645f2 100644 --- a/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt +++ b/app/src/main/java/com/getcode/view/main/home/components/HomeBottom.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -16,7 +15,9 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.res.painterResource @@ -33,6 +34,7 @@ import com.getcode.ui.components.chat.ChatNodeDefaults import com.getcode.ui.utils.heightOrZero import com.getcode.ui.utils.unboundedClickable import com.getcode.ui.utils.widthOrZero +import com.getcode.util.resources.icons.AutoMirroredMessageCircle import com.getcode.view.main.home.HomeAction import com.getcode.view.main.home.HomeUiModel @@ -60,6 +62,7 @@ internal fun HomeBottom( onClick = { onPress(action) } ) } + HomeAction.GET_KIN -> { BottomBarAction( modifier = Modifier.weight(1f), @@ -68,6 +71,7 @@ internal fun HomeBottom( onClick = { onPress(action) }, ) } + HomeAction.BALANCE -> { BottomBarAction( modifier = Modifier.weight(1f), @@ -89,6 +93,7 @@ internal fun HomeBottom( } ) } + HomeAction.TIP_CARD -> { BottomBarAction( modifier = Modifier.weight(1f), @@ -97,7 +102,24 @@ internal fun HomeBottom( onClick = { onPress(action) }, ) } - else -> Unit + + HomeAction.CHAT -> { + BottomBarAction( + modifier = Modifier.weight(1f), + label = stringResource(R.string.action_chat), + painter = rememberVectorPainter(AutoMirroredMessageCircle), + onClick = { onPress(action) }, + ) + } + + else -> { + BottomBarAction( + modifier = Modifier.weight(1f).alpha(0f), + label = "", + painter = painterResource(R.drawable.ic_tip_card), + onClick = null, + ) + } } } } @@ -113,14 +135,14 @@ private fun BottomBarAction( painter: Painter, imageSize: Dp = CodeTheme.dimens.staticGrid.x10, badge: @Composable () -> Unit = { }, - onClick: () -> Unit, + onClick: (() -> Unit)?, ) { Layout( modifier = modifier, content = { Column( modifier = Modifier - .unboundedClickable(rippleRadius = imageSize) { onClick() } + .unboundedClickable(enabled = onClick != null, rippleRadius = imageSize) { onClick?.invoke() } .layoutId("action"), horizontalAlignment = Alignment.CenterHorizontally ) { @@ -149,7 +171,7 @@ private fun BottomBarAction( measurables.find { it.layoutId == "badge" }?.measure(constraints) val maxWidth = widthOrZero(actionPlaceable) - val maxHeight = heightOrZero(actionPlaceable) // + heightOrZero(badgePlaceable) / 2 + val maxHeight = heightOrZero(actionPlaceable) layout( width = maxWidth, height = maxHeight, @@ -161,4 +183,5 @@ private fun BottomBarAction( ) } } -} \ No newline at end of file +} + diff --git a/app/src/main/res/drawable/ic_chat.xml b/app/src/main/res/drawable/ic_chat.xml new file mode 100644 index 000000000..d1d8828da --- /dev/null +++ b/app/src/main/res/drawable/ic_chat.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings-universal.xml b/app/src/main/res/values/strings-universal.xml index 328c3d16c..9cbb2942c 100644 --- a/app/src/main/res/values/strings-universal.xml +++ b/app/src/main/res/values/strings-universal.xml @@ -21,8 +21,8 @@ Show Connectivity Status Tip Card Tip Card on Home Screen - Tip Chats - Tip Chats Cash + Chats + Cash Transfers in Chat Currency Selection in Balance Buy Kin Internally Share Tweets to Tip @@ -37,8 +37,8 @@ If enabled, an option to unsubscribe from a chat will appear for supported chats. If enabled, you\'ll gain the ability to share a tip card. If enabled, your tip card will replace Get Cash on the home screen. - If enabled, you\'ll gain the ability to chat with tippers. - If enabled, you\'ll gain the ability to send Kin in Tip Chats. + If enabled, you\'ll gain the ability to chat with with other code users. + If enabled, you\'ll gain the ability to send cash in conversations. If enabled, the Buy Kin flow will open in an internal WebView. If enabled, you\'ll gain the ability to share tweets directly from Twitter to Code to tip the author. %1$s %2$s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a52bd9cbd..192f7066e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,4 +49,7 @@ Connect Account Connecting your X account allows you to reveal your identity to the people you tip. To connect your account post to X. Use Kado Sandbox + + Chat + Chat diff --git a/common/resources/build.gradle.kts b/common/resources/build.gradle.kts index 8374f9d0d..e8cac4b91 100644 --- a/common/resources/build.gradle.kts +++ b/common/resources/build.gradle.kts @@ -14,6 +14,14 @@ android { testInstrumentationRunner = Android.testInstrumentationRunner } + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose_compiler + } + kotlinOptions { jvmTarget = Versions.java freeCompilerArgs += listOf( @@ -37,5 +45,7 @@ dependencies { api(Libs.kotlinx_coroutines_core) api(Libs.kotlinx_coroutines_rx3) - api(Libs.kin_sdk) + implementation(platform(Libs.compose_bom)) + implementation(Libs.compose_ui) + implementation(Libs.compose_foundation) } diff --git a/common/resources/src/main/java/com/getcode/util/resources/icons/ImageVector.kt b/common/resources/src/main/java/com/getcode/util/resources/icons/ImageVector.kt new file mode 100644 index 000000000..c7aaf8f29 --- /dev/null +++ b/common/resources/src/main/java/com/getcode/util/resources/icons/ImageVector.kt @@ -0,0 +1,77 @@ +package com.getcode.util.resources.icons + +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.VectorGroup +import androidx.compose.ui.graphics.vector.VectorNode +import androidx.compose.ui.graphics.vector.VectorPath +import androidx.compose.ui.graphics.vector.group + +fun ImageVector.Companion.copyFrom( + src: ImageVector, + mirror: Boolean = false, + rotation: Float = src.root.rotation, + pivotX: Float = src.defaultWidth.value / 2, + pivotY: Float = src.defaultHeight.value / 2, +) = ImageVector.Builder( + name = src.name, + defaultWidth = src.defaultWidth, + defaultHeight = src.defaultHeight, + viewportWidth = src.viewportWidth, + viewportHeight = src.viewportHeight, + tintColor = src.tintColor, + tintBlendMode = src.tintBlendMode, + autoMirror = src.autoMirror, +).addGroup( + src = src.root, + rotation = rotation, + pivotX = pivotX, + pivotY = pivotY, + scaleX = if (mirror) -1f else 1f, + scaleY = if (mirror) 1f else 1f, +).build() + +private fun ImageVector.Builder.addNode(node: VectorNode) { + when (node) { + is VectorGroup -> addGroup(node) + is VectorPath -> addPath(node) + } +} + +private fun ImageVector.Builder.addGroup( + src: VectorGroup, + rotation: Float = src.rotation, + pivotX: Float = src.pivotX, + pivotY: Float = src.pivotY, + scaleX: Float = src.scaleX, + scaleY: Float = src.scaleY, +) = apply { + group( + name = src.name, + rotate = rotation, + pivotX = pivotX, + pivotY = pivotY, + scaleX = scaleX, + scaleY = scaleY, + translationX = src.translationX, + translationY = src.translationY, + clipPathData = src.clipPathData, + ) { + src.forEach { addNode(it) } + } +} + +private fun ImageVector.Builder.addPath(src: VectorPath) = apply { + addPath( + pathData = src.pathData, + pathFillType = src.pathFillType, + name = src.name, + fill = src.fill, + fillAlpha = src.fillAlpha, + stroke = src.stroke, + strokeAlpha = src.strokeAlpha, + strokeLineWidth = src.strokeLineWidth, + strokeLineCap = src.strokeLineCap, + strokeLineJoin = src.strokeLineJoin, + strokeLineMiter = src.strokeLineMiter, + ) +} \ No newline at end of file diff --git a/common/resources/src/main/java/com/getcode/util/resources/icons/MessageCircle.kt b/common/resources/src/main/java/com/getcode/util/resources/icons/MessageCircle.kt new file mode 100644 index 000000000..961853221 --- /dev/null +++ b/common/resources/src/main/java/com/getcode/util/resources/icons/MessageCircle.kt @@ -0,0 +1,52 @@ +package com.getcode.util.resources.icons + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp + +private var _MessageCircle: ImageVector? = null +val MessageCircle: ImageVector + get() { + if (_MessageCircle != null) { + return _MessageCircle!! + } + _MessageCircle = ImageVector.Builder( + name = "MessageCircle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path( + fill = null, + fillAlpha = 1.0f, + stroke = SolidColor(Color(0xFFFFFFFF)), + strokeAlpha = 1.0f, + strokeLineWidth = 2f, + strokeLineCap = StrokeCap.Round, + strokeLineJoin = StrokeJoin.Round, + strokeLineMiter = 1.0f, + pathFillType = PathFillType.NonZero + ) { + moveTo(7.9f, 20f) + arcTo(9f, 9f, 0f, isMoreThanHalf = true, isPositiveArc = false, 4f, 16.1f) + lineTo(2f, 22f) + close() + } + }.build() + return _MessageCircle!! + } + +val AutoMirroredMessageCircle: ImageVector + @Composable get() = ImageVector.copyFrom(MessageCircle, mirror = LocalLayoutDirection.current == LayoutDirection.Ltr) + +val MirroredMessageCircle: ImageVector + get() = ImageVector.copyFrom(MessageCircle, mirror = true) \ No newline at end of file