From 8bef463a7d581e020d04ebb6d0e0fc8035563dbd Mon Sep 17 00:00:00 2001 From: andhikayuana Date: Mon, 24 Mar 2025 00:08:26 +0700 Subject: [PATCH 01/17] feat: [WIP] implement new sync mechanism --- app/build.gradle.kts | 1 + .../presenter/activities/BreadActivity.java | 1 - .../brainwallet/tools/security/PostAuth.java | 21 +++++------- .../com/brainwallet/ui/BrainwalletActivity.kt | 12 ++++++- .../screens/inputwords/InputWordsViewModel.kt | 12 ++++--- .../ui/screens/welcome/WelcomeEvent.kt | 4 ++- .../ui/screens/welcome/WelcomeScreen.kt | 15 ++++----- .../ui/screens/welcome/WelcomeViewModel.kt | 14 ++++++++ .../com/brainwallet/worker/SyncBlockWorker.kt | 33 +++++++++++++++++++ gradle/libs.versions.toml | 2 ++ 10 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/com/brainwallet/worker/SyncBlockWorker.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b2692466..c4e1b960 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -199,6 +199,7 @@ dependencies { implementation(libs.androidx.preference) implementation(libs.androidx.lifecycle.runtime) implementation(libs.bundles.androidx.lifecycle) + implementation(libs.androidx.work) implementation(libs.androidx.browser) implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.androidx.compose) diff --git a/app/src/main/java/com/brainwallet/presenter/activities/BreadActivity.java b/app/src/main/java/com/brainwallet/presenter/activities/BreadActivity.java index a0f6e980..aec322e1 100644 --- a/app/src/main/java/com/brainwallet/presenter/activities/BreadActivity.java +++ b/app/src/main/java/com/brainwallet/presenter/activities/BreadActivity.java @@ -37,7 +37,6 @@ import com.brainwallet.R; import com.brainwallet.navigation.LegacyNavigation; import com.brainwallet.navigation.Route; -import com.brainwallet.presenter.activities.settings.SettingsActivity; import com.brainwallet.presenter.activities.settings.SyncBlockchainActivity; import com.brainwallet.presenter.activities.util.BRActivity; import com.brainwallet.presenter.customviews.BRNotificationBar; diff --git a/app/src/main/java/com/brainwallet/tools/security/PostAuth.java b/app/src/main/java/com/brainwallet/tools/security/PostAuth.java index f1a0e0a9..89127dd2 100644 --- a/app/src/main/java/com/brainwallet/tools/security/PostAuth.java +++ b/app/src/main/java/com/brainwallet/tools/security/PostAuth.java @@ -8,28 +8,23 @@ import androidx.fragment.app.FragmentActivity; +import com.brainwallet.R; import com.brainwallet.navigation.LegacyNavigation; import com.brainwallet.navigation.Route; -import com.brainwallet.tools.manager.BRSharedPrefs; -import com.brainwallet.tools.threads.BRExecutor; -import com.brainwallet.tools.threads.PaymentProtocolPostPaymentTask; -import com.brainwallet.tools.util.BRConstants; -import com.brainwallet.tools.util.TypesConverter; -import com.brainwallet.tools.util.Utils; -import com.brainwallet.R; -import com.brainwallet.presenter.activities.PaperKeyActivity; -import com.brainwallet.presenter.activities.SetPinActivity; import com.brainwallet.presenter.activities.intro.WriteDownActivity; import com.brainwallet.presenter.activities.util.ActivityUTILS; import com.brainwallet.presenter.entities.PaymentRequestWrapper; import com.brainwallet.presenter.entities.TransactionItem; +import com.brainwallet.tools.manager.BRSharedPrefs; +import com.brainwallet.tools.util.BRConstants; +import com.brainwallet.tools.util.TypesConverter; +import com.brainwallet.tools.util.Utils; import com.brainwallet.ui.BrainwalletActivity; import com.brainwallet.wallet.BRWalletManager; import com.platform.entities.TxMetaData; import com.platform.tools.KVStoreManager; import java.util.Arrays; -import java.util.List; import timber.log.Timber; @@ -88,8 +83,8 @@ public void onPhraseCheckAuth(Activity app, boolean authAsked) { String[] seedWords = cleanPhrase.split(" "); LegacyNavigation.openComposeScreen( - app, - new Route.YourSeedWords(Arrays.asList(seedWords)) + app, + new Route.YourSeedWords(Arrays.asList(seedWords)) ); app.overridePendingTransition(R.anim.enter_from_bottom, R.anim.empty_300); } @@ -195,7 +190,7 @@ public void onPublishTxAuth(final Context app, boolean authAsked) { Timber.d("timber: onPublishTxAuth: txhash:" + Arrays.toString(txHash)); if (Utils.isNullOrEmpty(txHash)) { Timber.d("timber: onPublishTxAuth: publishSerializedTransaction returned FALSE"); - } else { + } else { TxMetaData txMetaData = new TxMetaData(); txMetaData.comment = transactionItem.comment; KVStoreManager.getInstance().putTxMetaData(app, txMetaData, txHash); diff --git a/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt b/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt index 0484cac0..1163eca7 100644 --- a/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt +++ b/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt @@ -15,6 +15,7 @@ import com.brainwallet.data.repository.SettingRepository import com.brainwallet.navigation.LegacyNavigation import com.brainwallet.navigation.MainNavHost import com.brainwallet.navigation.Route +import com.brainwallet.presenter.activities.intro.WriteDownActivity import com.brainwallet.presenter.activities.util.BRActivity import com.brainwallet.tools.animation.BRAnimator import com.brainwallet.tools.animation.BRDialog @@ -226,7 +227,16 @@ class BrainwalletActivity : BRActivity() { getString(R.string.Alerts_pinSet), getString(R.string.UpdatePin_createInstruction), R.drawable.ic_check_mark_white - ) { PostAuth.getInstance().onCreateWalletAuth(this, false) } + ) { + val walletNotAvailable = BRWalletManager.getInstance().noWallet(this) + if (walletNotAvailable) { + PostAuth.getInstance().onCreateWalletAuth(this, false) + } else { + Intent(this, WriteDownActivity::class.java).also { + startActivity(it) + } + } + } } } diff --git a/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsViewModel.kt index 8cbf301e..734a1c54 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsViewModel.kt @@ -24,9 +24,10 @@ class InputWordsViewModel : BrainwalletViewModel() { init { //TODO: revisit later, please move to repository, for now just reuse the existing - Bip39Reader.bip39List(BrainwalletApp.breadContext, Language.ENGLISH.code).also { bip39Words -> - _state.update { it.copy(bip39Words = bip39Words) } - } + Bip39Reader.bip39List(BrainwalletApp.breadContext, Language.ENGLISH.code) + .also { bip39Words -> + _state.update { it.copy(bip39Words = bip39Words) } + } } override fun onEvent(event: InputWordsEvent) { @@ -53,8 +54,9 @@ class InputWordsViewModel : BrainwalletViewModel() { val cleanPhrase = SmartValidator.cleanPaperKey(event.context, paperKey) - if (SmartValidator.isPaperKeyValid(event.context, cleanPhrase) - .not() && SmartValidator.isPaperKeyCorrect(cleanPhrase, event.context).not() + //TODO: WIP HERE + if (SmartValidator.isPaperKeyValid(event.context, cleanPhrase).not() && + SmartValidator.isPaperKeyCorrect(cleanPhrase, event.context).not() ) { viewModelScope.launch { EventBus.emit( diff --git a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeEvent.kt b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeEvent.kt index 6b2cbbce..d162ff2c 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeEvent.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeEvent.kt @@ -1,9 +1,11 @@ package com.brainwallet.ui.screens.welcome -import com.brainwallet.data.model.Language +import android.content.Context import com.brainwallet.data.model.CurrencyEntity +import com.brainwallet.data.model.Language sealed class WelcomeEvent { + data class OnLoad(val context: Context) : WelcomeEvent() object OnToggleDarkMode : WelcomeEvent() object OnLanguageSelectorButtonClick : WelcomeEvent() object OnLanguageSelectorDismiss : WelcomeEvent() diff --git a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeScreen.kt index da80ef85..0835a1e4 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeScreen.kt @@ -2,9 +2,7 @@ package com.brainwallet.ui.screens.welcome import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -14,24 +12,20 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card -import androidx.compose.material3.Icon -import androidx.compose.material3.IconToggleButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable +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.draw.clip import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -61,12 +55,17 @@ fun WelcomeScreen( viewModel: WelcomeViewModel = koinInject() ) { val state by viewModel.state.collectAsState() + val context = LocalContext.current val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp var mainBoxFactor = 0.5 val thirdOfScreenHeight = (screenHeight * mainBoxFactor).toInt() + LaunchedEffect(Unit) { + viewModel.onEvent(WelcomeEvent.OnLoad(context)) + } + //todo: the following sizing can be move to BrainwalletTheme // Global layout val buttonFontSize = 16 diff --git a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt index 94079de4..a2dab9aa 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt @@ -3,10 +3,13 @@ package com.brainwallet.ui.screens.welcome import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.LocaleListCompat import androidx.lifecycle.viewModelScope +import androidx.work.WorkManager import com.brainwallet.data.model.AppSetting import com.brainwallet.data.model.Language import com.brainwallet.data.repository.SettingRepository import com.brainwallet.ui.BrainwalletViewModel +import com.brainwallet.wallet.BRWalletManager +import com.brainwallet.worker.SyncBlockWorker import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -108,6 +111,17 @@ class WelcomeViewModel( ) } } + + is WelcomeEvent.OnLoad -> { + val walletNotAvailable = BRWalletManager.getInstance().noWallet(event.context) + /** + * if wallet not available then generate new random seed and enqueue [SyncBlockWorker] + */ + if (walletNotAvailable) { + BRWalletManager.getInstance().generateRandomSeed(event.context) + WorkManager.getInstance(event.context).enqueue(SyncBlockWorker.request) + } + } } } diff --git a/app/src/main/java/com/brainwallet/worker/SyncBlockWorker.kt b/app/src/main/java/com/brainwallet/worker/SyncBlockWorker.kt new file mode 100644 index 00000000..56ffa2cf --- /dev/null +++ b/app/src/main/java/com/brainwallet/worker/SyncBlockWorker.kt @@ -0,0 +1,33 @@ +package com.brainwallet.worker + +import android.content.Context +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkerParameters +import com.brainwallet.wallet.BRWalletManager + +class SyncBlockWorker( + context: Context, + params: WorkerParameters +) : CoroutineWorker(context, params) { + + //todo: maybe need ongoing notification? + + override suspend fun doWork(): Result { + BRWalletManager.getInstance().initWallet(applicationContext) + return Result.success() + } + + companion object { + @JvmStatic + val request = OneTimeWorkRequestBuilder() + .setConstraints( + Constraints( + requiredNetworkType = NetworkType.CONNECTED + ) + ) + .build() + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1dd204b5..f4ab271f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ androidx-lifecycle = "2.7.0" androidx-compose-bom = "2025.01.00" androidx-activity-compose = "1.9.3" androidx-fragment-compose = "1.8.5" +androidx-work = "2.9.1" google-material = "1.11.0" google-zxing = "3.5.2" #google-dagger = "2.50" @@ -65,6 +66,7 @@ androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-toolin androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-work = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.7.1" } #google-dagger = { module = "com.google.dagger:dagger", version.ref = "google-dagger" } #google-dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "google-dagger" } From 9ac5b8a3710d2575a12d4d702f2f74b9228abbd0 Mon Sep 17 00:00:00 2001 From: andhikayuana Date: Mon, 24 Mar 2025 09:40:08 +0700 Subject: [PATCH 02/17] chore: refactor keystore manager provide and inject using koin --- app/src/main/java/com/brainwallet/BrainwalletApp.kt | 11 ++--------- app/src/main/java/com/brainwallet/di/Module.kt | 6 ++++++ .../com/brainwallet/tools/security/BRKeyStore.java | 10 +++++++--- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/brainwallet/BrainwalletApp.kt b/app/src/main/java/com/brainwallet/BrainwalletApp.kt index 5ada1bf1..045eb4bf 100644 --- a/app/src/main/java/com/brainwallet/BrainwalletApp.kt +++ b/app/src/main/java/com/brainwallet/BrainwalletApp.kt @@ -6,6 +6,7 @@ import android.app.Application import android.content.Context import android.content.res.Resources import com.appsflyer.AppsFlyerLib +import com.brainwallet.di.appModule import com.brainwallet.di.dataModule import com.brainwallet.di.viewModelModule import com.brainwallet.notification.setupNotificationChannels @@ -15,8 +16,6 @@ import com.brainwallet.tools.listeners.SyncReceiver import com.brainwallet.tools.manager.AnalyticsManager import com.brainwallet.tools.util.BRConstants import com.brainwallet.tools.util.Utils -import com.brainwallet.util.cryptography.KeyStoreKeyGenerator -import com.brainwallet.util.cryptography.KeyStoreManager import com.google.firebase.crashlytics.FirebaseCrashlytics import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger @@ -73,12 +72,10 @@ class BrainwalletApp : Application() { } protected fun initializeModule() { - keyStoreManager = KeyStoreManager(this, KeyStoreKeyGenerator.Impl()) - startKoin { androidLogger(if (BuildConfig.DEBUG) Level.DEBUG else Level.ERROR) androidContext(this@BrainwalletApp) - modules(dataModule, viewModelModule) + modules(dataModule, viewModelModule, appModule) } } @@ -109,10 +106,6 @@ class BrainwalletApp : Application() { @SuppressLint("StaticFieldLeak") private var currentActivity: Activity? = null - @SuppressLint("StaticFieldLeak") - @JvmField - var keyStoreManager: KeyStoreManager? = null - @JvmStatic val breadContext: Context get() = if (currentActivity == null) SyncReceiver.app else currentActivity!! diff --git a/app/src/main/java/com/brainwallet/di/Module.kt b/app/src/main/java/com/brainwallet/di/Module.kt index 69371158..643501ed 100644 --- a/app/src/main/java/com/brainwallet/di/Module.kt +++ b/app/src/main/java/com/brainwallet/di/Module.kt @@ -14,6 +14,8 @@ import com.brainwallet.ui.screens.unlock.UnLockViewModel import com.brainwallet.ui.screens.welcome.WelcomeViewModel import com.brainwallet.ui.screens.yourseedproveit.YourSeedProveItViewModel import com.brainwallet.ui.screens.yourseedwords.YourSeedWordsViewModel +import com.brainwallet.util.cryptography.KeyStoreKeyGenerator +import com.brainwallet.util.cryptography.KeyStoreManager import com.google.firebase.ktx.Firebase import com.google.firebase.remoteconfig.ktx.remoteConfig import org.koin.android.ext.koin.androidApplication @@ -45,6 +47,10 @@ val viewModelModule = module { viewModel { YourSeedWordsViewModel() } } +val appModule = module { + single { KeyStoreManager(get(), KeyStoreKeyGenerator.Impl()) } +} + private fun provideSharedPreferences( context: Context, name: String = "${BuildConfig.APPLICATION_ID}.prefs" diff --git a/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java b/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java index 363bad5a..4aad3597 100644 --- a/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java +++ b/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java @@ -17,7 +17,6 @@ import android.util.Base64; import android.view.View; -import com.brainwallet.BrainwalletApp; import com.brainwallet.R; import com.brainwallet.exceptions.BRKeystoreErrorException; import com.brainwallet.presenter.customviews.BRDialogView; @@ -27,11 +26,14 @@ import com.brainwallet.tools.util.BytesUtil; import com.brainwallet.tools.util.TypesConverter; import com.brainwallet.tools.util.Utils; +import com.brainwallet.util.cryptography.KeyStoreManager; import com.brainwallet.wallet.BRWalletManager; import com.google.firebase.crashlytics.FirebaseCrashlytics; import com.platform.entities.WalletInfo; import com.platform.tools.KVStoreManager; +import org.koin.java.KoinJavaComponent; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -148,7 +150,8 @@ private synchronized static boolean _setData(Context context, byte[] data, Strin try { lock.lock(); - return BrainwalletApp.keyStoreManager.setDataBlocking(new AliasObject(alias, alias_file, alias_iv), data); + KeyStoreManager keyStoreManager = KoinJavaComponent.get(KeyStoreManager.class); + return keyStoreManager.setDataBlocking(new AliasObject(alias, alias_file, alias_iv), data); } catch (UserNotAuthenticatedException e) { Timber.e(e, "timber:_setData: showAuthenticationScreen: %s", alias); showAuthenticationScreen(context, request_code, alias); @@ -253,7 +256,8 @@ private synchronized static byte[] _getData(final Context context, String alias, try { lock.lock(); - return BrainwalletApp.keyStoreManager.getDataBlocking(new AliasObject(alias, alias_file, alias_iv)); + KeyStoreManager keyStoreManager = KoinJavaComponent.get(KeyStoreManager.class); + return keyStoreManager.getDataBlocking(new AliasObject(alias, alias_file, alias_iv)); } catch (UserNotAuthenticatedException e) { Timber.e(e, "timber:_getData: showAuthenticationScreen: %s", alias); showAuthenticationScreen(context, request_code, alias); From f728a32d9e9216c3822b830f1a3a0b2a28327840 Mon Sep 17 00:00:00 2001 From: andhikayuana Date: Mon, 24 Mar 2025 10:56:30 +0700 Subject: [PATCH 03/17] feat: implement new sync flow --- .../main/java/com/brainwallet/di/Module.kt | 2 + .../tools/security/BRKeyStore.java | 198 ++--------------- .../ui/screens/inputwords/InputWordsState.kt | 4 +- .../screens/inputwords/InputWordsViewModel.kt | 16 +- .../ui/screens/ready/ReadyEvent.kt | 7 + .../ui/screens/ready/ReadyScreen.kt | 12 +- .../ui/screens/ready/ReadyViewModel.kt | 22 ++ .../ui/screens/welcome/WelcomeViewModel.kt | 10 +- .../brainwallet/wallet/BRWalletManager.java | 202 +++++++++++------- 9 files changed, 200 insertions(+), 273 deletions(-) create mode 100644 app/src/main/java/com/brainwallet/ui/screens/ready/ReadyEvent.kt create mode 100644 app/src/main/java/com/brainwallet/ui/screens/ready/ReadyViewModel.kt diff --git a/app/src/main/java/com/brainwallet/di/Module.kt b/app/src/main/java/com/brainwallet/di/Module.kt index 643501ed..75a2cf5f 100644 --- a/app/src/main/java/com/brainwallet/di/Module.kt +++ b/app/src/main/java/com/brainwallet/di/Module.kt @@ -9,6 +9,7 @@ import com.brainwallet.tools.manager.BRApiManager import com.brainwallet.tools.sqlite.CurrencyDataSource import com.brainwallet.ui.screens.home.SettingsViewModel import com.brainwallet.ui.screens.inputwords.InputWordsViewModel +import com.brainwallet.ui.screens.ready.ReadyViewModel import com.brainwallet.ui.screens.setpasscode.SetPasscodeViewModel import com.brainwallet.ui.screens.unlock.UnLockViewModel import com.brainwallet.ui.screens.welcome.WelcomeViewModel @@ -40,6 +41,7 @@ val dataModule = module { val viewModelModule = module { viewModelOf(::WelcomeViewModel) viewModelOf(::SettingsViewModel) + viewModel { ReadyViewModel() } viewModel { InputWordsViewModel() } viewModel { SetPasscodeViewModel() } viewModel { UnLockViewModel() } diff --git a/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java b/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java index 4aad3597..24169f87 100644 --- a/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java +++ b/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java @@ -95,6 +95,7 @@ public class BRKeyStore { private static final String AUTH_KEY_IV = "ivauthkey"; private static final String TOKEN_IV = "ivtoken"; private static final String PASS_TIME_IV = "passtimetoken"; + private static final String PHRASE_TEMP_IV = "ivphrasetemp"; //temp phrase, related with sync mechanism public static final String PHRASE_ALIAS = "phrase"; public static final String CANARY_ALIAS = "canary"; @@ -108,6 +109,7 @@ public class BRKeyStore { public static final String AUTH_KEY_ALIAS = "authKey"; public static final String TOKEN_ALIAS = "token"; public static final String PASS_TIME_ALIAS = "passTime"; + private static final String PHRASE_TEMP_ALIAS = "phrasetemp"; //temp phrase, related with sync mechanism private static final String PHRASE_FILENAME = "my_phrase"; private static final String CANARY_FILENAME = "my_canary"; @@ -121,6 +123,8 @@ public class BRKeyStore { private static final String AUTH_KEY_FILENAME = "my_auth_key"; private static final String TOKEN_FILENAME = "my_token"; private static final String PASS_TIME_FILENAME = "my_pass_time"; + private static final String PHRASE_TEMP_FILENAME = "my_phrasetemp"; //temp phrase, related with sync mechanism + private static boolean bugMessageShowing; public static final int AUTH_DURATION_SEC = 300; @@ -140,6 +144,7 @@ public class BRKeyStore { aliasObjectMap.put(TOKEN_ALIAS, new AliasObject(TOKEN_ALIAS, TOKEN_FILENAME, TOKEN_IV)); aliasObjectMap.put(PASS_TIME_ALIAS, new AliasObject(PASS_TIME_ALIAS, PASS_TIME_FILENAME, PASS_TIME_IV)); aliasObjectMap.put(TOTAL_LIMIT_ALIAS, new AliasObject(TOTAL_LIMIT_ALIAS, TOTAL_LIMIT_FILENAME, TOTAL_LIMIT_IV)); + aliasObjectMap.put(PHRASE_TEMP_ALIAS, new AliasObject(PHRASE_TEMP_ALIAS, PHRASE_TEMP_FILENAME, PHRASE_TEMP_IV)); } @@ -166,74 +171,6 @@ private synchronized static boolean _setData(Context context, byte[] data, Strin } - @Deprecated - private static boolean _setDataLegacy(Context context, byte[] data, String alias, String alias_iv, int request_code, boolean auth_required) throws UserNotAuthenticatedException { - KeyStore keyStore; - try { - lock.lock(); - keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); - keyStore.load(null); - SecretKey secretKey = (SecretKey) keyStore.getKey(alias, null); - Cipher inCipher = Cipher.getInstance(NEW_CIPHER_ALGORITHM); - - if (secretKey == null) { - //create key if not present - secretKey = createKeys(alias, auth_required); - inCipher.init(Cipher.ENCRYPT_MODE, secretKey); - } else { - - Timber.d("timber: KeyStore: is initialized"); - - //see if the key is old format, create a new one if it is - try { - inCipher.init(Cipher.ENCRYPT_MODE, secretKey); - } catch (InvalidKeyException ignored) { - Timber.e(ignored); - if (ignored instanceof UserNotAuthenticatedException) { - throw ignored; - } - Timber.d("timber: _setData: OLD KEY PRESENT: %s", alias); - //create new key and reinitialize the cipher - secretKey = createKeys(alias, auth_required); - inCipher.init(Cipher.ENCRYPT_MODE, secretKey); - } - } - - //the key cannot still be null - if (secretKey == null) { - Timber.e(new BRKeystoreErrorException("secret is null on _setData: " + alias)); - return false; - } - - - byte[] iv = inCipher.getIV(); - if (iv == null) throw new NullPointerException("iv is null!"); - - //store the iv - storeEncryptedData(context, iv, alias_iv); - byte[] encryptedData = inCipher.doFinal(data); - //store the encrypted data - storeEncryptedData(context, encryptedData, alias); - return true; - } catch (UserNotAuthenticatedException e) { - Timber.e(e, "timber:_setData: showAuthenticationScreen: %s", alias); - showAuthenticationScreen(context, request_code, alias); - throw e; - } catch (InvalidKeyException ex) { - if (ex instanceof KeyPermanentlyInvalidatedException) { - showKeyInvalidated(context); - throw new UserNotAuthenticatedException(); //just to make the flow stop - } - Timber.e(ex); - return false; - } catch (Exception e) { - Timber.e(e); - return false; - } finally { - lock.unlock(); - } - } - private static SecretKey createKeys(String alias, boolean auth_required) throws InvalidAlgorithmParameterException, KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); @@ -272,113 +209,6 @@ private synchronized static byte[] _getData(final Context context, String alias, } - @Deprecated - private static byte[] _getDataLegacy(Context context, String alias, String alias_file, String alias_iv, int request_code) throws UserNotAuthenticatedException { - KeyStore keyStore; - - try { - lock.lock(); - keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); - keyStore.load(null); - Timber.d("timber: BRKeyStore size: %d", keyStore.size()); - SecretKey secretKey = (SecretKey) keyStore.getKey(alias, null); - - byte[] encryptedData = retrieveEncryptedData(context, alias); - if (encryptedData != null) { - //new format data is present, good - byte[] iv = retrieveEncryptedData(context, alias_iv); - if (iv == null) { - NullPointerException exception = new NullPointerException("iv is missing when data isn't: " + alias); - FirebaseCrashlytics.getInstance().recordException(exception); - return null; - } - Cipher outCipher; - - outCipher = Cipher.getInstance(NEW_CIPHER_ALGORITHM); - outCipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, iv)); - try { - byte[] decryptedData = outCipher.doFinal(encryptedData); - if (decryptedData != null) { - return decryptedData; - } - } catch (IllegalBlockSizeException | BadPaddingException e) { - Timber.e(e, "failed to decrypt data: " + alias); - FirebaseCrashlytics.getInstance().recordException(e); - return null; - } - } - //no new format data, get the old one and migrate it to the new format - String encryptedDataFilePath = getFilePath(alias_file, context); - - if (secretKey == null) { - /* no such key, the key is just simply not there */ - boolean fileExists = new File(encryptedDataFilePath).exists(); - if (!fileExists) { - return null; - } - BRKeystoreErrorException exception = new BRKeystoreErrorException("file is present but the key is gone: " + alias); - Timber.e(exception); - FirebaseCrashlytics.getInstance().recordException(exception); - return null; - } - - boolean ivExists = new File(getFilePath(alias_iv, context)).exists(); - boolean aliasExists = new File(getFilePath(alias_file, context)).exists(); - //cannot happen, they all should be present - if (!ivExists || !aliasExists) { - removeAliasAndFiles(keyStore, alias, context); - //report it if one exists and not the other. - if (ivExists != aliasExists) { - BRKeystoreErrorException exception = new BRKeystoreErrorException("alias or iv isn't on the disk: " + alias + ", aliasExists:" + aliasExists); - Timber.e(exception); - FirebaseCrashlytics.getInstance().recordException(exception); - } else { - BRKeystoreErrorException exception = new BRKeystoreErrorException("!ivExists && !aliasExists: " + alias); - Timber.e(exception); - FirebaseCrashlytics.getInstance().recordException(exception); - } - return null; - } - - byte[] iv = readBytesFromFile(getFilePath(alias_iv, context)); - if (Utils.isNullOrEmpty(iv)) - throw new RuntimeException("iv is missing for " + alias); - Cipher outCipher = Cipher.getInstance(CIPHER_ALGORITHM); - outCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); - CipherInputStream cipherInputStream = new CipherInputStream(new FileInputStream(encryptedDataFilePath), outCipher); - byte[] result = BytesUtil.readBytesFromStream(cipherInputStream); - if (result == null) - throw new RuntimeException("Failed to read bytes from CipherInputStream for alias " + alias); - - //create the new format key - SecretKey newKey = createKeys(alias, (alias.equals(PHRASE_ALIAS) || alias.equals(CANARY_ALIAS))); - if (newKey == null) - throw new RuntimeException("Failed to create new key for alias " + alias); - Cipher inCipher = Cipher.getInstance(NEW_CIPHER_ALGORITHM); - //init the cipher - inCipher.init(Cipher.ENCRYPT_MODE, newKey); - iv = inCipher.getIV(); - //store the new iv - storeEncryptedData(context, iv, alias_iv); - //encrypt the data - encryptedData = inCipher.doFinal(result); - //store the new data - storeEncryptedData(context, encryptedData, alias); - return result; - - } catch (UserNotAuthenticatedException e) { - Timber.e(e, "timber:_getData: showAuthenticationScreen: %s", alias); - showAuthenticationScreen(context, request_code, alias); - throw e; - } catch (GeneralSecurityException | IOException e) { - Timber.e(e, "timber:getData: error retrieving"); - FirebaseCrashlytics.getInstance().recordException(e); - return null; - } finally { - lock.unlock(); - } - } - private static void validateGet(String alias, String alias_file, String alias_iv) throws IllegalArgumentException { AliasObject obj = aliasObjectMap.get(alias); if (!obj.alias.equals(alias) || !obj.datafileName.equals(alias_file) || !obj.ivFileName.equals(alias_iv)) { @@ -439,6 +269,24 @@ public synchronized static byte[] getPhrase(final Context context, int requestCo return _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); } + public synchronized static boolean putPhraseTemp(byte[] strToStore, Context context, int requestCode) throws UserNotAuthenticatedException { + if (PostAuth.isStuckWithAuthLoop) { + showLoopBugMessage(context); + throw new UserNotAuthenticatedException(); + } + AliasObject obj = aliasObjectMap.get(PHRASE_TEMP_ALIAS); + return !(strToStore == null || strToStore.length == 0) && _setData(context, strToStore, obj.alias, obj.datafileName, obj.ivFileName, requestCode, false); + } + + public synchronized static byte[] getPhraseTemp(final Context context, int requestCode) throws UserNotAuthenticatedException { + if (PostAuth.isStuckWithAuthLoop) { + showLoopBugMessage(context); + throw new UserNotAuthenticatedException(); + } + AliasObject obj = aliasObjectMap.get(PHRASE_TEMP_ALIAS); + return _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); + } + public synchronized static boolean putCanary(String strToStore, Context context, int requestCode) throws UserNotAuthenticatedException { if (PostAuth.isStuckWithAuthLoop) { showLoopBugMessage(context); diff --git a/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsState.kt b/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsState.kt index fba6dd32..eef233fb 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsState.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsState.kt @@ -17,4 +17,6 @@ fun SeedWords.asPaperKey(): String { fun InputWordsState.isFrom(from: Route.InputWords.Source): Boolean { return source == from -} \ No newline at end of file +} + +fun InputWordsState.isFromWelcome(): Boolean = source == null \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsViewModel.kt index 734a1c54..bb2243dc 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/inputwords/InputWordsViewModel.kt @@ -54,8 +54,20 @@ class InputWordsViewModel : BrainwalletViewModel() { val cleanPhrase = SmartValidator.cleanPaperKey(event.context, paperKey) - //TODO: WIP HERE - if (SmartValidator.isPaperKeyValid(event.context, cleanPhrase).not() && + if (currentState.isFromWelcome() && + SmartValidator.isPaperKeyValid(event.context, cleanPhrase).not() + ) { + viewModelScope.launch { + EventBus.emit( + EventBus.Event.Message( + LEGACY_DIALOG_INVALID + ) + ) + } + return + } + + if (currentState.isFromWelcome().not() && SmartValidator.isPaperKeyCorrect(cleanPhrase, event.context).not() ) { viewModelScope.launch { diff --git a/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyEvent.kt b/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyEvent.kt new file mode 100644 index 00000000..41b14a97 --- /dev/null +++ b/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyEvent.kt @@ -0,0 +1,7 @@ +package com.brainwallet.ui.screens.ready + +import android.content.Context + +sealed class ReadyEvent { + data class OnLoad(val context: Context) : ReadyEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyScreen.kt index d952472f..1201b0a7 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyScreen.kt @@ -33,23 +33,27 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp -import androidx.core.widget.TextViewCompat.AutoSizeTextType import com.brainwallet.R -import com.brainwallet.data.model.AppSetting import com.brainwallet.navigation.OnNavigate import com.brainwallet.navigation.Route import com.brainwallet.navigation.UiEffect import com.brainwallet.ui.composable.BorderedLargeButton import com.brainwallet.ui.composable.BrainwalletScaffold import com.brainwallet.ui.composable.BrainwalletTopAppBar +import org.koin.compose.koinInject @Composable fun ReadyScreen( - onNavigate: OnNavigate + onNavigate: OnNavigate, + viewModel: ReadyViewModel = koinInject() ) { val context = LocalContext.current + LaunchedEffect(Unit) { + viewModel.onEvent(ReadyEvent.OnLoad(context)) + } + /// Layout values val leadingCopyPadding = 16 @@ -127,7 +131,7 @@ fun ReadyScreen( BorderedLargeButton( onClick = { - onNavigate.invoke(UiEffect.Navigate(Route.SetPasscode())) + onNavigate.invoke(UiEffect.Navigate(Route.SetPasscode())) }, modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyViewModel.kt new file mode 100644 index 00000000..0bd04200 --- /dev/null +++ b/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyViewModel.kt @@ -0,0 +1,22 @@ +package com.brainwallet.ui.screens.ready + +import androidx.work.WorkManager +import com.brainwallet.ui.BrainwalletViewModel +import com.brainwallet.wallet.BRWalletManager +import com.brainwallet.worker.SyncBlockWorker + +class ReadyViewModel : BrainwalletViewModel() { + + override fun onEvent(event: ReadyEvent) { + when (event) { + is ReadyEvent.OnLoad -> { + /** + * inside [generateRandomSeed] + * if seed phrase exists, then will using it + */ + BRWalletManager.getInstance().generateRandomSeed(event.context) + WorkManager.getInstance(event.context).enqueue(SyncBlockWorker.request) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt index a2dab9aa..37cfec20 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt @@ -3,13 +3,11 @@ package com.brainwallet.ui.screens.welcome import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.LocaleListCompat import androidx.lifecycle.viewModelScope -import androidx.work.WorkManager import com.brainwallet.data.model.AppSetting import com.brainwallet.data.model.Language import com.brainwallet.data.repository.SettingRepository import com.brainwallet.ui.BrainwalletViewModel import com.brainwallet.wallet.BRWalletManager -import com.brainwallet.worker.SyncBlockWorker import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -113,14 +111,10 @@ class WelcomeViewModel( } is WelcomeEvent.OnLoad -> { - val walletNotAvailable = BRWalletManager.getInstance().noWallet(event.context) /** - * if wallet not available then generate new random seed and enqueue [SyncBlockWorker] + * generate temp random seed phrase */ - if (walletNotAvailable) { - BRWalletManager.getInstance().generateRandomSeed(event.context) - WorkManager.getInstance(event.context).enqueue(SyncBlockWorker.request) - } + BRWalletManager.getInstance().generateRandomSeed(event.context, true) } } } diff --git a/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java b/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java index 9a3eb11b..dc9f0f0e 100644 --- a/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java +++ b/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java @@ -59,7 +59,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Locale; import timber.log.Timber; @@ -104,33 +103,64 @@ public static BRWalletManager getInstance() { return instance; } - public synchronized boolean generateRandomSeed(final Context ctx) { - SecureRandom sr = new SecureRandom(); - final String[] words; - List list; - String languageCode = "en"; - list = Bip39Reader.bip39List(ctx, "en"); - words = list.toArray(new String[list.size()]); - final byte[] randomSeed = sr.generateSeed(16); - if (words.length != 2048) { - IllegalArgumentException ex = new IllegalArgumentException("the list is wrong, size: " + words.length); - Timber.e(ex); - throw ex; + public synchronized boolean generateRandomSeed(final Context context) { + return generateRandomSeed(context, false); + } + + //TODO: please revisit this later + public synchronized boolean generateRandomSeed(final Context ctx, boolean isTemp) { + + byte[] seedPhraseTemp = null; + try { + seedPhraseTemp = BRKeyStore.getPhraseTemp(ctx, 0); + } catch (UserNotAuthenticatedException e) { + //no-op } - if (randomSeed.length != 16) - throw new NullPointerException("failed to create the seed, seed length is not 128: " + randomSeed.length); - byte[] strPhrase = encodeSeed(randomSeed, words); - if (strPhrase == null || strPhrase.length == 0) { - NullPointerException ex = new NullPointerException("failed to encodeSeed"); - Timber.e(ex); - throw ex; + + byte[] strPhrase = null; + if (seedPhraseTemp == null) { + //seed phrase temp not exists + SecureRandom sr = new SecureRandom(); + final String[] words; + List list; + String languageCode = "en"; + list = Bip39Reader.bip39List(ctx, "en"); + words = list.toArray(new String[list.size()]); + final byte[] randomSeed = sr.generateSeed(16); + if (words.length != 2048) { + IllegalArgumentException ex = new IllegalArgumentException("the list is wrong, size: " + words.length); + Timber.e(ex); + throw ex; + } + if (randomSeed.length != 16) + throw new NullPointerException("failed to create the seed, seed length is not 128: " + randomSeed.length); + strPhrase = encodeSeed(randomSeed, words); + if (strPhrase == null || strPhrase.length == 0) { + NullPointerException ex = new NullPointerException("failed to encodeSeed"); + Timber.e(ex); + throw ex; + } + String[] splitPhrase = new String(strPhrase).split(" "); + if (splitPhrase.length != 12) { + NullPointerException ex = new NullPointerException("phrase does not have 12 words:" + splitPhrase.length + ", lang: " + languageCode); + Timber.e(ex); + throw ex; + } + } else { + strPhrase = seedPhraseTemp; } - String[] splitPhrase = new String(strPhrase).split(" "); - if (splitPhrase.length != 12) { - NullPointerException ex = new NullPointerException("phrase does not have 12 words:" + splitPhrase.length + ", lang: " + languageCode); - Timber.e(ex); - throw ex; + + /** + * if temp, we just save to [PHRASE_TEMP_ALIAS] & return true + */ + if (isTemp) { + try { + return BRKeyStore.putPhraseTemp(strPhrase, ctx, 0); + } catch (UserNotAuthenticatedException e) { + return false; + } } + boolean success; try { success = BRKeyStore.putPhrase(strPhrase, ctx, BRConstants.PUT_PHRASE_NEW_WALLET_REQUEST_CODE); @@ -166,8 +196,14 @@ public synchronized boolean generateRandomSeed(final Context ctx) { byte[] pubKey = BRWalletManager.getInstance().getMasterPubKey(strBytes); BRKeyStore.putMasterPublicKey(pubKey, ctx); - return true; + //after wallet created, then remove seed phrase temp + try { + BRKeyStore.putPhraseTemp(null, ctx, 0); + } catch (UserNotAuthenticatedException e) { + //no-op + } + return true; } public boolean wipeKeyStore(Context context) { @@ -368,8 +404,8 @@ public static void onBalanceChanged(final long balance) { public static void onTxAdded(byte[] tx, int blockHeight, long timestamp, final long amount, String hash) { - // DEV Uncomment to see values - // Timber.d("timber: onTxAdded: tx.length: %d, blockHeight: %d, timestamp: %d, amount: %d, hash: %s", tx.length, blockHeight, timestamp, amount, hash)); + // DEV Uncomment to see values + // Timber.d("timber: onTxAdded: tx.length: %d, blockHeight: %d, timestamp: %d, amount: %d, hash: %s", tx.length, blockHeight, timestamp, amount, hash)); final Context ctx = BrainwalletApp.getBreadContext(); if (amount > 0) { @@ -493,70 +529,70 @@ public void initWallet(final Context ctx) { Timber.d("timber: Showing seed fragment"); - if (!m.isCreated()) { - List transactions = TransactionDataSource.getInstance(ctx).getAllTransactions(); - Timber.d("timber: All transactions : %d",transactions.size()); + if (!m.isCreated()) { + List transactions = TransactionDataSource.getInstance(ctx).getAllTransactions(); + Timber.d("timber: All transactions : %d", transactions.size()); - int transactionsCount = transactions.size(); - if (transactionsCount > 0) { - m.createTxArrayWithCount(transactionsCount); - for (BRTransactionEntity entity : transactions) { - m.putTransaction(entity.getBuff(), entity.getBlockheight(), entity.getTimestamp()); - } + int transactionsCount = transactions.size(); + if (transactionsCount > 0) { + m.createTxArrayWithCount(transactionsCount); + for (BRTransactionEntity entity : transactions) { + m.putTransaction(entity.getBuff(), entity.getBlockheight(), entity.getTimestamp()); } + } - byte[] pubkeyEncoded = BRKeyStore.getMasterPublicKey(ctx); - if (Utils.isNullOrEmpty(pubkeyEncoded)) { - Timber.i("timber: initWallet: pubkey is missing"); - return; - } - //Save the first address for future check - m.createWallet(transactionsCount, pubkeyEncoded); - String firstAddress = BRWalletManager.getFirstAddress(pubkeyEncoded); - BRSharedPrefs.putFirstAddress(ctx, firstAddress); - FeeManager feeManager = FeeManager.getInstance(); - if (feeManager.isRegularFee()) { - feeManager.updateFeePerKb(ctx); - BRWalletManager.getInstance().setFeePerKb(feeManager.currentFees.regular); - } + byte[] pubkeyEncoded = BRKeyStore.getMasterPublicKey(ctx); + if (Utils.isNullOrEmpty(pubkeyEncoded)) { + Timber.i("timber: initWallet: pubkey is missing"); + return; } + //Save the first address for future check + m.createWallet(transactionsCount, pubkeyEncoded); + String firstAddress = BRWalletManager.getFirstAddress(pubkeyEncoded); + BRSharedPrefs.putFirstAddress(ctx, firstAddress); + FeeManager feeManager = FeeManager.getInstance(); + if (feeManager.isRegularFee()) { + feeManager.updateFeePerKb(ctx); + BRWalletManager.getInstance().setFeePerKb(feeManager.currentFees.regular); + } + } - if (!pm.isCreated()) { - List blocks = MerkleBlockDataSource.getInstance(ctx).getAllMerkleBlocks(); - List peers = PeerDataSource.getInstance(ctx).getAllPeers(); - final int blocksCount = blocks.size(); - final int peersCount = peers.size(); - if (blocksCount > 0) { - pm.createBlockArrayWithCount(blocksCount); - for (BRMerkleBlockEntity entity : blocks) { - pm.putBlock(entity.getBuff(), entity.getBlockHeight()); - } + if (!pm.isCreated()) { + List blocks = MerkleBlockDataSource.getInstance(ctx).getAllMerkleBlocks(); + List peers = PeerDataSource.getInstance(ctx).getAllPeers(); + final int blocksCount = blocks.size(); + final int peersCount = peers.size(); + if (blocksCount > 0) { + pm.createBlockArrayWithCount(blocksCount); + for (BRMerkleBlockEntity entity : blocks) { + pm.putBlock(entity.getBuff(), entity.getBlockHeight()); } - if (peersCount > 0) { - pm.createPeerArrayWithCount(peersCount); - for (BRPeerEntity entity : peers) { - pm.putPeer(entity.getAddress(), entity.getPort(), entity.getTimeStamp()); - } + } + if (peersCount > 0) { + pm.createPeerArrayWithCount(peersCount); + for (BRPeerEntity entity : peers) { + pm.putPeer(entity.getAddress(), entity.getPort(), entity.getTimeStamp()); } - Timber.d("timber: blocksCount before connecting: %s", blocksCount); - Timber.d("timber: peersCount before connecting: %s", peersCount); + } + Timber.d("timber: blocksCount before connecting: %s", blocksCount); + Timber.d("timber: peersCount before connecting: %s", peersCount); - int walletTime = BRKeyStore.getWalletCreationTime(ctx); + int walletTime = BRKeyStore.getWalletCreationTime(ctx); - Timber.d("timber: initWallet: walletTime: %s user preferred fpRate: %f", walletTime, fpRate); - pm.create(walletTime, blocksCount, peersCount, fpRate); - BRPeerManager.getInstance().updateFixedPeer(ctx); - } + Timber.d("timber: initWallet: walletTime: %s user preferred fpRate: %f", walletTime, fpRate); + pm.create(walletTime, blocksCount, peersCount, fpRate); + BRPeerManager.getInstance().updateFixedPeer(ctx); + } - pm.connect(); - if (BRSharedPrefs.getStartHeight(ctx) == 0) { - BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { - @Override - public void run() { - BRSharedPrefs.putStartHeight(ctx, BRPeerManager.getCurrentBlockHeight()); - } - }); - } + pm.connect(); + if (BRSharedPrefs.getStartHeight(ctx) == 0) { + BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { + @Override + public void run() { + BRSharedPrefs.putStartHeight(ctx, BRPeerManager.getCurrentBlockHeight()); + } + }); + } } finally { @@ -664,7 +700,7 @@ public void setFeePerKb(long fee) { public native String reverseTxHash(String txHash); public native String txHashToHex(byte[] txHash); - + public native long nativeBalance(); public native long defaultFee(); From 3b1b270da0dd2d19085941f6c7c7a740914b7655 Mon Sep 17 00:00:00 2001 From: andhikayuana Date: Mon, 24 Mar 2025 11:09:40 +0700 Subject: [PATCH 04/17] chore: update comment for BRWalletManager.generateRandomSeed --- app/src/main/java/com/brainwallet/wallet/BRWalletManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java b/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java index dc9f0f0e..6e7e6c58 100644 --- a/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java +++ b/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java @@ -151,7 +151,7 @@ public synchronized boolean generateRandomSeed(final Context ctx, boolean isTemp } /** - * if temp, we just save to [PHRASE_TEMP_ALIAS] & return true + * if temp, we just save to [PHRASE_TEMP_ALIAS] & return it */ if (isTemp) { try { From f29867b1641fbbf68403ad50cb70be208d920cf7 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Mon, 24 Mar 2025 14:21:06 +0000 Subject: [PATCH 05/17] Added in Currency code and symbol in the Welcome screen --- .../bottomsheet/FiatSelectorBottomSheet.kt | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/brainwallet/ui/composable/bottomsheet/FiatSelectorBottomSheet.kt b/app/src/main/java/com/brainwallet/ui/composable/bottomsheet/FiatSelectorBottomSheet.kt index a564b778..0d24c6ab 100644 --- a/app/src/main/java/com/brainwallet/ui/composable/bottomsheet/FiatSelectorBottomSheet.kt +++ b/app/src/main/java/com/brainwallet/ui/composable/bottomsheet/FiatSelectorBottomSheet.kt @@ -1,21 +1,33 @@ package com.brainwallet.ui.composable.bottomsheet +import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp import com.brainwallet.data.model.CurrencyEntity import com.brainwallet.tools.sqlite.CurrencyDataSource import com.brainwallet.ui.composable.BrainwalletBottomSheet import com.brainwallet.ui.theme.BrainwalletTheme +import androidx.compose.foundation.layout.size +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip /** * describe [FiatSelectorBottomSheet] for CurrencySelector @@ -27,6 +39,10 @@ fun FiatSelectorBottomSheet( onFiatSelect: (CurrencyEntity) -> Unit ) { val context = LocalContext.current + val unselectedCircleSize = 20 + val tinyPad = 2 + + BrainwalletBottomSheet( onDismissRequest = onDismissRequest, ) { @@ -41,7 +57,21 @@ fun FiatSelectorBottomSheet( ), modifier = Modifier.clickable { onFiatSelect.invoke(currency) }, headlineContent = { - Text(currency.name) + Row { + Text( + modifier = Modifier.padding(tinyPad.dp), + text = currency.name, + style = MaterialTheme.typography.labelLarge + .copy(textAlign = TextAlign.Left) + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + modifier = Modifier.padding(tinyPad.dp), + text = "${currency.code} (${currency.symbol})", + style = MaterialTheme.typography.labelLarge + .copy(textAlign = TextAlign.Left) + ) + } }, trailingContent = { if (selectedCurrency.code == currency.code) { Icon( @@ -49,7 +79,15 @@ fun FiatSelectorBottomSheet( contentDescription = null, tint = BrainwalletTheme.colors.affirm ) - } + } else { + Box( + modifier = Modifier + .size(unselectedCircleSize.dp) + .alpha(0.1f) + .clip(CircleShape) + .background(BrainwalletTheme.colors.content) + ) + } }) } } From 5c248b5b35ef664b47ee90fede0046a650b031e1 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Mon, 24 Mar 2025 15:05:09 +0000 Subject: [PATCH 06/17] tiny layout changes --- .../com/brainwallet/ui/screens/welcome/WelcomeScreen.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeScreen.kt index da80ef85..98dc818c 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeScreen.kt @@ -69,15 +69,13 @@ fun WelcomeScreen( //todo: the following sizing can be move to BrainwalletTheme // Global layout - val buttonFontSize = 16 - val thinButtonFontSize = 14 - val iconButtonSize = 32 + val buttonFontSize = 24 + val thinButtonFontSize = 22 val toggleButtonSize = 45 - val leadTrailPadding = 24 + val leadTrailPadding = 18 val halfLeadTrailPadding = leadTrailPadding / 2 val doubleLeadTrailPadding = leadTrailPadding * 2 val rowPadding = 8 - val tinyPad = 4 val activeRowHeight = 70 val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.welcomeemoji20250212)) From dcd544c9f5b70797d73a8365fa3115f2276af975 Mon Sep 17 00:00:00 2001 From: andhikayuana Date: Mon, 24 Mar 2025 22:10:03 +0700 Subject: [PATCH 07/17] chore: change sync flow --- .../tools/security/BRKeyStore.java | 22 ----- .../com/brainwallet/ui/BrainwalletActivity.kt | 7 +- .../ui/screens/ready/ReadyViewModel.kt | 12 +-- .../ui/screens/welcome/WelcomeViewModel.kt | 11 ++- .../brainwallet/wallet/BRWalletManager.java | 87 ++++++------------- 5 files changed, 36 insertions(+), 103 deletions(-) diff --git a/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java b/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java index 24169f87..c76bb892 100644 --- a/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java +++ b/app/src/main/java/com/brainwallet/tools/security/BRKeyStore.java @@ -95,7 +95,6 @@ public class BRKeyStore { private static final String AUTH_KEY_IV = "ivauthkey"; private static final String TOKEN_IV = "ivtoken"; private static final String PASS_TIME_IV = "passtimetoken"; - private static final String PHRASE_TEMP_IV = "ivphrasetemp"; //temp phrase, related with sync mechanism public static final String PHRASE_ALIAS = "phrase"; public static final String CANARY_ALIAS = "canary"; @@ -109,7 +108,6 @@ public class BRKeyStore { public static final String AUTH_KEY_ALIAS = "authKey"; public static final String TOKEN_ALIAS = "token"; public static final String PASS_TIME_ALIAS = "passTime"; - private static final String PHRASE_TEMP_ALIAS = "phrasetemp"; //temp phrase, related with sync mechanism private static final String PHRASE_FILENAME = "my_phrase"; private static final String CANARY_FILENAME = "my_canary"; @@ -123,7 +121,6 @@ public class BRKeyStore { private static final String AUTH_KEY_FILENAME = "my_auth_key"; private static final String TOKEN_FILENAME = "my_token"; private static final String PASS_TIME_FILENAME = "my_pass_time"; - private static final String PHRASE_TEMP_FILENAME = "my_phrasetemp"; //temp phrase, related with sync mechanism private static boolean bugMessageShowing; @@ -144,7 +141,6 @@ public class BRKeyStore { aliasObjectMap.put(TOKEN_ALIAS, new AliasObject(TOKEN_ALIAS, TOKEN_FILENAME, TOKEN_IV)); aliasObjectMap.put(PASS_TIME_ALIAS, new AliasObject(PASS_TIME_ALIAS, PASS_TIME_FILENAME, PASS_TIME_IV)); aliasObjectMap.put(TOTAL_LIMIT_ALIAS, new AliasObject(TOTAL_LIMIT_ALIAS, TOTAL_LIMIT_FILENAME, TOTAL_LIMIT_IV)); - aliasObjectMap.put(PHRASE_TEMP_ALIAS, new AliasObject(PHRASE_TEMP_ALIAS, PHRASE_TEMP_FILENAME, PHRASE_TEMP_IV)); } @@ -269,24 +265,6 @@ public synchronized static byte[] getPhrase(final Context context, int requestCo return _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); } - public synchronized static boolean putPhraseTemp(byte[] strToStore, Context context, int requestCode) throws UserNotAuthenticatedException { - if (PostAuth.isStuckWithAuthLoop) { - showLoopBugMessage(context); - throw new UserNotAuthenticatedException(); - } - AliasObject obj = aliasObjectMap.get(PHRASE_TEMP_ALIAS); - return !(strToStore == null || strToStore.length == 0) && _setData(context, strToStore, obj.alias, obj.datafileName, obj.ivFileName, requestCode, false); - } - - public synchronized static byte[] getPhraseTemp(final Context context, int requestCode) throws UserNotAuthenticatedException { - if (PostAuth.isStuckWithAuthLoop) { - showLoopBugMessage(context); - throw new UserNotAuthenticatedException(); - } - AliasObject obj = aliasObjectMap.get(PHRASE_TEMP_ALIAS); - return _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); - } - public synchronized static boolean putCanary(String strToStore, Context context, int requestCode) throws UserNotAuthenticatedException { if (PostAuth.isStuckWithAuthLoop) { showLoopBugMessage(context); diff --git a/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt b/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt index 1163eca7..34608ffd 100644 --- a/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt +++ b/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt @@ -15,7 +15,6 @@ import com.brainwallet.data.repository.SettingRepository import com.brainwallet.navigation.LegacyNavigation import com.brainwallet.navigation.MainNavHost import com.brainwallet.navigation.Route -import com.brainwallet.presenter.activities.intro.WriteDownActivity import com.brainwallet.presenter.activities.util.BRActivity import com.brainwallet.tools.animation.BRAnimator import com.brainwallet.tools.animation.BRDialog @@ -193,6 +192,8 @@ class BrainwalletActivity : BRActivity() { ) ) } + } else if (BRSharedPrefs.getPhraseWroteDown(this).not()) { + PostAuth.getInstance().onPhraseCheckAuth(this, false) } } @@ -232,9 +233,7 @@ class BrainwalletActivity : BRActivity() { if (walletNotAvailable) { PostAuth.getInstance().onCreateWalletAuth(this, false) } else { - Intent(this, WriteDownActivity::class.java).also { - startActivity(it) - } + PostAuth.getInstance().onPhraseCheckAuth(this, false) } } } diff --git a/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyViewModel.kt index 0bd04200..b66a6889 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/ready/ReadyViewModel.kt @@ -1,22 +1,12 @@ package com.brainwallet.ui.screens.ready -import androidx.work.WorkManager import com.brainwallet.ui.BrainwalletViewModel -import com.brainwallet.wallet.BRWalletManager -import com.brainwallet.worker.SyncBlockWorker class ReadyViewModel : BrainwalletViewModel() { override fun onEvent(event: ReadyEvent) { when (event) { - is ReadyEvent.OnLoad -> { - /** - * inside [generateRandomSeed] - * if seed phrase exists, then will using it - */ - BRWalletManager.getInstance().generateRandomSeed(event.context) - WorkManager.getInstance(event.context).enqueue(SyncBlockWorker.request) - } + is ReadyEvent.OnLoad -> Unit } } } \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt index 37cfec20..5045014e 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/welcome/WelcomeViewModel.kt @@ -3,11 +3,13 @@ package com.brainwallet.ui.screens.welcome import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.LocaleListCompat import androidx.lifecycle.viewModelScope +import androidx.work.WorkManager import com.brainwallet.data.model.AppSetting import com.brainwallet.data.model.Language import com.brainwallet.data.repository.SettingRepository import com.brainwallet.ui.BrainwalletViewModel import com.brainwallet.wallet.BRWalletManager +import com.brainwallet.worker.SyncBlockWorker import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -111,10 +113,11 @@ class WelcomeViewModel( } is WelcomeEvent.OnLoad -> { - /** - * generate temp random seed phrase - */ - BRWalletManager.getInstance().generateRandomSeed(event.context, true) + val walletNotExists = BRWalletManager.getInstance().noWallet(event.context) + if (walletNotExists) { + BRWalletManager.getInstance().generateRandomSeed(event.context) + } + WorkManager.getInstance(event.context).enqueue(SyncBlockWorker.request) } } } diff --git a/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java b/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java index 6e7e6c58..792fad8e 100644 --- a/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java +++ b/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java @@ -103,62 +103,32 @@ public static BRWalletManager getInstance() { return instance; } - public synchronized boolean generateRandomSeed(final Context context) { - return generateRandomSeed(context, false); - } - - //TODO: please revisit this later - public synchronized boolean generateRandomSeed(final Context ctx, boolean isTemp) { - - byte[] seedPhraseTemp = null; - try { - seedPhraseTemp = BRKeyStore.getPhraseTemp(ctx, 0); - } catch (UserNotAuthenticatedException e) { - //no-op + public synchronized boolean generateRandomSeed(final Context ctx) { + SecureRandom sr = new SecureRandom(); + final String[] words; + List list; + String languageCode = "en"; + list = Bip39Reader.bip39List(ctx, "en"); + words = list.toArray(new String[list.size()]); + final byte[] randomSeed = sr.generateSeed(16); + if (words.length != 2048) { + IllegalArgumentException ex = new IllegalArgumentException("the list is wrong, size: " + words.length); + Timber.e(ex); + throw ex; } - - byte[] strPhrase = null; - if (seedPhraseTemp == null) { - //seed phrase temp not exists - SecureRandom sr = new SecureRandom(); - final String[] words; - List list; - String languageCode = "en"; - list = Bip39Reader.bip39List(ctx, "en"); - words = list.toArray(new String[list.size()]); - final byte[] randomSeed = sr.generateSeed(16); - if (words.length != 2048) { - IllegalArgumentException ex = new IllegalArgumentException("the list is wrong, size: " + words.length); - Timber.e(ex); - throw ex; - } - if (randomSeed.length != 16) - throw new NullPointerException("failed to create the seed, seed length is not 128: " + randomSeed.length); - strPhrase = encodeSeed(randomSeed, words); - if (strPhrase == null || strPhrase.length == 0) { - NullPointerException ex = new NullPointerException("failed to encodeSeed"); - Timber.e(ex); - throw ex; - } - String[] splitPhrase = new String(strPhrase).split(" "); - if (splitPhrase.length != 12) { - NullPointerException ex = new NullPointerException("phrase does not have 12 words:" + splitPhrase.length + ", lang: " + languageCode); - Timber.e(ex); - throw ex; - } - } else { - strPhrase = seedPhraseTemp; + if (randomSeed.length != 16) + throw new NullPointerException("failed to create the seed, seed length is not 128: " + randomSeed.length); + byte[] strPhrase = encodeSeed(randomSeed, words); + if (strPhrase == null || strPhrase.length == 0) { + NullPointerException ex = new NullPointerException("failed to encodeSeed"); + Timber.e(ex); + throw ex; } - - /** - * if temp, we just save to [PHRASE_TEMP_ALIAS] & return it - */ - if (isTemp) { - try { - return BRKeyStore.putPhraseTemp(strPhrase, ctx, 0); - } catch (UserNotAuthenticatedException e) { - return false; - } + String[] splitPhrase = new String(strPhrase).split(" "); + if (splitPhrase.length != 12) { + NullPointerException ex = new NullPointerException("phrase does not have 12 words:" + splitPhrase.length + ", lang: " + languageCode); + Timber.e(ex); + throw ex; } boolean success; @@ -196,13 +166,6 @@ public synchronized boolean generateRandomSeed(final Context ctx, boolean isTemp byte[] pubKey = BRWalletManager.getInstance().getMasterPubKey(strBytes); BRKeyStore.putMasterPublicKey(pubKey, ctx); - //after wallet created, then remove seed phrase temp - try { - BRKeyStore.putPhraseTemp(null, ctx, 0); - } catch (UserNotAuthenticatedException e) { - //no-op - } - return true; } @@ -499,7 +462,7 @@ public void onDismiss(DialogInterface dialog) { } }, 0); } else { - if (!m.noWallet(app)) { + if (!m.noWallet(app) && BRSharedPrefs.getPhraseWroteDown(app)) { BRAnimator.startBreadActivity(app, true); } //else just sit in the intro screen From 767c6fbe1e4ac69013ca958344bfa7a78c7e8d51 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Tue, 25 Mar 2025 06:30:47 +0000 Subject: [PATCH 08/17] Changed to BrainwalletTheme --- .../ui/composable/bottomsheet/FiatSelectorBottomSheet.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/brainwallet/ui/composable/bottomsheet/FiatSelectorBottomSheet.kt b/app/src/main/java/com/brainwallet/ui/composable/bottomsheet/FiatSelectorBottomSheet.kt index 0d24c6ab..3ce93ad0 100644 --- a/app/src/main/java/com/brainwallet/ui/composable/bottomsheet/FiatSelectorBottomSheet.kt +++ b/app/src/main/java/com/brainwallet/ui/composable/bottomsheet/FiatSelectorBottomSheet.kt @@ -61,14 +61,14 @@ fun FiatSelectorBottomSheet( Text( modifier = Modifier.padding(tinyPad.dp), text = currency.name, - style = MaterialTheme.typography.labelLarge + style = BrainwalletTheme.typography.labelLarge .copy(textAlign = TextAlign.Left) ) Spacer(modifier = Modifier.weight(1f)) Text( modifier = Modifier.padding(tinyPad.dp), text = "${currency.code} (${currency.symbol})", - style = MaterialTheme.typography.labelLarge + style = BrainwalletTheme.typography.labelLarge .copy(textAlign = TextAlign.Left) ) } From a9c50fe1607a98f2bbed4ba1ec5bc2f27dc4bef5 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Tue, 25 Mar 2025 08:35:25 +0000 Subject: [PATCH 09/17] Checks is Fagement is alive Avoids illegal state crash https://play.google.com/console/developers/7339395838440115507/app/4975829327597071616/vitals/crashes/8d65e00364aeb060543c512bee5684ae/details?days=7&pli=1 --- .../com/brainwallet/presenter/fragments/FragmentSignal.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/brainwallet/presenter/fragments/FragmentSignal.java b/app/src/main/java/com/brainwallet/presenter/fragments/FragmentSignal.java index cee4c1e6..1dc41372 100644 --- a/app/src/main/java/com/brainwallet/presenter/fragments/FragmentSignal.java +++ b/app/src/main/java/com/brainwallet/presenter/fragments/FragmentSignal.java @@ -68,9 +68,9 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat new Handler().postDelayed(new Runnable() { @Override public void run() { - - getParentFragmentManager().popBackStack(); - + if(isAdded()) { + getParentFragmentManager().popBackStack(); + } new Handler().postDelayed(new Runnable() { @Override public void run() { From 0f6174885ec0cfd64e60b3f5cd8144aa9311f567 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Tue, 25 Mar 2025 12:42:52 +0000 Subject: [PATCH 10/17] updated test -fixed the currency entity unwrap --- .../tools/manager/BRApiManager.java | 18 +++++++++--------- .../com/brainwallet/currency/CurrencyTests.kt | 7 ++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/brainwallet/tools/manager/BRApiManager.java b/app/src/main/java/com/brainwallet/tools/manager/BRApiManager.java index d57f9f71..9b7a81de 100644 --- a/app/src/main/java/com/brainwallet/tools/manager/BRApiManager.java +++ b/app/src/main/java/com/brainwallet/tools/manager/BRApiManager.java @@ -61,21 +61,21 @@ private Set getCurrencies(Activity context) { String selectedISO = BRSharedPrefs.getIsoSymbol(context); int length = arr.length(); for (int i = 0; i < length; i++) { - CurrencyEntity tmp = new CurrencyEntity(); + CurrencyEntity tempCurrencyEntity = new CurrencyEntity(); try { - JSONObject tmpObj = (JSONObject) arr.get(i); - tmp.name = tmpObj.getString("name"); - tmp.code = tmpObj.getString("code"); - tmp.rate = (float) tmpObj.getDouble("n"); - if (tmp.code.equalsIgnoreCase(selectedISO)) { - BRSharedPrefs.putIso(context, tmp.code); + JSONObject tmpJSONObj = (JSONObject) arr.get(i); + tempCurrencyEntity.name = tmpJSONObj.getString("name"); + tempCurrencyEntity.code = tmpJSONObj.getString("code"); + tempCurrencyEntity.rate = (float) tmpJSONObj.getDouble("n"); + tempCurrencyEntity.symbol = new String(""); + if (tempCurrencyEntity.code.equalsIgnoreCase(selectedISO)) { + BRSharedPrefs.putIso(context, tempCurrencyEntity.code); BRSharedPrefs.putCurrencyListPosition(context, i - 1); } - set.add(tmp); + set.add(tempCurrencyEntity); } catch (JSONException e) { Timber.e(e); } - } } else { Timber.d("timber: getCurrencies: failed to get currencies"); diff --git a/app/src/test/java/com/brainwallet/currency/CurrencyTests.kt b/app/src/test/java/com/brainwallet/currency/CurrencyTests.kt index 5b05252f..37b7e46a 100644 --- a/app/src/test/java/com/brainwallet/currency/CurrencyTests.kt +++ b/app/src/test/java/com/brainwallet/currency/CurrencyTests.kt @@ -60,7 +60,7 @@ class CurrencyTests { fun `invoke CurrencyDataSource instance database, should return currency by ISO`() { val expected = - CurrencyEntity("USD", "USD", 110.345f) + CurrencyEntity("USD", "USD", 110.345f, "$") mockCursorDataFromDatabase(isEmpty = false, expected = expected) val actual = currencyDataSource?.getCurrencyByIso("USD") @@ -72,11 +72,12 @@ class CurrencyTests { @Test fun `invoke putCurrencies, then should save the CurrencyEntity into database`() { val fetchedCurrencies = listOf( - CurrencyEntity("USD", "USD", 110.345f), + CurrencyEntity("USD", "USD", 110.345f, "$"), CurrencyEntity( "IDR", "IDR", - 1752020.9450135066f + 1752020.9450135066f, + "$" ) ) From 4e252ac29cfb6c3ab26ae5ea4a8f6de154311f2e Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Tue, 25 Mar 2025 17:17:29 +0000 Subject: [PATCH 11/17] Updated the APIManager - Using MOSHI lib - Redacted BRApiManager - Updated the Tests --- app/build.gradle.kts | 1 + .../data/model/CurrencyEntity.java | 6 +- .../main/java/com/brainwallet/di/Module.kt | 4 +- .../presenter/activities/util/BRActivity.java | 4 +- .../brainwallet/tools/manager/APIManager.kt | 117 +++++++ .../tools/manager/BRApiManager.java | 317 +++++++++--------- .../currency/BackupRateFetchTests.kt | 6 +- .../java/com/brainwallet/data/BaseURLTests.kt | 19 +- .../tools/util/ProdAPIManagerTests.kt | 26 +- 9 files changed, 315 insertions(+), 185 deletions(-) create mode 100644 app/src/main/java/com/brainwallet/tools/manager/APIManager.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c4e1b960..adebc638 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -188,6 +188,7 @@ dependencies { } implementation("androidx.webkit:webkit:1.9.0") + implementation("com.squareup.moshi:moshi-kotlin:1.15.2") implementation(libs.androidx.core) implementation(libs.androidx.appcompat) implementation(libs.androidx.legacy.support) diff --git a/app/src/main/java/com/brainwallet/data/model/CurrencyEntity.java b/app/src/main/java/com/brainwallet/data/model/CurrencyEntity.java index f0c31051..319ed216 100644 --- a/app/src/main/java/com/brainwallet/data/model/CurrencyEntity.java +++ b/app/src/main/java/com/brainwallet/data/model/CurrencyEntity.java @@ -5,13 +5,13 @@ public class CurrencyEntity implements Serializable { //Change this after modifying the class - private static final long serialVersionUID = 7526472295622776147L; + private static final long serialVersionUID = 7526472295622777000L; public static final String TAG = CurrencyEntity.class.getName(); public String code; - public String name; + public String name = ""; public float rate; - public String symbol; + public String symbol = ""; public CurrencyEntity(String code, String name, float rate, String symbol) { this.code = code; diff --git a/app/src/main/java/com/brainwallet/di/Module.kt b/app/src/main/java/com/brainwallet/di/Module.kt index 75a2cf5f..05db629b 100644 --- a/app/src/main/java/com/brainwallet/di/Module.kt +++ b/app/src/main/java/com/brainwallet/di/Module.kt @@ -5,7 +5,7 @@ import android.content.SharedPreferences import com.brainwallet.BuildConfig import com.brainwallet.data.repository.SettingRepository import com.brainwallet.data.source.RemoteConfigSource -import com.brainwallet.tools.manager.BRApiManager +import com.brainwallet.tools.manager.APIManager import com.brainwallet.tools.sqlite.CurrencyDataSource import com.brainwallet.ui.screens.home.SettingsViewModel import com.brainwallet.ui.screens.inputwords.InputWordsViewModel @@ -32,7 +32,7 @@ val dataModule = module { it.initialize() } } - single { BRApiManager(get()) } + single { APIManager(get()) } single { CurrencyDataSource.getInstance(get()) } single { provideSharedPreferences(context = androidApplication()) } single { SettingRepository.Impl(get(), get()) } diff --git a/app/src/main/java/com/brainwallet/presenter/activities/util/BRActivity.java b/app/src/main/java/com/brainwallet/presenter/activities/util/BRActivity.java index 6f62d97c..bd118415 100644 --- a/app/src/main/java/com/brainwallet/presenter/activities/util/BRActivity.java +++ b/app/src/main/java/com/brainwallet/presenter/activities/util/BRActivity.java @@ -16,7 +16,7 @@ import com.brainwallet.presenter.activities.intro.RecoverActivity; import com.brainwallet.presenter.activities.intro.WriteDownActivity; import com.brainwallet.tools.animation.BRAnimator; -import com.brainwallet.tools.manager.BRApiManager; +import com.brainwallet.tools.manager.APIManager; import com.brainwallet.tools.manager.InternetManager; import com.brainwallet.tools.security.AuthManager; import com.brainwallet.tools.security.BRKeyStore; @@ -148,7 +148,7 @@ public static void init(Activity app) { if (!(app instanceof RecoverActivity || app instanceof WriteDownActivity)) { - BRApiManager apiManager = KoinJavaComponent.get(BRApiManager.class); + APIManager apiManager = KoinJavaComponent.get(APIManager.class); apiManager.startTimer(app); } diff --git a/app/src/main/java/com/brainwallet/tools/manager/APIManager.kt b/app/src/main/java/com/brainwallet/tools/manager/APIManager.kt new file mode 100644 index 00000000..14f1af01 --- /dev/null +++ b/app/src/main/java/com/brainwallet/tools/manager/APIManager.kt @@ -0,0 +1,117 @@ +package com.brainwallet.tools.manager + +import android.app.Activity +import android.content.Context +import com.brainwallet.data.model.CurrencyEntity +import com.brainwallet.data.source.RemoteConfigSource +import com.brainwallet.tools.sqlite.CurrencyDataSource +import com.brainwallet.tools.util.BRConstants.BW_API_DEV_HOST +import com.brainwallet.tools.util.BRConstants.BW_API_PROD_HOST +import com.brainwallet.tools.util.Utils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import com.squareup.moshi.Moshi +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Types +import timber.log.Timber +import java.io.IOException +import java.util.* +import kotlin.collections.LinkedHashSet + +class APIManager (private val remoteConfigSource: RemoteConfigSource) { + fun getPRODBaseURL(): String = BW_API_PROD_HOST + fun getDEVBaseURL(): String = BW_API_DEV_HOST + + private var timer: Timer? = null + private var pollPeriod : Long = 4000 + + private val client = OkHttpClient() + private val moshi: Moshi = Moshi.Builder().build() + private val type = Types.newParameterizedType(List::class.java, CurrencyEntity::class.java) + private val jsonAdapter: JsonAdapter> = moshi.adapter(type) + + fun getCurrencies(context: Activity): Set { + val set = LinkedHashSet() + try { + val arr = fetchRates(context) + FeeManager.updateFeePerKb(context) + arr?.let { currencyList -> + val selectedISO = BRSharedPrefs.getIsoSymbol(context) + currencyList.forEachIndexed { i, tempCurrencyEntity -> + if (tempCurrencyEntity.code.equals(selectedISO, ignoreCase = true)) { + BRSharedPrefs.putIso(context, tempCurrencyEntity.code) + BRSharedPrefs.putCurrencyListPosition(context, i - 1) + } + set.add(tempCurrencyEntity) + } + } ?: Timber.d("timber: getCurrencies: failed to get currencies") + } catch (e: Exception) { + Timber.e(e) + } + return set.reversed().toSet() + } + + private fun initializeTimerTask(context: Context) { + timer = Timer().apply { + schedule(object : TimerTask() { + override fun run() { + CoroutineScope(Dispatchers.IO).launch { + val tmp = getCurrencies(context as Activity) + withContext(Dispatchers.Main) { + CurrencyDataSource.getInstance(context).putCurrencies(tmp) + } + } + } + }, 0, pollPeriod) + } + } + + fun startTimer(context: Context) { + if (timer != null) return + initializeTimerTask(context) + } + + fun fetchRates(activity: Activity): List? { + val jsonString = createGETRequestURL(activity, BW_API_PROD_HOST + "/api/v1/rates") + return parseJsonArray(jsonString) ?: backupFetchRates(activity) + } + + private fun backupFetchRates(activity: Activity): List? { + val jsonString = createGETRequestURL(activity, BW_API_DEV_HOST + "/api/v1/rates") + return parseJsonArray(jsonString) + } + + private fun parseJsonArray(jsonString: String?): List? { + return try { + jsonString?.let { jsonAdapter.fromJson(it) } + } catch (e: IOException) { + Timber.e(e) + null + } + } + + private fun createGETRequestURL(app: Context, url: String): String? { + val request = Request.Builder() + .url(url) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("User-agent", Utils.getAgentString(app, "android/HttpURLConnection")) + .get() + .build() + + return try { + client.newCall(request).execute().use { response -> + response.body?.string().also { + BRSharedPrefs.putSecureTime(app, System.currentTimeMillis()) + } + } + } catch (e: IOException) { + Timber.e(e) + null + } + } +} diff --git a/app/src/main/java/com/brainwallet/tools/manager/BRApiManager.java b/app/src/main/java/com/brainwallet/tools/manager/BRApiManager.java index 9b7a81de..e298b178 100644 --- a/app/src/main/java/com/brainwallet/tools/manager/BRApiManager.java +++ b/app/src/main/java/com/brainwallet/tools/manager/BRApiManager.java @@ -30,6 +30,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.Set; import java.util.Timer; import java.util.TimerTask; @@ -37,158 +38,164 @@ import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; - -public class BRApiManager { - private Timer timer; - - private TimerTask timerTask; - - private Handler handler; - - private RemoteConfigSource remoteConfigSource; - - public BRApiManager(RemoteConfigSource remoteConfigSource) { - this.remoteConfigSource = remoteConfigSource; - this.handler = new Handler(); - } - - private Set getCurrencies(Activity context) { - Set set = new LinkedHashSet<>(); - try { - JSONArray arr = fetchRates(context); - FeeManager.updateFeePerKb(context); - if (arr != null) { - String selectedISO = BRSharedPrefs.getIsoSymbol(context); - int length = arr.length(); - for (int i = 0; i < length; i++) { - CurrencyEntity tempCurrencyEntity = new CurrencyEntity(); - try { - JSONObject tmpJSONObj = (JSONObject) arr.get(i); - tempCurrencyEntity.name = tmpJSONObj.getString("name"); - tempCurrencyEntity.code = tmpJSONObj.getString("code"); - tempCurrencyEntity.rate = (float) tmpJSONObj.getDouble("n"); - tempCurrencyEntity.symbol = new String(""); - if (tempCurrencyEntity.code.equalsIgnoreCase(selectedISO)) { - BRSharedPrefs.putIso(context, tempCurrencyEntity.code); - BRSharedPrefs.putCurrencyListPosition(context, i - 1); - } - set.add(tempCurrencyEntity); - } catch (JSONException e) { - Timber.e(e); - } - } - } else { - Timber.d("timber: getCurrencies: failed to get currencies"); - } - } catch (Exception e) { - Timber.e(e); - } - List tempList = new ArrayList<>(set); - Collections.reverse(tempList); - return new LinkedHashSet<>(set); - } - - - private void initializeTimerTask(final Context context) { - timerTask = new TimerTask() { - public void run() { - //use a handler to run a toast that shows the current timestamp - handler.post(new Runnable() { - public void run() { - BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { - @Override - public void run() { - Set tmp = getCurrencies((Activity) context); - CurrencyDataSource.getInstance(context).putCurrencies(tmp); - } - }); - } - }); - } - }; - } - - public void startTimer(Context context) { - //set a new Timer - if (timer != null) return; - timer = new Timer(); - - //initialize the TimerTask's job - initializeTimerTask(context); - - //schedule the timer, after the first 0ms the TimerTask will run every 60000ms - timer.schedule(timerTask, 0, 4000); - } - - public JSONArray fetchRates(Activity activity) { - String jsonString = createGETRequestURL(activity, BW_API_PROD_HOST + "/api/v1/rates"); - JSONArray jsonArray = null; - if (jsonString == null) return null; - try { - jsonArray = new JSONArray(jsonString); - // DEV Uncomment to view values - // Timber.d("timber: baseUrlProd: %s", getBaseUrlProd()); - // Timber.d("timber: JSON %s",jsonArray.toString()); - } catch (JSONException ex) { - Timber.e(ex); - } - if (jsonArray != null && !BuildConfig.DEBUG) { - AnalyticsManager.logCustomEvent(_20250222_PAC); - } - return jsonArray == null ? backupFetchRates(activity) : jsonArray; - } - - public JSONArray backupFetchRates(Activity activity) { - String jsonString = createGETRequestURL(activity, BW_API_DEV_HOST + "/api/v1/rates"); - - JSONArray jsonArray = null; - if (jsonString == null) return null; - try { - jsonArray = new JSONArray(jsonString); - - } catch (JSONException e) { - Timber.e(e); - } - if (jsonArray != null && !BuildConfig.DEBUG) { - AnalyticsManager.logCustomEvent(_20230113_BAC); - } - - return jsonArray; - } - - // createGETRequestURL - // Creates the params and headers to make a GET Request - private String createGETRequestURL(Context app, String myURL) { - Request request = new Request.Builder() - .url(myURL) - .header("Content-Type", "application/json") - .header("Accept", "application/json") - .header("User-agent", Utils.getAgentString(app, "android/HttpURLConnection")) - .get().build(); - String response = null; - Response resp = APIClient.getInstance(app).sendRequest(request, false, 0); - - try { - if (resp == null) { - Timber.i("timber: urlGET: %s resp is null", myURL); - return null; - } - ///Set timestamp to prefs - long timeStamp = new Date().getTime(); - BRSharedPrefs.putSecureTime(app, timeStamp); - - assert resp.body() != null; - response = resp.body().string(); - - } catch (IOException e) { - Timber.e(e); - } finally { - if (resp != null) resp.close(); - } - return response; - } - - public String getBaseUrlProd() { - return BW_API_PROD_HOST; - } -} +// +//public class BRApiManager { +// private Timer timer; +// +// private TimerTask timerTask; +// +// private Handler handler; +// +// private RemoteConfigSource remoteConfigSource; +// +// public BRApiManager(RemoteConfigSource remoteConfigSource) { +// this.remoteConfigSource = remoteConfigSource; +// this.handler = new Handler(); +// } +// +// private Set getCurrencies(Activity context) { +// Set set = new LinkedHashSet<>(); +// try { +// JSONArray arr = fetchRates(context); +// FeeManager.updateFeePerKb(context); +// if (arr != null) { +// String selectedISO = BRSharedPrefs.getIsoSymbol(context); +// int length = arr.length(); +// for (int i = 0; i < length; i++) { +// CurrencyEntity tempCurrencyEntity = new CurrencyEntity(); +// try { +// JSONObject tmpJSONObj = (JSONObject) arr.get(i); +// tempCurrencyEntity.name = "no_currency_name"; +// if (tmpJSONObj.getString("name").toString() != null) { +// tempCurrencyEntity.name = tmpJSONObj.getString("name"); +// } +// tempCurrencyEntity.code = tmpJSONObj.getString("code"); +// tempCurrencyEntity.rate = (float) tmpJSONObj.getDouble("n"); +// tempCurrencyEntity.symbol = new String(""); +// if (tempCurrencyEntity.code.equalsIgnoreCase(selectedISO)) { +// BRSharedPrefs.putIso(context, tempCurrencyEntity.code); +// BRSharedPrefs.putCurrencyListPosition(context, i - 1); +// } +// set.add(tempCurrencyEntity); +// Timber.d(":::timber: CE: %s",tempCurrencyEntity.code); +// +// } catch (JSONException e) { +// ///Timber.e(e); +// // Timber.d(":::timber: ERROR: %s",e); +// } +// } +// } else { +// Timber.d("timber: getCurrencies: failed to get currencies"); +// } +// } catch (Exception e) { +// Timber.e(e); +// } +// List tempList = new ArrayList<>(set); +// Collections.reverse(tempList); +// return new LinkedHashSet<>(set); +// } +// +// +// private void initializeTimerTask(final Context context) { +// timerTask = new TimerTask() { +// public void run() { +// //use a handler to run a toast that shows the current timestamp +// handler.post(new Runnable() { +// public void run() { +// BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { +// @Override +// public void run() { +// Set tmp = getCurrencies((Activity) context); +// CurrencyDataSource.getInstance(context).putCurrencies(tmp); +// } +// }); +// } +// }); +// } +// }; +// } +// +// public void startTimer(Context context) { +// //set a new Timer +// if (timer != null) return; +// timer = new Timer(); +// +// //initialize the TimerTask's job +// initializeTimerTask(context); +// +// //schedule the timer, after the first 0ms the TimerTask will run every 60000ms +// timer.schedule(timerTask, 0, 4000); +// } +// +// public JSONArray fetchRates(Activity activity) { +// String jsonString = createGETRequestURL(activity, BW_API_PROD_HOST + "/api/v1/rates"); +// JSONArray jsonArray = null; +// if (jsonString == null) return null; +// try { +// jsonArray = new JSONArray(jsonString); +// // DEV Uncomment to view values +// // Timber.d("timber: baseUrlProd: %s", getBaseUrlProd()); +// // Timber.d("timber: JSON %s",jsonArray.toString()); +// } catch (JSONException ex) { +// Timber.e(ex); +// } +// if (jsonArray != null && !BuildConfig.DEBUG) { +// AnalyticsManager.logCustomEvent(_20250222_PAC); +// } +// return jsonArray == null ? backupFetchRates(activity) : jsonArray; +// } +// +// public JSONArray backupFetchRates(Activity activity) { +// String jsonString = createGETRequestURL(activity, BW_API_DEV_HOST + "/api/v1/rates"); +// +// JSONArray jsonArray = null; +// if (jsonString == null) return null; +// try { +// jsonArray = new JSONArray(jsonString); +// +// } catch (JSONException e) { +// Timber.e(e); +// } +// if (jsonArray != null && !BuildConfig.DEBUG) { +// AnalyticsManager.logCustomEvent(_20230113_BAC); +// } +// +// return jsonArray; +// } +// +// // createGETRequestURL +// // Creates the params and headers to make a GET Request +// private String createGETRequestURL(Context app, String myURL) { +// Request request = new Request.Builder() +// .url(myURL) +// .header("Content-Type", "application/json") +// .header("Accept", "application/json") +// .header("User-agent", Utils.getAgentString(app, "android/HttpURLConnection")) +// .get().build(); +// String response = null; +// Response resp = APIClient.getInstance(app).sendRequest(request, false, 0); +// +// try { +// if (resp == null) { +// Timber.i("timber: urlGET: %s resp is null", myURL); +// return null; +// } +// ///Set timestamp to prefs +// long timeStamp = new Date().getTime(); +// BRSharedPrefs.putSecureTime(app, timeStamp); +// +// assert resp.body() != null; +// response = resp.body().string(); +// +// } catch (IOException e) { +// Timber.e(e); +// } finally { +// if (resp != null) resp.close(); +// } +// return response; +// } +// +// public String getBaseUrlProd() { +// return BW_API_PROD_HOST; +// } +//} diff --git a/app/src/test/java/com/brainwallet/currency/BackupRateFetchTests.kt b/app/src/test/java/com/brainwallet/currency/BackupRateFetchTests.kt index ba5c19ec..4932a7fb 100644 --- a/app/src/test/java/com/brainwallet/currency/BackupRateFetchTests.kt +++ b/app/src/test/java/com/brainwallet/currency/BackupRateFetchTests.kt @@ -4,7 +4,7 @@ import android.app.Activity import android.content.Context import com.brainwallet.data.source.RemoteConfigSource import com.brainwallet.presenter.activities.util.ActivityUTILS -import com.brainwallet.tools.manager.BRApiManager +import com.brainwallet.tools.manager.APIManager import com.brainwallet.tools.util.BRConstants import com.brainwallet.tools.util.Utils import com.platform.APIClient @@ -22,11 +22,11 @@ import org.junit.Test class BackupRateFetchTests { private val remoteConfigSource: RemoteConfigSource = mockk() - private lateinit var apiManager: BRApiManager + private lateinit var apiManager: APIManager @Before fun setUp() { - apiManager = spyk(BRApiManager(remoteConfigSource), recordPrivateCalls = true) + apiManager = spyk(APIManager(remoteConfigSource), recordPrivateCalls = true) } @Test diff --git a/app/src/test/java/com/brainwallet/data/BaseURLTests.kt b/app/src/test/java/com/brainwallet/data/BaseURLTests.kt index d0163299..fa603bf2 100644 --- a/app/src/test/java/com/brainwallet/data/BaseURLTests.kt +++ b/app/src/test/java/com/brainwallet/data/BaseURLTests.kt @@ -1,7 +1,7 @@ package com.brainwallet.data import com.brainwallet.data.source.RemoteConfigSource -import com.brainwallet.tools.manager.BRApiManager +import com.brainwallet.tools.manager.APIManager import com.brainwallet.tools.util.BRConstants import io.mockk.every import io.mockk.mockk @@ -13,21 +13,26 @@ import org.junit.Test class BaseURLTests { private val remoteConfigSource: RemoteConfigSource = mockk() - private lateinit var apiManager: BRApiManager + private lateinit var apiManager: APIManager @Before fun setUp() { - apiManager = spyk(BRApiManager(remoteConfigSource), recordPrivateCalls = true) + apiManager = spyk(APIManager(remoteConfigSource), recordPrivateCalls = true) } @Test - fun `invoke getBaseUrlProd with KEY_API_BASEURL_PROD_NEW_ENABLED true, then should return new baseUrlProd`() { + fun `invoke getPRODBaseURL with KEY_API_BASEURL_PROD_NEW_ENABLED true, then should return new getPRODBaseURL`() { every { remoteConfigSource.getBoolean(RemoteConfigSource.KEY_API_BASEURL_PROD_NEW_ENABLED) } returns true - - val actual = apiManager.baseUrlProd - + val actual = apiManager.getPRODBaseURL() assertEquals(BRConstants.BW_API_PROD_HOST, actual) } + + @Test + fun `invoke getDEVBaseURL with KEY_API_BASEURL_DEV_NEW_ENABLED true, then should return new getDEVBaseURL`() { + every { remoteConfigSource.getBoolean(RemoteConfigSource.KEY_API_BASEURL_DEV_NEW_ENABLED) } returns true + val actual = apiManager.getDEVBaseURL() + assertEquals(BRConstants.BW_API_DEV_HOST, actual) + } } diff --git a/app/src/test/java/com/brainwallet/tools/util/ProdAPIManagerTests.kt b/app/src/test/java/com/brainwallet/tools/util/ProdAPIManagerTests.kt index 2386d1f0..75af20f7 100644 --- a/app/src/test/java/com/brainwallet/tools/util/ProdAPIManagerTests.kt +++ b/app/src/test/java/com/brainwallet/tools/util/ProdAPIManagerTests.kt @@ -4,7 +4,7 @@ import android.app.Activity import android.content.Context import com.brainwallet.data.source.RemoteConfigSource import com.brainwallet.presenter.activities.util.ActivityUTILS -import com.brainwallet.tools.manager.BRApiManager +import com.brainwallet.tools.manager.APIManager import com.platform.APIClient import io.mockk.every import io.mockk.mockk @@ -22,11 +22,11 @@ import org.junit.Test class ProdAPIManagerTests { private val remoteConfigSource: RemoteConfigSource = mockk() - private lateinit var apiManager: BRApiManager + private lateinit var apiManager: APIManager @Before fun setUp() { - apiManager = spyk(BRApiManager(remoteConfigSource), recordPrivateCalls = true) + apiManager = spyk(APIManager(remoteConfigSource), recordPrivateCalls = true) } @Test @@ -85,17 +85,17 @@ class ProdAPIManagerTests { .body(responseString.toResponseBody()) .build() - val result = apiManager.fetchRates(activity) - val jsonUSD = result.getJSONObject(154) - val jsonEUR = result.getJSONObject(49) - val jsonGBP = result.getJSONObject(52) + val currencyEntityList = apiManager.fetchRates(activity) + val jsonUSD = currencyEntityList?.get(154) + val jsonEUR = currencyEntityList?.get(49) + val jsonGBP = currencyEntityList?.get(52) - assertEquals("USD", jsonUSD.optString("code")) - assertEquals("US Dollar", jsonUSD.optString("name")) - assertEquals("EUR", jsonEUR.optString("code")) - assertEquals("Euro", jsonEUR.optString("name")) - assertEquals("GBP", jsonGBP.optString("code")) - assertEquals("British Pound Sterling", jsonGBP.optString("name")) + assertEquals("USD", jsonUSD?.code) + assertEquals("US Dollar", jsonUSD?.name) + assertEquals("EUR", jsonEUR?.code) + assertEquals("Euro", jsonEUR?.name) + assertEquals("GBP", jsonGBP?.code) + assertEquals("British Pound Sterling", jsonGBP?.name) ///DEV: Very flaky test not enough time for the response verifyAll { From 45e822be3c0c72cdd2736a9e66a7c95c13f84a93 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Tue, 25 Mar 2025 18:11:51 +0000 Subject: [PATCH 12/17] Added special header code --- .../brainwallet/presenter/entities/ServiceItems.kt | 3 ++- .../java/com/brainwallet/tools/manager/APIManager.kt | 8 +++++--- .../com/brainwallet/tools/manager/FeeManager.java | 2 ++ .../main/java/com/brainwallet/tools/util/Utils.java | 11 +++-------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/brainwallet/presenter/entities/ServiceItems.kt b/app/src/main/java/com/brainwallet/presenter/entities/ServiceItems.kt index a9aa4a28..1a8b7a4d 100644 --- a/app/src/main/java/com/brainwallet/presenter/entities/ServiceItems.kt +++ b/app/src/main/java/com/brainwallet/presenter/entities/ServiceItems.kt @@ -4,5 +4,6 @@ enum class ServiceItems(val key: String) { WALLETOPS("wallet-ops"), OPSALL("wallet-ops"), WALLETSTART("start-date"), - AFDEVID("af-dev-id") + AFDEVID("af-dev-id"), + CLIENTCODE("client-code"), } \ No newline at end of file diff --git a/app/src/main/java/com/brainwallet/tools/manager/APIManager.kt b/app/src/main/java/com/brainwallet/tools/manager/APIManager.kt index 14f1af01..fe3b2072 100644 --- a/app/src/main/java/com/brainwallet/tools/manager/APIManager.kt +++ b/app/src/main/java/com/brainwallet/tools/manager/APIManager.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import com.brainwallet.data.model.CurrencyEntity import com.brainwallet.data.source.RemoteConfigSource +import com.brainwallet.presenter.entities.ServiceItems import com.brainwallet.tools.sqlite.CurrencyDataSource import com.brainwallet.tools.util.BRConstants.BW_API_DEV_HOST import com.brainwallet.tools.util.BRConstants.BW_API_PROD_HOST @@ -27,7 +28,7 @@ class APIManager (private val remoteConfigSource: RemoteConfigSource) { fun getDEVBaseURL(): String = BW_API_DEV_HOST private var timer: Timer? = null - private var pollPeriod : Long = 4000 + private var pollPeriod : Long = 5000 private val client = OkHttpClient() private val moshi: Moshi = Moshi.Builder().build() @@ -76,12 +77,12 @@ class APIManager (private val remoteConfigSource: RemoteConfigSource) { } fun fetchRates(activity: Activity): List? { - val jsonString = createGETRequestURL(activity, BW_API_PROD_HOST + "/api/v1/rates") + val jsonString = createGETRequestURL(activity, "$BW_API_PROD_HOST/api/v1/rates") return parseJsonArray(jsonString) ?: backupFetchRates(activity) } private fun backupFetchRates(activity: Activity): List? { - val jsonString = createGETRequestURL(activity, BW_API_DEV_HOST + "/api/v1/rates") + val jsonString = createGETRequestURL(activity, "$BW_API_DEV_HOST/api/v1/rates") return parseJsonArray(jsonString) } @@ -100,6 +101,7 @@ class APIManager (private val remoteConfigSource: RemoteConfigSource) { .header("Content-Type", "application/json") .header("Accept", "application/json") .header("User-agent", Utils.getAgentString(app, "android/HttpURLConnection")) + .header("bw-client-code", Utils.fetchServiceItem(app, ServiceItems.CLIENTCODE)) .get() .build() diff --git a/app/src/main/java/com/brainwallet/tools/manager/FeeManager.java b/app/src/main/java/com/brainwallet/tools/manager/FeeManager.java index 13a33093..3ca79a7b 100644 --- a/app/src/main/java/com/brainwallet/tools/manager/FeeManager.java +++ b/app/src/main/java/com/brainwallet/tools/manager/FeeManager.java @@ -4,6 +4,7 @@ import androidx.annotation.StringDef; +import com.brainwallet.presenter.entities.ServiceItems; import com.brainwallet.tools.util.Utils; import com.brainwallet.presenter.entities.Fee; import com.platform.APIClient; @@ -100,6 +101,7 @@ private static String createGETRequestURL(Context app, String myURL) { .header("Content-Type", "application/json") .header("Accept", "application/json") .header("User-agent", Utils.getAgentString(app, "android/HttpURLConnection")) + .header("BW-client-code", Utils.fetchServiceItem(app, ServiceItems.CLIENTCODE)) .get().build(); String response = null; Response resp = APIClient.getInstance(app).sendRequest(request, false, 0); diff --git a/app/src/main/java/com/brainwallet/tools/util/Utils.java b/app/src/main/java/com/brainwallet/tools/util/Utils.java index 530002a7..8e7e6062 100644 --- a/app/src/main/java/com/brainwallet/tools/util/Utils.java +++ b/app/src/main/java/com/brainwallet/tools/util/Utils.java @@ -242,14 +242,9 @@ else if (name == ServiceItems.OPSALL) { else if (name == ServiceItems.AFDEVID) { return keyObject.optString(name.getKey()); } -// else if (name == ServiceItems.PUSHER) { -// JSONObject jsonObj = new JSONObject(keyObject.get(name.getKey()).toString()); -// return jsonObj.toString(); -// } -// else if (name == ServiceItems.PUSHERSTAGING) { -// JSONObject jsonObj = new JSONObject(keyObject.get(name.getKey()).toString()); -// return jsonObj.toString(); -// } + else if (name == ServiceItems.CLIENTCODE) { + return keyObject.optString(name.getKey()); + } Timber.d("timber: fetchServiceItem name key found %s",name.getKey()); return keyObject.get(name.getKey()).toString(); From 6edd7254f078740dff8c655873e43a5b00412226 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Wed, 26 Mar 2025 08:21:02 +0000 Subject: [PATCH 13/17] version and code bump --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index adebc638..82e30d5e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "ltd.grunt.brainwallet" minSdk = 29 targetSdk = 34 - versionCode = 202503221 - versionName = "v4.4.0" + versionCode = 202503261 + versionName = "v4.4.1" multiDexEnabled = true base.archivesName.set("${defaultConfig.versionName}(${defaultConfig.versionCode})") From 53050d874fe7ca113cbf5dd8c0afdeb0a2c840d9 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Thu, 27 Mar 2025 15:06:45 +0000 Subject: [PATCH 14/17] attempt bug fix of crash --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 6 +++ .../java/com/brainwallet/BrainwalletApp.kt | 8 +-- .../presenter/fragments/FragmentSignal.java | 52 +++++++++++-------- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 82e30d5e..4ca562d4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -207,6 +207,7 @@ dependencies { implementation(libs.google.material) implementation(libs.google.zxing) implementation(platform(libs.firebase.bom)) + implementation(platform(libs.firebase.analytics)) implementation(libs.bundles.firebase) implementation(libs.bundles.google.play.asset.delivery) implementation(libs.bundles.google.play.feature.delivery) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e68a66c1..ffbd7a0e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,12 @@ + + + + + + diff --git a/app/src/main/java/com/brainwallet/BrainwalletApp.kt b/app/src/main/java/com/brainwallet/BrainwalletApp.kt index 045eb4bf..237bf5a1 100644 --- a/app/src/main/java/com/brainwallet/BrainwalletApp.kt +++ b/app/src/main/java/com/brainwallet/BrainwalletApp.kt @@ -16,7 +16,11 @@ import com.brainwallet.tools.listeners.SyncReceiver import com.brainwallet.tools.manager.AnalyticsManager import com.brainwallet.tools.util.BRConstants import com.brainwallet.tools.util.Utils +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.analytics.setConsent import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.google.firebase.ktx.Firebase import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger import org.koin.core.context.GlobalContext.startKoin @@ -34,10 +38,8 @@ class BrainwalletApp : Application() { initializeModule() - /** DEV: Top placement requirement. */ + /** DEV: Top placement requirement. **/ val enableCrashlytics = !Utils.isEmulatorOrDebug(this) - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enableCrashlytics) - setupNotificationChannels(this) AnalyticsManager.init(this) diff --git a/app/src/main/java/com/brainwallet/presenter/fragments/FragmentSignal.java b/app/src/main/java/com/brainwallet/presenter/fragments/FragmentSignal.java index 1dc41372..05bbed1e 100644 --- a/app/src/main/java/com/brainwallet/presenter/fragments/FragmentSignal.java +++ b/app/src/main/java/com/brainwallet/presenter/fragments/FragmentSignal.java @@ -3,6 +3,7 @@ import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,24 +18,33 @@ import com.brainwallet.R; import com.brainwallet.presenter.interfaces.BROnSignalCompletion; +import timber.log.Timber; + public class FragmentSignal extends Fragment { public static final String TITLE = "title"; public static final String ICON_DESCRIPTION = "iconDescription"; public static final String RES_ID = "resId"; public TextView mTitle; - // public TextView mDescription; public ImageView mIcon; private BROnSignalCompletion completion; private LinearLayout signalLayout; + private final Handler handler = new Handler(Looper.getMainLooper()); + private final Runnable popBackStackRunnable = new Runnable() { + @Override + public void run() { + if (isAdded()) { + getParentFragmentManager().popBackStack(); + handler.postDelayed(completionRunnable, 300); + } + } + }; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, final Bundle savedInstanceState) { - // The last two arguments ensure LayoutParams are inflated - // properly. View rootView = inflater.inflate(R.layout.fragment_signal, container, false); mTitle = (TextView) rootView.findViewById(R.id.title); -// mDescription = (TextView) rootView.findViewById(R.id.description); mIcon = (ImageView) rootView.findViewById(R.id.qr_image); signalLayout = (LinearLayout) rootView.findViewById(R.id.signal_layout); signalLayout.setOnClickListener(new View.OnClickListener() { @@ -49,7 +59,6 @@ public void onClick(View v) { if (bundle != null) { String title = bundle.getString(TITLE, ""); int resId = bundle.getInt(RES_ID, 0); - mTitle.setText(title); mIcon.setImageResource(resId); } @@ -64,24 +73,25 @@ public void setCompletion(BROnSignalCompletion completion) { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + handler.postDelayed(popBackStackRunnable, 1000); + } - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if(isAdded()) { - getParentFragmentManager().popBackStack(); - } - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (completion != null) { - completion.onComplete(); - completion = null; - } - } - }, 300); + private final Runnable completionRunnable = new Runnable() { + @Override + public void run() { + if (completion != null) { + completion.onComplete(); + completion = null; } - }, 1500); + } + }; + + @Override + public void onDestroyView() { + super.onDestroyView(); + // Remove callbacks to prevent execution after the fragment is destroyed + handler.removeCallbacks(popBackStackRunnable); + handler.removeCallbacks(completionRunnable); } @Override From 56adc5c77fb349eeaddcfa13d7401d2757a17162 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Fri, 28 Mar 2025 11:05:51 +0000 Subject: [PATCH 15/17] code bump --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4ca562d4..f8ecf111 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,7 +20,7 @@ android { applicationId = "ltd.grunt.brainwallet" minSdk = 29 targetSdk = 34 - versionCode = 202503261 + versionCode = 202503271 versionName = "v4.4.1" multiDexEnabled = true From 2777eee2de75661e23136be548e252bf801afc6b Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 29 Mar 2025 21:06:09 +0000 Subject: [PATCH 16/17] updated the analytic error using cgpt bugfix --- app/build.gradle.kts | 2 +- .../tools/animation/BRAnimator.java | 43 +++++++++++++------ .../brainwallet/tools/util/BRConstants.java | 2 +- .../brainwallet/tools/util/BRConstantsTest.kt | 2 +- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f8ecf111..d20ad2c8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,7 +20,7 @@ android { applicationId = "ltd.grunt.brainwallet" minSdk = 29 targetSdk = 34 - versionCode = 202503271 + versionCode = 202503281 versionName = "v4.4.1" multiDexEnabled = true diff --git a/app/src/main/java/com/brainwallet/tools/animation/BRAnimator.java b/app/src/main/java/com/brainwallet/tools/animation/BRAnimator.java index 78c5364f..cfddf17b 100644 --- a/app/src/main/java/com/brainwallet/tools/animation/BRAnimator.java +++ b/app/src/main/java/com/brainwallet/tools/animation/BRAnimator.java @@ -26,9 +26,11 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; import com.brainwallet.navigation.LegacyNavigation; import com.brainwallet.presenter.fragments.FragmentMoonpay; +import com.brainwallet.tools.manager.AnalyticsManager; import com.brainwallet.tools.threads.BRExecutor; import com.brainwallet.tools.util.BRConstants; import com.brainwallet.R; @@ -49,31 +51,46 @@ import timber.log.Timber; public class BRAnimator { - private static FragmentSignal fragmentSignal; private static boolean clickAllowed = true; public static int SLIDE_ANIMATION_DURATION = 300; - public static boolean supportIsShowing; - - private static boolean shouldShowSettingsComposable = false; + public static void showBreadSignal(@NonNull FragmentActivity activity, + @NonNull String title, + @NonNull String iconDescription, + int drawableId, + @Nullable BROnSignalCompletion completion) { + if (activity.isFinishing() || activity.isDestroyed()) { + return; // Avoid crashes if the activity is not in a valid state + } - public static void showBreadSignal(FragmentActivity activity, String title, String iconDescription, int drawableId, BROnSignalCompletion completion) { - fragmentSignal = new FragmentSignal(); + FragmentSignal fragmentSignal = new FragmentSignal(); Bundle bundle = new Bundle(); bundle.putString(FragmentSignal.TITLE, title); bundle.putString(FragmentSignal.ICON_DESCRIPTION, iconDescription); - fragmentSignal.setCompletion(completion); bundle.putInt(FragmentSignal.RES_ID, drawableId); + fragmentSignal.setCompletion(completion); fragmentSignal.setArguments(bundle); - androidx.fragment.app.FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction(); - transaction.setCustomAnimations(R.animator.from_bottom, R.animator.to_bottom, R.animator.from_bottom, R.animator.to_bottom); - transaction.add(android.R.id.content, fragmentSignal, fragmentSignal.getClass().getName()); + + FragmentManager fragmentManager = activity.getSupportFragmentManager(); + + if (fragmentManager.isStateSaved()) { + return; + } + + androidx.fragment.app.FragmentTransaction transaction = fragmentManager.beginTransaction(); + transaction.setCustomAnimations(R.animator.from_bottom, R.animator.to_bottom, + R.animator.from_bottom, R.animator.to_bottom); + transaction.add(android.R.id.content, fragmentSignal, FragmentSignal.class.getName()); transaction.addToBackStack(null); - if (!activity.isDestroyed()) + + try { transaction.commit(); + } catch (IllegalStateException e) { + transaction.commitAllowingStateLoss(); + AnalyticsManager.logCustomEvent(BRConstants._20200112_ERR); + } } - - public static void showBalanceSeedFragment(@NonNull FragmentActivity app) { + public static void showBalanceSeedFragment(@NonNull FragmentActivity app) { Timber.d("timber: fetched info"); androidx.fragment.app.FragmentManager fragmentManager = app.getSupportFragmentManager(); diff --git a/app/src/main/java/com/brainwallet/tools/util/BRConstants.java b/app/src/main/java/com/brainwallet/tools/util/BRConstants.java index e43d4c1a..5788f6de 100644 --- a/app/src/main/java/com/brainwallet/tools/util/BRConstants.java +++ b/app/src/main/java/com/brainwallet/tools/util/BRConstants.java @@ -122,7 +122,7 @@ private BRConstants() { public static final String _20200111_WNI = "wallet_not_initialized"; public static final String _20200111_PNI = "phrase_not_initialized"; public static final String _20200111_UTST = "unable_to_sign_transaction"; - public static final String _20200112_ERR = "lwa_error"; + public static final String _20200112_ERR = "brainwallet_android_error"; public static final String _20200112_DSR = "did_start_resync"; public static final String _20200125_DSRR = "did_show_review_request"; public static final String _20201118_DTGS = "did_tap_get_support"; diff --git a/app/src/test/java/com/brainwallet/tools/util/BRConstantsTest.kt b/app/src/test/java/com/brainwallet/tools/util/BRConstantsTest.kt index dce67c37..82eabe93 100644 --- a/app/src/test/java/com/brainwallet/tools/util/BRConstantsTest.kt +++ b/app/src/test/java/com/brainwallet/tools/util/BRConstantsTest.kt @@ -37,7 +37,7 @@ class BRConstantsTest { assertSame(BRConstants._20200111_WNI,"wallet_not_initialized"); assertSame(BRConstants._20200111_PNI,"phrase_not_initialized"); assertSame(BRConstants._20200111_UTST,"unable_to_sign_transaction"); - assertSame(BRConstants._20200112_ERR,"lwa_error"); + assertSame(BRConstants._20200112_ERR,"brainwallet_android_error"); assertSame(BRConstants._20200112_DSR,"did_start_resync"); assertSame(BRConstants._20200125_DSRR,"did_show_review_request"); assertSame(BRConstants._20201118_DTGS,"did_tap_get_support"); From fe9dbec8dd446b304bd123dc0ce9f21748a70ab3 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 29 Mar 2025 21:28:39 +0000 Subject: [PATCH 17/17] remove data library --- .gitmodules | 3 --- app/src/main/remote_data | 1 - 2 files changed, 4 deletions(-) delete mode 160000 app/src/main/remote_data diff --git a/.gitmodules b/.gitmodules index 03fab3eb..17190631 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "app/src/main/jni/core"] path = app/src/main/jni/core url = https://github.com/brainwallet-co/core.git -[submodule "app/src/main/remote_data"] - path = app/src/main/remote_data - url = https://github.com/brainwallet-co/data.git diff --git a/app/src/main/remote_data b/app/src/main/remote_data deleted file mode 160000 index 0e012e83..00000000 --- a/app/src/main/remote_data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e012e8343a7c186ffe31d2b6caff63cbb50413c