From d6688189a0060137d4b5043ddf3bea8386ff5c06 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 11 May 2024 23:00:41 -0400 Subject: [PATCH 1/4] chore: add additional startupLog; move account/token fetch off main thread drop account reads from 2-3s on certain devices to 8-15ms Signed-off-by: Brandon McAnsh --- .../main/java/com/getcode/db/AppDatabase.kt | 4 +- .../com/getcode/manager/SessionManager.kt | 1 - .../java/com/getcode/network/TipController.kt | 17 +++- .../java/com/getcode/network/client/Client.kt | 19 +++- .../com/getcode/network/client/Client_Chat.kt | 8 +- .../main/java/com/getcode/utils/Logging.kt | 23 +++++ app/src/main/java/com/getcode/App.kt | 6 +- app/src/main/java/com/getcode/CodeApp.kt | 95 +++++++++++++++---- app/src/main/java/com/getcode/CodeAppState.kt | 6 +- .../com/getcode/manager/AccountManager.kt | 14 +++ .../java/com/getcode/manager/AuthManager.kt | 64 ++++++++----- .../navigation/screens/LoginScreens.kt | 13 ++- .../getcode/navigation/screens/MainScreens.kt | 2 +- .../com/getcode/navigation/screens/Modals.kt | 40 ++++---- .../notifications/CodePushMessagingService.kt | 2 +- .../com/getcode/ui/components/AuthCheck.kt | 9 +- .../ui/components/BottomBarContainer.kt | 2 +- .../getcode/ui/components/BottomBarView.kt | 1 + .../getcode/ui/components/TopBarContainer.kt | 5 +- .../ui/components/chat/DateWithStatus.kt | 38 +++----- .../com/getcode/util/AccountAuthenticator.kt | 4 +- .../java/com/getcode/util/AccountUtils.kt | 44 +++++---- .../java/com/getcode/view/MainActivity.kt | 35 +------ .../java/com/getcode/view/login/LoginHome.kt | 27 ++++-- .../getcode/view/login/SeedInputViewModel.kt | 16 ++++ .../com/getcode/view/main/bill/CashBill.kt | 22 ++--- .../view/main/home/components/CodeScanner.kt | 12 +-- 27 files changed, 329 insertions(+), 200 deletions(-) create mode 100644 api/src/main/java/com/getcode/utils/Logging.kt create mode 100644 app/src/main/java/com/getcode/manager/AccountManager.kt diff --git a/api/src/main/java/com/getcode/db/AppDatabase.kt b/api/src/main/java/com/getcode/db/AppDatabase.kt index b6489b986..4cb6d4be0 100644 --- a/api/src/main/java/com/getcode/db/AppDatabase.kt +++ b/api/src/main/java/com/getcode/db/AppDatabase.kt @@ -12,6 +12,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.getcode.model.* import com.getcode.network.repository.decodeBase64 +import com.getcode.utils.startupLog import com.getcode.vendor.Base58 import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.subjects.BehaviorSubject @@ -72,7 +73,7 @@ object Database { fun requireInstance() = instance!! fun init(context: Context, entropyB64: String) { - Timber.d("init") + startupLog("database init start") instance?.close() val dbUniqueName = Base58.encode(entropyB64.toByteArray().subByteArray(0, 3)) dbName = "$dbNamePrefix-$dbUniqueName$dbNameSuffix" @@ -88,6 +89,7 @@ object Database { instance?.conversationMessageRemoteKeyDao()?.clearRemoteKeys() isInitSubject.onNext(true) + startupLog("database init end") } fun close() { diff --git a/api/src/main/java/com/getcode/manager/SessionManager.kt b/api/src/main/java/com/getcode/manager/SessionManager.kt index e6dc5fd04..2f0409374 100644 --- a/api/src/main/java/com/getcode/manager/SessionManager.kt +++ b/api/src/main/java/com/getcode/manager/SessionManager.kt @@ -27,7 +27,6 @@ import javax.inject.Singleton @Singleton class SessionManager @Inject constructor( private val client: Client, - private val tipController: TipController ) { data class SessionState( val entropyB64: String? = null, diff --git a/api/src/main/java/com/getcode/network/TipController.kt b/api/src/main/java/com/getcode/network/TipController.kt index 6a1591fb7..8d14d3536 100644 --- a/api/src/main/java/com/getcode/network/TipController.kt +++ b/api/src/main/java/com/getcode/network/TipController.kt @@ -1,5 +1,10 @@ package com.getcode.network +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner import com.getcode.manager.SessionManager import com.getcode.model.CodePayload import com.getcode.model.TipMetadata @@ -38,7 +43,7 @@ typealias TipUser = Pair class TipController @Inject constructor( private val client: Client, private val prefRepository: PrefRepository, -) { +): LifecycleEventObserver { private var pollTimer: Timer? = null private var lastPoll: Long = 0L @@ -139,7 +144,15 @@ class TipController @Inject constructor( prefRepository.set(PrefsBool.SEEN_TIP_CARD, true) } - fun stopTimer() { + private fun stopTimer() { pollTimer?.cancel() } + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + when (event) { + Lifecycle.Event.ON_RESUME -> checkForConnection() + Lifecycle.Event.ON_STOP -> stopTimer() + else -> Unit + } + } } \ No newline at end of file diff --git a/api/src/main/java/com/getcode/network/client/Client.kt b/api/src/main/java/com/getcode/network/client/Client.kt index 5225766cf..32f5d0adc 100644 --- a/api/src/main/java/com/getcode/network/client/Client.kt +++ b/api/src/main/java/com/getcode/network/client/Client.kt @@ -1,6 +1,11 @@ package com.getcode.network.client import android.content.Context +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner import com.getcode.analytics.AnalyticsService import com.getcode.manager.SessionManager import com.getcode.network.BalanceController @@ -46,7 +51,7 @@ class Client @Inject constructor( internal val networkObserver: NetworkConnectivityListener, internal val chatService: ChatService, internal val deviceService: DeviceService, -) { +) : LifecycleEventObserver { private val TAG = "PollTimer" private val scope = CoroutineScope(Dispatchers.IO) @@ -96,7 +101,7 @@ class Client @Inject constructor( } } - fun startTimer() { + private fun startTimer() { startPollTimerWhenAuthenticated() } @@ -108,8 +113,16 @@ class Client @Inject constructor( } } - fun stopTimer() { + private fun stopTimer() { Timber.tag(TAG).i("Cancelling Poller") pollTimer?.cancel() } + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + when (event) { + Lifecycle.Event.ON_RESUME -> startTimer() + Lifecycle.Event.ON_STOP -> stopTimer() + else -> Unit + } + } } \ No newline at end of file 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 44f8b6462..48e4b5eec 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 @@ -31,15 +31,15 @@ suspend fun Client.setSubscriptionState(owner: KeyPair, chatId: ID, subscribed: suspend fun Client.fetchMessagesFor(owner: KeyPair, chat: Chat, cursor: Cursor? = null, limit: Int? = null) : Result> { return chatService.fetchMessagesFor(owner, chat.id, cursor, limit) - .map { + .mapCatching { val domain = if (chat.title is Title.Domain) { Domain.from(chat.title.value) } else { null - } ?: return@map it + } ?: return@mapCatching it - val organizer = SessionManager.getOrganizer() ?: return@map it - val relationship = organizer.relationshipFor(domain) ?: return@map it + val organizer = SessionManager.getOrganizer() ?: return@mapCatching it + val relationship = organizer.relationshipFor(domain) ?: return@mapCatching it val hasEncryptedContent = it.firstOrNull { it.hasEncryptedContent } != null if (hasEncryptedContent) { diff --git a/api/src/main/java/com/getcode/utils/Logging.kt b/api/src/main/java/com/getcode/utils/Logging.kt new file mode 100644 index 000000000..93db547b8 --- /dev/null +++ b/api/src/main/java/com/getcode/utils/Logging.kt @@ -0,0 +1,23 @@ +package com.getcode.utils + +import com.bugsnag.android.Bugsnag +import timber.log.Timber + + +fun startupLog(message: String, error: Throwable? = null) { + val tag = "app-startup" + + Timber.tag(tag).let { + if (error != null) { + it.e(error, message) + } else { + it.d(message) + } + } + + if (Bugsnag.isStarted()) { + Bugsnag.leaveBreadcrumb("$tag | $message") + } + + error?.let(ErrorUtils::handleError) +} \ No newline at end of file diff --git a/app/src/main/java/com/getcode/App.kt b/app/src/main/java/com/getcode/App.kt index ffa02ff71..35206746f 100644 --- a/app/src/main/java/com/getcode/App.kt +++ b/app/src/main/java/com/getcode/App.kt @@ -6,6 +6,7 @@ import com.bugsnag.android.Bugsnag import com.getcode.manager.AuthManager import com.getcode.network.integrity.DeviceCheck import com.getcode.utils.ErrorUtils +import com.getcode.utils.startupLog import com.getcode.view.main.bill.CashBillAssets import com.google.firebase.Firebase import com.google.firebase.initialize @@ -22,12 +23,12 @@ class App : Application() { override fun onCreate() { super.onCreate() - + println("app-startup onCreate start") CashBillAssets.load(this) Firebase.initialize(this) DeviceCheck.register(this) - authManager.init(this) + authManager.init() AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) @@ -61,5 +62,6 @@ class App : Application() { } else { Bugsnag.start(this) } + startupLog("app onCreate end") } } \ No newline at end of file diff --git a/app/src/main/java/com/getcode/CodeApp.kt b/app/src/main/java/com/getcode/CodeApp.kt index 0f7687eb3..f83c81dac 100644 --- a/app/src/main/java/com/getcode/CodeApp.kt +++ b/app/src/main/java/com/getcode/CodeApp.kt @@ -1,24 +1,36 @@ package com.getcode +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState 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.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.core.screen.ScreenKey +import cafe.adriel.voyager.core.screen.uniqueScreenKey import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.CurrentScreen import cafe.adriel.voyager.navigator.Navigator @@ -29,19 +41,19 @@ import com.getcode.navigation.core.BottomSheetNavigator import com.getcode.navigation.core.CombinedNavigator import com.getcode.navigation.core.LocalCodeNavigator import com.getcode.navigation.screens.LoginScreen -import com.getcode.navigation.screens.MainRoot import com.getcode.navigation.transitions.SheetSlideTransition -import com.getcode.network.repository.BetaOptions import com.getcode.theme.CodeTheme import com.getcode.theme.LocalCodeColors -import com.getcode.ui.utils.getActivity -import com.getcode.ui.utils.getActivityScopedViewModel -import com.getcode.ui.utils.measured import com.getcode.ui.components.AuthCheck import com.getcode.ui.components.BottomBarContainer +import com.getcode.ui.components.CodeCircularProgressIndicator import com.getcode.ui.components.CodeScaffold import com.getcode.ui.components.TitleBar import com.getcode.ui.components.TopBarContainer +import com.getcode.ui.utils.getActivity +import com.getcode.ui.utils.getActivityScopedViewModel +import com.getcode.ui.utils.measured +import kotlinx.coroutines.delay @Composable fun CodeApp() { @@ -108,22 +120,22 @@ fun CodeApp() { } } } + } - //Listen for authentication changes here - AuthCheck( - navigator = codeNavigator, - onNavigate = { screens -> - codeNavigator.replaceAll(screens, inSheet = false) - }, - onSwitchAccounts = { seed -> - activity?.let { - tlvm.logout(it) { - appState.navigator.replaceAll(LoginScreen(seed)) - } + //Listen for authentication changes here + AuthCheck( + navigator = codeNavigator, + onNavigate = { screens -> + codeNavigator.replaceAll(screens, inSheet = false) + }, + onSwitchAccounts = { seed -> + activity?.let { + tlvm.logout(it) { + appState.navigator.replaceAll(LoginScreen(seed)) } } - ) - } + } + ) } } @@ -175,4 +187,49 @@ private fun CrossfadeTransition( content = content, transition = { fadeIn() togetherWith fadeOut() } ) +} + +internal data object MainRoot : Screen { + + override val key: ScreenKey = uniqueScreenKey + + private fun readResolve(): Any = this + + @Composable + override fun Content() { + Box( + modifier = Modifier + .fillMaxSize() + .background(CodeTheme.colors.background), + contentAlignment = Alignment.Center + ) { + var show by remember { + mutableStateOf(false) + } + + AnimatedContent(show, transitionSpec = { fadeIn() togetherWith fadeOut() }) { + if (it) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.inset) + ) { + Image( + painter = painterResource(R.drawable.ic_code_logo_near_white), + contentDescription = "", + modifier = Modifier + .fillMaxWidth(0.65f) + .fillMaxHeight(0.65f) + ) + + CodeCircularProgressIndicator() + } + } + } + + LaunchedEffect(Unit) { + delay(1_000) + show = true + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/getcode/CodeAppState.kt b/app/src/main/java/com/getcode/CodeAppState.kt index 39045ecc2..705efe522 100644 --- a/app/src/main/java/com/getcode/CodeAppState.kt +++ b/app/src/main/java/com/getcode/CodeAppState.kt @@ -3,6 +3,7 @@ package com.getcode import androidx.compose.material.ScaffoldState import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -16,6 +17,7 @@ import com.getcode.navigation.screens.LoginPhoneVerificationScreen import com.getcode.navigation.screens.LoginScreen import com.getcode.navigation.screens.NamedScreen import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch /** @@ -82,8 +84,8 @@ class CodeAppState( ) } - val topBarMessage = MutableLiveData() - val bottomBarMessage = MutableLiveData() + val topBarMessage = MutableStateFlow(null) + val bottomBarMessage = MutableStateFlow(null) fun upPress() { diff --git a/app/src/main/java/com/getcode/manager/AccountManager.kt b/app/src/main/java/com/getcode/manager/AccountManager.kt new file mode 100644 index 000000000..eec72539f --- /dev/null +++ b/app/src/main/java/com/getcode/manager/AccountManager.kt @@ -0,0 +1,14 @@ +package com.getcode.manager + +import android.content.Context +import com.getcode.util.AccountUtils +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class AccountManager @Inject constructor( + @ApplicationContext private val context: Context +) { + suspend fun getToken(): String? { + return AccountUtils.getToken(context) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/getcode/manager/AuthManager.kt b/app/src/main/java/com/getcode/manager/AuthManager.kt index a48288fa3..e25ac6858 100644 --- a/app/src/main/java/com/getcode/manager/AuthManager.kt +++ b/app/src/main/java/com/getcode/manager/AuthManager.kt @@ -26,6 +26,7 @@ import com.getcode.network.repository.isMock import com.getcode.util.AccountUtils import com.getcode.utils.ErrorUtils import com.getcode.utils.installationId +import com.getcode.utils.startupLog import com.getcode.utils.token import com.google.firebase.Firebase import com.google.firebase.installations.installations @@ -57,11 +58,12 @@ class AuthManager @Inject constructor( private val historyController: HistoryController, private val inMemoryDao: InMemoryDao, private val analytics: AnalyticsService, -): CoroutineScope by CoroutineScope(Dispatchers.IO) { + private val mnemonicManager: MnemonicManager, +) : CoroutineScope by CoroutineScope(Dispatchers.IO) { private var softLoginDisabled: Boolean = false @SuppressLint("CheckResult") - fun init(context: Context, onInitialized: () -> Unit = { }) { + fun init(onInitialized: () -> Unit = { }) { launch { LibsodiumInitializer.initialize() val token = AccountUtils.getToken(context) @@ -76,8 +78,12 @@ class AuthManager @Inject constructor( return login(entropyB64, isSoftLogin = true) } - fun login(entropyB64: String, isSoftLogin: Boolean = false, rollbackOnError: Boolean = false): Completable { - Timber.i("Login: entropyB64: $entropyB64, isSoftLogin: $isSoftLogin, rollbackOnError: $rollbackOnError") + fun login( + entropyB64: String, + isSoftLogin: Boolean = false, + rollbackOnError: Boolean = false + ): Completable { + startupLog("Login: isSoftLogin: $isSoftLogin, rollbackOnError: $rollbackOnError") if (entropyB64.isEmpty()) { sessionManager.clear() @@ -98,19 +104,18 @@ class AuthManager @Inject constructor( loginAnalytics(entropyB64) } it.onSuccess(originalSessionState) - } - .flatMapCompletable { - val fetchData = fetchAdditionalAccountData(context, entropyB64, isSoftLogin, rollbackOnError, it) - if (isSoftLogin) { - fetchData.onErrorComplete { - ErrorUtils.handleError(it) - true - } - } else { - fetchData + }.flatMapCompletable { + val fetchData = + fetchAdditionalAccountData(context, entropyB64, isSoftLogin, rollbackOnError, it) + if (isSoftLogin) { + fetchData.onErrorComplete { + ErrorUtils.handleError(it) + true } + } else { + fetchData } - .doOnError { softLoginDisabled = false } + }.doOnError { softLoginDisabled = false } } private fun fetchAdditionalAccountData( @@ -137,10 +142,15 @@ class AuthManager @Inject constructor( } } .doOnError { - val isTimelockUnlockedException = it is AuthManagerException.TimelockUnlockedException + val isTimelockUnlockedException = + it is AuthManagerException.TimelockUnlockedException if (!isSoftLogin) { if (rollbackOnError) { - login(originalSessionState?.entropyB64.orEmpty(), isSoftLogin, rollbackOnError = false) + login( + originalSessionState?.entropyB64.orEmpty(), + isSoftLogin, + rollbackOnError = false + ) } else { clearToken() } @@ -184,6 +194,8 @@ class AuthManager @Inject constructor( private fun fetchData(context: Context, entropyB64: String): Single> { + startupLog("fetching account data") + var owner = SessionManager.authState.value.keyPair if (owner == null || SessionManager.authState.value.entropyB64 != entropyB64) { owner = MnemonicPhrase.fromEntropyB64(context, entropyB64).getSolanaKeyPair(context) @@ -214,16 +226,21 @@ class AuthManager @Inject constructor( .toSingleDefault(Pair(phone!!, user!!)) } .doOnSuccess { + startupLog("account data fetched successfully") launch { savePrefs(phone!!, user!!) } launch { exchange.fetchRatesIfNeeded() } launch { historyController.fetchChats() } - if (!BuildConfig.DEBUG) Bugsnag.setUser(null, phone?.phoneNumber, null) + if (!BuildConfig.DEBUG) { + if (Bugsnag.isStarted()) { + Bugsnag.setUser(null, phone?.phoneNumber, null) + } + } } } private fun loginAnalytics(entropyB64: String) { - val owner = MnemonicPhrase.fromEntropyB64(context, entropyB64) - .getSolanaKeyPair(context) + val owner = mnemonicManager.getKeyPair(entropyB64) + startupLog("analytics login event") analytics.login( ownerPublicKey = owner.getPublicKeyBase58(), autoCompleteCount = 0, @@ -241,6 +258,7 @@ class AuthManager @Inject constructor( Database.delete(context) if (!BuildConfig.DEBUG) Bugsnag.setUser(null, null, null) } + private suspend fun savePrefs( phone: PhoneRepository.GetAssociatedPhoneNumberResponse, user: IdentityRepository.GetUserResponse @@ -285,8 +303,8 @@ class AuthManager @Inject constructor( } } - sealed class AuthManagerException: Exception() { - class PhoneInvalidException: AuthManagerException() - class TimelockUnlockedException: AuthManagerException() + sealed class AuthManagerException : Exception() { + class PhoneInvalidException : AuthManagerException() + class TimelockUnlockedException : AuthManagerException() } } diff --git a/app/src/main/java/com/getcode/navigation/screens/LoginScreens.kt b/app/src/main/java/com/getcode/navigation/screens/LoginScreens.kt index c2471cddb..5dd84722d 100644 --- a/app/src/main/java/com/getcode/navigation/screens/LoginScreens.kt +++ b/app/src/main/java/com/getcode/navigation/screens/LoginScreens.kt @@ -9,7 +9,6 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey import cafe.adriel.voyager.hilt.getViewModel import com.getcode.R import com.getcode.navigation.core.LocalCodeNavigator -import com.getcode.ui.components.startupLog import com.getcode.ui.utils.getStackScopedViewModel import com.getcode.view.login.AccessKey import com.getcode.view.login.AccessKeyViewModel @@ -25,7 +24,6 @@ import com.getcode.view.login.SeedInput import com.getcode.view.login.SeedInputViewModel import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize -import timber.log.Timber @Parcelize data class LoginScreen(val seed: String? = null) : LoginGraph { @@ -37,11 +35,18 @@ data class LoginScreen(val seed: String? = null) : LoginGraph { @Composable override fun Content() { - startupLog("seed=$seed") + val navigator = LocalCodeNavigator.current if (seed != null) { SeedDeepLink(getViewModel(), seed) } else { - LoginHome() + LoginHome( + createAccount = { + navigator.push(LoginPhoneVerificationScreen(isNewAccount = true)) + }, + login = { + navigator.push(AccessKeyLoginScreen()) + } + ) } } } 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 f5e24fa8f..671a8aa5c 100644 --- a/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt +++ b/app/src/main/java/com/getcode/navigation/screens/MainScreens.kt @@ -12,9 +12,9 @@ import com.getcode.analytics.AnalyticsManager import com.getcode.analytics.AnalyticsScreenWatcher import com.getcode.model.KinAmount import com.getcode.navigation.core.LocalCodeNavigator -import com.getcode.ui.components.startupLog import com.getcode.ui.utils.RepeatOnLifecycle import com.getcode.ui.utils.getActivityScopedViewModel +import com.getcode.utils.startupLog import com.getcode.view.main.account.AccountHome import com.getcode.view.main.account.AccountSheetViewModel import com.getcode.view.main.giveKin.GiveKinScreen diff --git a/app/src/main/java/com/getcode/navigation/screens/Modals.kt b/app/src/main/java/com/getcode/navigation/screens/Modals.kt index f446aa426..d1d2c12ca 100644 --- a/app/src/main/java/com/getcode/navigation/screens/Modals.kt +++ b/app/src/main/java/com/getcode/navigation/screens/Modals.kt @@ -1,8 +1,11 @@ package com.getcode.navigation.screens +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column @@ -14,21 +17,29 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.painterResource import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.screen.uniqueScreenKey import com.getcode.LocalBetaFlags +import com.getcode.MainRoot +import com.getcode.R import com.getcode.TopLevelViewModel import com.getcode.navigation.core.CodeNavigator import com.getcode.navigation.core.LocalCodeNavigator import com.getcode.theme.CodeTheme +import com.getcode.ui.components.CodeCircularProgressIndicator import com.getcode.ui.components.SheetTitle import com.getcode.ui.components.SheetTitleText import com.getcode.ui.components.keyboardAsState @@ -116,17 +127,19 @@ internal fun NamedScreen.ModalContainer( SheetTitle( modifier = Modifier, title = { - titleString(this@ModalContainer)?.let { - SheetTitleText(text = it) - } ?: title() + titleString(this@ModalContainer)?.let { + SheetTitleText(text = it) + } ?: title() }, displayLogo = displayLogo, onLogoClicked = onLogoClicked, // hide while transitioning to/from other destinations backButton = isBackEnabled, closeButton = isCloseEnabled, - onBackIconClicked = onBackClicked?.let { { it() } } ?: { hideSheet { navigator.pop() } }, - onCloseIconClicked = onCloseClicked?.let { { it() } } ?: { hideSheet { navigator.hide() } } + onBackIconClicked = onBackClicked?.let { { it() } } + ?: { hideSheet { navigator.pop() } }, + onCloseIconClicked = onCloseClicked?.let { { it() } } + ?: { hideSheet { navigator.hide() } } ) Box( modifier = Modifier @@ -145,19 +158,4 @@ internal fun NamedScreen.ModalContainer( } internal interface ModalContent -internal sealed interface ModalRoot : ModalContent - -data object MainRoot : Screen { - override val key: ScreenKey = uniqueScreenKey - private fun readResolve(): Any = this - - @Composable - override fun Content() { - // TODO: potentially add a loading state here - // so app doesn't appear stuck in a dead state - // while we wait for auth check to complete - Box(modifier = Modifier - .fillMaxSize() - .background(CodeTheme.colors.background)) - } -} \ No newline at end of file +internal sealed interface ModalRoot : ModalContent \ 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 ca26b214d..87ccc37af 100644 --- a/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt +++ b/app/src/main/java/com/getcode/notifications/CodePushMessagingService.kt @@ -74,7 +74,7 @@ class CodePushMessagingService : FirebaseMessagingService(), if (SessionManager.isAuthenticated() == null) { // sodium initialized internally during init Timber.d("initializing session") - authManager.init(this) { + authManager.init { handleMessage(remoteMessage) } } else { 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 ee0dce80c..136c249c1 100644 --- a/app/src/main/java/com/getcode/ui/components/AuthCheck.kt +++ b/app/src/main/java/com/getcode/ui/components/AuthCheck.kt @@ -23,6 +23,7 @@ import com.getcode.navigation.screens.LoginScreen import com.getcode.util.DeeplinkHandler import com.getcode.util.DeeplinkResult import com.getcode.ui.utils.getActivity +import com.getcode.utils.startupLog import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -37,7 +38,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber -private const val APP_STARTUP_TAG = "app-startup" private typealias DeeplinkFlowState = Pair @Composable @@ -143,13 +143,6 @@ fun AuthCheck( } } -fun startupLog(message: String) { - Timber.tag(APP_STARTUP_TAG).d(message) - if (Bugsnag.isStarted()) { - Bugsnag.leaveBreadcrumb("$APP_STARTUP_TAG | $message") - } -} - private fun Flow.mapSeedToHome(): Flow = map { (data, auth) -> startupLog("checking type") diff --git a/app/src/main/java/com/getcode/ui/components/BottomBarContainer.kt b/app/src/main/java/com/getcode/ui/components/BottomBarContainer.kt index a25bc741e..266b8b036 100644 --- a/app/src/main/java/com/getcode/ui/components/BottomBarContainer.kt +++ b/app/src/main/java/com/getcode/ui/components/BottomBarContainer.kt @@ -17,7 +17,7 @@ import kotlin.concurrent.timerTask @Composable fun BottomBarContainer(appState: CodeAppState) { - val bottomBarMessage by appState.bottomBarMessage.observeAsState() + val bottomBarMessage by appState.bottomBarMessage.collectAsState() val bottomBarVisibleState = remember { MutableTransitionState(false) } var bottomBarMessageDismissId by remember { mutableLongStateOf(0L) } val onClose: (bottomBarActionType: BottomBarManager.BottomBarActionType?) -> Unit = { diff --git a/app/src/main/java/com/getcode/ui/components/BottomBarView.kt b/app/src/main/java/com/getcode/ui/components/BottomBarView.kt index 4c77c66d3..cc8db3abd 100644 --- a/app/src/main/java/com/getcode/ui/components/BottomBarView.kt +++ b/app/src/main/java/com/getcode/ui/components/BottomBarView.kt @@ -22,6 +22,7 @@ import com.getcode.theme.BrandLight import com.getcode.theme.CodeTheme import com.getcode.theme.White import com.getcode.ui.utils.rememberedClickable +import com.getcode.utils.startupLog @Composable fun BottomBarView( diff --git a/app/src/main/java/com/getcode/ui/components/TopBarContainer.kt b/app/src/main/java/com/getcode/ui/components/TopBarContainer.kt index 88986b49c..7c65e701f 100644 --- a/app/src/main/java/com/getcode/ui/components/TopBarContainer.kt +++ b/app/src/main/java/com/getcode/ui/components/TopBarContainer.kt @@ -33,7 +33,7 @@ import kotlin.concurrent.timerTask @Composable fun TopBarContainer(appState: CodeAppState) { - val topBarMessage by appState.topBarMessage.observeAsState() + val topBarMessage by appState.topBarMessage.collectAsState() val topBarVisibleState = remember { MutableTransitionState(false) } var topBarMessageDismissId by remember { mutableLongStateOf(0L) } @@ -155,7 +155,8 @@ private fun TopBarView( .height(CodeTheme.dimens.border) .background(Black10) ) - Row(modifier = Modifier.height(IntrinsicSize.Min) + Row(modifier = Modifier + .height(IntrinsicSize.Min) .drawBehind { val strokeWidth = Dp.Hairline.toPx() drawLine( diff --git a/app/src/main/java/com/getcode/ui/components/chat/DateWithStatus.kt b/app/src/main/java/com/getcode/ui/components/chat/DateWithStatus.kt index 6e50f9291..cb88bc13f 100644 --- a/app/src/main/java/com/getcode/ui/components/chat/DateWithStatus.kt +++ b/app/src/main/java/com/getcode/ui/components/chat/DateWithStatus.kt @@ -3,6 +3,7 @@ package com.getcode.ui.components.chat import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -79,7 +80,10 @@ internal fun DateWithStatus( @Composable private fun Preview_DateWithStatus() { CodeTheme { - Column { + @Composable + fun Bubble( + status: MessageStatus + ) { Box( modifier = Modifier .wrapContentWidth() @@ -89,30 +93,16 @@ private fun Preview_DateWithStatus() { ) .padding(CodeTheme.dimens.grid.x2) ) { - DateWithStatus(date = Clock.System.now(), status = MessageStatus.Sent) - } - Box( - modifier = Modifier - .wrapContentWidth() - .background( - color = ChatOutgoing, - shape = MessageNodeDefaults.DefaultShape - ) - .padding(CodeTheme.dimens.grid.x2) - ) { - DateWithStatus(date = Clock.System.now(), status = MessageStatus.Delivered) - } - Box( - modifier = Modifier - .wrapContentWidth() - .background( - color = ChatOutgoing, - shape = MessageNodeDefaults.DefaultShape - ) - .padding(CodeTheme.dimens.grid.x2) - ) { - DateWithStatus(date = Clock.System.now(), status = MessageStatus.Read) + DateWithStatus(date = Clock.System.now(), status = status) } } + + Column( + verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x3) + ) { + Bubble(status = MessageStatus.Sent) + Bubble(status = MessageStatus.Delivered) + Bubble(status = MessageStatus.Read) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/getcode/util/AccountAuthenticator.kt b/app/src/main/java/com/getcode/util/AccountAuthenticator.kt index ee55687a0..c4c3a0337 100644 --- a/app/src/main/java/com/getcode/util/AccountAuthenticator.kt +++ b/app/src/main/java/com/getcode/util/AccountAuthenticator.kt @@ -3,6 +3,7 @@ package com.getcode.util import android.accounts.* import android.content.Context import android.os.Bundle +import com.getcode.utils.startupLog class AccountAuthenticator(private val mContext: Context) : AbstractAccountAuthenticator(mContext) { @@ -33,7 +34,7 @@ class AccountAuthenticator(private val mContext: Context) : AbstractAccountAuthe // Extract the username and password from the Account Manager, then, generate token val am = AccountManager.get(mContext) var authToken = am.peekAuthToken(account, authTokenType) - + startupLog("authenticator: authToken ${authToken != null}") // Lets give another try to authenticate the user if (null != authToken) { if (authToken.isEmpty()) { @@ -55,6 +56,7 @@ class AccountAuthenticator(private val mContext: Context) : AbstractAccountAuthe } } + startupLog("authenticator failure", Throwable("Failed to retrieve authToken from AccountManager")) // If we get here, then we couldn't access the user's password return Bundle() } diff --git a/app/src/main/java/com/getcode/util/AccountUtils.kt b/app/src/main/java/com/getcode/util/AccountUtils.kt index 4fa1dcec1..e70df3727 100644 --- a/app/src/main/java/com/getcode/util/AccountUtils.kt +++ b/app/src/main/java/com/getcode/util/AccountUtils.kt @@ -2,7 +2,6 @@ package com.getcode.util import android.accounts.Account import android.accounts.AccountManager -import android.accounts.AccountManagerCallback import android.accounts.AuthenticatorException import android.app.Activity import android.content.Context @@ -10,15 +9,13 @@ import android.os.Bundle import android.os.Handler import android.os.HandlerThread import android.os.Looper -import com.getcode.App import com.getcode.BuildConfig -import com.getcode.ui.components.startupLog +import com.getcode.utils.startupLog 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.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.datetime.Clock @@ -84,11 +81,21 @@ object AccountUtils { } } + private val handler: Handler by lazy { Handler(handlerThread.looper) } + + private val handlerThread: HandlerThread by lazy { + HandlerThread("RenetikBackgroundThread").apply { + setUncaughtExceptionHandler { _, e -> run { throw RuntimeException(e) } } + start() + } + } + private suspend fun getAccountNoActivity(context: Context) : Pair? = suspendCancellableCoroutine {cont -> startupLog("getAuthToken") val am: AccountManager = AccountManager.get(context) val accountthing = am.accounts.getOrNull(0) if (accountthing == null) { + startupLog("no associated account found") cont.resume(null to null) return@suspendCancellableCoroutine } @@ -96,26 +103,25 @@ object AccountUtils { am.getAuthToken( accountthing, acctType, null, false, { future -> - CoroutineScope(Dispatchers.Default).launch { - try { - val bundle = future?.result - val authToken = bundle?.getString(AccountManager.KEY_AUTHTOKEN) - val accountName = bundle?.getString(AccountManager.KEY_ACCOUNT_NAME) - val account: Account? = getAccount(context, accountName) + try { + val bundle = future?.result + val authToken = bundle?.getString(AccountManager.KEY_AUTHTOKEN) + val accountName = bundle?.getString(AccountManager.KEY_ACCOUNT_NAME) + val account: Account? = getAccount(context, accountName) - val end = Clock.System.now() - startupLog("auth token fetch took ${end.toEpochMilliseconds() - start.toEpochMilliseconds()} ms") + val end = Clock.System.now() + startupLog("auth token fetch took ${end.toEpochMilliseconds() - start.toEpochMilliseconds()} ms") - cont.resume(authToken.orEmpty() to account) + cont.resume(authToken.orEmpty() to account) - if (null == account && authToken != null) { - addAccount(context, accountName.orEmpty(), "", authToken.orEmpty()) - } - } catch (e: AuthenticatorException) { - cont.resume(null to null) + if (null == account && authToken != null) { + addAccount(context, accountName.orEmpty(), "", authToken) } + } catch (e: AuthenticatorException) { + startupLog("failed to read account", e) + cont.resume(null to null) } - }, null + }, handler ) } diff --git a/app/src/main/java/com/getcode/view/MainActivity.kt b/app/src/main/java/com/getcode/view/MainActivity.kt index 8e198ab8a..1886465a6 100644 --- a/app/src/main/java/com/getcode/view/MainActivity.kt +++ b/app/src/main/java/com/getcode/view/MainActivity.kt @@ -14,40 +14,21 @@ import com.getcode.LocalExchange import com.getcode.LocalNetworkObserver import com.getcode.LocalPhoneFormatter import com.getcode.analytics.AnalyticsService -import com.getcode.manager.AuthManager -import com.getcode.manager.SessionManager -import com.getcode.network.TipController -import com.getcode.network.client.Client import com.getcode.network.exchange.Exchange -import com.getcode.network.repository.PrefRepository -import com.getcode.ui.components.startupLog +import com.getcode.ui.utils.handleUncaughtException import com.getcode.util.CurrencyUtils import com.getcode.util.DeeplinkHandler import com.getcode.util.PhoneUtils -import com.getcode.ui.utils.handleUncaughtException import com.getcode.util.vibration.LocalVibrator import com.getcode.util.vibration.Vibrator import com.getcode.utils.network.NetworkConnectivityListener +import com.getcode.utils.startupLog import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint class MainActivity : FragmentActivity() { - @Inject - lateinit var authManager: AuthManager - - @Inject - lateinit var sessionManager: SessionManager - - @Inject - lateinit var prefRepository: PrefRepository - - @Inject - lateinit var client: Client - - @Inject - lateinit var tipController: TipController @Inject lateinit var analyticsManager: AnalyticsService @@ -112,18 +93,6 @@ class MainActivity : FragmentActivity() { deeplinkHandler.debounceIntent = intent } - override fun onResume() { - super.onResume() - client.startTimer() - tipController.checkForConnection() - } - - override fun onPause() { - super.onPause() - client.stopTimer() - tipController.stopTimer() - } - private fun setFullscreen() { enableEdgeToEdge() } 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 10acc06f0..4cdef546c 100644 --- a/app/src/main/java/com/getcode/view/login/LoginHome.kt +++ b/app/src/main/java/com/getcode/view/login/LoginHome.kt @@ -42,11 +42,13 @@ import com.getcode.ui.components.CodeButton import com.getcode.ui.components.ImageWithBackground -@Preview + @Composable -fun LoginHome() { +fun LoginHome( + createAccount: () -> Unit, + login: () -> Unit, +) { val context = LocalContext.current - val navigator = LocalCodeNavigator.current Box { ConstraintLayout( @@ -92,9 +94,7 @@ fun LoginHome() { bottom.linkTo(buttonLogin.top) } .padding(horizontal = CodeTheme.dimens.inset), - onClick = { - navigator.push(LoginPhoneVerificationScreen(isNewAccount = true)) - }, + onClick = createAccount, text = stringResource(R.string.action_createAccount), buttonState = ButtonState.Filled, ) @@ -105,9 +105,7 @@ fun LoginHome() { top.linkTo(buttonCreate.bottom) } .padding(horizontal = CodeTheme.dimens.inset), - onClick = { - navigator.push(AccessKeyLoginScreen()) - }, + onClick = login, text = stringResource(R.string.action_logIn), buttonState = ButtonState.Subtle, ) @@ -169,3 +167,14 @@ fun LoginHome() { } } + +@Preview +@Composable +private fun Preview_Login() { + CodeTheme { + LoginHome( + createAccount = { }, + login = {} + ) + } +} diff --git a/app/src/main/java/com/getcode/view/login/SeedInputViewModel.kt b/app/src/main/java/com/getcode/view/login/SeedInputViewModel.kt index 2b3c38483..90290f277 100644 --- a/app/src/main/java/com/getcode/view/login/SeedInputViewModel.kt +++ b/app/src/main/java/com/getcode/view/login/SeedInputViewModel.kt @@ -2,10 +2,12 @@ package com.getcode.view.login import android.annotation.SuppressLint import android.app.Activity +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import com.getcode.App import com.getcode.R import com.getcode.crypt.MnemonicPhrase +import com.getcode.manager.AccountManager import com.getcode.manager.AuthManager import com.getcode.manager.BottomBarManager import com.getcode.manager.MnemonicManager @@ -13,7 +15,9 @@ import com.getcode.manager.TopBarManager import com.getcode.navigation.core.CodeNavigator import com.getcode.navigation.screens.HomeScreen import com.getcode.navigation.screens.LoginPhoneVerificationScreen +import com.getcode.util.AccountUtils import com.getcode.util.resources.ResourceHelper +import com.getcode.utils.ErrorUtils import com.getcode.view.* import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable @@ -39,10 +43,22 @@ class SeedInputViewModel @Inject constructor( private val authManager: AuthManager, private val resources: ResourceHelper, private val mnemonicManager: MnemonicManager, + private val accountManager: AccountManager, ) : BaseViewModel(resources) { val uiFlow = MutableStateFlow(SeedInputUiModel()) private val mnemonicCode = mnemonicManager.mnemonicCode + init { + viewModelScope.launch { + val token = accountManager.getToken() + if (token != null) { + ErrorUtils.handleError( + Throwable("We shouldn't be here. Login screen visible with associated account in AccountManager.") + ) + } + } + } + fun onTextChange(wordsString: String) { val isLoading = uiFlow.value.isLoading val isSuccess = uiFlow.value.isSuccess diff --git a/app/src/main/java/com/getcode/view/main/bill/CashBill.kt b/app/src/main/java/com/getcode/view/main/bill/CashBill.kt index 3561a99ed..8b6377c2c 100644 --- a/app/src/main/java/com/getcode/view/main/bill/CashBill.kt +++ b/app/src/main/java/com/getcode/view/main/bill/CashBill.kt @@ -92,17 +92,17 @@ object CashBillAssets { fun load(context: Context) { CoroutineScope(Dispatchers.IO).launch { - globe = getBitmapFromImage( - context = context, - drawable = R.drawable.ic_bill_globe, - ratio = 0.18f, - )?.asImageBitmap() - - grid = getBitmapFromImage( - context = context, - ratio = 0.3f, - drawable = R.drawable.ic_bill_grid, - )?.asImageBitmap() +// globe = getBitmapFromImage( +// context = context, +// drawable = R.drawable.ic_bill_globe, +// ratio = 0.18f, +// )?.asImageBitmap() +// +// grid = getBitmapFromImage( +// context = context, +// ratio = 0.3f, +// drawable = R.drawable.ic_bill_grid, +// )?.asImageBitmap() } } diff --git a/app/src/main/java/com/getcode/view/main/home/components/CodeScanner.kt b/app/src/main/java/com/getcode/view/main/home/components/CodeScanner.kt index ee58fac61..516fb4693 100644 --- a/app/src/main/java/com/getcode/view/main/home/components/CodeScanner.kt +++ b/app/src/main/java/com/getcode/view/main/home/components/CodeScanner.kt @@ -26,15 +26,13 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.asFlow import com.getcode.theme.CodeTheme -import com.getcode.ui.components.startupLog import com.getcode.ui.utils.AnimationUtils +import com.getcode.utils.startupLog import com.kik.kikx.kikcodes.implementation.KikCodeAnalyzer import com.kik.kikx.kikcodes.implementation.KikCodeScannerImpl import com.kik.kikx.models.ScannableKikCode import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -109,11 +107,9 @@ fun CodeScanner( .distinctUntilChanged() .onEach { Timber.d(it.name) } .onEach { streamState = it } - .map { - it.also { - val streaming = it == PreviewView.StreamState.STREAMING - onPreviewStateChanged(streaming) - } == PreviewView.StreamState.STREAMING + .onEach { + val streaming = it == PreviewView.StreamState.STREAMING + onPreviewStateChanged(streaming) } .launchIn(this) } From 4abeef37447914b10b1a6f6e3593386c8ac3e13d Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 11 May 2024 23:24:32 -0400 Subject: [PATCH 2/4] chore(home): allow handling cash links while camera is coming online Signed-off-by: Brandon McAnsh --- .../com/getcode/view/main/home/HomeScan.kt | 18 ++++++++---------- .../view/main/home/components/CodeScanner.kt | 8 ++------ 2 files changed, 10 insertions(+), 16 deletions(-) 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 270c4d59d..21c815dca 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 @@ -176,17 +176,15 @@ private fun HomeScan( focusManager.clearFocus() } - if (previewing) { - if (!deepLinkSaved.isNullOrBlank()) { - homeViewModel.openCashLink(deepLink) - deepLinkSaved = null - } + if (!deepLinkSaved.isNullOrBlank()) { + homeViewModel.openCashLink(deepLink) + deepLinkSaved = null + } - if (!requestPayloadSaved.isNullOrBlank() && dataState.balance != null) { - delay(500.milliseconds) - homeViewModel.handleRequest(requestPayload) - requestPayloadSaved = null - } + if (!requestPayloadSaved.isNullOrBlank() && dataState.balance != null) { + delay(500.milliseconds) + homeViewModel.handleRequest(requestPayload) + requestPayloadSaved = null } } diff --git a/app/src/main/java/com/getcode/view/main/home/components/CodeScanner.kt b/app/src/main/java/com/getcode/view/main/home/components/CodeScanner.kt index 516fb4693..7070d8301 100644 --- a/app/src/main/java/com/getcode/view/main/home/components/CodeScanner.kt +++ b/app/src/main/java/com/getcode/view/main/home/components/CodeScanner.kt @@ -34,7 +34,6 @@ import com.kik.kikx.models.ScannableKikCode import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext import timber.log.Timber @@ -110,8 +109,7 @@ fun CodeScanner( .onEach { val streaming = it == PreviewView.StreamState.STREAMING onPreviewStateChanged(streaming) - } - .launchIn(this) + }.launchIn(this) } AndroidView(factory = { previewView }, modifier = Modifier.fillMaxSize()) @@ -119,9 +117,7 @@ fun CodeScanner( AnimatedVisibility( modifier = Modifier.fillMaxSize(), visible = streamState != PreviewView.StreamState.STREAMING, - enter = fadeIn( - animationSpec = tween(AnimationUtils.animationTime / 2) - ), + enter = fadeIn(tween(AnimationUtils.animationTime / 2)), exit = fadeOut(tween(AnimationUtils.animationTime / 2)) ) { Box( From b48d18581088db99059e29386cb150c41f0a8e4d Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sun, 12 May 2024 00:18:27 -0400 Subject: [PATCH 3/4] chore: scope authenticator type to namespace prevent collision when dev installed alongside prod Signed-off-by: Brandon McAnsh --- app/build.gradle.kts | 2 ++ app/src/main/java/com/getcode/CodeApp.kt | 33 ++----------------- .../main/java/com/getcode/inject/ApiModule.kt | 12 +++---- .../com/getcode/util/AccountAuthenticator.kt | 15 ++++++--- app/src/main/res/xml/authenticator.xml | 2 +- 5 files changed, 21 insertions(+), 43 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e05884612..c3abe784a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,6 +37,8 @@ android { buildToolsVersion = Android.buildToolsVersion testInstrumentationRunner = Android.testInstrumentationRunner + resValue("string", "applicationId", Android.namespace) + buildConfigField("String", "MIXPANEL_API_KEY", "\"${tryReadProperty(rootProject.rootDir, "MIXPANEL_API_KEY")}\"") buildConfigField("String", "KADO_API_KEY", "\"${tryReadProperty(rootProject.rootDir, "KADO_API_KEY")}\"") buildConfigField("Boolean", "NOTIFY_ERRORS", "false") diff --git a/app/src/main/java/com/getcode/CodeApp.kt b/app/src/main/java/com/getcode/CodeApp.kt index f83c81dac..6bde791fc 100644 --- a/app/src/main/java/com/getcode/CodeApp.kt +++ b/app/src/main/java/com/getcode/CodeApp.kt @@ -200,36 +200,7 @@ internal data object MainRoot : Screen { Box( modifier = Modifier .fillMaxSize() - .background(CodeTheme.colors.background), - contentAlignment = Alignment.Center - ) { - var show by remember { - mutableStateOf(false) - } - - AnimatedContent(show, transitionSpec = { fadeIn() togetherWith fadeOut() }) { - if (it) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.inset) - ) { - Image( - painter = painterResource(R.drawable.ic_code_logo_near_white), - contentDescription = "", - modifier = Modifier - .fillMaxWidth(0.65f) - .fillMaxHeight(0.65f) - ) - - CodeCircularProgressIndicator() - } - } - } - - LaunchedEffect(Unit) { - delay(1_000) - show = true - } - } + .background(CodeTheme.colors.background) + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/getcode/inject/ApiModule.kt b/app/src/main/java/com/getcode/inject/ApiModule.kt index f19f2ce2b..5ec79d1a6 100644 --- a/app/src/main/java/com/getcode/inject/ApiModule.kt +++ b/app/src/main/java/com/getcode/inject/ApiModule.kt @@ -73,12 +73,8 @@ object ApiModule { val DEV_URL = "api.codeinfra.dev" val PROD_URL = "api.codeinfra.net" - return AndroidChannelBuilder.usingBuilder( - OkHttpChannelBuilderForcedTls12.forAddress( - PROD_URL, - TLS_PORT - ) - ) + return AndroidChannelBuilder + .usingBuilder(OkHttpChannelBuilderForcedTls12.forAddress(PROD_URL, TLS_PORT)) .context(context) .userAgent("Code/Android/${BuildConfig.VERSION_NAME}") .keepAliveTime(4, TimeUnit.MINUTES) @@ -87,7 +83,9 @@ object ApiModule { @Singleton @Provides - fun provideAccountAuthenticator(@ApplicationContext context: Context): AccountAuthenticator { + fun provideAccountAuthenticator( + @ApplicationContext context: Context, + ): AccountAuthenticator { return AccountAuthenticator(context) } diff --git a/app/src/main/java/com/getcode/util/AccountAuthenticator.kt b/app/src/main/java/com/getcode/util/AccountAuthenticator.kt index c4c3a0337..87d034d3a 100644 --- a/app/src/main/java/com/getcode/util/AccountAuthenticator.kt +++ b/app/src/main/java/com/getcode/util/AccountAuthenticator.kt @@ -3,10 +3,14 @@ package com.getcode.util import android.accounts.* import android.content.Context import android.os.Bundle +import com.getcode.model.PrefsString +import com.getcode.network.repository.PrefRepository import com.getcode.utils.startupLog -class AccountAuthenticator(private val mContext: Context) : AbstractAccountAuthenticator(mContext) { +class AccountAuthenticator( + private val context: Context, +) : AbstractAccountAuthenticator(context) { @Throws(NetworkErrorException::class) override fun addAccount( response: AccountAuthenticatorResponse, @@ -32,9 +36,9 @@ class AccountAuthenticator(private val mContext: Context) : AbstractAccountAuthe options: Bundle ): Bundle { // Extract the username and password from the Account Manager, then, generate token - val am = AccountManager.get(mContext) + val am = AccountManager.get(context) var authToken = am.peekAuthToken(account, authTokenType) - startupLog("authenticator: authToken ${authToken != null}") + startupLog("authenticator: authToken ${authToken != null}, $authTokenType") // Lets give another try to authenticate the user if (null != authToken) { if (authToken.isEmpty()) { @@ -56,7 +60,10 @@ class AccountAuthenticator(private val mContext: Context) : AbstractAccountAuthe } } - startupLog("authenticator failure", Throwable("Failed to retrieve authToken from AccountManager")) + startupLog( + "authenticator failure", + Throwable("Failed to retrieve authToken from AccountManager") + ) // If we get here, then we couldn't access the user's password return Bundle() } diff --git a/app/src/main/res/xml/authenticator.xml b/app/src/main/res/xml/authenticator.xml index 0c8a24a10..415269cbc 100644 --- a/app/src/main/res/xml/authenticator.xml +++ b/app/src/main/res/xml/authenticator.xml @@ -1,7 +1,7 @@ \ No newline at end of file From a2bd41fb3bdf19558109857fcb08d24ed7c16eb0 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sun, 12 May 2024 01:06:59 -0400 Subject: [PATCH 4/4] chore: remove context from Client Signed-off-by: Brandon McAnsh --- api/build.gradle.kts | 1 + .../com/getcode/manager/MnemonicManager.kt | 4 ++ .../java/com/getcode/network/TipController.kt | 4 +- .../java/com/getcode/network/client/Client.kt | 53 ++++++------------- .../network/client/Client_Transaction.kt | 8 ++- .../repository/TransactionRepository.kt | 2 - .../main/java/com/getcode/inject/ApiModule.kt | 5 +- .../java/com/getcode/view/MainActivity.kt | 14 +++++ buildSrc/src/main/java/Dependencies.kt | 2 +- 9 files changed, 44 insertions(+), 49 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 1f4e6b5bd..955c91675 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -78,6 +78,7 @@ dependencies { implementation(Libs.grpc_okhttp) implementation(Libs.grpc_kotlin) + implementation(Libs.androidx_lifecycle_runtime) implementation(Libs.androidx_room_runtime) implementation(Libs.androidx_room_ktx) implementation(Libs.androidx_room_rxjava3) diff --git a/api/src/main/java/com/getcode/manager/MnemonicManager.kt b/api/src/main/java/com/getcode/manager/MnemonicManager.kt index 5df4e6421..01b5132e5 100644 --- a/api/src/main/java/com/getcode/manager/MnemonicManager.kt +++ b/api/src/main/java/com/getcode/manager/MnemonicManager.kt @@ -30,5 +30,9 @@ class MnemonicManager @Inject constructor( return mnemonicPhrase.getBase64EncodedEntropy(context) } + fun getEncodedBase58(mnemonicPhrase: MnemonicPhrase): String { + return mnemonicPhrase.getBase58EncodedEntropy(context) + } + val mnemonicCode: MnemonicCode = context.resources.let(::MnemonicCode) } \ No newline at end of file diff --git a/api/src/main/java/com/getcode/network/TipController.kt b/api/src/main/java/com/getcode/network/TipController.kt index 8d14d3536..66ffb01f5 100644 --- a/api/src/main/java/com/getcode/network/TipController.kt +++ b/api/src/main/java/com/getcode/network/TipController.kt @@ -1,15 +1,13 @@ package com.getcode.network -import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner import com.getcode.manager.SessionManager import com.getcode.model.CodePayload -import com.getcode.model.TipMetadata import com.getcode.model.PrefsBool import com.getcode.model.PrefsString +import com.getcode.model.TipMetadata import com.getcode.model.TwitterUser import com.getcode.network.client.Client import com.getcode.network.client.fetchTwitterUser diff --git a/api/src/main/java/com/getcode/network/client/Client.kt b/api/src/main/java/com/getcode/network/client/Client.kt index 32f5d0adc..17b0c661b 100644 --- a/api/src/main/java/com/getcode/network/client/Client.kt +++ b/api/src/main/java/com/getcode/network/client/Client.kt @@ -1,30 +1,27 @@ package com.getcode.network.client -import android.content.Context -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.LifecycleOwner import com.getcode.analytics.AnalyticsService +import com.getcode.manager.MnemonicManager import com.getcode.manager.SessionManager import com.getcode.network.BalanceController -import com.getcode.network.HistoryController import com.getcode.network.exchange.Exchange import com.getcode.network.repository.AccountRepository import com.getcode.network.repository.IdentityRepository import com.getcode.network.repository.MessagingRepository import com.getcode.network.repository.PrefRepository import com.getcode.network.repository.TransactionRepository -import com.getcode.utils.network.NetworkConnectivityListener import com.getcode.network.service.ChatService import com.getcode.network.service.DeviceService import com.getcode.utils.ErrorUtils -import dagger.hilt.android.qualifiers.ApplicationContext +import com.getcode.utils.network.NetworkConnectivityListener import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber import java.util.Timer @@ -36,8 +33,6 @@ internal const val TAG = "Client" @Singleton class Client @Inject constructor( - @ApplicationContext - internal val context: Context, internal val identityRepository: IdentityRepository, internal val transactionRepository: TransactionRepository, internal val messagingRepository: MessagingRepository, @@ -51,9 +46,9 @@ class Client @Inject constructor( internal val networkObserver: NetworkConnectivityListener, internal val chatService: ChatService, internal val deviceService: DeviceService, -) : LifecycleEventObserver { + internal val mnemonicManager: MnemonicManager, +) { - private val TAG = "PollTimer" private val scope = CoroutineScope(Dispatchers.IO) private var pollTimer: Timer? = null @@ -62,13 +57,15 @@ class Client @Inject constructor( private fun startPollTimerWhenAuthenticated() { Timber.tag(TAG).i("Creating poll timer") scope.launch { - SessionManager.authState.collect { - if (it.isAuthenticated == true) { + SessionManager.authState + .map { it.isAuthenticated } + .filterNotNull() + .filter { it } + .onEach { Timber.tag(TAG).i("User Authenticated - starting timer") startPollTimer() this.cancel() - } - } + }.launchIn(this) } } @@ -101,28 +98,12 @@ class Client @Inject constructor( } } - private fun startTimer() { + fun startTimer() { startPollTimerWhenAuthenticated() } - fun pollOnce(delay: Long = 2_000L) { - scope.launch { - delay(delay) - Timber.tag(TAG).i("Poll Once") - poll() - } - } - - private fun stopTimer() { + fun stopTimer() { Timber.tag(TAG).i("Cancelling Poller") pollTimer?.cancel() } - - override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - when (event) { - Lifecycle.Event.ON_RESUME -> startTimer() - Lifecycle.Event.ON_STOP -> stopTimer() - else -> Unit - } - } } \ 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 fa0a0e0a2..53c210e28 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 @@ -96,7 +96,7 @@ fun Client.transferWithResultSingle( return getTransferPreflightAction(amount.kin) .andThen(Single.defer { transactionRepository.transfer( - context, amount, fee, additionalFees, organizer, rendezvousKey, destination, isWithdrawal, tipMetadata + amount, fee, additionalFees, organizer, rendezvousKey, destination, isWithdrawal, tipMetadata ) }) .map { @@ -141,7 +141,6 @@ fun Client.sendRemotely( getTransferPreflightAction(truncatedAmount.kin) .andThen( sendRemotely( - context = context, amount = truncatedAmount, organizer = organizer, rendezvousKey = rendezvousKey, @@ -150,7 +149,7 @@ fun Client.sendRemotely( .doOnComplete { val giftCardItem = GiftCard( key = giftCard.cluster.vaultPublicKey.base58(), - entropy = giftCard.mnemonicPhrase.getBase58EncodedEntropy(context), + entropy = mnemonicManager.getEncodedBase58(giftCard.mnemonicPhrase), amount = truncatedAmount.kin.quarks, date = System.currentTimeMillis() ) @@ -367,7 +366,6 @@ private fun Client.withdraw( } fun Client.sendRemotely( - context: Context, amount: KinAmount, organizer: Organizer, rendezvousKey: PublicKey, @@ -375,7 +373,7 @@ fun Client.sendRemotely( ): Completable { return Completable.defer { transactionRepository.sendRemotely( - context, amount, organizer, rendezvousKey, giftCard + amount, organizer, rendezvousKey, giftCard ) .map { if (it is IntentRemoteSend) { 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 cfe8a36f0..be44f6843 100644 --- a/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt +++ b/api/src/main/java/com/getcode/network/repository/TransactionRepository.kt @@ -128,7 +128,6 @@ class TransactionRepository @Inject constructor( } fun transfer( - context: Context, amount: KinAmount, fee: Kin, additionalFees: List, @@ -254,7 +253,6 @@ class TransactionRepository @Inject constructor( } fun sendRemotely( - context: Context, amount: KinAmount, organizer: Organizer, rendezvousKey: PublicKey, diff --git a/app/src/main/java/com/getcode/inject/ApiModule.kt b/app/src/main/java/com/getcode/inject/ApiModule.kt index 5ec79d1a6..1150a533c 100644 --- a/app/src/main/java/com/getcode/inject/ApiModule.kt +++ b/app/src/main/java/com/getcode/inject/ApiModule.kt @@ -4,6 +4,7 @@ import android.content.Context import com.getcode.BuildConfig import com.getcode.R import com.getcode.analytics.AnalyticsService +import com.getcode.manager.MnemonicManager import com.getcode.model.Currency import com.getcode.network.BalanceController import com.getcode.network.PrivacyMigration @@ -155,7 +156,6 @@ object ApiModule { @Singleton @Provides fun provideClient( - @ApplicationContext context: Context, identityRepository: IdentityRepository, transactionRepository: TransactionRepository, messagingRepository: MessagingRepository, @@ -169,9 +169,9 @@ object ApiModule { networkObserver: NetworkConnectivityListener, chatService: ChatService, deviceService: DeviceService, + mnemonicManager: MnemonicManager, ): Client { return Client( - context, identityRepository, transactionRepository, messagingRepository, @@ -185,6 +185,7 @@ object ApiModule { networkObserver, chatService, deviceService, + mnemonicManager ) } diff --git a/app/src/main/java/com/getcode/view/MainActivity.kt b/app/src/main/java/com/getcode/view/MainActivity.kt index 1886465a6..a3edd1c28 100644 --- a/app/src/main/java/com/getcode/view/MainActivity.kt +++ b/app/src/main/java/com/getcode/view/MainActivity.kt @@ -14,6 +14,7 @@ import com.getcode.LocalExchange import com.getcode.LocalNetworkObserver import com.getcode.LocalPhoneFormatter import com.getcode.analytics.AnalyticsService +import com.getcode.network.client.Client import com.getcode.network.exchange.Exchange import com.getcode.ui.utils.handleUncaughtException import com.getcode.util.CurrencyUtils @@ -30,6 +31,9 @@ import javax.inject.Inject @AndroidEntryPoint class MainActivity : FragmentActivity() { + @Inject + lateinit var client: Client + @Inject lateinit var analyticsManager: AnalyticsService @@ -96,5 +100,15 @@ class MainActivity : FragmentActivity() { private fun setFullscreen() { enableEdgeToEdge() } + + override fun onResume() { + super.onResume() + client.startTimer() + } + + override fun onStop() { + super.onStop() + client.stopTimer() + } } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 94f60be0e..7f3177ee2 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -22,7 +22,7 @@ object Versions { const val androidx_camerax = "1.3.2" const val androidx_core = "1.12.0" const val androidx_constraint_layout = "2.1.3" - const val androidx_lifecycle = "2.6.2" + const val androidx_lifecycle = "2.7.0" const val androidx_navigation = "2.7.4" const val androidx_browser = "1.4.0" const val androidx_paging = "3.2.1"