diff --git a/.github/workflows/auto-translate.yml b/.github/workflows/auto-translate.yml index c80bb89e..505e475a 100644 --- a/.github/workflows/auto-translate.yml +++ b/.github/workflows/auto-translate.yml @@ -20,9 +20,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 0 - token: ${{ secrets.TRANSLATION_PAT_GHA_CLAUDE_JUL082026 }} - + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: @@ -58,7 +56,7 @@ jobs: - name: Open Pull Request if: steps.changes.outputs.changed == 'true' env: - GH_TOKEN: ${{ secrets.TRANSLATION_PAT_GHA_CLAUDE_JUL082026 }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh pr create \ --title "🌍 Auto-translations: missing strings updated" \ diff --git a/.gitmodules b/.gitmodules index d8df3607..a80b36a0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,7 @@ [submodule "android-build-logic"] path = android-build-logic url = https://github.com/gruntsoftware/android-build-logic.git -[submodule "modules/bw-gdlib"] - path = modules/bw-gdlib +[submodule "bw-gdlib"] + path = bw-gdlib url = https://github.com/gruntsoftware/bw-gdlib.git + branch = main diff --git a/android-build-logic b/android-build-logic index 124541f9..bae909c1 160000 --- a/android-build-logic +++ b/android-build-logic @@ -1 +1 @@ -Subproject commit 124541f95776c2674fa867fe4eb066f6b22f7624 +Subproject commit bae909c1e602818c1ac32b136d463d1d5794fe54 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3f8e16d6..9b590970 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties import org.gradle.kotlin.dsl.androidTestImplementation import org.gradle.kotlin.dsl.grunt +import org.gradle.kotlin.dsl.project plugins { alias(grunt.plugins.android.application) @@ -18,25 +19,20 @@ val localProperties = gradleLocalProperties(rootDir, providers) android { namespace = "com.brainwallet" - compileSdk = 35 + compileSdk = 36 firebaseCrashlytics { nativeSymbolUploadEnabled = true } - /// For screengrab testing - val screengrabPaperKey = localProperties - .getProperty("SCREENGRAB_PAPERKEY", "") - .split(" ") - .filter { it.isNotBlank() } - .joinToString(separator = ", ") { "\"$it\"" } + sourceSets["main"].assets.srcDir("${rootProject.projectDir}/bw-gdlib/assets") defaultConfig { applicationId = "ltd.grunt.brainwallet" minSdk = 29 targetSdk = 35 - versionCode = 202506336 - versionName = "v4.9.4" + versionCode = 202506342 + versionName = "v4.10.4" multiDexEnabled = true base.archivesName.set("${defaultConfig.versionName}(${defaultConfig.versionCode})") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -51,12 +47,6 @@ android { arguments("-DANDROID_TOOLCHAIN=clang") } } - - buildConfigField( - "String[]", - "SCREENGRAB_PAPERKEY", - "new String[] {$screengrabPaperKey}" - ) } assetPacks.addAll(setOf(":install_time_asset_pack")) @@ -92,7 +82,7 @@ android { signingConfig = signingConfigs.getByName("release") isDebuggable = false isMinifyEnabled = true - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") resValue("string", "firebase_analytics_collection_enabled", "true") ndk { isDebuggable = false @@ -128,24 +118,6 @@ android { } } } - - create("screengrab") { - dimension = "mode" - - applicationId = "ltd.grunt.brainwallet.screengrab" - versionNameSuffix = "-screengrab" - resValue("string", "app_name", "Brainwallet (screengrab)") - - externalNativeBuild { - cmake { - // When you specify a version of CMake, as shown below, - // the Android plugin searches for its binary within your - // PATH environmental variable. - cFlags("-DLITECOIN_TESTNET=0") - targets("core-lib") - } - } - } } testOptions { @@ -167,6 +139,7 @@ android { compileOptions { sourceCompatibility = JavaVersion.valueOf("VERSION_${grunt.versions.jvm.target.get()}") targetCompatibility = JavaVersion.valueOf("VERSION_${grunt.versions.jvm.target.get()}") + isCoreLibraryDesugaringEnabled = true } kotlin { jvmToolchain(grunt.versions.jvm.target.get().toInt()) @@ -196,11 +169,11 @@ android { } dependencies { - implementation(project(":games")) implementation(project(":iap")) implementation(project(":core")) implementation("androidx.webkit:webkit:1.9.0") implementation(libs.androidx.benchmark.traceprocessor) + implementation(libs.androidx.ui.graphics) testImplementation(testFixtures(project(":app"))) androidTestImplementation(testFixtures(project(":app"))) implementation(grunt.androidx.core.ktx) @@ -240,7 +213,6 @@ dependencies { implementation(libs.androidx.espresso.core) implementation(libs.androidx.runtime) implementation(libs.androidx.foundation) - implementation(libs.androidx.animation) implementation(libs.play.services.games) ksp(grunt.koin.annotation.compiler) implementation(platform(libs.squareup.okhttp.bom)) @@ -256,17 +228,20 @@ dependencies { implementation(libs.razir.progressbutton) implementation(libs.appsflyer) implementation(libs.android.installreferrer) - implementation("androidx.compose.animation:animation:1.5.0") - implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7") - implementation("io.coil-kt:coil-compose:2.0.0-rc01") + implementation(libs.androidx.material3) testImplementation(libs.junit) testImplementation(libs.mockk) testImplementation(libs.turbine) testImplementation(libs.slf4j.android) testImplementation(libs.kotlinx.coroutines.tests) + implementation("androidx.compose.animation:animation:1.10.6") + implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7") + implementation("io.coil-kt:coil-compose:2.0.0-rc01") + implementation("androidx.emoji2:emoji2-emojipicker:1.6.0") testImplementation("io.mockk:mockk:1.13.5") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") testImplementation("app.cash.turbine:turbine:1.0.0") + implementation("androidx.core:core-splashscreen:1.0.1") androidTestImplementation(platform(grunt.androidx.compose.bom)) androidTestImplementation("androidx.test:core-ktx:1.5.0") androidTestImplementation(grunt.bundles.androidx.compose.ui.test) @@ -278,9 +253,19 @@ dependencies { androidTestImplementation("androidx.test:rules:1.5.0") androidTestImplementation("io.mockk:mockk-android:1.13.8") androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") + val gdxVersion = "1.14.1" + val miniaudioVersion = "0.7" + implementation("com.badlogicgames.gdx:gdx-backend-android:${gdxVersion}") + implementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a") + implementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a") + implementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86") + implementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64") + implementation("ltd.grunt.brainwallet.gdx:core:1.14.1") + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") + + } tasks.withType { jvmArgs("-XX:+EnableDynamicAgentLoading") } - diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 8fd81126..379f16aa 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -43,6 +43,9 @@ -dontwarn java.lang.reflect.AnnotatedType -dontwarn com.google.api.client.http.** -dontwarn org.joda.time.** +-dontwarn com.badlogic.gdx.maps.tiled.AtlasTmxMapLoader$AtlasTiledMapLoaderParameters +-dontwarn com.badlogic.gdx.maps.tiled.TmxMapLoader$Parameters +-dontwarn com.badlogic.gdx.utils.StringBuilder -keepclasseswithmembernames class * { native ; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a2374a66..3a9ff95f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,7 +38,10 @@ - + + + + + tools:replace="android:theme,android:fullBackupContent,android:allowBackup"> - - - - - - - + + + + + @@ -87,6 +95,7 @@ @@ -115,41 +124,49 @@ android:name=".presenter.activities.settings.SyncBlockchainActivity" android:exported="true" android:launchMode="singleTask" + android:screenOrientation="portrait" /> Activity?, + +) { + public fun showInAppReviewDialogIfNeeded() { + val activity = activityProvider() ?: return + if (!BRSharedPrefs.isInAppReviewDone(app) && + BRSharedPrefs.getSendTransactionCount(app) > 2 + ) { + val manager = ReviewManagerFactory.create(app) + val request = manager.requestReviewFlow() + request.addOnCompleteListener( + OnCompleteListener { task: Task? -> + AnalyticsManager.logCustomEvent(BWConstants._20241006_DRR) + if (task!!.isSuccessful()) { + val reviewInfo = task.getResult() + val flow = manager.launchReviewFlow(activity, reviewInfo) + flow.addOnCompleteListener( + OnCompleteListener { task1: Task? -> + // The flow has finished. The API does not indicate whether the user + // reviewed or not, or even whether the review dialog was shown. Thus, no + // matter the result, we continue our app flow. + Timber.i( + "timber: In-app LaunchReviewFlow completed successful (%s)", + task1!!.isSuccessful() + ) + if (task1.isSuccessful()) { + BRSharedPrefs.inAppReviewDone(app) + AnalyticsManager.logCustomEvent(BWConstants._20241006_UCR) + } + } + ) + } else { + Timber.e(task.getException(), "In-app request review flow failed") + } + } + ) + } + } +} diff --git a/app/src/main/java/com/brainwallet/constants/BWConstants.kt b/app/src/main/java/com/brainwallet/constants/BWConstants.kt index 77c4fef7..fe413c28 100644 --- a/app/src/main/java/com/brainwallet/constants/BWConstants.kt +++ b/app/src/main/java/com/brainwallet/constants/BWConstants.kt @@ -46,6 +46,11 @@ object BWConstants { const val PUT_PHRASE_NEW_WALLET_REQUEST_CODE: Int = 114 const val PUT_PHRASE_RECOVERY_WALLET_REQUEST_CODE: Int = 115 const val PROVE_PHRASE_REQUEST: Int = 119 + const val SHOW_EMOJIS_REQUEST_CODE: Int = 120 + + const val PUT_EMOJIS_REQUEST_CODE: Int = 121 + + const val CLEAR_EMOJIS_REQUEST_CODE: Int = 122 /** * Request codes for taking pictures @@ -71,6 +76,7 @@ object BWConstants { const val CURRENT_UNIT: String = "currencyUnit" const val POSITION: String = "position" const val PHRASE_WRITTEN: String = "phraseWritten" + const val EMOJIS_CHOSEN: String = "emojisChosen" const val ALLOW_SPEND: String = "allowSpend" const val USER_ID: String = "userId" const val GEO_PERMISSIONS_REQUESTED: String = "geoPermissionsRequested" diff --git a/app/src/main/java/com/brainwallet/gameinterface/GameKoinModule.kt b/app/src/main/java/com/brainwallet/gameinterface/GameKoinModule.kt new file mode 100644 index 00000000..524e3db7 --- /dev/null +++ b/app/src/main/java/com/brainwallet/gameinterface/GameKoinModule.kt @@ -0,0 +1,6 @@ +package com.brainwallet.gameinterface +import org.koin.dsl.module + +val gameModule = module { + single { GdxGameSlot() } +} diff --git a/app/src/main/java/com/brainwallet/gameinterface/GameSlot.kt b/app/src/main/java/com/brainwallet/gameinterface/GameSlot.kt new file mode 100644 index 00000000..5f2257ec --- /dev/null +++ b/app/src/main/java/com/brainwallet/gameinterface/GameSlot.kt @@ -0,0 +1,16 @@ +package com.brainwallet.gameinterface + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.Modifier + +interface GameSlot { + @Composable fun Render( + modifier: Modifier, + visible: Boolean, + launchParams: String, + onExit: (String, ByteArray?) -> Unit + ) +} + +val LocalGameSlot = staticCompositionLocalOf { null } diff --git a/app/src/main/java/com/brainwallet/gameinterface/GdxFallinmojiGameFragment.kt b/app/src/main/java/com/brainwallet/gameinterface/GdxFallinmojiGameFragment.kt new file mode 100644 index 00000000..4279b28b --- /dev/null +++ b/app/src/main/java/com/brainwallet/gameinterface/GdxFallinmojiGameFragment.kt @@ -0,0 +1,81 @@ +package com.brainwallet.gameinterface +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration +import com.badlogic.gdx.backends.android.AndroidFragmentApplication +import com.badlogic.gdx.backends.android.AndroidGraphics +import ltd.grunt.brainwallet.gamegdx.AndroidEmojiRenderer +import ltd.grunt.brainwallet.gamegdx.FallinmojiMainApplication +import ltd.grunt.brainwallet.gamegdx.interfaces.AudioSetup + +class GdxFallinmojiGameFragment( + var onExit: (String, ByteArray?) -> Unit = { _, _ -> }, +) : AndroidFragmentApplication() { + private val mainHandler = Handler(Looper.getMainLooper()) + + private val emojiRenderer = AndroidEmojiRenderer() + + private val launchParams: String + get() = arguments?.getString(ARG_LAUNCH_PARAMS).orEmpty() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val config = AndroidApplicationConfiguration().apply { + useImmersiveMode = true + useGL30 = false + } + + return initializeForView( + FallinmojiMainApplication( + { + jsonString, data -> + mainHandler.post { onExit(jsonString, data) } + }, + launchParams, + emojiRenderer, + AudioSetup { miniAudio -> miniAudio.setupAndroid(requireContext().assets) } + ), + config + ) + } + + override fun onPause() { + val graphics = Gdx.graphics + if (graphics is AndroidGraphics) { + // Pump draw frames so AndroidGraphics.pause() can observe the pause + // flag clear. lifecycleScope is dead during teardown, so use a plain + // handler loop on the main thread instead. + val deadline = System.currentTimeMillis() + 1000 + val pump = object : Runnable { + override fun run() { + try { + graphics.onDrawFrame(null) + } catch (_: Throwable) { + return + } + if (System.currentTimeMillis() < deadline) { + mainHandler.post(this) + } + } + } + mainHandler.post(pump) + } + super.onPause() + } + companion object { + private const val ARG_LAUNCH_PARAMS = "launch_params" + fun newInstance(launchParams: String) = GdxFallinmojiGameFragment().apply { + arguments = Bundle().apply { + putString(ARG_LAUNCH_PARAMS, launchParams) + } + } + } +} diff --git a/app/src/main/java/com/brainwallet/gameinterface/GdxGameSlot.kt b/app/src/main/java/com/brainwallet/gameinterface/GdxGameSlot.kt new file mode 100644 index 00000000..bd33366e --- /dev/null +++ b/app/src/main/java/com/brainwallet/gameinterface/GdxGameSlot.kt @@ -0,0 +1,14 @@ +package com.brainwallet.gameinterface + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +class GdxGameSlot : GameSlot { + @Composable override fun Render( + modifier: Modifier, + visible: Boolean, + launchParams: String, + onExit: (String, ByteArray?) -> Unit + ) { + GdxGameView(visible, launchParams, onExit, modifier) + } +} diff --git a/app/src/main/java/com/brainwallet/gameinterface/GdxGameView.kt b/app/src/main/java/com/brainwallet/gameinterface/GdxGameView.kt new file mode 100644 index 00000000..c3114a98 --- /dev/null +++ b/app/src/main/java/com/brainwallet/gameinterface/GdxGameView.kt @@ -0,0 +1,159 @@ +package com.brainwallet.gameinterface + +import android.view.View +import androidx.activity.compose.LocalActivity +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.viewinterop.AndroidView +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentContainerView +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.commit +import com.badlogic.gdx.Gdx +import com.brainwallet.R +import com.brainwallet.ui.bentosections.gamehubbento.FallinScene +import com.brainwallet.ui.screens.gamehub.GameHubEvent +import com.brainwallet.ui.screens.gamehub.GameHubViewModel +import com.brainwallet.ui.screens.main.MainScreenEvent +import com.brainwallet.ui.screens.main.MainViewModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import org.koin.compose.viewmodel.koinViewModel +import timber.log.Timber + +@Serializable +private data class GameExitData( + @SerialName("social_network") val socialNetwork: String = "none", + val timestamp: Long = 0L, + @SerialName("total_score") val totalScore: Int = 0, + @SerialName("bonus_amount") val bonusAmount: Int = 0, + @SerialName("score_a") val scoreA: Int = 0, + @SerialName("score_b") val scoreB: Int = 0, + @SerialName("score_c") val scoreC: Int = 0, +) + +private val lenientJson = Json { ignoreUnknownKeys = true } + +@Composable +fun GdxGameView( + visible: Boolean, + launchParams: String, + onExit: (String, ByteArray?) -> Unit, + modifier: Modifier = Modifier, + gameHubViewModel: GameHubViewModel = koinViewModel(), + viewModel: MainViewModel = koinViewModel(), +) { + val activity = LocalActivity.current as FragmentActivity + val fm = activity.supportFragmentManager + val containerId = remember { View.generateViewId() } + val tag = remember { "gdx_fallinmoji_$containerId" } + val currentOnExit by rememberUpdatedState(onExit) + val currentLaunchParams by rememberUpdatedState(launchParams) + + AndroidView( + modifier = modifier, + factory = { ctx -> + FragmentContainerView(ctx).apply { id = containerId } + }, + update = { _ -> + if (visible) { + if (fm.findFragmentByTag(tag) == null && !fm.isStateSaved) { + val fragment = GdxFallinmojiGameFragment + .newInstance(currentLaunchParams) + .apply { + this.onExit = { jsonString, bytes -> + removeFragment(fm, tag) + viewModel.onEvent(MainScreenEvent.OnToggleGameHub) + handleGameExit(jsonString, bytes, gameHubViewModel) + currentOnExit(jsonString, bytes) + } + } + fm.commit { add(containerId, fragment, tag) } + } + try { + Gdx.app?.postRunnable { Gdx.graphics?.requestRendering() } + } catch (_: Throwable) { } + } + } + ) + + DisposableEffect(Unit) { + onDispose { removeFragment(fm, tag) } + } +} + +private fun removeFragment(fm: FragmentManager, tag: String) { + val frag = fm.findFragmentByTag(tag) + if (frag != null && !fm.isStateSaved) { + fm.commit(allowStateLoss = true) { remove(frag) } + } +} + +private fun handleGameExit( + jsonString: String, + bytes: ByteArray?, + gameHubViewModel: GameHubViewModel, +) { + val exitData = try { + lenientJson.decodeFromString(jsonString) + } catch (e: Exception) { + Timber.w(e, "Failed to parse game exit JSON: %s", jsonString) + return + } + + Timber.d( + "Game exit: social=%s score=%d bonus=%d", + exitData.socialNetwork, + exitData.totalScore, + exitData.bonusAmount + ) + + when (exitData.socialNetwork) { + "twitter", "instagram" -> { + val screenshot = bytes?.takeIf { it.isNotEmpty() } ?: run { + Timber.w("Social share requested but no screenshot data") + return + } + gameHubViewModel.onEvent( + GameHubEvent.OnGameExited( + jsonPayload = jsonString, + byteArray = screenshot + ) + ) + } + + else -> Timber.d("Game completed, returning to wallet") + } +} + +@Composable +fun GameSplash(modifier: Modifier = Modifier) { + Box(modifier = modifier.fillMaxSize()) { + Image( + painter = painterResource(R.drawable.game_hub_bk), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + FallinScene( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .align(Alignment.CenterEnd), + dotQuantity = 24 + ) + } +} diff --git a/app/src/main/java/com/brainwallet/initializer/AppInitializer.kt b/app/src/main/java/com/brainwallet/initializer/AppInitializer.kt index 9b6d8f01..651d2f06 100644 --- a/app/src/main/java/com/brainwallet/initializer/AppInitializer.kt +++ b/app/src/main/java/com/brainwallet/initializer/AppInitializer.kt @@ -12,6 +12,7 @@ import org.koin.core.component.inject import org.koin.core.context.loadKoinModules import org.koin.ksp.generated.module import kotlin.concurrent.thread +import com.brainwallet.gameinterface.gameModule class AppInitializer( private val componentProvider: Components = object : Components { @@ -22,7 +23,8 @@ class AppInitializer( listOf( AppModule.dataModule, AppModule.module, - remoteConfigModule + remoteConfigModule, + gameModule ) ) } diff --git a/app/src/main/java/com/brainwallet/navigation/MainNavigationHost.kt b/app/src/main/java/com/brainwallet/navigation/MainNavigationHost.kt index e0883d71..f04fc51e 100644 --- a/app/src/main/java/com/brainwallet/navigation/MainNavigationHost.kt +++ b/app/src/main/java/com/brainwallet/navigation/MainNavigationHost.kt @@ -1,5 +1,7 @@ package com.brainwallet.navigation +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideInHorizontally import androidx.compose.runtime.Composable import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController @@ -8,13 +10,15 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.toRoute import com.brainwallet.constants.BWConstants +import com.brainwallet.ui.bentosections.buyreceivebento.receive.ReceiveDialog import com.brainwallet.ui.screens.buyreceive.BuyReceiveScreen import com.brainwallet.ui.screens.gamehub.GameHubScreen import com.brainwallet.ui.screens.home.MainScreen -import com.brainwallet.ui.bentosections.buyreceivebento.receive.ReceiveDialog import com.brainwallet.ui.screens.main.WebModalScreen -import com.brainwallet.ui.screens.restore.RestoreScreen +import com.brainwallet.ui.screens.emojis.EmojiPagerScreen +import com.brainwallet.ui.screens.emojis.YourEmojisScreen import com.brainwallet.ui.screens.ready.ReadyScreen +import com.brainwallet.ui.screens.restore.RestoreScreen import com.brainwallet.ui.screens.send.SendScreen import com.brainwallet.ui.screens.setpasscode.SetPasscodeScreen import com.brainwallet.ui.screens.topup.TopUpScreen @@ -22,6 +26,7 @@ import com.brainwallet.ui.screens.unlock.UnLockScreen import com.brainwallet.ui.screens.welcome.WelcomeScreen import com.brainwallet.ui.screens.yourseedproveit.YourSeedProveItScreen import com.brainwallet.ui.screens.yourseedwords.YourSeedWordsScreen +import kotlinx.collections.immutable.toImmutableList /** * Main Navigation Host for compose @@ -102,6 +107,15 @@ fun NavGraphBuilder.mainNavGraph( seedWords = route.seedWords, ) } + + composable { navBackStackEntry -> + val route: Route.YourEmojis = navBackStackEntry.toRoute() + YourEmojisScreen( + onNavigate = onNavigate, + emojis = route.emojis.toImmutableList(), + ) + } + composable { navBackStackEntry -> val route: Route.YourSeedProveIt = navBackStackEntry.toRoute() YourSeedProveItScreen( @@ -135,7 +149,7 @@ fun NavGraphBuilder.mainNavGraph( GameHubScreen(onNavigate = onNavigate) } composable { navBackStackEntry -> - val route: Route.GameHub = navBackStackEntry.toRoute() + val route: Route.MoonPayBuy = navBackStackEntry.toRoute() BuyReceiveScreen(onNavigate = onNavigate) } @@ -154,4 +168,16 @@ fun NavGraphBuilder.mainNavGraph( url = route.url ) } + + composable( + enterTransition = { + slideInHorizontally( + initialOffsetX = { fullWidth -> fullWidth }, + animationSpec = tween(350) + ) + }, + ) { navBackStackEntry -> + val route: Route.EmojiPickerPager = navBackStackEntry.toRoute() + EmojiPagerScreen(onNavigate = onNavigate) + } } diff --git a/app/src/main/java/com/brainwallet/navigation/Route.kt b/app/src/main/java/com/brainwallet/navigation/Route.kt index 3f2385e4..c80f353f 100644 --- a/app/src/main/java/com/brainwallet/navigation/Route.kt +++ b/app/src/main/java/com/brainwallet/navigation/Route.kt @@ -1,5 +1,6 @@ package com.brainwallet.navigation +import androidx.compose.runtime.Immutable import kotlinx.serialization.Serializable import java.io.Serializable as JavaSerializable @@ -37,6 +38,10 @@ sealed class Route : JavaSerializable { @Serializable data class YourSeedProveIt(val seedWords: List) : Route() + @Serializable + @Immutable + data class YourEmojis(val emojis: List) : Route() + @Serializable object Main : Route() @@ -67,6 +72,15 @@ sealed class Route : JavaSerializable { @Serializable data class BitrefillWeb(val url: String) : Route() + @Serializable + object EmojiPickerPager : Route() + + @Serializable + object HowToSetEmojis : Route() + + @Serializable + object PickEmojis : Route() + @Serializable object LinktreeWeb : Route() } diff --git a/app/src/main/java/com/brainwallet/notification/NotificationHandler.kt b/app/src/main/java/com/brainwallet/notification/NotificationHandler.kt index 893d37a6..81d183b5 100644 --- a/app/src/main/java/com/brainwallet/notification/NotificationHandler.kt +++ b/app/src/main/java/com/brainwallet/notification/NotificationHandler.kt @@ -11,9 +11,6 @@ import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.brainwallet.R -import com.brainwallet.notification.NotificationHandler.Companion.NOTIFICATION_CHANNEL_ID_BRAINWALLET_UPDATE -import com.brainwallet.notification.NotificationHandler.Companion.NOTIFICATION_CHANNEL_ID_GENERAL -import com.brainwallet.notification.NotificationHandler.Companion.NOTIFICATION_CHANNEL_ID_LITECOIN_NEWS import com.brainwallet.presenter.activities.BreadActivity import com.google.firebase.messaging.RemoteMessage import org.koin.core.annotation.Single @@ -39,6 +36,59 @@ class NotificationHandler( } ) { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { +// setupNotificationPermission() +// } +// private val requestNotificationPermissionLauncher: ActivityResultLauncher = +// registerForActivityResult( +// ActivityResultContracts.RequestPermission(), +// ActivityResultCallback { isGranted: Boolean? -> +// if (isGranted) { +// Toast.makeText( +// BreadActivity.app, +// R.string.permission_notification_granted, +// Toast.LENGTH_SHORT +// ).show() +// } +// }) +// +// private val requestNotificationPermissionLauncher = +// registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> +// if (isGranted) { +// Toast.makeText( +// this, +// R.string.permission_notification_granted, +// Toast.LENGTH_SHORT, +// ).show() +// +// Timber.d("Notification permission granted") +// } else { +// Timber.d("Notification permission denied") +// // Optionally show a message explaining reduced functionality +// } +// } +// @RequiresApi(Build.VERSION_CODES.TIRAMISU) +// // private fun setupNotificationPermission() { +// // if (hasPermission(this, Manifest.permission.POST_NOTIFICATIONS)) return +// // +// // if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { +// // showNotificationRationaleDialog() +// // } else { +// // requestPermission(requestNotificationPermissionLauncher, Manifest.permission.POST_NOTIFICATIONS) +// // } +// // } + +// @RequiresApi(Build.VERSION_CODES.TIRAMISU) +// private fun showNotificationRationaleDialog() { +// // AlertDialog.Builder(this) +// // .setTitle(R.string.permission_info) +// // .setMessage(R.string.please_grant_notification_permission) +// // .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } +// // .setPositiveButton(R.string.ok) { _, _ -> +// // requestPermission(requestNotificationPermissionLauncher, Manifest.permission.POST_NOTIFICATIONS) +// // } +// // .show() +// } fun handleMessageReceived(context: Context, remoteMessage: RemoteMessage): Boolean { if (remoteMessage.data.containsKey(KEY_DATA_BRAINWALLET).not()) { return false 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 51828632..29c94850 100644 --- a/app/src/main/java/com/brainwallet/presenter/activities/BreadActivity.java +++ b/app/src/main/java/com/brainwallet/presenter/activities/BreadActivity.java @@ -105,12 +105,7 @@ public static BreadActivity getApp() { return app; } - private final ActivityResultLauncher requestNotificationPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - if (isGranted) { - Toast.makeText(app, R.string.permission_notification_granted, Toast.LENGTH_SHORT).show(); - } - }); + @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/app/src/main/java/com/brainwallet/tools/adapter/CountryLanguageAdapter.kt b/app/src/main/java/com/brainwallet/tools/adapter/CountryLanguageAdapter.kt index a2313283..681cfadc 100644 --- a/app/src/main/java/com/brainwallet/tools/adapter/CountryLanguageAdapter.kt +++ b/app/src/main/java/com/brainwallet/tools/adapter/CountryLanguageAdapter.kt @@ -18,7 +18,7 @@ class CountryLanguageAdapter(context: Context, val languages: Array? = null private var mInflater: LayoutInflater? = null private var mSelectedItem = -1 - private var barlowFont: Typeface? = null + private var ibmPlexSansFont: Typeface? = null private var mContext: Context? = null private var mediaPlayer: MediaPlayer? = null @@ -34,7 +34,7 @@ class CountryLanguageAdapter(context: Context, val languages: Array aliasObjectMap; private static final String PHRASE_IV = "ivphrase"; + private static final String EMOJI_IV = "ivemoji"; private static final String CANARY_IV = "ivcanary"; private static final String PUB_KEY_IV = "ivpubkey"; private static final String WALLET_CREATION_TIME_IV = "ivtime"; @@ -98,6 +99,7 @@ public class BRKeyStore { private static final String PASS_TIME_IV = "passtimetoken"; public static final String PHRASE_ALIAS = "phrase"; + public static final String EMOJIS_ALIAS = "emojis"; public static final String CANARY_ALIAS = "canary"; public static final String PUB_KEY_ALIAS = "pubKey"; public static final String WALLET_CREATION_TIME_ALIAS = "creationTime"; @@ -109,8 +111,8 @@ 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_FILENAME = "my_phrase"; + private static final String EMOJIS_FILENAME = "my_emojis"; private static final String CANARY_FILENAME = "my_canary"; private static final String PUB_KEY_FILENAME = "my_pub_key"; private static final String WALLET_CREATION_TIME_FILENAME = "my_creation_time"; @@ -131,6 +133,7 @@ public class BRKeyStore { static { aliasObjectMap = new HashMap<>(); aliasObjectMap.put(PHRASE_ALIAS, new AliasObject(PHRASE_ALIAS, PHRASE_FILENAME, PHRASE_IV)); + aliasObjectMap.put(EMOJIS_ALIAS, new AliasObject(EMOJIS_ALIAS, EMOJIS_FILENAME, EMOJI_IV)); aliasObjectMap.put(CANARY_ALIAS, new AliasObject(CANARY_ALIAS, CANARY_FILENAME, CANARY_IV)); aliasObjectMap.put(PUB_KEY_ALIAS, new AliasObject(PUB_KEY_ALIAS, PUB_KEY_FILENAME, PUB_KEY_IV)); aliasObjectMap.put(WALLET_CREATION_TIME_ALIAS, new AliasObject(WALLET_CREATION_TIME_ALIAS, WALLET_CREATION_TIME_FILENAME, WALLET_CREATION_TIME_IV)); @@ -265,8 +268,27 @@ public synchronized static byte[] getPhrase(final Context context, int requestCo AliasObject obj = aliasObjectMap.get(PHRASE_ALIAS); return _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); } + public synchronized static boolean putEmojis(byte[] emojisStringToStore, Context context, int requestCode) + throws UserNotAuthenticatedException { + if (PostAuth.isStuckWithAuthLoop) { + showLoopBugMessage(context); + throw new UserNotAuthenticatedException(); + } + AliasObject obj = aliasObjectMap.get(EMOJIS_ALIAS); + return !(emojisStringToStore == null) && + _setData(context, emojisStringToStore, obj.alias, obj.datafileName, obj.ivFileName, requestCode, false); + } + public synchronized static byte[] getEmojis(final Context context, int requestCode) throws UserNotAuthenticatedException { + if (PostAuth.isStuckWithAuthLoop) { + showLoopBugMessage(context); + throw new UserNotAuthenticatedException(); + } + AliasObject obj = aliasObjectMap.get(EMOJIS_ALIAS); + return _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); + } - public synchronized static boolean putCanary(String strToStore, Context context, int requestCode) throws UserNotAuthenticatedException { + public synchronized static boolean putCanary(String strToStore, Context context, int requestCode) + throws UserNotAuthenticatedException { if (PostAuth.isStuckWithAuthLoop) { showLoopBugMessage(context); throw new UserNotAuthenticatedException(); 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 7a778d2b..1619ca45 100644 --- a/app/src/main/java/com/brainwallet/tools/security/PostAuth.java +++ b/app/src/main/java/com/brainwallet/tools/security/PostAuth.java @@ -25,7 +25,10 @@ import com.platform.entities.TxMetaData; import com.platform.tools.KVStoreManager; +import java.text.BreakIterator; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import timber.log.Timber; @@ -87,6 +90,51 @@ public void onPhraseCheckAuth(Activity app, boolean authAsked) { app.overridePendingTransition(R.anim.enter_from_bottom, R.anim.empty_300); } + public void onShowEmojis(Activity app, boolean authAsked) { + String cleanEmojis; + try { + byte[] raw = BRKeyStore.getEmojis(app, BWConstants.SHOW_EMOJIS_REQUEST_CODE); + if (raw == null) { + LegacyNavigation.openComposeScreen( + app, + Route.EmojiPickerPager.INSTANCE + ); + app.overridePendingTransition(R.anim.enter_from_bottom, R.anim.empty_300); + return; + } + cleanEmojis = new String(raw); + } catch (UserNotAuthenticatedException e) { + if (authAsked) { + Timber.d("timber: %s: WARNING!!!! LOOP", new Object() { + }.getClass().getEnclosingMethod().getName()); + isStuckWithAuthLoop = true; + } + return; + } + + List emojiList = splitIntoGraphemeClusters(cleanEmojis); + + LegacyNavigation.openComposeScreen( + app, + new Route.YourEmojis(emojiList) + ); + app.overridePendingTransition(R.anim.enter_from_bottom, R.anim.empty_300); + } + + private static List splitIntoGraphemeClusters(String text) { + List clusters = new ArrayList<>(); + BreakIterator boundary = BreakIterator.getCharacterInstance(); + boundary.setText(text); + int start = boundary.first(); + int end = boundary.next(); + while (end != BreakIterator.DONE) { + clusters.add(text.substring(start, end)); + start = end; + end = boundary.next(); + } + return clusters; + } + public void onPhraseProveAuth(Activity app, boolean authAsked) { String cleanPhrase; try { diff --git a/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt b/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt index 64f57370..815f337f 100644 --- a/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt +++ b/app/src/main/java/com/brainwallet/ui/BrainwalletActivity.kt @@ -1,18 +1,13 @@ package com.brainwallet.ui -import android.Manifest import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.ConnectivityManager -import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.RequiresApi -import androidx.appcompat.app.AlertDialog import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.lifecycle.lifecycleScope @@ -51,21 +46,15 @@ import com.brainwallet.ui.screens.settings.SettingsViewModel import com.brainwallet.ui.screens.yourseedproveit.YourSeedProveItViewModel.Companion.LEGACY_EFFECT_ON_PAPERKEY_PROVED import com.brainwallet.ui.theme.BrainwalletAppTheme import com.brainwallet.util.EventBus -import com.brainwallet.util.PermissionUtil.hasPermission -import com.brainwallet.util.PermissionUtil.requestPermission import com.brainwallet.wallet.BRPeerManager import com.brainwallet.wallet.BRWalletManager -import com.google.android.gms.tasks.OnCompleteListener -import com.google.android.gms.tasks.Task -import com.google.android.play.core.review.ReviewInfo -import com.google.android.play.core.review.ReviewManagerFactory import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import timber.log.Timber - +import com.badlogic.gdx.backends.android.AndroidFragmentApplication /** * Compose entry point here */ @@ -74,33 +63,21 @@ class BrainwalletActivity : BRWalletManager.OnBalanceChanged, BRPeerManager.OnTxStatusUpdate, TransactionDataSource.OnTxAddedListener, - InternetManager.ConnectionReceiverListener { + InternetManager.ConnectionReceiverListener, + AndroidFragmentApplication.Callbacks { + + override fun exit() { android.util.Log.d("GDX", "Callbacks.exit() called") } + private val settingRepository by inject() private var mConnectionReceiver: InternetManager? = null var appVisible: Boolean = false - private val requestNotificationPermissionLauncher = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> - if (isGranted) { - Toast.makeText( - this, - R.string.permission_notification_granted, - Toast.LENGTH_SHORT, - ).show() - - Timber.d("Notification permission granted") - } else { - Timber.d("Notification permission denied") - // Optionally show a message explaining reduced functionality - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) onConnectionChanged(InternetManager.getInstance().isConnected(this)) BRSharedPrefs.getLTCViewingPreference(application) - showInAppReviewDialogIfNeeded() + val startDestination = intent.getSerializableExtra(EXTRA_START_DESTINATION) ?: Route.Welcome @@ -158,6 +135,9 @@ class BrainwalletActivity : SettingsViewModel.LEGACY_EFFECT_ON_SEED_PHRASE -> { PostAuth.getInstance().onPhraseCheckAuth(this@BrainwalletActivity, true) } + SettingsViewModel.LEGACY_EFFECT_ON_BRAINWALLET_PHRASE -> { + PostAuth.getInstance().onShowEmojis(this@BrainwalletActivity, true) + } SettingsViewModel.LEGACY_EFFECT_ON_SHARE_ANALYTICS_DATA_TOGGLE -> { val current = BRSharedPrefs.getShareData(this) BRSharedPrefs.putShareData(this, !current) @@ -215,62 +195,6 @@ class BrainwalletActivity : } } - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private fun setupNotificationPermission() { - if (hasPermission(this, Manifest.permission.POST_NOTIFICATIONS)) return - - if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { - showNotificationRationaleDialog() - } else { - requestPermission(requestNotificationPermissionLauncher, Manifest.permission.POST_NOTIFICATIONS) - } - } - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - private fun showNotificationRationaleDialog() { - AlertDialog.Builder(this) - .setTitle(R.string.permission_info) - .setMessage(R.string.please_grant_notification_permission) - .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } - .setPositiveButton(R.string.ok) { _, _ -> - requestPermission(requestNotificationPermissionLauncher, Manifest.permission.POST_NOTIFICATIONS) - } - .show() - } - - private fun showInAppReviewDialogIfNeeded() { - if (!BRSharedPrefs.isInAppReviewDone(this) && BRSharedPrefs.getSendTransactionCount(this) > 2) { - val manager = ReviewManagerFactory.create(this) - val request = manager.requestReviewFlow() - request.addOnCompleteListener( - OnCompleteListener { task: Task? -> - AnalyticsManager.logCustomEvent(BWConstants._20241006_DRR) - if (task!!.isSuccessful()) { - val reviewInfo = task.getResult() - val flow = manager.launchReviewFlow(this@BrainwalletActivity, reviewInfo) - flow.addOnCompleteListener( - OnCompleteListener { task1: Task? -> - // The flow has finished. The API does not indicate whether the user - // reviewed or not, or even whether the review dialog was shown. Thus, no - // matter the result, we continue our app flow. - Timber.i( - "timber: In-app LaunchReviewFlow completed successful (%s)", - task1!!.isSuccessful() - ) - if (task1.isSuccessful()) { - BRSharedPrefs.inAppReviewDone(this@BrainwalletActivity) - AnalyticsManager.logCustomEvent(BWConstants._20241006_UCR) - } - } - ) - } else { - Timber.e(task.getException(), "In-app request review flow failed") - } - } - ) - } - } - private fun setupNetworking() { if (mConnectionReceiver == null) mConnectionReceiver = InternetManager.getInstance() val mNetworkStateFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) @@ -313,9 +237,6 @@ class BrainwalletActivity : * then should go to setpasscode */ private fun onCheckPin() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - setupNotificationPermission() - } val pin = BRKeyStore.getPinCode(this) if (pin.isEmpty() && pin.length != BW_PIN_LENGTH) { lifecycleScope.launch { @@ -337,9 +258,6 @@ class BrainwalletActivity : */ private fun onLegacyLogic() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - setupNotificationPermission() - } if (Utils.isEmulatorOrDebug(this)) Utils.printPhoneSpecs() val masterPubKey = BRKeyStore.getMasterPublicKey(this) diff --git a/app/src/main/java/com/brainwallet/ui/bentosections/balancebento/BalanceBentoViewModel.kt b/app/src/main/java/com/brainwallet/ui/bentosections/balancebento/BalanceBentoViewModel.kt index 76a63767..480fe46a 100644 --- a/app/src/main/java/com/brainwallet/ui/bentosections/balancebento/BalanceBentoViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/bentosections/balancebento/BalanceBentoViewModel.kt @@ -52,7 +52,7 @@ class BalanceBentoViewModel( currentBlockHeight = blockInfo.blockHeight, lastTimeStamp = formatter.format(Date(blockInfo.timestamp * 1000L)), syncProgress = blockInfo.syncProgress, - brainwalletIsSyncing = blockInfo.syncProgress <= 0.99f, + brainwalletIsSyncing = blockInfo.syncProgress <= 0.999f, ) } Timber.d("brainwalletIsSyncing Collecting PeerManager %s", state.value.brainwalletIsSyncing) @@ -137,7 +137,7 @@ class BalanceBentoViewModel( fiatCode = currentSettings.currency.code, symbol = currentSettings.currency.symbol, syncProgress = syncProgress, - brainwalletIsSyncing = syncProgress <= 0.99f + brainwalletIsSyncing = syncProgress <= 0.999f ) } } diff --git a/app/src/main/java/com/brainwallet/ui/bentosections/gamehubbento/GameHubBentoPagerScreen.kt b/app/src/main/java/com/brainwallet/ui/bentosections/gamehubbento/GameHubBentoPagerScreen.kt deleted file mode 100644 index 7790e4f7..00000000 --- a/app/src/main/java/com/brainwallet/ui/bentosections/gamehubbento/GameHubBentoPagerScreen.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.brainwallet.ui.bentosections.gamehubbento - -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.PreviewLightDark -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.delay - -@Composable -fun GameHubBentoPagerScreen( - modifier: Modifier = Modifier, - onClick: (Int) -> Unit = {} -) { - val hubPageCount = 3 - val pagerState = rememberPagerState(pageCount = { hubPageCount }) - val context = LocalContext.current - - LaunchedEffect(pagerState.pageCount) { - var forward = true - while (true) { - delay(15_000) - if (!pagerState.isScrollInProgress) { - val nextPage = if (forward) { - (pagerState.currentPage + 1).also { - if (it >= pagerState.pageCount - 1) forward = false - } - } else { - (pagerState.currentPage - 1).also { - if (it <= 0) forward = true - } - } - pagerState.animateScrollToPage(nextPage) - } - } - } - - Box( - modifier = Modifier - .fillMaxWidth() - .background( - color = Color.Transparent, - shape = RoundedCornerShape(16.dp) - ) - .border( - width = 1.5.dp, - color = Color.Transparent, - shape = RoundedCornerShape(16.dp) - ), - ) { - HorizontalPager( - state = pagerState, - userScrollEnabled = true, - pageSpacing = 8.dp, - modifier = modifier.fillMaxSize() - ) { page -> - - when (page) { - 0 -> GameHubBentoScreen(onClick = { onClick(page) }) - 1 -> GameHubBentoRiceScreen(onClick = { onClick(page) }) - 2 -> GameHubBentoUnagiScreen(onClick = { onClick(page) }) - } - } - } -} - -@PreviewLightDark -@Composable -private fun GameHubBentoPagerScreenPreview() { - Box(modifier = Modifier.height(120.dp)) { - GameHubBentoPagerScreen() - } -} diff --git a/app/src/main/java/com/brainwallet/ui/bentosections/gamehubbento/GameHubBentoScreen.kt b/app/src/main/java/com/brainwallet/ui/bentosections/gamehubbento/GameHubBentoScreen.kt index 8e4cf431..a7f188a0 100644 --- a/app/src/main/java/com/brainwallet/ui/bentosections/gamehubbento/GameHubBentoScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/bentosections/gamehubbento/GameHubBentoScreen.kt @@ -1,8 +1,11 @@ package com.brainwallet.ui.bentosections.gamehubbento +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,7 +14,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -25,168 +27,164 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shadow +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.brainwallet.R +import com.brainwallet.constants.BWConstants.FADE_IN_DURATION +import com.brainwallet.constants.BWConstants.FADE_OUT_DURATION import com.brainwallet.constants.bentoCornerRadius -import com.brainwallet.ui.theme.BoldenVan import com.brainwallet.ui.theme.IBMPlexSans -import com.brainwallet.ui.theme.balanceGameBentoSurface +import com.brainwallet.ui.theme.LilitaOne import com.brainwallet.ui.theme.gameTaglineGradient import com.brainwallet.ui.theme.gameTitleGradient @Composable fun GameHubBentoScreen( + isGameHubOpen: Boolean, modifier: Modifier = Modifier, - onClick: () -> Unit = {} + onToggle: () -> Unit = {} ) { val gameHubBackground = R.drawable.game_hub_bk var resizedTaglineFontSize by remember { mutableStateOf(14.sp) } - val hubPageCount = 3 - val pagerState = rememberPagerState(pageCount = { hubPageCount }) - Box( modifier = Modifier .fillMaxSize() - .balanceGameBentoSurface(isDarkMode = true) - .clickable(onClick = onClick) ) { - Image( - painter = painterResource(gameHubBackground), - contentDescription = "game_hub_background", - modifier = Modifier.fillMaxSize() - .clip(RoundedCornerShape(bentoCornerRadius)), - contentScale = ContentScale.Crop, - ) - val textWidthRatio = 0.85f - - FallinScene( - modifier = Modifier - .fillMaxWidth(textWidthRatio) - .fillMaxHeight(1f) - .align(Alignment.CenterEnd), - dotQuantity = 14 - ) - Column( - modifier = Modifier - .padding(start = 16.dp) - .fillMaxHeight(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.Start + AnimatedVisibility( + visible = !isGameHubOpen, + enter = fadeIn(tween(FADE_IN_DURATION)), + exit = fadeOut(tween(FADE_OUT_DURATION)), ) { - Box( + Image( + painter = painterResource(gameHubBackground), + contentDescription = "game_hub_background", + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(bentoCornerRadius)), + contentScale = ContentScale.Crop, + ) + val textWidthRatio = 0.85f + FallinScene( modifier = Modifier .fillMaxWidth(textWidthRatio) - .height(44.dp) + .fillMaxHeight(1f) + .align(Alignment.CenterEnd) + .pointerInput(Unit) {}, + dotQuantity = 14 + ) + Column( + modifier = Modifier + .padding(start = 16.dp) + .fillMaxHeight(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start ) { - Text( - text = "FALLINMOJI", - style = TextStyle( - fontFamily = BoldenVan, - fontWeight = FontWeight.Normal, - fontSize = 39.sp, - shadow = Shadow( - color = Color.Black.copy(alpha = 0.5f), - offset = Offset(x = 4f, y = 4f), - blurRadius = 4f - ) - ), - maxLines = 1, - modifier = Modifier.fillMaxWidth(textWidthRatio) - ) - Text( - text = "FALLINMOJI", - style = TextStyle( - brush = gameTitleGradient, - fontFamily = BoldenVan, - fontWeight = FontWeight.Normal, - fontSize = 39.sp - ), - maxLines = 1, - modifier = Modifier.fillMaxWidth(textWidthRatio) - ) + Box( + modifier = Modifier + .fillMaxWidth(textWidthRatio) + .height(44.dp) + ) { + Text( + text = "FALLINMOJI", + style = TextStyle( + fontFamily = LilitaOne, + fontWeight = FontWeight.Normal, + fontSize = 39.sp, + shadow = Shadow( + color = Color.Black.copy(alpha = 0.5f), + offset = Offset(x = 4f, y = 4f), + blurRadius = 4f + ) + ), + maxLines = 1, + modifier = Modifier.fillMaxWidth(textWidthRatio) + ) + Text( + text = "FALLINMOJI", + style = TextStyle( + brush = gameTitleGradient, + fontFamily = LilitaOne, + fontWeight = FontWeight.Normal, + fontSize = 39.sp + ), + maxLines = 1, + modifier = Modifier.fillMaxWidth(textWidthRatio) + ) + } + Box( + modifier = Modifier + .fillMaxWidth(textWidthRatio) + .padding(top = 1.dp) + ) { + Text( + text = stringResource(R.string.game_hub_tagline), + onTextLayout = { textLayoutResult -> + if (textLayoutResult.hasVisualOverflow) { + resizedTaglineFontSize *= 0.95f + } + }, + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Medium, + fontSize = resizedTaglineFontSize, + shadow = Shadow( + color = Color.Black.copy(alpha = 0.5f), + offset = Offset(x = 4f, y = 4f), + blurRadius = 4f + ) + ), + maxLines = 1, + modifier = Modifier.fillMaxWidth(textWidthRatio) + ) + Text( + text = stringResource(R.string.game_hub_tagline), + onTextLayout = { textLayoutResult -> + if (textLayoutResult.hasVisualOverflow) { + resizedTaglineFontSize *= 0.95f + } + }, + style = TextStyle( + brush = gameTaglineGradient, + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Medium, + fontSize = resizedTaglineFontSize + ), + maxLines = 1, + modifier = Modifier.fillMaxWidth(textWidthRatio) + ) + } } - Box( + + Column( modifier = Modifier - .fillMaxWidth(textWidthRatio) - .padding(top = 1.dp) + .padding(10.dp) + .fillMaxHeight(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start ) { Text( - text = stringResource(R.string.game_hub_tagline), - onTextLayout = { textLayoutResult -> - if (textLayoutResult.hasVisualOverflow) { - resizedTaglineFontSize *= 0.95f - } - }, - style = TextStyle( - fontFamily = IBMPlexSans, - fontWeight = FontWeight.Medium, - fontSize = resizedTaglineFontSize, - shadow = Shadow( - color = Color.Black.copy(alpha = 0.5f), - offset = Offset(x = 4f, y = 4f), - blurRadius = 4f - ) - ), - maxLines = 1, - modifier = Modifier.fillMaxWidth(textWidthRatio) - ) - Text( - text = stringResource(R.string.game_hub_tagline), - onTextLayout = { textLayoutResult -> - if (textLayoutResult.hasVisualOverflow) { - resizedTaglineFontSize *= 0.95f - } - }, + text = stringResource(R.string.game_hub_title).uppercase(), + color = Color.White, style = TextStyle( - brush = gameTaglineGradient, fontFamily = IBMPlexSans, - fontWeight = FontWeight.Medium, - fontSize = resizedTaglineFontSize + fontWeight = FontWeight.Normal, + fontSize = 10.sp ), - maxLines = 1, - modifier = Modifier.fillMaxWidth(textWidthRatio) + modifier = Modifier + .padding(start = 1.dp, end = 1.dp) + .background( + color = Color.White.copy(alpha = 0.2f), + shape = RoundedCornerShape(6.dp) + ) + .padding(horizontal = 10.dp, vertical = 2.dp) ) } } - - Column( - modifier = Modifier - .padding(10.dp) - .fillMaxHeight(), - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.Start - ) { - Text( - text = stringResource(R.string.game_hub_title).uppercase(), - color = Color.White, - style = TextStyle( - fontFamily = IBMPlexSans, - fontWeight = FontWeight.Normal, - fontSize = 10.sp - ), - modifier = Modifier - .padding(start = 1.dp, end = 1.dp) - .background( - color = Color.White.copy(alpha = 0.2f), - shape = RoundedCornerShape(6.dp) - ) - .padding(horizontal = 10.dp, vertical = 2.dp) - ) - } - } -} - -@PreviewLightDark -@Composable -private fun GameHubBentoScreenPreview() { - Box(modifier = Modifier.height(120.dp)) { - GameHubBentoScreen() } } diff --git a/app/src/main/java/com/brainwallet/ui/bentosections/transactionbento/subscreens/TransactionDetail.kt b/app/src/main/java/com/brainwallet/ui/bentosections/transactionbento/subscreens/TransactionDetail.kt index 038a17e9..78b9616b 100644 --- a/app/src/main/java/com/brainwallet/ui/bentosections/transactionbento/subscreens/TransactionDetail.kt +++ b/app/src/main/java/com/brainwallet/ui/bentosections/transactionbento/subscreens/TransactionDetail.kt @@ -24,19 +24,22 @@ import com.brainwallet.R import com.brainwallet.constants.BWConstants import com.brainwallet.constants.bentoSpacer import com.brainwallet.constants.transactionQRSize +import com.brainwallet.presenter.entities.ServiceItems import com.brainwallet.presenter.entities.TxItem import com.brainwallet.tools.manager.BRSharedPrefs import com.brainwallet.tools.qrcode.QRUtils import com.brainwallet.tools.util.BRCurrency import com.brainwallet.tools.util.BRExchange import com.brainwallet.tools.util.BRExchange.ONE_LITECOIN_OF_LITOSHIS +import com.brainwallet.tools.util.Utils import com.brainwallet.ui.theme.IBMPlexSans import com.brainwallet.ui.theme.mainBentoSurface import timber.log.Timber import java.math.BigDecimal import java.text.SimpleDateFormat import java.util.Date -import java.util.Locale +import kotlin.collections.toHashSet +import androidx.compose.ui.platform.LocalLocale @Composable fun TransactionDetail( @@ -45,10 +48,33 @@ fun TransactionDetail( modifier: Modifier = Modifier, ) { val context = LocalContext.current - val ltcAddressString = currentTransaction?.to?.firstOrNull() ?: "" + + // LTC address to recipient or Brainwallet user + val ltcAddress: String? + + // determine sent vs received state + val wasSentVsReceived = ((currentTransaction?.received ?: 0L) - (currentTransaction?.sent ?: 0L)) < 0 + + // Output addresses + val outputAddressSet = currentTransaction?.to?.toHashSet() + val opsSet = Utils.fetchServiceItem(context, ServiceItems.OPSALL) + .split(",") + .toHashSet() + + ltcAddress = outputAddressSet + ?.filter { it !in opsSet } + ?.filterNotNull() + ?.firstOrNull() ?: "ERROR-ADDRESS" + + val toFromAddressFormatting: String = if (wasSentVsReceived) { + String.format(stringResource(R.string.TransactionDetails_to), ltcAddress) + } else { + String.format(stringResource(R.string.TransactionDetails_from), ltcAddress) + } + val formatter = SimpleDateFormat( "MMM dd, yyyy hh:mm a", - Locale.getDefault() + LocalLocale.current.platformLocale ) val dateTimestamp = formatter.format(Date(currentTransaction?.timeStamp?.times(1000L) ?: 0L)) val wasReceived = currentTransaction?.getSent() == 0L @@ -99,7 +125,7 @@ fun TransactionDetail( val combinedFees = BigDecimal(currentTransaction?.fee ?: 0L) + BigDecimal(opsAmount) val feesLitoshis = combinedFees.divide(BigDecimal(ONE_LITECOIN_OF_LITOSHIS)) val feesTotal = String.format("-Ł $feesLitoshis") - val qrBitmap = QRUtils.generateQR(context, "litecoin:$ltcAddressString").asImageBitmap() + val qrBitmap = QRUtils.generateQR(context, "litecoin:$ltcAddress").asImageBitmap() val txIDBrowserURL: String = run { "${BWConstants.BLOCKCHAIR_EXPLORER_BASE_URL}${currentTransaction?.txHashHexReversed ?: ""}" } @@ -282,7 +308,7 @@ fun TransactionDetail( modifier = Modifier .fillMaxWidth(0.5f) .padding(4.dp), - text = ltcAddressString, + text = ltcAddress, style = TextStyle( fontFamily = IBMPlexSans, fontWeight = FontWeight.SemiBold, diff --git a/app/src/main/java/com/brainwallet/ui/bentosections/transactionbento/subscreens/TransactionRow.kt b/app/src/main/java/com/brainwallet/ui/bentosections/transactionbento/subscreens/TransactionRow.kt index f4409d5b..972b0a8e 100644 --- a/app/src/main/java/com/brainwallet/ui/bentosections/transactionbento/subscreens/TransactionRow.kt +++ b/app/src/main/java/com/brainwallet/ui/bentosections/transactionbento/subscreens/TransactionRow.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLocale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight @@ -40,7 +41,6 @@ import timber.log.Timber import java.math.BigDecimal import java.text.SimpleDateFormat import java.util.Date -import java.util.Locale @Composable fun TransactionRow( @@ -55,7 +55,7 @@ fun TransactionRow( val state by viewModel.state.collectAsState() val formatter = SimpleDateFormat( "MMM dd, yyyy hh:mm a", - Locale.getDefault() + LocalLocale.current.platformLocale ) // the transactions amount diff --git a/app/src/main/java/com/brainwallet/ui/bentosections/tutorials/TutorialsBentoScreen.kt b/app/src/main/java/com/brainwallet/ui/bentosections/tutorials/TutorialsBentoScreen.kt index 7312c215..6f99195e 100644 --- a/app/src/main/java/com/brainwallet/ui/bentosections/tutorials/TutorialsBentoScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/bentosections/tutorials/TutorialsBentoScreen.kt @@ -89,12 +89,12 @@ fun TutorialsBentoScreen( when (page) { 0 -> TutorialWalkthroughBentoScreen( onClick = { - AnalyticsManager.logCustomAdHocEvent("user_tapped_walkthrough_tutorial") + AnalyticsManager.logCustomAdHocEvent("user_tapped_walkthrough_tutorial", null) onClick(page) } ) 1 -> TutorialSendBentoScreen(onClick = { - AnalyticsManager.logCustomAdHocEvent("user_tapped_send_tutorial") + AnalyticsManager.logCustomAdHocEvent("user_tapped_send_tutorial", null) onClick(page) }) } diff --git a/app/src/main/java/com/brainwallet/ui/composable/BentoBottomNavBar.kt b/app/src/main/java/com/brainwallet/ui/composable/BentoBottomNavBar.kt index 7fd49911..a5609889 100644 --- a/app/src/main/java/com/brainwallet/ui/composable/BentoBottomNavBar.kt +++ b/app/src/main/java/com/brainwallet/ui/composable/BentoBottomNavBar.kt @@ -127,7 +127,6 @@ fun BentoBottomNavBar( NavigationBarItem( selected = currentRoute is Route.GameHub, onClick = { onItemClick(Route.GameHub) }, - enabled = false, // Disabled until the game integration icon = { Icon( painter = painterResource(R.drawable.ic_game_hub), diff --git a/app/src/main/java/com/brainwallet/ui/screens/emojis/EmojiPagerScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/emojis/EmojiPagerScreen.kt new file mode 100644 index 00000000..c7e376fd --- /dev/null +++ b/app/src/main/java/com/brainwallet/ui/screens/emojis/EmojiPagerScreen.kt @@ -0,0 +1,63 @@ +package com.brainwallet.ui.screens.emojis +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.brainwallet.navigation.OnNavigate +import com.brainwallet.ui.screens.main.MainViewModel +import kotlinx.coroutines.launch +import org.koin.compose.viewmodel.koinViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EmojiPagerScreen( + onNavigate: OnNavigate, + modifier: Modifier = Modifier, + viewModel: MainViewModel = koinViewModel(), + onDismissPickEmojis: () -> Unit = {}, + onDismiss: () -> Unit = {}, +) { + val hubPageCount = 2 + val pagerState = rememberPagerState(pageCount = { hubPageCount }) + val scope = rememberCoroutineScope() + + Box( + modifier = Modifier + .fillMaxWidth() + ) { + HorizontalPager( + state = pagerState, + userScrollEnabled = false, + pageSpacing = 8.dp, + modifier = modifier.fillMaxSize() + ) { page -> + when (page) { + 0 -> HowToSetEmojisScreen( + onNextPage = { + scope.launch { + pagerState.animateScrollToPage(page + 1) + } + } + ) + 1 -> PickEmojisScreen( + firstThreeWords = viewModel.walletManager.getSeedWords(), + onEmojisPicked = { wereEmojisPicked -> + scope.launch { + if (wereEmojisPicked) { + onDismissPickEmojis() + } else { + onDismiss() + } + } + }, + ) + } + } + } +} diff --git a/app/src/main/java/com/brainwallet/ui/screens/emojis/HowToSetEmojisScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/emojis/HowToSetEmojisScreen.kt new file mode 100644 index 00000000..c65e6ba4 --- /dev/null +++ b/app/src/main/java/com/brainwallet/ui/screens/emojis/HowToSetEmojisScreen.kt @@ -0,0 +1,378 @@ +@file:OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) + +package com.brainwallet.ui.screens.emojis + +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.ExperimentalLayoutApi +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.brainwallet.R +import com.brainwallet.ui.theme.DesignTheme +import com.brainwallet.ui.theme.IBMPlexSans +import com.brainwallet.ui.theme.bentoHowToSectionGradient +import com.brainwallet.ui.theme.bentoModalDarkGradient +import com.brainwallet.ui.theme.colorMidnite + +@Composable +fun HowToSetEmojisScreen( + modifier: Modifier = Modifier, + onNextPage: () -> Unit = {} +) { + val horizontalVerticalSpacing = 8 + + val iconSizeBox = 85 + val iconSize = 30 + var resizedDescriptlineFontSize by remember { mutableStateOf(15.sp) } + + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth() + .background(brush = bentoModalDarkGradient) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(15.dp) + .background( + color = Color.Transparent, + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(horizontalVerticalSpacing.dp), + ) { + Text( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(10.dp), + text = stringResource(R.string.how_to_choose_emojis_title), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + fontSize = 22.sp, + color = Color.White + ), + maxLines = 2 + ) + + Text( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(15.dp), + text = stringResource(R.string.how_to_choose_emojis_description), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Normal, + textAlign = TextAlign.Center, + fontSize = 16.sp, + color = Color.White + ), + maxLines = 2 + ) + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth() + .fillMaxHeight(0.65f) + .clip(DesignTheme.shapes.large) + .background( + brush = bentoHowToSectionGradient, + ) + .border( + width = 0.3.dp, + color = Color.White.copy(alpha = 0.1f), + shape = DesignTheme.shapes.large + ), + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.SpaceEvenly + + ) { + Row( + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Box( + modifier = Modifier + .align(Alignment.CenterVertically) + .size(iconSizeBox.dp) + .padding(10.dp) + .padding(start = 10.dp) + .padding(top = 10.dp, bottom = 10.dp) + .aspectRatio(1f) + .clip(CircleShape) + .border( + 0.3.dp, + Color.White, + CircleShape + ) + .background(Color.White.copy(0.1f)) + ) { + Icon( + modifier = Modifier + .align(Alignment.Center) + .size(iconSize.dp), + tint = Color.White, + painter = painterResource(R.drawable.sentiment_satisfied_24px), + contentDescription = stringResource(id = R.string.ok) + ) + } + Spacer(modifier = Modifier.weight(1f)) + Column( + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(end = 20.dp) + ) { + Text( + modifier = Modifier + .align(Alignment.Start) + .padding(start = 10.dp, top = 5.dp, bottom = 5.dp), + text = stringResource(R.string.how_to_choose_emojis_bullet_one_title), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.SemiBold, + fontSize = 17.sp, + color = Color.White + ), + maxLines = 1 + ) + + Text( + modifier = Modifier + .align(Alignment.Start) + .padding(start = 10.dp, top = 5.dp, bottom = 10.dp, end = 10.dp), + text = stringResource(R.string.how_to_choose_emojis_bullet_one_description), + onTextLayout = { textLayoutResult -> + if (textLayoutResult.hasVisualOverflow) { + resizedDescriptlineFontSize *= 0.95f + } + }, + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Light, + fontSize = resizedDescriptlineFontSize, + color = Color.White + ), + maxLines = 3 + ) + } + } + HorizontalDivider( + modifier = Modifier + .padding(start = 20.dp, end = 20.dp), + thickness = 0.5.dp, + Color.White.copy(0.2f) + ) + Row( + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Box( + modifier = Modifier + .align(Alignment.CenterVertically) + .size(iconSizeBox.dp) + .padding(10.dp) + .padding(start = 10.dp) + .padding(top = 10.dp, bottom = 10.dp) + .aspectRatio(1f) + .clip(CircleShape) + .border( + 0.3.dp, + Color.White, + CircleShape + ) + .background(Color.White.copy(0.1f)) + ) { + Icon( + modifier = Modifier + .align(Alignment.Center) + .size(iconSize.dp), + tint = Color.White, + painter = painterResource(R.drawable.check_box_24px), + contentDescription = stringResource(id = R.string.ok) + ) + } + Spacer(modifier = Modifier.weight(1f)) + Column( + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(end = 20.dp) + ) { + Text( + modifier = Modifier + .align(Alignment.Start) + .padding(start = 10.dp, top = 5.dp, bottom = 5.dp), + text = stringResource(R.string.how_to_choose_emojis_bullet_two_title), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.SemiBold, + fontSize = 17.sp, + color = Color.White + ), + maxLines = 1 + ) + + Text( + modifier = Modifier + .align(Alignment.Start) + .padding(start = 10.dp, top = 5.dp, bottom = 10.dp, end = 10.dp), + text = stringResource(R.string.how_to_choose_emojis_bullet_two_description), + onTextLayout = { textLayoutResult -> + if (textLayoutResult.hasVisualOverflow) { + resizedDescriptlineFontSize *= 0.95f + } + }, + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Light, + fontSize = resizedDescriptlineFontSize, + color = Color.White + ), + maxLines = 3 + ) + } + } + HorizontalDivider( + modifier = Modifier + .padding(start = 20.dp, end = 20.dp), + thickness = 0.5.dp, + Color.White.copy(0.2f) + ) + Row( + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Box( + modifier = Modifier + .align(Alignment.CenterVertically) + .size(iconSizeBox.dp) + .padding(10.dp) + .padding(start = 10.dp) + .padding(top = 10.dp, bottom = 10.dp) + .aspectRatio(1f) + .clip(CircleShape) + .border( + 0.3.dp, + Color.White, + CircleShape + ) + .background(Color.White.copy(0.1f)) + ) { + Icon( + modifier = Modifier + .align(Alignment.Center) + .size(iconSize.dp), + tint = Color.White, + painter = painterResource(R.drawable.lock_24px), + contentDescription = stringResource(id = R.string.settings_title_lock) + ) + } + Spacer(modifier = Modifier.weight(1f)) + Column( + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(end = 20.dp) + ) { + Text( + modifier = Modifier + .align(Alignment.Start) + .padding(start = 10.dp, top = 5.dp, bottom = 5.dp), + text = stringResource(R.string.how_to_choose_emojis_bullet_three_title), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.SemiBold, + fontSize = 17.sp, + color = Color.White + ), + maxLines = 1 + ) + + Text( + modifier = Modifier + .align(Alignment.Start) + .padding(start = 10.dp, top = 5.dp, bottom = 10.dp, end = 10.dp), + text = stringResource(R.string.how_to_choose_emojis_bullet_three_description), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Light, + fontSize = resizedDescriptlineFontSize, + color = Color.White + ), + maxLines = 3 + ) + } + } + } + } + + Spacer(modifier = Modifier.weight(0.9f)) + + FilledTonalButton( + modifier = Modifier + .fillMaxWidth() + .height(44.dp) + .padding( + start = 20.dp, + end = 20.dp, + ), + enabled = true, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = Color.White, + contentColor = colorMidnite + ), + onClick = { + onNextPage() + }, + shape = RoundedCornerShape(11.dp) + + ) { + Text( + modifier = Modifier, + text = stringResource(R.string.RecoverWallet_next), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.SemiBold, + fontSize = 18.sp + ) + ) + } + Spacer(modifier = Modifier.weight(0.2f)) + } + } +} diff --git a/app/src/main/java/com/brainwallet/ui/screens/emojis/PickEmojisScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/emojis/PickEmojisScreen.kt new file mode 100644 index 00000000..207337a0 --- /dev/null +++ b/app/src/main/java/com/brainwallet/ui/screens/emojis/PickEmojisScreen.kt @@ -0,0 +1,389 @@ +package com.brainwallet.ui.screens.emojis + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +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 +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.emoji2.emojipicker.EmojiPickerView +import com.brainwallet.R +import com.brainwallet.constants.BWConstants +import com.brainwallet.tools.manager.AnalyticsManager +import com.brainwallet.tools.manager.BRSharedPrefs +import com.brainwallet.tools.security.BRKeyStore +import com.brainwallet.ui.theme.DesignTheme +import com.brainwallet.ui.theme.IBMPlexSans +import com.brainwallet.ui.theme.bentoEmojiSectionGradient +import com.brainwallet.ui.theme.bentoModalDarkGradient +import com.brainwallet.ui.theme.colorMidnite +import kotlinx.collections.immutable.ImmutableList +import kotlin.collections.toByteArray + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PickEmojisScreen( + firstThreeWords: ImmutableList, + modifier: Modifier = Modifier, + onEmojisPicked: (Boolean) -> Unit = {}, +) { + val context = LocalContext.current + var selectedEmojis by remember { mutableStateOf(listOf(null, null, null)) } + var activeSlot by remember { mutableStateOf(null) } + var shouldShowSeedWords by remember { mutableStateOf(false) } + + fun updateEmojiState(): Boolean { + return selectedEmojis.toSet().size == selectedEmojis.size + } + + Box( + modifier = modifier + .fillMaxSize() + .background(brush = bentoModalDarkGradient) + ) { + Column( + modifier = Modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier + .padding(vertical = 8.dp), + contentAlignment = Alignment.Center + ) { + Row(modifier = Modifier) { + IconButton( + onClick = { onEmojisPicked(false) }, + ) { + Icon( + painter = painterResource(id = com.brainwallet.R.drawable.btn_clear), + contentDescription = stringResource(R.string.clear), + tint = Color.White + ) + } + Spacer(modifier = Modifier.weight(1f)) + } + + Row { + Text( + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(start = 30.dp, end = 30.dp), + text = stringResource(R.string.pick_emojis_title), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + fontSize = 22.sp, + color = Color.White + ), + ) + } + } + + Text( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(start = 30.dp, end = 30.dp) + .padding(top = 2.dp, bottom = 2.dp), + text = stringResource(R.string.pick_emojis_description), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Normal, + textAlign = TextAlign.Center, + fontSize = 16.sp, + color = Color.White + ), + maxLines = 4 + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // 3 emoji picker slots + val sectionSize = 120.dp + + Box( + modifier = Modifier + .fillMaxWidth() + .height(sectionSize + 32.dp) + .padding(6.dp) + .clip(DesignTheme.shapes.large) + .background(brush = bentoEmojiSectionGradient) + .border( + width = 0.3.dp, + color = Color.White.copy(alpha = 0.1f), + shape = DesignTheme.shapes.large + ), + contentAlignment = Alignment.Center, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + selectedEmojis.forEachIndexed { index, emoji -> + EmojiSlot( + emoji = emoji, + word = firstThreeWords[index], + index = index, + shouldShowWord = shouldShowSeedWords, + isActive = activeSlot == index, + onClick = { activeSlot = index }, + modifier = Modifier + .width(sectionSize) + .height(sectionSize) + .padding(horizontal = 2.dp) + ) + } + } + } + + Spacer(modifier = Modifier.height(4.dp)) + + Icon( + modifier = Modifier.align(Alignment.CenterHorizontally) + .padding(bottom = 5.dp) + .size(30.dp) + .clickable { + shouldShowSeedWords = !shouldShowSeedWords + }, + painter = if (shouldShowSeedWords) { + painterResource(id = R.drawable.visibility_off) + } else { + painterResource(id = R.drawable.visibility_svg) + }, + contentDescription = "Toggle Seed words show", + tint = Color.White + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Box( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.7f) + .padding(start = 16.dp, end = 16.dp) + .clip(DesignTheme.shapes.large) + .background(brush = bentoEmojiSectionGradient) + .border( + width = 0.5.dp, + color = Color.White.copy(alpha = 0.2f), + shape = DesignTheme.shapes.large + ) + ) { + Column(modifier = Modifier.fillMaxSize()) { + EmojiPickerSection( + modifier = Modifier.fillMaxSize(), + onEmojiSelected = { emoji -> + activeSlot?.let { slot -> + selectedEmojis = selectedEmojis + .toMutableList().apply { + this[slot] = emoji + shouldShowSeedWords = !shouldShowSeedWords + } + activeSlot = null + } + } + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + FilledTonalButton( + modifier = Modifier + .fillMaxWidth() + .height(44.dp) + .padding( + start = 20.dp, + end = 20.dp, + ), + enabled = true, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = Color.White, + contentColor = if (updateEmojiState()) { + colorMidnite + } else { + colorMidnite.copy( + alpha = 0.3f + ) + } + ), + onClick = { + if (updateEmojiState()) { + val joined = selectedEmojis.joinToString(separator = "") + val bytes = joined.toByteArray(Charsets.UTF_8) + BRKeyStore.putEmojis( + bytes, + context, + BWConstants.PUT_EMOJIS_REQUEST_CODE + ) + BRSharedPrefs.putEmojisChosen(context, true) + onEmojisPicked(true) + AnalyticsManager + .logCustomAdHocEvent("user_set_first_set_emojis", null) + } + }, + shape = RoundedCornerShape(11.dp) + + ) { + Text( + modifier = Modifier, + text = if (selectedEmojis.all { it != null }) { + stringResource(R.string.lets_play_label) + } else { + stringResource(R.string.pick_emojis_button_title) + }, + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.SemiBold, + fontSize = 18.sp + ) + ) + } + Spacer(modifier = Modifier.weight(0.05f)) + } + } +} + +@Composable +fun EmojiPickerSection( + modifier: Modifier = Modifier, + onEmojiSelected: (String) -> Unit = {}, + emojiIndex: Int = 0 +) { + AndroidView( + factory = { context -> + EmojiPickerView(context).apply { + setOnEmojiPickedListener { emojiViewItem -> + onEmojiSelected(emojiViewItem.emoji) + } + } + }, + modifier = modifier + .fillMaxSize() + .nestedScroll(rememberNestedScrollInteropConnection()), + ) +} + +@Composable +private fun EmojiSlot( + emoji: String?, + word: String?, + index: Int, + shouldShowWord: Boolean, + isActive: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .clip(RoundedCornerShape(12.dp)) + .border( + if (isActive) { + 1.dp + } else { + 0.3.dp + }, + if (isActive) { + Color.White + } else { + Color.White.copy(alpha = 0.1f) + }, + RoundedCornerShape(12.dp) + ) + .background(brush = bentoEmojiSectionGradient) + .clickable { onClick.invoke() } + .padding(vertical = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Box( + modifier = Modifier, + contentAlignment = Alignment.Center + ) { + if (shouldShowWord) { + if (word != null) { + Box { + val number = index + 1 + Text( + modifier = Modifier + .padding(horizontal = 8.dp, vertical = 3.dp), + text = number.toString(), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Normal, + textAlign = TextAlign.Start, + fontSize = 9.sp, + color = Color.White.copy(alpha = 0.6f) + ), + ) + Text( + modifier = Modifier + .clip(RoundedCornerShape(percent = 50)) + .background(Color.White.copy(alpha = 0.08f)) + .padding(horizontal = 20.dp, vertical = 5.dp), + text = word, + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Normal, + textAlign = TextAlign.Center, + fontSize = 16.sp, + color = Color.White + ), + ) + } + } + } else { + if (emoji != null) { + Text( + text = emoji, + fontSize = 48.sp, + ) + } else { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.sentiment_satisfied_24px), + contentDescription = null, + tint = Color.White.copy(alpha = 0.6f) + ) + } + } + } + } +} diff --git a/app/src/main/java/com/brainwallet/ui/screens/emojis/YourEmojisScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/emojis/YourEmojisScreen.kt new file mode 100644 index 00000000..b5380c49 --- /dev/null +++ b/app/src/main/java/com/brainwallet/ui/screens/emojis/YourEmojisScreen.kt @@ -0,0 +1,200 @@ +@file:OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) + +package com.brainwallet.ui.screens.emojis +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.brainwallet.R +import com.brainwallet.navigation.OnNavigate +import com.brainwallet.navigation.UiEffect +import com.brainwallet.ui.composable.BrainwalletScaffold +import com.brainwallet.ui.composable.BrainwalletTopAppBar +import com.brainwallet.ui.composable.LargeButton +import com.brainwallet.ui.screens.main.MainScreenEvent +import com.brainwallet.ui.screens.main.MainViewModel +import com.brainwallet.ui.theme.DesignTheme +import com.brainwallet.ui.theme.IBMPlexSans +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.koin.compose.koinInject +import kotlin.time.Duration.Companion.milliseconds + +@Composable +fun YourEmojisScreen( + onNavigate: OnNavigate, + emojis: ImmutableList, + modifier: Modifier = Modifier, + viewModel: MainViewModel = koinInject() +) { + val columnPadding = 12 + val horizontalVerticalSpacing = 8 + val leadingCopyPadding = 8 + val detailLineHeight = 24 + val coroutineScope = rememberCoroutineScope() + + BrainwalletScaffold( + topBar = { + BrainwalletTopAppBar( + navigationIcon = { + IconButton( + onClick = { onNavigate.invoke(UiEffect.Navigate.Back()) }, + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back), + ) + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(columnPadding.dp) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(horizontalVerticalSpacing.dp), + ) { + Text( + text = stringResource(R.string.your_emojis_title), + style = MaterialTheme.typography.headlineSmall, + ) + + Text( + modifier = Modifier + .padding(top = leadingCopyPadding.dp), + text = stringResource(R.string.your_emojis_description), + style = MaterialTheme.typography.bodyLarge.copy( + textAlign = TextAlign.Center, + lineHeight = detailLineHeight.sp, + color = Color.Gray + ) + ) + + Spacer(modifier = Modifier.weight(0.1f)) + + EmojisLayout(modifier = Modifier.weight(1f)) { + itemsIndexed(items = emojis) { index, emoji -> + EmojiItem( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + indexLabel = "${index + 1}", + emoji = emoji + ) + } + } + + Spacer(modifier = Modifier.weight(0.1f)) + + LargeButton( + onClick = { + viewModel.onEvent(MainScreenEvent.OnUserClearsEmojis) + coroutineScope.launch { + delay(200L.milliseconds) + onNavigate.invoke(UiEffect.Navigate.Back()) + } + }, + ) { + Text( + text = stringResource(R.string.your_emojis_reset), + style = MaterialTheme.typography.bodyLarge + ) + } + Spacer(modifier = Modifier.height(16.dp)) + } + } +} + +@Composable +fun EmojisLayout( + modifier: Modifier = Modifier, + content: LazyGridScope.() -> Unit +) { + LazyVerticalGrid( + modifier = modifier + .height(220.dp), + columns = GridCells.Fixed(3), // fixed 3 + contentPadding = PaddingValues(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + content = content + ) +} + +@Composable +fun EmojiItem( + indexLabel: String, + emoji: String, + modifier: Modifier = Modifier + +) { + Box( + modifier = Modifier + .background( + color = DesignTheme.colors.background.copy(alpha = 0.3f), + shape = MaterialTheme.shapes.extraLarge + ) + .padding(horizontal = 16.dp, vertical = 8.dp), + contentAlignment = Alignment.Center + + ) { + Text( + text = indexLabel, + modifier = Modifier + .align(Alignment.TopStart) + .offset(x = 4.dp, y = (-2).dp), + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Normal, + fontSize = 11.sp + ), + color = DesignTheme.colors.content, + ) + + Text( + text = emoji, + style = TextStyle( + fontFamily = IBMPlexSans, + fontWeight = FontWeight.Normal, + fontSize = 26.sp + ), + ) + } +} diff --git a/app/src/main/java/com/brainwallet/ui/screens/gamehub/ExitGameButton.kt b/app/src/main/java/com/brainwallet/ui/screens/gamehub/ExitGameButton.kt new file mode 100644 index 00000000..206049a2 --- /dev/null +++ b/app/src/main/java/com/brainwallet/ui/screens/gamehub/ExitGameButton.kt @@ -0,0 +1,32 @@ +package com.brainwallet.ui.screens.gamehub + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import com.brainwallet.R +import com.brainwallet.constants.topNavButtonSize + +@Composable +fun ExitGameButton( + modifier: Modifier = Modifier, + onClick: () -> Unit +) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .size(topNavButtonSize) + .clickable(onClick = onClick) + ) { + Icon( + painter = painterResource(id = R.drawable.home_24px), + contentDescription = "ExitGame", + tint = Color.White + ) + } +} diff --git a/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubEvent.kt b/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubEvent.kt index 1d309b08..246d2754 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubEvent.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubEvent.kt @@ -4,4 +4,5 @@ import android.content.Context sealed class GameHubEvent { data class OnLoad(val context: Context) : GameHubEvent() + data class OnGameExited(val jsonPayload: String, val byteArray: ByteArray) : GameHubEvent() } diff --git a/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubState.kt b/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubState.kt index a335eb8a..66812d37 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubState.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubState.kt @@ -1,8 +1,7 @@ package com.brainwallet.ui.screens.gamehub +import androidx.work.Data data class GameHubState( - val biometricEnabled: Boolean = false, - val iso: String = "USD", - val formattedCurrency: String = "", - val formattedVersion: String = "" + val languageISO: String = "en_US", + val lastGameResultTuple: Pair = Pair(null, null) ) diff --git a/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubViewModel.kt index fbd59e95..2da1e95a 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/gamehub/GameHubViewModel.kt @@ -1,17 +1,28 @@ package com.brainwallet.ui.screens.gamehub -import com.brainwallet.data.repository.LtcRepository +import android.app.Application +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.Bundle +import android.os.Environment +import androidx.core.content.FileProvider +import com.brainwallet.tools.manager.AnalyticsManager import com.brainwallet.ui.BrainwalletViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import org.koin.android.annotation.KoinViewModel +import java.io.File +import java.io.FileOutputStream @KoinViewModel class GameHubViewModel( - private val ltcRepository: LtcRepository + private val app: Application, ) : BrainwalletViewModel() { - private val _state = MutableStateFlow(GameHubState()) val state: StateFlow = _state.asStateFlow() @@ -20,6 +31,91 @@ class GameHubViewModel( when (event) { is GameHubEvent.OnLoad -> { } + is GameHubEvent.OnGameExited -> { + val unixTimestamp = System.currentTimeMillis() / 1000 + val bitmap = BitmapFactory.decodeByteArray(event.byteArray, 0, event.byteArray.size) + ?: throw error("Failed to decode image data") + val directory = app.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + ?: throw error("External storage is unavailable") + val file = File(directory, "$unixTimestamp.png") + + val jsonElement = Json.parseToJsonElement(event.jsonPayload) + val socialNetworkString = jsonElement.jsonObject["social_network"]?.jsonPrimitive?.content ?: "other" + + FileOutputStream(file).use { fos -> + bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos) + } + + val contentUri = FileProvider.getUriForFile( + app, + "${app.packageName}.fileprovider", + file + ) + + val shareText = "Look at my top score on Brainwallet! #Brainwallet #Litecoin" + val encodedText = java.net.URLEncoder.encode(shareText, "UTF-8") + + // Generic share intent + val genericIntent = Intent(Intent.ACTION_SEND).apply { + type = "image/png" + putExtra(Intent.EXTRA_TEXT, shareText) + putExtra(Intent.EXTRA_STREAM, contentUri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + // X app intent + val xAppIntent = Intent(genericIntent).apply { + setPackage("com.twitter.android") + } + + // X web fallback (text only, no image) + val xWebIntent = Intent(Intent.ACTION_VIEW).apply { + data = android.net.Uri.parse("https://x.com/intent/tweet?text=$encodedText") + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + // Instagram Stories intent (image only, shares as a sticker on a Story) + val instagramIntent = Intent("com.instagram.share.ADD_TO_STORY").apply { + setDataAndType(contentUri, "image/png") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + val pm = app.packageManager + val xAppAvailable = pm.resolveActivity(xAppIntent, 0) != null + val xWebAvailable = pm.resolveActivity(xWebIntent, 0) != null + val instagramAvailable = pm.resolveActivity(instagramIntent, 0) != null + + val params = Bundle() + params.putString("social_network", socialNetworkString) + AnalyticsManager.logCustomAdHocEvent("user_may_post_score_to_social", params) + + when (socialNetworkString) { + "twitter" -> when { + xAppAvailable -> app.startActivity(xAppIntent) + xWebAvailable -> app.startActivity(xWebIntent) + else -> app.startActivity( + Intent.createChooser(genericIntent, "Share Brainwallet Score").also { + it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) + } + "instagram" -> when { + instagramAvailable -> app.startActivity(instagramIntent) + else -> app.startActivity( + Intent.createChooser(genericIntent, "Share Brainwallet Score").also { + it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) + } + else -> app.startActivity( + Intent.createChooser(genericIntent, "Share Brainwallet Score").also { + it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) + } + } } } } diff --git a/app/src/main/java/com/brainwallet/ui/screens/main/MainScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/main/MainScreen.kt index 4de5a036..920cd67f 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/main/MainScreen.kt @@ -1,5 +1,4 @@ package com.brainwallet.ui.screens.home - import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState @@ -7,6 +6,7 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -41,9 +41,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp @@ -62,15 +64,16 @@ import com.brainwallet.constants.topNavButtonSize import com.brainwallet.constants.topNavStartEndPadding import com.brainwallet.constants.transactionRowHt import com.brainwallet.data.model.AppSetting +import com.brainwallet.gameinterface.LocalGameSlot import com.brainwallet.navigation.OnNavigate import com.brainwallet.navigation.Route import com.brainwallet.navigation.UiEffect import com.brainwallet.tools.animation.BRAnimator -import com.brainwallet.tools.manager.AnalyticsManager +import com.brainwallet.tools.manager.BRSharedPrefs import com.brainwallet.tools.util.withTheme import com.brainwallet.ui.bentosections.balancebento.BalanceBentoScreen import com.brainwallet.ui.bentosections.buyreceivebento.receive.ReceiveDialog -import com.brainwallet.ui.bentosections.gamehubbento.GameHubBentoPagerScreen +import com.brainwallet.ui.bentosections.gamehubbento.GameHubBentoScreen import com.brainwallet.ui.bentosections.ltcpickerbento.LTCPickerBentoScreen import com.brainwallet.ui.bentosections.shopbento.ShopBentoEvent import com.brainwallet.ui.bentosections.shopbento.ShopBentoScreen @@ -80,11 +83,14 @@ import com.brainwallet.ui.bentosections.tutorials.TutorialsBentoScreen import com.brainwallet.ui.bentosections.tutorials.send.TutorialSendPagerScreen import com.brainwallet.ui.bentosections.tutorials.walkthrough.TutorialWalkthroughPagerScreen import com.brainwallet.ui.composable.BentoBottomNavBar +import com.brainwallet.gameinterface.GameSplash +import com.brainwallet.ui.screens.gamehub.GameHubViewModel import com.brainwallet.ui.screens.main.MainScreenEvent import com.brainwallet.ui.screens.main.MainViewModel import com.brainwallet.ui.screens.main.SettingsButton import com.brainwallet.ui.screens.main.ThemeButton import com.brainwallet.ui.screens.main.WebModalScreen +import com.brainwallet.ui.screens.emojis.EmojiPagerScreen import com.brainwallet.ui.screens.send.SendScreen import com.brainwallet.ui.screens.settings.settingsrows.HomeSettingDrawerSheet import com.brainwallet.ui.theme.BrainwalletAppTheme @@ -96,6 +102,7 @@ import com.brainwallet.ui.theme.mainScreenDarkSurfaceGradient import com.brainwallet.ui.theme.mainScreenLightSurfaceGradient import com.brainwallet.util.EventBus import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -108,7 +115,8 @@ fun MainScreen( onNavigate: OnNavigate, modifier: Modifier = Modifier, viewModel: MainViewModel = koinViewModel(), - shopBentoViewModel: ShopBentoViewModel = koinViewModel() + shopBentoViewModel: ShopBentoViewModel = koinViewModel(), + gameHubViewModel: GameHubViewModel = koinViewModel() ) { val context = LocalContext.current var currentRoute by remember { mutableStateOf(Route.Main) } @@ -123,6 +131,19 @@ fun MainScreen( val state by viewModel.state.collectAsStateWithLifecycle() val showTransactionDetail = state.showTransactionDetail val noTxItemsPresent = state.transactionItems.isEmpty() + val address = BRSharedPrefs.getReceiveAddress(context) + + // / Splash setup vars + var splashAlpha by remember { mutableStateOf(0f) } + val animatedSplashAlpha by animateFloatAsState( + targetValue = splashAlpha, + animationSpec = tween(durationMillis = 400), + label = "splashAlpha" + ) + + var gameVisible by remember { mutableStateOf(false) } + var isFirstComposition by remember { mutableStateOf(true) } + val blurRadiusWhen by animateFloatAsState( targetValue = when { !isSheetOpen -> 0f @@ -154,6 +175,50 @@ fun MainScreen( } } + // / Splash setup animation + // / sequence the open/close whenever isGameHubOpen flips + LaunchedEffect(state.isGameHubOpen) { + if (isFirstComposition) { + isFirstComposition = false + gameVisible = state.isGameHubOpen + return@LaunchedEffect + } + if (state.isGameHubOpen) { + splashAlpha = 0.6f + delay(50) + gameVisible = true + delay(600) + splashAlpha = 0f + } else { + splashAlpha = 0.6f + delay(550) + gameVisible = false + delay(50) + splashAlpha = 0f + Timber.d( + "timber splashAlpha=$splashAlpha" + ) + } + } + + LaunchedEffect(state.isDrawerOpen) { + if (state.isDrawerOpen) { + if (drawerState.isClosed) drawerState.open() + } else { + if (drawerState.isOpen) drawerState.close() + } + } + + LaunchedEffect(drawerState) { + snapshotFlow { drawerState.currentValue } + .collect { value -> + val isOpen = value == DrawerValue.Open + if (isOpen != state.isDrawerOpen) { + viewModel.onEvent(MainScreenEvent.OnDrawerVisibilityChanged(isOpen)) + } + } + } + ModalNavigationDrawer( drawerState = drawerState, gesturesEnabled = drawerState.isOpen, @@ -167,209 +232,246 @@ fun MainScreen( topNavStartEndPadding + topNavButtonSize ), + isDrawerOpen = drawerState.isOpen ) } ) { - Scaffold( - modifier = modifier - .background( - if (isDarkMode) { - mainScreenDarkSurfaceGradient - } else { - mainScreenLightSurfaceGradient - } - ) - .blurAnimatedWith(blurRadiusWhen), - containerColor = Color.Transparent, - bottomBar = { - BentoBottomNavBar( - isDarkMode = appSetting.isDarkMode, - currentRoute = currentRoute, - isShowingTransactionDetail = showTransactionDetail, - canUserSend = canUserSend, - noTxItemsPresent = noTxItemsPresent, - onToggleTransactionDetail = { - viewModel.onEvent(MainScreenEvent.OnToggleTransactionsDetail) - if (showTransactionDetail) currentRoute = Route.Main - }, - onItemClick = { route: Route -> - currentRoute = route - - if (route == Route.Main) { - scope.launch { sheetState.hide() }.invokeOnCompletion { - if (!sheetState.isVisible) { - isSheetOpen = false - } - } - onNavigate.invoke(UiEffect.Navigate(route)) - } else if (route == Route.History) { - viewModel.onEvent(MainScreenEvent.OnToggleTransactionsDetail) - if (showTransactionDetail) currentRoute = Route.Main + Box(Modifier.fillMaxSize()) { + Scaffold( + modifier = modifier + .background( + if (isDarkMode) { + mainScreenDarkSurfaceGradient } else { - modalContentRoute = route - isSheetOpen = true + mainScreenLightSurfaceGradient } - }, - ) - } + ) + .blurAnimatedWith(blurRadiusWhen), + containerColor = Color.Transparent, + bottomBar = { + BentoBottomNavBar( + isDarkMode = appSetting.isDarkMode, + currentRoute = currentRoute, + isShowingTransactionDetail = showTransactionDetail, + canUserSend = canUserSend, + noTxItemsPresent = noTxItemsPresent, + onToggleTransactionDetail = { + viewModel.onEvent(MainScreenEvent.OnToggleTransactionsDetail) + if (showTransactionDetail) currentRoute = Route.Main + }, + onItemClick = { route: Route -> + currentRoute = route - ) { padding -> - BoxWithConstraints( - modifier = Modifier - .fillMaxSize() - .padding(padding) - ) { - val topNavRowHt = topNavButtonSize + (topNavStartEndPadding * 2) - val availableHeight = maxHeight - - topNavRowHt - - (bentoSpacer * 5) - - balanceGameBentoHt - - transactionRowHt - - gameHubHt - val transactionsDetailHeight = availableHeight + gameHubHt + balanceGameBentoHt - val ltcPickerBentoHeight = (availableHeight * 0.7f) - (bentoSpacer / 2) - val storeBentoHeight = (availableHeight * 0.3f) - (bentoSpacer / 2) - val transactionBentoHeight by animateDpAsState( - targetValue = if (showTransactionDetail) transactionsDetailHeight else transactionRowHt, - animationSpec = tween(EXPAND_DURATION), - label = "transactionHeight" - ) - Timber.d( - "bento layout maxHeight=$maxHeight " - ) - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = topNavStartEndPadding, - vertical = topNavStartEndPadding - ), - verticalAlignment = Alignment.CenterVertically, - ) { - ThemeButton( - isDarkMode = isDarkMode, - onClick = { - viewModel.onEvent(MainScreenEvent.OnToggleDarkMode) - } - ) - Spacer(modifier = Modifier.weight(1f)) - SettingsButton( - isDarkMode = isDarkMode, - onClick = { - scope.launch { - drawerState.apply { - if (isClosed) open() else close() + if (route == Route.Main) { + scope.launch { sheetState.hide() }.invokeOnCompletion { + if (!sheetState.isVisible) { + isSheetOpen = false } } + onNavigate.invoke(UiEffect.Navigate(route)) + } else if (route == Route.History) { + viewModel.onEvent(MainScreenEvent.OnToggleTransactionsDetail) + if (showTransactionDetail) currentRoute = Route.Main + } else if (route == Route.GameHub) { + if (!BRSharedPrefs.wereEmojisChosen(context)) { + modalContentRoute = Route.EmojiPickerPager + isSheetOpen = true + } else { + viewModel.onEvent(MainScreenEvent.OnToggleGameHub) + } + } else { + modalContentRoute = route + isSheetOpen = true } - ) - } - LazyVerticalGrid( - columns = GridCells.Fixed(2), - modifier = Modifier - .padding( - horizontal = bentoSpacer, - vertical = 1.dp - ) - .weight(1f), - horizontalArrangement = Arrangement.spacedBy(bentoSpacer), - verticalArrangement = Arrangement.spacedBy(bentoSpacer), - userScrollEnabled = false - ) { - item(span = { GridItemSpan(2) }) { - Box(modifier = Modifier.height(balanceGameBentoHt)) { - BalanceBentoScreen(transactions = state.transactionItems.toImmutableList()) - } - } - item(span = { GridItemSpan(2) }) { - TransactionsBentoScreen( - modifier = Modifier - .fillMaxWidth() - .height(transactionBentoHeight), - transactions = state.transactionItems.toImmutableList(), - toggleState = state.filterState, - showTransactionDetail = state.showTransactionDetail, - shouldShowFiatValues = state.shouldShowFiatValues, - onBentoTap = { - if (noTxItemsPresent) { - onNavigate.invoke(UiEffect.Navigate(Route.MoonPayBuy)) - } else { - viewModel.onEvent(MainScreenEvent.OnToggleTransactionsDetail) - } + }, + ) + } + + ) { padding -> + BoxWithConstraints( + modifier = Modifier + .fillMaxSize() + .padding(padding) + ) { + val topNavRowHt = topNavButtonSize + (topNavStartEndPadding * 2) + val availableHeight = maxHeight - + topNavRowHt - + (bentoSpacer * 5) - + balanceGameBentoHt - + transactionRowHt - + gameHubHt + val transactionsDetailHeight = availableHeight + gameHubHt + balanceGameBentoHt + val ltcPickerBentoHeight = (availableHeight * 0.7f) - (bentoSpacer / 2) + val storeBentoHeight = (availableHeight * 0.3f) - (bentoSpacer / 2) + val transactionBentoHeight by animateDpAsState( + targetValue = if (showTransactionDetail) transactionsDetailHeight else transactionRowHt, + animationSpec = tween(EXPAND_DURATION), + label = "transactionHeight" + ) + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = topNavStartEndPadding, + vertical = topNavStartEndPadding + ), + verticalAlignment = Alignment.CenterVertically, + ) { + ThemeButton( + isDarkMode = isDarkMode, + onClick = { + viewModel.onEvent(MainScreenEvent.OnToggleDarkMode) } ) + Spacer(modifier = Modifier.weight(1f)) + SettingsButton( + isDarkMode = isDarkMode, + onClick = { viewModel.onEvent(MainScreenEvent.OnToggleDrawer) } + ) } - item(span = { GridItemSpan(1) }) { - AnimatedVisibility( - visible = !showTransactionDetail, - enter = fadeIn(tween(FADE_IN_DURATION)), - exit = fadeOut(tween(FADE_OUT_DURATION)), - ) { - Box(modifier = Modifier.height(availableHeight)) { - TutorialsBentoScreen(onClick = { page -> - when (page) { - 0 -> { - modalContentRoute = Route.TutorialWalkthrough - isSheetOpen = true - } - 1 -> { - modalContentRoute = Route.TutorialSend - isSheetOpen = true - } - } - }) + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = Modifier + .padding( + horizontal = bentoSpacer, + vertical = 1.dp + ) + .weight(1f), + horizontalArrangement = Arrangement.spacedBy(bentoSpacer), + verticalArrangement = Arrangement.spacedBy(bentoSpacer), + userScrollEnabled = false + ) { + item(span = { GridItemSpan(2) }) { + Box(modifier = Modifier.height(balanceGameBentoHt)) { + BalanceBentoScreen(transactions = state.transactionItems.toImmutableList()) } } - } - item(span = { GridItemSpan(1) }) { - AnimatedVisibility( - visible = !showTransactionDetail, - enter = fadeIn(tween(FADE_IN_DURATION)), - exit = fadeOut(tween(FADE_OUT_DURATION)), - ) { - Column(verticalArrangement = Arrangement.spacedBy(bentoSpacer)) { - Box(modifier = Modifier.height(ltcPickerBentoHeight)) { - LTCPickerBentoScreen() + item(span = { GridItemSpan(2) }) { + TransactionsBentoScreen( + modifier = Modifier + .fillMaxWidth() + .height(transactionBentoHeight), + transactions = state.transactionItems.toImmutableList(), + toggleState = state.filterState, + showTransactionDetail = state.showTransactionDetail, + shouldShowFiatValues = state.shouldShowFiatValues, + onBentoTap = { + if (noTxItemsPresent) { + onNavigate.invoke(UiEffect.Navigate(Route.MoonPayBuy)) + } else { + viewModel.onEvent(MainScreenEvent.OnToggleTransactionsDetail) + } } - Box(modifier = Modifier.height(storeBentoHeight)) { - ShopBentoScreen(onClick = { - modalContentRoute = Route.BitrefillWeb(url = shopBentoState.shopBaseUrl) - isSheetOpen = true + ) + } + item(span = { GridItemSpan(1) }) { + AnimatedVisibility( + visible = !showTransactionDetail, + enter = fadeIn(tween(FADE_IN_DURATION)), + exit = fadeOut(tween(FADE_OUT_DURATION)), + ) { + Box( + modifier = Modifier.height(availableHeight) + ) { + TutorialsBentoScreen(onClick = { page -> + when (page) { + 0 -> { + modalContentRoute = Route.TutorialWalkthrough + isSheetOpen = true + } + + 1 -> { + modalContentRoute = Route.TutorialSend + isSheetOpen = true + } + } }) } } } - } - item(span = { GridItemSpan(2) }) { - AnimatedVisibility( - visible = !showTransactionDetail, - enter = fadeIn(tween(FADE_IN_DURATION)), - exit = fadeOut(tween(FADE_OUT_DURATION)), - ) { - Box(modifier = Modifier.height(gameHubHt)) { - GameHubBentoPagerScreen(onClick = { page -> - when (page) { - 0 -> { - AnalyticsManager.logCustomEvent("user_did_tap_fallinmoji_no_op") - } - 1 -> { - modalContentRoute = Route.BuyReceive + item(span = { GridItemSpan(1) }) { + AnimatedVisibility( + visible = !showTransactionDetail, + enter = fadeIn(tween(FADE_IN_DURATION)), + exit = fadeOut(tween(FADE_OUT_DURATION)), + ) { + Column(verticalArrangement = Arrangement.spacedBy(bentoSpacer)) { + Box( + modifier = Modifier.height( + ltcPickerBentoHeight + ) + ) { + LTCPickerBentoScreen() + } + Box( + modifier = Modifier.height( + storeBentoHeight + ) + ) { + ShopBentoScreen(onClick = { + modalContentRoute = + Route.BitrefillWeb(url = shopBentoState.shopBaseUrl) isSheetOpen = true - AnalyticsManager.logCustomEvent("user_did_tap_mp_in_gh") + }) + } + } + } + } + item(span = { GridItemSpan(2) }) { + AnimatedVisibility( + visible = !showTransactionDetail, + enter = fadeIn(tween(FADE_IN_DURATION)), + exit = fadeOut(tween(FADE_OUT_DURATION)), + ) { + Box( + modifier = Modifier + .height(gameHubHt) + .clickable( + enabled = !showTransactionDetail + ) { + if (BRSharedPrefs.wereEmojisChosen(context)) { + viewModel.onEvent(MainScreenEvent.OnToggleGameHub) + } else { + modalContentRoute = Route.EmojiPickerPager + isSheetOpen = true + } } - 2 -> { - modalContentRoute = Route.LinktreeWeb - isSheetOpen = true + ) { + GameHubBentoScreen( + isGameHubOpen = state.isGameHubOpen, + onToggle = { + viewModel.onEvent(MainScreenEvent.OnToggleGameHub) } - } - }) + ) + } } } } } } } + + val openGameSlot = LocalGameSlot.current + openGameSlot?.Render( + modifier = Modifier.fillMaxSize(), + visible = gameVisible, + launchParams = viewModel.produceLaunchParams(), + onExit = { _, _ -> + modalContentRoute = null + currentRoute = Route.Main + } + ) + + // splash overlay — fades in/out OVER the surface, + // masking the snap & first frame + if (animatedSplashAlpha > 0f) { + GameSplash( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { alpha = animatedSplashAlpha } + ) + } } if (isSheetOpen) { @@ -386,7 +488,8 @@ fun MainScreen( } else { Color.Black.copy(0.1f) }, - shape = RoundedCornerShape(24.dp) + shape = RoundedCornerShape(24.dp), + sheetGesturesEnabled = modalContentRoute != Route.EmojiPickerPager, ) { Box( modifier = Modifier @@ -395,8 +498,9 @@ fun MainScreen( .fillMaxHeight( if (modalContentRoute == Route.TutorialSend || modalContentRoute == Route.TutorialWalkthrough || - modalContentRoute is Route.BitrefillWeb || - modalContentRoute == Route.LinktreeWeb + modalContentRoute == Route.BitrefillWeb || + modalContentRoute == Route.LinktreeWeb || + modalContentRoute == Route.EmojiPickerPager ) { 1f } else { @@ -404,7 +508,9 @@ fun MainScreen( } ) .background( - brush = if (modalContentRoute == Route.BuyReceive) { + brush = if (modalContentRoute == Route.BuyReceive || + modalContentRoute == Route.EmojiPickerPager + ) { bentoClearGradient } else if (isDarkMode && modalContentRoute == Route.Send) { bentoModalDarkGradient @@ -467,7 +573,19 @@ fun MainScreen( shopBentoViewModel.onEvent(ShopBentoEvent.InvoiceCreated) } ) - else -> {} + is Route.EmojiPickerPager -> EmojiPagerScreen( + modifier = Modifier.fillMaxSize(), + onNavigate = onNavigate, + onDismissPickEmojis = { + isSheetOpen = false + viewModel.onEvent(MainScreenEvent.OnToggleGameHub) + }, + onDismiss = { + isSheetOpen = false + } + ) + else + -> {} } } } diff --git a/app/src/main/java/com/brainwallet/ui/screens/main/MainScreenEvent.kt b/app/src/main/java/com/brainwallet/ui/screens/main/MainScreenEvent.kt index de21f42d..88c07011 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/main/MainScreenEvent.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/main/MainScreenEvent.kt @@ -15,4 +15,16 @@ sealed class MainScreenEvent { data object OnToggleTransactionsFilter : MainScreenEvent() data object OnExportTransactions : MainScreenEvent() data class OnCopyTransactions(val transactionItem: TxItem) : MainScreenEvent() + data object OnToggleGameHub : MainScreenEvent() + data object OnToggleDrawer : MainScreenEvent() + data class OnDrawerVisibilityChanged(val isOpen: Boolean) : MainScreenEvent() + + data object OnUserClearsEmojis : MainScreenEvent() + + data object OnUserChoosesEmojis : MainScreenEvent() + + data class OnPostSocial( + val jsonString: String, + val screenShotData: ByteArray + ) : MainScreenEvent() } diff --git a/app/src/main/java/com/brainwallet/ui/screens/main/MainScreenState.kt b/app/src/main/java/com/brainwallet/ui/screens/main/MainScreenState.kt index 3ccdc304..01a0fad2 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/main/MainScreenState.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/main/MainScreenState.kt @@ -8,6 +8,7 @@ import com.brainwallet.ui.bentosections.transactionbento.TransactionFilterState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import java.math.BigDecimal +import java.net.URI data class MainScreenState( val moonpayCurrencyLimit: MoonpayCurrencyLimit = MoonpayCurrencyLimit(), @@ -22,6 +23,7 @@ data class MainScreenState( val showTransactionDetail: Boolean = false, val shouldShowFiatValues: Boolean = false, val brainwalletIsSyncing: Boolean = false, + val isGameHubOpen: Boolean = false, val transactionItems: ImmutableList = persistentListOf(), val filterState: TransactionFilterState = TransactionFilterState.ALL, val allTransactionItems: ImmutableList = persistentListOf(), @@ -39,8 +41,11 @@ data class MainScreenState( 0f, "$" ), + val isDrawerOpen: Boolean = false, val isInternetReachable: Boolean = true, - val formattedCurrency: String = "" + val formattedCurrency: String = "", + val gameURLString: String = "", + val gameImageURI: URI = URI.create("") ) { val fiatBalance: BigDecimal get() = litoshiBalance diff --git a/app/src/main/java/com/brainwallet/ui/screens/main/MainViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/main/MainViewModel.kt index 8d1d092a..13d8fa1b 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/main/MainViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/main/MainViewModel.kt @@ -17,6 +17,7 @@ import com.brainwallet.data.repository.SettingRepository import com.brainwallet.data.repository.TxRepository import com.brainwallet.tools.manager.AnalyticsManager import com.brainwallet.tools.manager.BRSharedPrefs +import com.brainwallet.tools.security.BRKeyStore import com.brainwallet.tools.sqlite.TransactionDataSource import com.brainwallet.tools.util.BRExchange.ONE_LITECOIN_OF_LITOSHIS import com.brainwallet.ui.BrainwalletViewModel @@ -26,6 +27,8 @@ import com.brainwallet.util.CurrencyDataGetter import com.brainwallet.util.VersionCodeProvider import com.brainwallet.wallet.BRPeerManager import com.brainwallet.wallet.BRWalletManager +import com.brainwallet.wallet.WalletOperations +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers @@ -43,12 +46,16 @@ import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import org.json.JSONArray +import org.json.JSONObject import org.koin.android.annotation.KoinViewModel import timber.log.Timber import java.math.BigDecimal +import java.text.BreakIterator import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import kotlin.String @OptIn(FlowPreview::class) @KoinViewModel @@ -59,6 +66,7 @@ class MainViewModel( private val txRepository: TxRepository, private val connectivityRepository: ConnectivityRepository, private val balanceBentoViewModel: BalanceBentoViewModel, + private val getWalletManager: () -> WalletOperations = { BRWalletManager.getInstance() }, versionCodeProvider: VersionCodeProvider, ) : BrainwalletViewModel(), BRWalletManager.OnBalanceChanged, @@ -74,6 +82,8 @@ class MainViewModel( ) val state: StateFlow = _state.asStateFlow() val appSetting: StateFlow = settingRepository.currentSettings + val walletManager = getWalletManager() + val seedWords: ImmutableList = walletManager.getSeedWords() init { @@ -171,6 +181,38 @@ class MainViewModel( } } } + fun produceLaunchParams(): String { + val address = BRSharedPrefs.getReceiveAddress(app) + val timestamp = java.util.Date().time + val userLanguage = settingRepository.getCurrentLanguage().code + val emojis = runCatching { BRKeyStore.getEmojis(app, BWConstants.SHOW_EMOJIS_REQUEST_CODE) } + .getOrNull()?.decodeToString() ?: "NO_EMOJIS" + + val emojiArray = JSONArray().apply { + val boundary = BreakIterator.getCharacterInstance() + boundary.setText(emojis) + var start = boundary.first() + var end = boundary.next() + while (end != BreakIterator.DONE) { + put(emojis.substring(start, end)) + start = end + end = boundary.next() + } + } + + val launchParameters = JSONObject().apply { + put("address", address) + put("language", userLanguage) + put("timestamp", timestamp) + put("emojis", emojiArray) + } + + val obj = JSONObject().apply { + put("launchParameters", launchParameters) + } + + return obj.toString() + } fun onResume( isWalletCreated: () -> Boolean = { BRWalletManager.getInstance().isCreated() }, @@ -344,6 +386,10 @@ class MainViewModel( onLoading(false) } } + is MainScreenEvent.OnPostSocial -> viewModelScope.launch { + val jsonString = event.jsonString + val screenShotData = event.screenShotData + } is MainScreenEvent.OnToggleDarkMode -> viewModelScope.launch { val currentSettings = appSetting.value settingRepository.save( @@ -377,11 +423,14 @@ class MainViewModel( transactionItems = filteredTransactions ) } - AnalyticsManager.logCustomAdHocEvent("did_toggle_txn_filter") + AnalyticsManager.logCustomAdHocEvent("did_toggle_txn_filter", null) } is MainScreenEvent.OnExportTransactions -> { // TODO: Implement } + is MainScreenEvent.OnToggleGameHub -> { + _state.update { it.copy(isGameHubOpen = !it.isGameHubOpen) } + } is MainScreenEvent.OnCopyTransactions -> { val currentTransaction = event.transactionItem @@ -460,6 +509,25 @@ class MainViewModel( Timber.e(e) } } + is MainScreenEvent.OnToggleDrawer -> { + _state.update { it.copy(isDrawerOpen = !it.isDrawerOpen) } + } + + is MainScreenEvent.OnDrawerVisibilityChanged -> { + _state.update { it.copy(isDrawerOpen = event.isOpen) } + } + is MainScreenEvent.OnUserChoosesEmojis -> { + } + is MainScreenEvent.OnUserClearsEmojis -> { + BRSharedPrefs.putEmojisChosen(app, false) + val emptyBytes = "".toByteArray(Charsets.UTF_8) + + BRKeyStore.putEmojis( + emptyBytes, + app, + BWConstants.PUT_EMOJIS_REQUEST_CODE + ) + } } } } diff --git a/app/src/main/java/com/brainwallet/ui/screens/send/SendScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/send/SendScreen.kt index d7d59269..d19709d2 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/send/SendScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/send/SendScreen.kt @@ -44,7 +44,6 @@ fun SendScreen( onOpenScanner: () -> Unit = {}, onDimissSendModal: () -> Unit = {}, viewModel: SendViewModel = koinViewModel(), - mainViewModel: MainViewModel = koinViewModel() ) { val uiState by viewModel.state.collectAsState() val context = LocalContext.current diff --git a/app/src/main/java/com/brainwallet/ui/screens/setpasscode/SetPasscodeViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/setpasscode/SetPasscodeViewModel.kt index 625042c0..a739a674 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/setpasscode/SetPasscodeViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/setpasscode/SetPasscodeViewModel.kt @@ -73,7 +73,7 @@ class SetPasscodeViewModel : BrainwalletViewModel() { ) } - // if passcode confirm all filled, then goto your seed words + // if passcode confirm all filled if (it.isMatchPasscode()) { viewModelScope.launch { EventBus.emit( diff --git a/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsEvent.kt b/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsEvent.kt index 0ed4df00..0ca3ce63 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsEvent.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsEvent.kt @@ -12,7 +12,9 @@ sealed class SettingsEvent { ) : SettingsEvent() object OnSecurityUpdatePinClick : SettingsEvent() object OnSecuritySeedPhraseClick : SettingsEvent() + object OnSecurityBrainwalletPhraseClick : SettingsEvent() object OnSecurityShareAnalyticsDataClick : SettingsEvent() + object OnToggleDarkMode : SettingsEvent() object OnToggleLock : SettingsEvent() object OnLanguageSelectorButtonClick : SettingsEvent() diff --git a/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsState.kt b/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsState.kt index 145b6f7b..919a77ad 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsState.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsState.kt @@ -19,6 +19,7 @@ data class SettingsState( "$" ), // -1 = need to fetch val languageSelectorBottomSheetVisible: Boolean = false, + val brainwalletPhraseBottomSheetVisible: Boolean = false, val fiatSelectorBottomSheetVisible: Boolean = false, val shareAnalyticsDataEnabled: Boolean = false, val lastSyncMetadata: SyncAnalyticsRepository.SyncMetadata? = null, diff --git a/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsViewModel.kt index 9f25d4d8..86440b06 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/settings/SettingsViewModel.kt @@ -1,5 +1,6 @@ package com.brainwallet.ui.screens.settings +import android.app.Application import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.LocaleListCompat import androidx.lifecycle.viewModelScope @@ -9,6 +10,7 @@ import com.brainwallet.data.model.toFeeOptions import com.brainwallet.data.repository.LtcRepository import com.brainwallet.data.repository.SettingRepository import com.brainwallet.domain.LanguageSwitcherUseCase +import com.brainwallet.tools.manager.BRSharedPrefs import com.brainwallet.tools.manager.FeeManager import com.brainwallet.ui.BrainwalletViewModel import com.brainwallet.util.EventBus @@ -34,6 +36,7 @@ class SettingsViewModel( private val languageSwitcherUseCase: LanguageSwitcherUseCase, private val ltcRepository: LtcRepository, versionCodeProvider: VersionCodeProvider, + private val app: Application, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : BrainwalletViewModel() { @@ -87,6 +90,9 @@ class SettingsViewModel( } } } + fun hasUserSetEmojis(): Boolean { + return BRSharedPrefs.wereEmojisChosen(app) + } override fun onEvent(event: SettingsEvent) { when (event) { @@ -183,6 +189,15 @@ class SettingsViewModel( ) } + SettingsEvent.OnSecurityBrainwalletPhraseClick -> viewModelScope.launch { + EventBus.emit( + EventBus.Event.Message( + LEGACY_EFFECT_ON_BRAINWALLET_PHRASE, + address = null + ) + ) + } + SettingsEvent.OnSecurityUpdatePinClick -> viewModelScope.launch { EventBus.emit( EventBus.Event.Message( @@ -216,6 +231,8 @@ class SettingsViewModel( const val LEGACY_EFFECT_ON_SYNC = "onSyncInvoked" const val LEGACY_EFFECT_ON_SEC_UPDATE_PIN = "onSecUpdatePin" const val LEGACY_EFFECT_ON_SEED_PHRASE = "onSeedPhrase" + + const val LEGACY_EFFECT_ON_BRAINWALLET_PHRASE = "onBrainwalletPhrase" const val LEGACY_EFFECT_ON_SHARE_ANALYTICS_DATA_TOGGLE = "onShareAnalyticsDataToggle" } } diff --git a/app/src/main/java/com/brainwallet/ui/screens/settings/settingsrows/HomeSettingDrawerSheet.kt b/app/src/main/java/com/brainwallet/ui/screens/settings/settingsrows/HomeSettingDrawerSheet.kt index 5bfd38a0..8649a678 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/settings/settingsrows/HomeSettingDrawerSheet.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/settings/settingsrows/HomeSettingDrawerSheet.kt @@ -18,6 +18,9 @@ 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.platform.AbstractComposeView @@ -44,28 +47,36 @@ import kotlinx.coroutines.flow.onEach import org.koin.compose.koinInject import org.koin.compose.viewmodel.koinViewModel import org.koin.java.KoinJavaComponent.inject +import timber.log.Timber @Composable fun HomeSettingDrawerSheet( modifier: Modifier = Modifier, + isDrawerOpen: Boolean = false, syncAnalyticsRepository: SyncAnalyticsRepository = koinInject(), viewModel: SettingsViewModel = koinViewModel() ) { val state by viewModel.state.collectAsState() val context = LocalContext.current + var hasUserSetEmojis by remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - viewModel.onEvent( - SettingsEvent.OnLoad( - shareAnalyticsDataEnabled = BRSharedPrefs.getShareData( - context - ), // currently just load analytics share data here - lastSyncMetadata = syncAnalyticsRepository.getLastSyncMetadata(), // currently just load sync metadata here + LaunchedEffect(isDrawerOpen) { + if (isDrawerOpen) { + Timber.d("timber drawer open") + viewModel.onEvent( + SettingsEvent.OnLoad( + shareAnalyticsDataEnabled = BRSharedPrefs.getShareData(context), + lastSyncMetadata = syncAnalyticsRepository.getLastSyncMetadata(), + ) ) - ) + hasUserSetEmojis = viewModel.hasUserSetEmojis() + } else { + hasUserSetEmojis = viewModel.hasUserSetEmojis() + Timber.d("timber drawer closed") + } } - // / Layout values + // Layout values val headerPadding = 56 ModalDrawerSheet( @@ -89,6 +100,7 @@ fun HomeSettingDrawerSheet( .fillMaxSize() .wrapContentHeight(), shareAnalyticsDataEnabled = state.shareAnalyticsDataEnabled, + userSetEmojis = hasUserSetEmojis, onEvent = { viewModel.onEvent(it) } diff --git a/app/src/main/java/com/brainwallet/ui/screens/settings/settingsrows/SecurityDetail.kt b/app/src/main/java/com/brainwallet/ui/screens/settings/settingsrows/SecurityDetail.kt index 3fc9c2df..f4f965d3 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/settings/settingsrows/SecurityDetail.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/settings/settingsrows/SecurityDetail.kt @@ -10,8 +10,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.brainwallet.R import com.brainwallet.ui.screens.settings.SettingsEvent @@ -20,6 +26,7 @@ import com.brainwallet.ui.screens.settings.SettingsEvent fun SecurityDetail( modifier: Modifier = Modifier, shareAnalyticsDataEnabled: Boolean = false, + userSetEmojis: Boolean = false, onEvent: (SettingsEvent) -> Unit ) { // / Layout values @@ -62,18 +69,33 @@ fun SecurityDetail( } } -// Row( -// modifier = Modifier -// .height(contentHeight.dp), -// verticalAlignment = Alignment.CenterVertically -// ) { -// Text(stringResource(R.string.security_brainwallet_phrase_title)) -// Spacer(modifier = Modifier.weight(1f)) -// Button(onClick = { -// }) { -// Text(stringResource(R.string.security_phrase_button)) -// } -// } + if (userSetEmojis) { + Row( + modifier = Modifier + .height(contentHeight.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text(stringResource(R.string.security_brainwallet_phrase_title)) + Spacer(modifier = Modifier.weight(1f)) + Button(onClick = { + onEvent.invoke(SettingsEvent.OnSecurityBrainwalletPhraseClick) + }) { + Text( + text = "😅", + style = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 20.sp, + shadow = Shadow( + color = Color.Black.copy(alpha = 0.5f), + offset = Offset(x = 4f, y = 4f), + blurRadius = 4f + ) + ), + maxLines = 1 + ) + } + } + } Row( modifier = Modifier .height(contentHeight.dp), 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 66c4da7b..f609a494 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 @@ -58,7 +58,7 @@ import com.brainwallet.ui.composable.bottomsheet.LanguageSelectorBottomSheet import com.brainwallet.constants.BWConstants import com.brainwallet.constants.bentoCornerRadius import com.brainwallet.ui.bentosections.gamehubbento.FallinScene -import com.brainwallet.ui.theme.BoldenVan +import com.brainwallet.ui.theme.LilitaOne import com.brainwallet.ui.theme.DesignTheme import com.brainwallet.ui.theme.gameTitleGradient import kotlinx.coroutines.delay @@ -162,7 +162,7 @@ fun WelcomeScreen( Text( text = displayedText, style = TextStyle( - fontFamily = BoldenVan, + fontFamily = LilitaOne, fontWeight = FontWeight.Normal, fontSize = 60.sp, shadow = Shadow( @@ -177,7 +177,7 @@ fun WelcomeScreen( text = displayedText, style = TextStyle( brush = gameTitleGradient, - fontFamily = BoldenVan, + fontFamily = LilitaOne, fontWeight = FontWeight.Normal, fontSize = 60.sp ), diff --git a/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsScreen.kt b/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsScreen.kt index 1de5bab8..23911d28 100644 --- a/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsScreen.kt +++ b/app/src/main/java/com/brainwallet/ui/screens/yourseedwords/YourSeedWordsScreen.kt @@ -44,10 +44,9 @@ fun YourSeedWordsScreen( seedWords: List, viewModel: YourSeedWordsViewModel = koinInject() ) { - // / Layout values + // Layout values val columnPadding = 12 val horizontalVerticalSpacing = 8 - val spacerHeight = 36 val leadingCopyPadding = 8 val detailLineHeight = 24 diff --git a/app/src/main/java/com/brainwallet/ui/theme/DesignCore.kt b/app/src/main/java/com/brainwallet/ui/theme/DesignCore.kt index 6f08fc9a..4ac014b3 100644 --- a/app/src/main/java/com/brainwallet/ui/theme/DesignCore.kt +++ b/app/src/main/java/com/brainwallet/ui/theme/DesignCore.kt @@ -19,6 +19,9 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat +import com.brainwallet.gameinterface.GameSlot +import com.brainwallet.gameinterface.LocalGameSlot +import org.koin.compose.getKoin val LocalLanguageISOCode = staticCompositionLocalOf { "en" @@ -59,6 +62,7 @@ fun DesignTheme( LocalBWColors provides colors, LocalLanguageISOCode provides languageCode, LocalIsDarkModeFlag provides isDarkMode, + LocalGameSlot provides getKoin().getOrNull() ) { MaterialTheme( typography = BWTypography, diff --git a/app/src/main/java/com/brainwallet/ui/theme/Fonts.kt b/app/src/main/java/com/brainwallet/ui/theme/Fonts.kt index 066293a4..59427e83 100644 --- a/app/src/main/java/com/brainwallet/ui/theme/Fonts.kt +++ b/app/src/main/java/com/brainwallet/ui/theme/Fonts.kt @@ -6,8 +6,8 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import com.brainwallet.R -val BoldenVan = FontFamily( - Font(R.font.bolden_van, FontWeight.Normal) +val LilitaOne = FontFamily( + Font(R.font.lilita_one_regular, FontWeight.Normal) ) val IBMPlexSans = FontFamily( diff --git a/app/src/main/java/com/brainwallet/ui/theme/Gradient.kt b/app/src/main/java/com/brainwallet/ui/theme/Gradient.kt index 490fed01..c363a226 100644 --- a/app/src/main/java/com/brainwallet/ui/theme/Gradient.kt +++ b/app/src/main/java/com/brainwallet/ui/theme/Gradient.kt @@ -189,8 +189,8 @@ val bentoModalDarkGradient: Brush Color(0xFF09082B).copy(alpha = 1f), Color(0xFF280589).copy(alpha = 0.7f) ), - start = Offset(Float.MIN_VALUE, Float.POSITIVE_INFINITY), - end = Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY), + start = Offset(Float.MIN_VALUE, Float.MIN_VALUE), + end = Offset(Float.MIN_VALUE, Float.POSITIVE_INFINITY), ) fun giftCardGradient1(size: androidx.compose.ui.geometry.Size): Brush { @@ -228,3 +228,27 @@ fun giftCardGradient3(size: androidx.compose.ui.geometry.Size): Brush { tileMode = TileMode.Clamp ) } + +val bentoEmojiSectionGradient: Brush + + @Composable + get() = Brush.linearGradient( + colors = listOf( + Color(0xFF14134c).copy(alpha = 0.8f), + Color(0xFF5827e2).copy(alpha = 0.1f) + ), + start = Offset(Float.MIN_VALUE, Float.POSITIVE_INFINITY), + end = Offset(Float.MIN_VALUE, Float.POSITIVE_INFINITY), + ) + +val bentoHowToSectionGradient: Brush + + @Composable + get() = Brush.linearGradient( + colors = listOf( + Color(0xFF14134c).copy(alpha = 0.9f), + Color(0xFF5827e2).copy(alpha = 0.25f) + ), + start = Offset(Float.MIN_VALUE, Float.POSITIVE_INFINITY), + end = Offset(Float.MIN_VALUE, Float.POSITIVE_INFINITY), + ) diff --git a/app/src/main/java/com/brainwallet/ui/theme/Typography.kt b/app/src/main/java/com/brainwallet/ui/theme/Typography.kt index 7819ae33..649d0c95 100644 --- a/app/src/main/java/com/brainwallet/ui/theme/Typography.kt +++ b/app/src/main/java/com/brainwallet/ui/theme/Typography.kt @@ -4,81 +4,81 @@ import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp -import com.grunt.brainwallet.core.presentation.theme.openSauceOneFamily +import com.grunt.brainwallet.core.presentation.theme.ibmPlexSansFamily val BWTypography = Typography( displayLarge = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.SemiBold, fontSize = 57.sp ), displayMedium = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Normal, fontSize = 45.sp ), displaySmall = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Normal, fontSize = 36.sp ), headlineLarge = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Normal, fontSize = 32.sp ), headlineMedium = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Normal, fontSize = 28.sp ), headlineSmall = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Normal, fontSize = 24.sp ), titleLarge = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Normal, fontSize = 22.sp ), titleMedium = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Medium, fontSize = 16.sp ), titleSmall = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Medium, fontSize = 14.sp ), bodyLarge = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Normal, fontSize = 16.sp ), bodyMedium = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Normal, fontSize = 14.sp ), bodySmall = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Normal, fontSize = 12.sp ), labelLarge = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.SemiBold, fontSize = 14.sp ), labelMedium = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Medium, fontSize = 12.sp ), labelSmall = TextStyle( - fontFamily = openSauceOneFamily, + fontFamily = ibmPlexSansFamily, fontWeight = FontWeight.Thin, fontSize = 11.sp ) diff --git a/app/src/main/java/com/brainwallet/util/ImageHelper.kt b/app/src/main/java/com/brainwallet/util/ImageHelper.kt new file mode 100644 index 00000000..fc3db922 --- /dev/null +++ b/app/src/main/java/com/brainwallet/util/ImageHelper.kt @@ -0,0 +1,47 @@ +package com.brainwallet.util + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.Environment +import java.io.File +import java.io.FileOutputStream +import java.net.URI + +interface ImageStorage { + fun saveImage(data: ByteArray, timestamp: Long): URI + fun deleteImage(path: URI): Boolean +} + +class ImageHelper( + public val context: Context +) : ImageStorage { + + override fun saveImage(data: ByteArray, timestamp: Long): URI { + val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size) + ?: throw error("Failed to decode image data") + + val directory = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + ?: throw error("External storage is unavailable") + + val file = File(directory, "$timestamp.png") + + FileOutputStream(file).use { fos -> + bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos) + } + return URI.create(file.absolutePath) + } + + override fun deleteImage(path: URI): Boolean { + return try { + val file = File(path) + when { + !file.exists() -> false + !file.isFile -> false + else -> file.delete() + } + } catch (e: Exception) { + false + } + } +} diff --git a/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java b/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java index 4d0bc438..ca98d165 100644 --- a/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java +++ b/app/src/main/java/com/brainwallet/wallet/BRWalletManager.java @@ -17,9 +17,6 @@ import android.view.View; import android.widget.EditText; import android.widget.Toast; - -import androidx.fragment.app.FragmentActivity; - import com.brainwallet.BrainwalletApp; import com.brainwallet.R; import com.brainwallet.presenter.activities.BreadActivity; @@ -30,7 +27,6 @@ import com.brainwallet.presenter.entities.BWDatabaseTransactionEntity; import com.brainwallet.presenter.entities.ImportPrivKeyEntity; import com.brainwallet.presenter.entities.TxItem; -import com.brainwallet.presenter.interfaces.BROnSignalCompletion; import com.brainwallet.tools.animation.BRAnimator; import com.brainwallet.tools.animation.SpringAnimator; import com.brainwallet.tools.manager.AnalyticsManager; @@ -51,13 +47,19 @@ import com.brainwallet.tools.util.Utils; import com.platform.entities.WalletInfo; +import org.jetbrains.annotations.NotNull; + import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import kotlin.Suppress; +import kotlinx.collections.immutable.ImmutableList; +import kotlinx.collections.immutable.adapters.ImmutableListAdapter; import timber.log.Timber; public class BRWalletManager implements WalletOperations, @@ -163,14 +165,29 @@ public synchronized boolean generateRandomSeed(final Context ctx) { BRKeyStore.putWalletCreationTime(walletCreationTime, ctx); final WalletInfo info = new WalletInfo(); info.creationDate = walletCreationTime; - byte[] strBytes = TypesConverter.getNullTerminatedPhrase(strPhrase); byte[] pubKey = BRWalletManager.getInstance().getMasterPubKey(strBytes); BRKeyStore.putMasterPublicKey(pubKey, ctx); - return true; } - + public List getSeedWords(Context context) { + byte[] phrase = null; + try { + phrase = BRKeyStore.getPhrase(context, 0); + if (phrase == null) { + return Collections.emptyList(); + } + String[] words = new String(phrase, StandardCharsets.UTF_8).split(" "); + return Arrays.asList(words); + } catch (UserNotAuthenticatedException e) { + throw new RuntimeException( + "Failed to retrieve the phrase even though at this point the system auth was asked for sure.", e); + } finally { + if (phrase != null) { + Arrays.fill(phrase, (byte) 0); + } + } + } public boolean wipeKeyStore(Context context) { Timber.d("timber: wipeKeyStore"); return BRKeyStore.resetWalletKeyStore(context); @@ -549,6 +566,13 @@ public String getSeedPhrase(Context context) { return new String(phraseBytes); } + @Override + public @NotNull ImmutableList getSeedWords() { + Context context = BrainwalletApp.getBreadContext(); + List words = getSeedWords(context); + return new ImmutableListAdapter<>(words); + } + public interface OnBalanceChanged { void onBalanceChanged(long balance); } diff --git a/app/src/main/java/com/brainwallet/wallet/WalletOperations.kt b/app/src/main/java/com/brainwallet/wallet/WalletOperations.kt index 1279a3d3..76e10a0a 100644 --- a/app/src/main/java/com/brainwallet/wallet/WalletOperations.kt +++ b/app/src/main/java/com/brainwallet/wallet/WalletOperations.kt @@ -1,7 +1,11 @@ package com.brainwallet.wallet +import kotlinx.collections.immutable.ImmutableList + interface WalletOperations { fun tryTransactionWithOps(sendAddress: String, sendAmount: Long, opsAddress: String, opsFeeAmount: Long): ByteArray? fun getMinOutputAmount(): Long fun getMinOutputAmountRequested(): Long + + fun getSeedWords(): ImmutableList } diff --git a/app/src/main/jniLibs/arm64-v8a/libgdx-box2d.so b/app/src/main/jniLibs/arm64-v8a/libgdx-box2d.so new file mode 100755 index 00000000..01230b0d Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libgdx-box2d.so differ diff --git a/app/src/main/jniLibs/arm64-v8a/libgdx-freetype.so b/app/src/main/jniLibs/arm64-v8a/libgdx-freetype.so new file mode 100755 index 00000000..da35b1a9 Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libgdx-freetype.so differ diff --git a/app/src/main/jniLibs/arm64-v8a/libgdx.so b/app/src/main/jniLibs/arm64-v8a/libgdx.so new file mode 100755 index 00000000..d63d8171 Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libgdx.so differ diff --git a/app/src/main/jniLibs/armeabi-v7a/libgdx-box2d.so b/app/src/main/jniLibs/armeabi-v7a/libgdx-box2d.so new file mode 100755 index 00000000..a6be043b Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libgdx-box2d.so differ diff --git a/app/src/main/jniLibs/armeabi-v7a/libgdx-freetype.so b/app/src/main/jniLibs/armeabi-v7a/libgdx-freetype.so new file mode 100755 index 00000000..1678fd9f Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libgdx-freetype.so differ diff --git a/app/src/main/jniLibs/armeabi-v7a/libgdx.so b/app/src/main/jniLibs/armeabi-v7a/libgdx.so new file mode 100755 index 00000000..63629020 Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libgdx.so differ diff --git a/app/src/main/jniLibs/x86/libgdx-box2d.so b/app/src/main/jniLibs/x86/libgdx-box2d.so new file mode 100755 index 00000000..aa436950 Binary files /dev/null and b/app/src/main/jniLibs/x86/libgdx-box2d.so differ diff --git a/app/src/main/jniLibs/x86/libgdx-freetype.so b/app/src/main/jniLibs/x86/libgdx-freetype.so new file mode 100755 index 00000000..565c3819 Binary files /dev/null and b/app/src/main/jniLibs/x86/libgdx-freetype.so differ diff --git a/app/src/main/jniLibs/x86/libgdx.so b/app/src/main/jniLibs/x86/libgdx.so new file mode 100755 index 00000000..5ceaf3a9 Binary files /dev/null and b/app/src/main/jniLibs/x86/libgdx.so differ diff --git a/app/src/main/jniLibs/x86_64/libgdx-box2d.so b/app/src/main/jniLibs/x86_64/libgdx-box2d.so new file mode 100755 index 00000000..26184e62 Binary files /dev/null and b/app/src/main/jniLibs/x86_64/libgdx-box2d.so differ diff --git a/app/src/main/jniLibs/x86_64/libgdx-freetype.so b/app/src/main/jniLibs/x86_64/libgdx-freetype.so new file mode 100755 index 00000000..34ae0f85 Binary files /dev/null and b/app/src/main/jniLibs/x86_64/libgdx-freetype.so differ diff --git a/app/src/main/jniLibs/x86_64/libgdx.so b/app/src/main/jniLibs/x86_64/libgdx.so new file mode 100755 index 00000000..abc2d7f0 Binary files /dev/null and b/app/src/main/jniLibs/x86_64/libgdx.so differ diff --git a/app/src/main/res/drawable/check_box_24px.xml b/app/src/main/res/drawable/check_box_24px.xml new file mode 100644 index 00000000..0249f815 --- /dev/null +++ b/app/src/main/res/drawable/check_box_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/lock_24px.xml b/app/src/main/res/drawable/lock_24px.xml new file mode 100644 index 00000000..67e91830 --- /dev/null +++ b/app/src/main/res/drawable/lock_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/sentiment_satisfied_24px.xml b/app/src/main/res/drawable/sentiment_satisfied_24px.xml new file mode 100644 index 00000000..302b96f6 --- /dev/null +++ b/app/src/main/res/drawable/sentiment_satisfied_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/font/bolden_van.ttf b/app/src/main/res/font/bolden_van.ttf deleted file mode 100755 index 99e8a205..00000000 Binary files a/app/src/main/res/font/bolden_van.ttf and /dev/null differ diff --git a/app/src/main/res/font/lilita_one_regular.ttf b/app/src/main/res/font/lilita_one_regular.ttf new file mode 100644 index 00000000..091ddf9e Binary files /dev/null and b/app/src/main/res/font/lilita_one_regular.ttf differ diff --git a/app/src/main/res/font/open_sauce_one_bold.ttf b/app/src/main/res/font/open_sauce_one_bold.ttf deleted file mode 100644 index 7693d469..00000000 Binary files a/app/src/main/res/font/open_sauce_one_bold.ttf and /dev/null differ diff --git a/app/src/main/res/font/open_sauce_one_extra_bold.ttf b/app/src/main/res/font/open_sauce_one_extra_bold.ttf deleted file mode 100644 index 105393d7..00000000 Binary files a/app/src/main/res/font/open_sauce_one_extra_bold.ttf and /dev/null differ diff --git a/app/src/main/res/font/open_sauce_one_italic.ttf b/app/src/main/res/font/open_sauce_one_italic.ttf deleted file mode 100644 index 2e1b1337..00000000 Binary files a/app/src/main/res/font/open_sauce_one_italic.ttf and /dev/null differ diff --git a/app/src/main/res/font/open_sauce_one_light.ttf b/app/src/main/res/font/open_sauce_one_light.ttf deleted file mode 100644 index dd7645ac..00000000 Binary files a/app/src/main/res/font/open_sauce_one_light.ttf and /dev/null differ diff --git a/app/src/main/res/font/open_sauce_one_medium.ttf b/app/src/main/res/font/open_sauce_one_medium.ttf deleted file mode 100644 index e2daa1bc..00000000 Binary files a/app/src/main/res/font/open_sauce_one_medium.ttf and /dev/null differ diff --git a/app/src/main/res/font/open_sauce_one_regular.ttf b/app/src/main/res/font/open_sauce_one_regular.ttf deleted file mode 100644 index 395a8571..00000000 Binary files a/app/src/main/res/font/open_sauce_one_regular.ttf and /dev/null differ diff --git a/app/src/main/res/font/open_sauce_one_semi_bold.ttf b/app/src/main/res/font/open_sauce_one_semi_bold.ttf deleted file mode 100644 index d04ee46d..00000000 Binary files a/app/src/main/res/font/open_sauce_one_semi_bold.ttf and /dev/null differ diff --git a/app/src/main/res/layout/activity_bread.xml b/app/src/main/res/layout/activity_bread.xml index 829da68c..1a22edc4 100644 --- a/app/src/main/res/layout/activity_bread.xml +++ b/app/src/main/res/layout/activity_bread.xml @@ -67,7 +67,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="12dp" android:layout_marginBottom="4dp" - android:fontFamily="@font/open_sauce_one_medium" + android:fontFamily="@font/ibm_plex_sans_medium" android:text="@string/ManageWallet.balance" android:textColor="@color/white" android:textSize="18sp" @@ -81,7 +81,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" - android:fontFamily="@font/open_sauce_one_medium" + android:fontFamily="@font/ibm_plex_sans_medium" android:minWidth="0dp" android:minHeight="0dp" android:textColor="@color/white" @@ -93,7 +93,7 @@ android:id="@+id/equals" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:fontFamily="@font/open_sauce_one_medium" + android:fontFamily="@font/ibm_plex_sans_medium" android:text="=" android:textColor="@color/white" android:textSize="14sp" @@ -104,7 +104,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" - android:fontFamily="@font/open_sauce_one_medium" + android:fontFamily="@font/ibm_plex_sans_medium" android:gravity="start" android:minWidth="0dp" android:minHeight="0dp" diff --git a/app/src/main/res/layout/activity_pin.xml b/app/src/main/res/layout/activity_pin.xml index a26b47da..87576d57 100644 --- a/app/src/main/res/layout/activity_pin.xml +++ b/app/src/main/res/layout/activity_pin.xml @@ -20,7 +20,7 @@ android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" - android:fontFamily="@font/open_sauce_one_medium" + android:fontFamily="@font/ibm_plex_sans_medium" tools:text="@string/Login.ltcPrice" android:textColor="@color/white" android:textSize="18sp" @@ -36,7 +36,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" - android:fontFamily="@font/open_sauce_one_light" + android:fontFamily="@font/ibm_plex_sans_light" tools:text="@string/Login.currentLtcPrice" android:textColor="@color/white" app:layout_constraintEnd_toEndOf="parent" @@ -192,4 +192,4 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index e8fa0f81..f43aae14 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -33,7 +33,7 @@ app:layout_constraintBottom_toTopOf="@id/composeView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - android:fontFamily="@font/open_sauce_one_regular" + android:fontFamily="@font/ibm_plex_sans_regular" android:text="@string/TransactionDetails.emptyMessage" android:textColor="@color/cheddar" android:textSize="18sp" @@ -49,4 +49,4 @@ app:layout_constraintBottom_toBottomOf="parent"/> - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml index ada224dc..2077907f 100644 --- a/app/src/main/res/layout/fragment_send.xml +++ b/app/src/main/res/layout/fragment_send.xml @@ -103,7 +103,7 @@ android:layout_height="44dp" android:layout_gravity="center_vertical" android:layout_marginEnd="5dp" - android:fontFamily="@font/open_sauce_one_light" + android:fontFamily="@font/ibm_plex_sans_light" android:text="@string/Send.pasteLabel" app:buttonType="2" app:isBreadButton="true" /> @@ -115,7 +115,7 @@ android:layout_height="44dp" android:layout_gravity="center_vertical" android:layout_marginEnd="@dimen/bread_margin" - android:fontFamily="@font/open_sauce_one_light" + android:fontFamily="@font/ibm_plex_sans_light" android:text="@string/Send.scanLabel" app:buttonType="2" app:isBreadButton="true" /> @@ -209,7 +209,7 @@ android:layout_marginTop="12dp" android:layout_marginEnd="16dp" android:layout_marginBottom="8dp" - android:fontFamily="@font/open_sauce_one_medium" + android:fontFamily="@font/ibm_plex_sans_medium" android:text="iso" android:textSize="18sp" app:buttonType="2" @@ -297,4 +297,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/language_list.xml b/app/src/main/res/layout/language_list.xml index 29c894f1..0b7f6681 100644 --- a/app/src/main/res/layout/language_list.xml +++ b/app/src/main/res/layout/language_list.xml @@ -13,10 +13,10 @@ android:paddingTop="10dp" android:paddingBottom="20dp" android:layout_centerInParent="true" - android:fontFamily="@font/open_sauce_one_light" + android:fontFamily="@font/ibm_plex_sans_light" android:text="English" android:textSize="20sp" android:textColor="?attr/colorOnPrimary" /> - \ No newline at end of file + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index f55a3768..4e2a6601 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -306,4 +306,20 @@ السماح بالوصول إلى الكاميرا من الإعدادات المتجر اشترِ بطاقات هدايا باستخدام LTC! + عبارة Brainwallet + اختر 3 رموز تعبيرية + اختر 3 رموز تعبيرية فريدة + اضغط على صندوق. أظهر أو أخف الكلمات لاختيار رموزك التعبيرية. اضغط على رمز تعبيري. يجب أن يكون كل رمز تعبيري مختلفاً. + اختر 3 رموز تعبيرية خاصة بك للعبة + هنا حيث تبني محفظتك الحقيقية. احفظ عبارة البذرة الخاصة بك من خلال اللعب. + اختر أول 3 رموز تعبيرية + هناك 12 كلمة بذرة في عبارتك. استخدم أول 3 كلمات للاختيار. + رموز تعبيرية ذات معنى + استخدم تطابقات الكلمة مثل الليمون. أو الأصفر. أو لا. الأمر متروك لك! + البذرة في ذاكرتك + شاهد الرمز التعبيري، ستتذكر كلمة البذرة. احفظ رمزك التعبيري...احصل على نقاط! + تم تعيين الرموز التعبيرية. لننطلق! + رموز Brainwallet التعبيرية الخاصة بك + اخترت هذه الرموز التعبيرية للعبة لمساعدتك على تذكر عبارة البذرة الخاصة بك + مسح الرموز التعبيرية \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c4759610..7be95b6a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -436,4 +436,20 @@ Kamerazugriff in den Einstellungen aktivieren Shop Kaufe Geschenkgutscheine mit LTC! + Brainwallet-Phrase + Wähle 3 Emojis + Wähle 3 unterschiedliche Emojis + Tippe auf ein Feld. Blende Wörter ein oder aus, um deine Emojis auszuwählen. Tippe auf ein Emoji. Jedes Emoji muss unterschiedlich sein. + Wähle deine 3 Emojis für das Spiel + Hier erstellst du deine echte Brainwallet. Merke dir deine Seed-Phrase durch Spielen. + Wähle deine ersten 3 Emojis + Es gibt 12 Seed-Wörter in deiner Phrase. Nutze die ersten 3 Wörter zur Auswahl. + Emojis mit Bedeutung + Nutze Ähnlichkeiten zum Wort wie Zitrone. Oder gelb. Oder nicht. Du entscheidest! + Seed in deinem Gedächtnis + Siehst du das Emoji, wirst du dich an das Seed-Wort erinnern. Merke dir das Emoji...sammle PUNKTE! + Emojis gespeichert. Lass uns spielen! + Deine Brainwallet-Emojis + Du hast diese Emojis für das Spiel gewählt, um dir deine Seed-Phrase zu merken + Emojis löschen \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b83dbf63..22c94848 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -511,4 +511,20 @@ Permitir acceso a la cámara en Configuración Tienda ¡Compra Tarjetas de Regalo con LTC! + Frase Brainwallet + Elige 3 Emojis + Elige 3 Emojis Únicos + Toca una casilla. Muestra u oculta palabras para elegir tus emojis. Toca un emoji. Cada emoji debe ser diferente. + Elige tus 3 emojis para el juego + Aquí es donde construyes tu Brainwallet real. Memoriza tu frase de recuperación jugando. + Elige tus primeros 3 emojis + Hay 12 palabras de recuperación en tu frase. Usa las primeras 3 palabras para elegir. + Emojis con significado + Usa coincidencias con la palabra como limón. O, amarillo. ¡O no! ¡Tú decides! + Recuperación en tu memoria + Ve el emoji, recordarás la palabra de recuperación. Memoriza tu emoji...¡consigue PUNTOS! + Emojis configurados. ¡Vamos a jugar! + Tus Emojis Brainwallet + Elegiste estos emojis para el juego y te ayudan a recordar tu frase de recuperación + Borrar emojis \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 7ca6cee3..e1b7ec3b 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -647,4 +647,20 @@ دسترسی دوربین را در تنظیمات فعال کنید فروشگاه خرید کارت هدیه با LTC! + عبارت Brainwallet + انتخاب 3 ایموجی + انتخاب 3 ایموجی منحصر به فرد + روی یک جعبه ضربه بزنید. کلمات را نمایش یا پنهان کنید تا ایموجی خود را انتخاب کنید. روی یک ایموجی ضربه بزنید. هر ایموجی باید متفاوت باشد. + 3 ایموجی خود را برای بازی انتخاب کنید + این جایی است که Brainwallet واقعی خود را می‌سازید. عبارت بذر خود را از طریق بازی به خاطر بسپارید. + 3 ایموجی اول خود را انتخاب کنید + 12 کلمه بذر در عبارت شما وجود دارد. از 3 کلمه اول استفاده کنید تا انتخاب کنید. + ایموجی‌هایی که معنی دارند + از تطابق‌های کلمه مانند لیمو استفاده کنید. یا زرد. یا نه. این انتخاب شماست! + بذر در حافظه شما + ایموجی را ببینید، کلمه بذر را به یاد خواهید آورد. ایموجی خود را به خاطر بسپارید...امتیاز بگیرید! + ایموجی‌ها تنظیم شدند. بیایید بازی کنیم! + ایموجی‌های Brainwallet شما + شما این ایموجی‌ها را برای بازی انتخاب کردید و به یادآوری عبارت بذر خود کمک می‌کنند + پاک کردن ایموجی‌ها \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e05eb164..cc417c9d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -662,4 +662,20 @@ Autoriser l\'accès à la caméra dans les Paramètres Boutique Achetez des Cartes Cadeaux avec LTC ! - \ No newline at end of file + Phrase Brainwallet + Choisir 3 Emojis + Choisir 3 Emojis Uniques + Appuyez sur une case. Affichez ou masquez les mots pour choisir vos emojis. Appuyez sur un emoji. Chaque emoji doit être différent. + Choisissez vos 3 emojis pour le jeu + C\'est ici que vous construisez votre vrai Brainwallet. Mémorisez votre phrase de départ en jouant. + Choisissez vos 3 premiers emojis + Il y a 12 mots de départ dans votre phrase. Utilisez les 3 premiers mots pour choisir. + Emojis qui ont du sens + Utilisez des correspondances avec le mot, comme citron. Ou, jaune. Ou, non. C\'est à vous de décider ! + Graine pour votre mémoire + Voyez l\'emoji, vous vous souviendrez du mot de départ. Mémorisez votre emoji...obtenez des POINTS ! + Emojis configurés. C\'est parti ! + Vos Emojis Brainwallet + Vous avez choisi ces emojis pour le jeu et ils vous aident à vous souvenir de votre phrase de départ + Effacer les emojis + diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index b75967b5..051734cb 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -306,4 +306,20 @@ सेटिंग्स में कैमरा एक्सेस की अनुमति दें दुकान LTC के साथ गिफ्ट कार्ड खरीदें! + Brainwallet phrase + 3 Emojis चुनें + 3 अद्वितीय Emojis चुनें + एक बॉक्स पर टैप करें। शब्दों को दिखाने या छिपाने के लिए टैप करें अपने emojis चुनने के लिए। एक emoji पर टैप करें। प्रत्येक emoji अलग होना चाहिए। + गेम के लिए अपने 3 emojis चुनें + यहाँ आप अपना असली Brainwallet बनाते हैं। गेमिंग के माध्यम से अपने seed phrase को याद रखें। + अपने पहले 3 emojis चुनें + आपके phrase में 12 seed words हैं। चुनने के लिए पहले 3 words का उपयोग करें। + Emojis जिनका अर्थ है + शब्द से मेल खाएं जैसे नींबू। या, पीला। या, नहीं। यह आप पर निर्भर है! + अपनी स्मृति के लिए Seed + emoji देखें, आप seed word को याद रखेंगे। अपने emoji को याद रखें...POINTS प्राप्त करें! + Emojis सेट हो गए। चलिए खेलते हैं! + आपके Brainwallet Emojis + आपने गेम के लिए ये emojis चुने हैं और अपने seed phrase को याद रखने में मदद करते हैं + Emojis साफ़ करें \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 8e91ddc1..281f9872 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -662,4 +662,20 @@ Izinkan akses kamera di Pengaturan Toko Beli Kartu Hadiah dengan LTC! + Frasa Brainwallet + Pilih 3 Emoji + Pilih 3 Emoji Unik + Ketuk kotak. Tampilkan atau sembunyikan kata untuk memilih emoji Anda. Ketuk emoji. Setiap emoji harus berbeda. + Pilih 3 emoji Anda untuk permainan + Di sini Anda membangun brainwallet asli Anda. Hafalkan frasa seed Anda melalui permainan. + Pilih 3 emoji pertama Anda + Ada 12 kata seed dalam frasa Anda. Gunakan 3 kata pertama untuk memilih. + Emoji yang memiliki makna + Gunakan kecocokan dengan kata seperti lemon. Atau, kuning. Atau, tidak. Serah pada Anda! + Seed ke memori Anda + Lihat emoji, Anda akan mengingat kata seed. Hafalkan emoji Anda...dapatkan POIN! + Emoji sudah diatur. Mari bermain! + Emoji Brainwallet Anda + Anda memilih emoji ini untuk permainan dan membantu Anda mengingat frasa seed Anda + Hapus emoji \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4b39065e..3f3f60e3 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -662,4 +662,20 @@ Consenti l\'accesso alla fotocamera in Impostazioni Negozio Acquista Carte Regalo con LTC! + Frase Brainwallet + Scegli 3 Emoji + Scegli 3 Emoji Diversi + Tocca una casella. Mostra o nascondi le parole per scegliere i tuoi emoji. Tocca un emoji. Ogni emoji deve essere diverso. + Scegli i tuoi 3 emoji per il gioco + Qui è dove costruisci il tuo vero brainwallet. Memorizza la tua frase seed attraverso il gioco. + Scegli i tuoi primi 3 emoji + Ci sono 12 parole seed nella tua frase. Usa le prime 3 parole per scegliere. + Emoji che hanno significato + Usa corrispondenze con la parola come limone. O, giallo. O, no. Sta a te! + Seed nella tua memoria + Vedi l\'emoji, ricorderai la parola seed. Memorizza il tuo emoji...ottieni PUNTI! + Emoji impostati. Giochiamo! + I Tuoi Emoji Brainwallet + Hai scelto questi emoji per il gioco e per aiutarti a ricordare la tua frase seed + Cancella emoji \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index ec3d2212..148fff88 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -662,4 +662,20 @@ 設定からカメラへのアクセスを許可してください ショップ LTCでギフトカードを購入! + Brainwalletフレーズ + 絵文字を3つ選択 + 3つのユニークな絵文字を選択 + ボックスをタップします。単語を表示または非表示にして、絵文字を選択します。絵文字をタップします。各絵文字は異なるものである必要があります。 + 3つの絵文字をゲーム用に選択 + ここで本物のBrainwalletを構築します。ゲームを通じてシードフレーズを暗記します。 + 最初の3つの絵文字を選択 + シードフレーズには12個の単語があります。最初の3つの単語を使用して選択します。 + 意味のある絵文字 + レモンのような単語とのマッチを使用します。または黄色。またはそうではない。あなた次第です! + メモリへのシード + 絵文字を見ると、シードワードを思い出します。絵文字を暗記します...ポイントを獲得します! + 絵文字を設定しました。遊びましょう! + あなたのBrainwallet絵文字 + ゲーム用に選択したこれらの絵文字は、シードフレーズを覚えるのに役立ちます + 絵文字をクリア \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 98d1ddbb..4f745a21 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -662,4 +662,20 @@ 설정에서 카메라 접근을 허용해주세요 상점 LTC로 기프트 카드를 구매하세요! + Brainwallet 문구 + 이모지 3개 선택 + 고유한 이모지 3개 선택 + 상자를 탭하세요. 단어를 표시하거나 숨겨 이모지를 선택하세요. 이모지를 탭하세요. 각 이모지는 달라야 합니다. + 게임용 이모지 3개 선택 + 실제 Brainwallet을 만드는 곳입니다. 게임을 통해 시드 문구를 외우세요. + 첫 번째 이모지 3개 선택 + 시드 단어는 12개입니다. 처음 3개 단어를 사용하여 선택하세요. + 의미 있는 이모지 + 단어와의 연결을 사용하세요(예: 레몬, 노란색, 또는 기타). 선택은 자유입니다! + 기억에 남기는 시드 + 이모지를 보면 시드 단어를 기억할 수 있습니다. 이모지를 외우면 포인트를 얻습니다! + 이모지 설정 완료. 게임을 시작하세요! + 당신의 Brainwallet 이모지 + 게임을 위해 선택한 이모지로 시드 문구를 기억하는 데 도움이 됩니다 + 이모지 초기화 \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 23850a43..b9fb16c4 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -306,4 +306,20 @@ Sta cameratoegang toe in Instellingen Winkel Koop cadeaukaarten met LTC! - \ No newline at end of file + Brainwallet-zin + Kies 3 Emoji\'s + Kies 3 Unieke Emoji\'s + Tik op een vak. Toon of verberg woorden om je emoji\'s te kiezen. Tik op een emoji. Elke emoji moet anders zijn. + Kies je 3 emoji\'s voor het spel + Hier bouw je je echte brainwallet. Onthoud je zaadzin door te spelen. + Kies je eerste 3 emoji\'s + Er zijn 12 zaadwoorden in je zin. Gebruik de eerste 3 woorden om te kiezen. + Emoji\'s met betekenis + Gebruik overeenkomsten met het woord zoals citroen. Of geel. Of niet. Het is aan jou! + Zaad in je geheugen + Zie de emoji, je herinnert je het zaadwoord. Onthoud je emoji...verdien PUNTEN! + Emoji\'s ingesteld. Laten we spelen! + Je Brainwallet Emoji\'s + Je hebt deze emoji\'s voor het spel gekozen en ze helpen je je zaadzin te onthouden + Emoji\'s wissen + diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 72fce716..182e9091 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -672,4 +672,20 @@ ਸੈਟਿੰਗਜ਼ ਵਿੱਚ ਕੈਮਰੇ ਦੀ ਅਨੁਮਤੀ ਦਿਓ ਦੁਕਾਨ LTC ਨਾਲ ਗਿਫ਼ਟ ਕਾਰਡ ਖਰੀਦੋ! + Brainwallet ਵਾਕਾਂਸ਼ + 3 Emojis ਚੁਣੋ + 3 ਵਿਲੱਖਣ Emojis ਚੁਣੋ + ਇੱਕ ਬਾਕਸ ਨੂੰ ਟੈਪ ਕਰੋ। ਆਪਣੇ emojis ਚੁਣਨ ਲਈ ਸ਼ਬਦ ਦਿਖਾਓ ਜਾਂ ਲੁਕਾਓ। ਇੱਕ emoji ਨੂੰ ਟੈਪ ਕਰੋ। ਹਰ emoji ਵੱਖਰਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। + ਆਪਣੇ 3 emojis ਖੇਲ ਲਈ ਚੁਣੋ + ਇਹ ਉਹ ਥਾਂ ਹੈ ਜਿੱਥੇ ਤੁਸੀਂ ਆਪਣੇ ਅਸਲ Brainwallet ਨੂੰ ਬਣਾਉਂਦੇ ਹੋ। ਖੇਲ ਦੇ ਮਾਧਿਅਮ ਨਾਲ ਆਪਣੇ ਸੀਡ ਵਾਕਾਂਸ਼ ਨੂੰ ਯਾਦ ਰੱਖੋ। + ਆਪਣੇ ਪਹਿਲੇ 3 emojis ਚੁਣੋ + ਆਪਣੇ ਵਾਕਾਂਸ਼ ਵਿੱਚ 12 ਸੀਡ ਸ਼ਬਦ ਹਨ। ਚੁਣਨ ਲਈ ਪਹਿਲੇ 3 ਸ਼ਬਦਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ। + Emojis ਜਿਨ੍ਹਾਂ ਦਾ ਅਰਥ ਹੈ + ਸ਼ਬਦ ਦੇ ਅਨੁਸਾਰ ਮੇਲ ਵਰਤੋਂ, ਜਿਵੇਂ ਨਿੰਬੂ। ਜਾਂ, ਪੀਲਾ। ਜਾਂ, ਨਹੀਂ। ਇਹ ਤੁਹਾਡੇ ਉਤੇ ਹੈ! + ਆਪਣੀ ਯਾਦ ਨੂੰ ਸੀਡ ਕਰੋ + emoji ਨੂੰ ਦੇਖੋ, ਤੁਸੀਂ ਸੀਡ ਸ਼ਬਦ ਯਾਦ ਕਰੋਗੇ। ਆਪਣੇ emoji ਨੂੰ ਯਾਦ ਰੱਖੋ...ਪੁਆਇੰਟ ਪ੍ਰਾਪਤ ਕਰੋ! + Emojis ਸੈਟ ਕੀਤੇ ਗਏ। ਚਲੋ ਖੇਡੀਏ! + ਤੁਹਾਡੇ Brainwallet Emojis + ਤੁਸੀਂ ਇਹ emojis ਖੇਲ ਲਈ ਚੁਣੇ ਹਨ ਅਤੇ ਆਪਣੇ ਸੀਡ ਵਾਕਾਂਸ਼ ਨੂੰ ਯਾਦ ਕਰਨ ਵਿੱਚ ਮਦਦ ਕਰਦੇ ਹਨ + Emojis ਨੂੰ ਸਾਫ਼ ਕਰੋ \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ea238811..491e9cc3 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -667,4 +667,20 @@ Zezwól na dostęp do aparatu w Ustawieniach Sklep Kup karty podarunkowe za LTC! + Fraza Brainwallet + Wybierz 3 emoji + Wybierz 3 unikalne emoji + Dotknij pole. Pokazuj lub ukrywaj słowa, aby wybrać swoje emoji. Dotknij emoji. Każde emoji musi być różne. + Wybierz swoje 3 emoji do gry + Tu budujesz swoją prawdziwą Brainwallet. Zapamiętuj frazę seed grając w grę. + Wybierz swoje pierwsze 3 emoji + W twojej frazie jest 12 słów seed. Użyj pierwszych 3 słów, aby wybrać. + Emoji, które mają znaczenie + Użyj dopasowań do słowa, jak cytryna. Lub żółty. Lub inny. To zależy od Ciebie! + Seed w twojej pamięci + Zobaczysz emoji, zapamiętasz słowo seed. Zapamiętaj emoji...zdobądź PUNKTY! + Emoji ustawione. Gramy! + Twoje emoji Brainwallet + Wybrałeś te emoji do gry i pomagają Ci zapamiętać frazę seed + Wyczyść emoji \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index f1e88df2..8c4256a6 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -306,4 +306,20 @@ Permita o acesso à câmera nas Configurações Loja Compre Cartões Presentes com LTC! + Frase Brainwallet + Escolha 3 Emojis + Escolha 3 Emojis Únicos + Toque em uma caixa. Mostre ou oculte palavras para escolher seus emojis. Toque em um emoji. Cada emoji deve ser diferente. + Escolha seus 3 emojis para o jogo + Aqui é onde você constrói sua Brainwallet real. Memorize sua frase-semente jogando. + Escolha seus primeiros 3 emojis + Há 12 palavras-semente em sua frase. Use as 3 primeiras palavras para escolher. + Emojis que têm significado + Use correspondências com a palavra, como limão. Ou amarelo. Ou não. Fica a seu critério! + Semente para sua memória + Veja o emoji, você lembrará da palavra-semente. Memorize seu emoji...ganhe PONTOS! + Emojis definidos. Vamos jogar! + Seus Emojis Brainwallet + Você escolheu esses emojis para o jogo e para ajudá-lo a lembrar sua frase-semente + Limpar emojis \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c45af3d4..0ea4c5fa 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -662,4 +662,20 @@ Разрешите доступ к камере в Настройках Магазин Покупайте подарочные карты за LTC! + Фраза Brainwallet + Выберите 3 эмодзи + Выберите 3 уникальных эмодзи + Нажмите на поле. Показывайте или скрывайте слова, чтобы выбрать эмодзи. Нажмите на эмодзи. Каждый эмодзи должен быть разным. + Выберите 3 эмодзи для игры + Здесь вы создаёте свой настоящий Brainwallet. Запомните фразу-семя через игру. + Выберите первые 3 эмодзи + В вашей фразе 12 слов-семян. Используйте первые 3 слова для выбора. + Эмодзи со смыслом + Подберите эмодзи к слову, как лимон. Или жёлтый. Или нет. Решайте сами! + Фраза-семя в памяти + Видите эмодзи — вспомните слово-семя. Запомните эмодзи...получайте ОЧКИ! + Эмодзи готовы. Играем! + Ваши эмодзи Brainwallet + Вы выбрали эти эмодзи для игры, чтобы помочь себе запомнить фразу-семя + Очистить эмодзи \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index a655a7c6..4b0e9f8d 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -306,4 +306,20 @@ Tillåt kameraåtkomst i Inställningar Butik Köp presentkort med LTC! + Brainwallet-fraser + Välj 3 Emojis + Välj 3 Unika Emojis + Tryck på en ruta. Visa eller dölj ord för att välja dina emojis. Tryck på en emoji. Varje emoji måste vara olika. + Välj dina 3 emojis för spelet + Här bygger du din riktiga brainwallet. Memorera din seed-fraser genom att spela. + Välj dina första 3 emojis + Det finns 12 seed-ord i din fraser. Använd de första 3 orden för att välja. + Emojis med betydelse + Använd matchningar till ordet som citron. Eller gul. Eller inte. Det är upp till dig! + Seed till ditt minne + Se emojin, du kommer ihåg seed-ordet. Memorera din emoji...få POÄNG! + Emojis inställda. Låt oss spela! + Dina Brainwallet-Emojis + Du valde dessa emojis för spelet och för att hjälpa dig komma ihåg din seed-fraser + Rensa emojis \ No newline at end of file diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 0c90e5ce..14d063b5 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -306,4 +306,20 @@ อนุญาตการเข้าถึงกล้องในการตั้งค่า ร้านค้า ซื้อบัตรของขวัญด้วย LTC! + วลี Brainwallet + เลือก Emoji 3 ตัว + เลือก Emoji ที่ต่างกัน 3 ตัว + แตะกล่อง แสดงหรือซ่อนคำเพื่อเลือก emoji ของคุณ แตะ emoji คำแต่ละคำต้องต่างกัน + เลือก emoji 3 ตัวของคุณสำหรับเกม + ที่นี่คือสถานที่ที่คุณสร้าง brainwallet จริง จำวลี seed ของคุณผ่านเกม + เลือก emoji 3 ตัวแรก + มีคำ seed 12 คำในวลีของคุณ ใช้คำ 3 คำแรกในการเลือก + Emoji ที่มีความหมาย + ใช้การจับคู่กับคำเช่น มะนาว หรือ สีเหลือง หรือ ไม่ ขึ้นอยู่กับคุณ! + Seed ไปยังความจำของคุณ + เห็น emoji คุณจะจำคำ seed ได้ จำ emoji ของคุณ...รับ POINTS! + ตั้งค่า Emoji แล้ว มาเล่นกัน! + Emoji Brainwallet ของคุณ + คุณเลือก emoji เหล่านี้สำหรับเกมและช่วยให้คุณจำวลี seed ได้ + ล้าง emoji \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 02bc40cf..01455e48 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -681,4 +681,20 @@ Ayarlar\'da kamera erişimine izin verin Mağaza LTC ile Hediye Kartı Satın Al! - \ No newline at end of file + Brainwallet ifadesi + 3 Emoji Seçin + 3 Benzersiz Emoji Seçin + Bir kutuya dokunun. Emojilerinizi seçmek için sözcükleri gösterin veya gizleyin. Bir emojiye dokunun. Her emoji farklı olmalıdır. + Oyun için 3 emojinizi seçin + Burada gerçek brainwallet\'inizi oluşturursunuz. Oyunlar aracılığıyla seed ifadenizi ezberleyin. + İlk 3 emojinizi seçin + İfadenizde 12 seed kelimesi vardır. Seçmek için ilk 3 kelimeyi kullanın. + Anlamı olan emojiler + Limon gibi kelimeyle eşleşmeleri kullanın. Veya sarı. Veya değil. Seçim sizindir! + Seed hafızanıza + Emojiye bakın, seed kelimesini hatırlayacaksınız. Emojinizi ezberleyin...PUAN kazanın! + Emojiler ayarlandı. Oynayalım! + Brainwallet Emojileriniz + Oyun için bu emojileri seçtiniz ve seed ifadenizi hatırlamanıza yardımcı oluyorlar + Emojileri temizle + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 81d90e99..67148a14 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -664,4 +664,20 @@ Дозвольте доступ до камери в Налаштуваннях Магазин Купуйте подарункові карти за LTC! - \ No newline at end of file + Фраза Brainwallet + Виберіть 3 емодзі + Виберіть 3 унікальні емодзі + Торкніться поля. Показуйте або приховуйте слова для вибору емодзі. Торкніться емодзі. Кожне емодзі має бути різним. + Виберіть 3 ваших емодзі для гри + Тут ви будуєте свою справжню Brainwallet. Запам\'ятовуйте фразу насіння через гру. + Виберіть перші 3 емодзі + У вашій фразі 12 слів насіння. Використовуйте перші 3 слова для вибору. + Емодзі зі значенням + Використовуйте відповідності до слова, як лимон. Або жовтий. Або ні. Вибір за вами! + Насіння у вашу пам\'ять + Бачите емодзі — пригадаєте слово насіння. Запам\'ятайте емодзі... отримайте БАЛИ! + Емодзі встановлені. Грайте! + Ваші емодзі Brainwallet + Ви виbrали ці емодзі для гри, щоб допомогти запам\'ятати вашу фразу насіння + Очистити емодзі + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 90a78ab7..727aa125 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -662,4 +662,20 @@ 在设置中允许摄像头访问 商店 用 LTC 购买礼品卡! + Brainwallet短语 + 选择3个表情符号 + 选择3个独特的表情符号 + 点击一个方框。显示或隐藏单词来选择你的表情符号。点击一个表情符号。每个表情符号必须不同。 + 为游戏选择你的3个表情符号 + 这是你构建真实Brainwallet的地方。通过游戏来记住你的助记词。 + 选择你的前3个表情符号 + 你的短语中有12个助记词。使用前3个单词来选择。 + 有意义的表情符号 + 使用与单词的匹配,如柠檬。或者黄色。或者不是。由你决定! + 助记词到你的记忆 + 看到表情符号,你就会记起助记词。记住你的表情符号...赚取积分! + 表情符号已设置。让我们开始玩吧! + 你的Brainwallet表情符号 + 你为游戏选择的这些表情符号可以帮助你记住你的助记词 + 清除表情符号 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 08c5214d..2932a86b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -662,4 +662,20 @@ 在設定中允許相機存取 商店 用 LTC 購買禮品卡! + Brainwallet 助記詞 + 選擇 3 個表情符號 + 選擇 3 個不同的表情符號 + 點擊方框。顯示或隱藏單詞以選擇您的表情符號。點擊表情符號。每個表情符號必須不同。 + 為遊戲選擇您的 3 個表情符號 + 這是您建立真正 Brainwallet 的地方。通過遊戲記住您的助記詞。 + 選擇您的前 3 個表情符號 + 您的助記詞中有 12 個單詞。使用前 3 個單詞進行選擇。 + 有意義的表情符號 + 使用與單詞的關聯,例如檸檬。或黃色。或否。由您決定! + 助記詞進入您的記憶 + 看到表情符號,您就會記住助記詞。記住您的表情符號...獲得積分! + 表情符號已設定。讓我們開始遊戲吧! + 您的 Brainwallet 表情符號 + 您為遊戲選擇的這些表情符號可幫助您記住您的助記詞 + 清除表情符號 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d094fac8..5f5ede36 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -402,6 +402,7 @@ PIN Seed phrase Share analytics data + Brainwallet phrase Update PIN Show Buy LTC @@ -492,5 +493,19 @@ Scan or paste the destination address. Double check! Change your local fiat to figure out how much you can send. Add a memo as a handy reminder. Tap Continue. Shop Buy Gift Cards with LTC! - + Pick 3 Emojis + Pick 3 Unique Emojis + Tap a box. Show or hide words to choose your emojis. Tap an emoji. Each emoji must be different. + Choose your 3 emojis for the game + Here is where you build your real brainwallet. Memorize your seed phrase through gaming. + Pick your first 3 emojis + There are 12 seed words in your phrase. Use the first 3 words to choose. + Emojis that have meaning + Use matches to the word like lemon. Or, yellow. Or, not. It\'s up to you! + Seed to your memory + See the emoji, you\'ll remember the seed word. Memorize your emoji...get POINTS! + Emojis set. Let\'s play! + Your Brainwallet Emojis + You choose these emojis for the game and help you remember your seed phrase + Clear emojis diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 76266c04..a2afcfdd 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -23,8 +23,8 @@ @@ -38,26 +38,26 @@ 10dp @color/content 18sp - @font/open_sauce_one_medium + @font/ibm_plex_sans_medium diff --git a/app/src/main/res/xml/file_provider_paths.xml b/app/src/main/res/xml/file_provider_paths.xml new file mode 100644 index 00000000..b91d0178 --- /dev/null +++ b/app/src/main/res/xml/file_provider_paths.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/screengrab/AndroidManifest.xml b/app/src/screengrab/AndroidManifest.xml deleted file mode 100644 index 62ad9087..00000000 --- a/app/src/screengrab/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/screengrab/kotlin/com/brainwallet/BrainwalletScreengrabApp.kt b/app/src/screengrab/kotlin/com/brainwallet/BrainwalletScreengrabApp.kt deleted file mode 100644 index 25610020..00000000 --- a/app/src/screengrab/kotlin/com/brainwallet/BrainwalletScreengrabApp.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.brainwallet - -import com.brainwallet.data.source.RemoteConfigSource -import org.koin.android.ext.koin.androidContext -import org.koin.android.ext.koin.androidLogger -import org.koin.core.context.GlobalContext.startKoin -import org.koin.core.logger.Level -import com.brainwallet.di.AppModule -import org.koin.ksp.generated.module -import timber.log.Timber - -class BrainwalletScreengrabApp : BrainwalletApp() { - - override fun initializeModule() { - Timber.d("Timber: initializeModule Screengrab") -// module = Module( -// remoteConfigSource = MockRemoteConfigSource() -// ) -// module.remoteConfigSource.initialize() -// keyStoreManager = KeyStoreManager(this, KeyStoreKeyGenerator.Impl()) - - startKoin { - androidLogger(if (BuildConfig.DEBUG) Level.DEBUG else Level.ERROR) - androidContext(this@BrainwalletScreengrabApp) - modules(AppModule.dataModule, AppModule.module, mainViewModelModule) - } - } -} - -class MockRemoteConfigSource : RemoteConfigSource { - - override fun initialize() { - - } - - override fun getString(key: String): String { - return "" - } - - override fun getNumber(key: String): Double { - return 0.0 - } - - override fun getBoolean(key: String): Boolean { - Timber.d("timber: getBoolean") - return true - } - -} \ No newline at end of file diff --git a/app/src/screengrabTests/kotlin/flow/RecoverWalletScreenGrabsTest.kt b/app/src/screengrabTests/kotlin/flow/RecoverWalletScreenGrabsTest.kt deleted file mode 100644 index a753067c..00000000 --- a/app/src/screengrabTests/kotlin/flow/RecoverWalletScreenGrabsTest.kt +++ /dev/null @@ -1,260 +0,0 @@ -package flow - -import android.Manifest -import androidx.compose.ui.test.ExperimentalTestApi -import androidx.compose.ui.test.hasTestTag -import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onChild -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.onRoot -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.performScrollTo -import androidx.compose.ui.test.performScrollToNode -import androidx.compose.ui.test.performTextInput -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.filters.LargeTest -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.GrantPermissionRule -import androidx.test.uiautomator.UiDevice -import com.brainwallet.R -import com.brainwallet.test.BuildConfig -import com.brainwallet.ui.BrainwalletActivity -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import timber.log.Timber -import tools.fastlane.screengrab.FalconScreenshotStrategy -import tools.fastlane.screengrab.Screengrab -import tools.fastlane.screengrab.cleanstatusbar.BluetoothState -import tools.fastlane.screengrab.cleanstatusbar.CleanStatusBar -import tools.fastlane.screengrab.cleanstatusbar.IconVisibility -import tools.fastlane.screengrab.cleanstatusbar.MobileDataType -import tools.fastlane.screengrab.locale.LocaleTestRule - -/** - * TODO: revisit this, since breaking with new navigation - * - * this will require pixel 7 pro - */ -@RunWith(JUnit4::class) -@LargeTest -class RecoverWalletScreenGrabsTest { - - @Rule - @JvmField - val localeTestRule = LocaleTestRule() - - @Rule - @JvmField - val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.CAMERA, - Manifest.permission.POST_NOTIFICATIONS, - ) - - @get:Rule - val composeTestRule = createAndroidComposeRule() - - private val uiDevice - get() = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - - @Before - fun setUp() { - CleanStatusBar() - .setBluetoothState(BluetoothState.DISCONNECTED) - .setMobileNetworkDataType(MobileDataType.LTE) - .setWifiVisibility(IconVisibility.HIDE) - .setShowNotifications(false) - .setClock("0900") - .setBatteryLevel(100) - .enable() - } - - @After - fun tearDown() { - CleanStatusBar.disable() - } - - @OptIn(ExperimentalTestApi::class) - @Test - fun onRecoverFlowSuccess() { - composeTestRule.activityRule.scenario.onActivity { - Screengrab.setDefaultScreenshotStrategy(FalconScreenshotStrategy(it)) - } - - waitUntilReady() - - uiDevice.waitForIdle(60_000) - composeTestRule.waitForIdle() - - Screengrab.screenshot("1_welcome_screen") - - composeTestRule.onNode(hasTestTag("buttonRestore")).performClick() - - uiDevice.waitForIdle() - - Screengrab.screenshot("2_input_words_screen") - - // seed words input - val editTextTags = (0..11).map { index -> "textFieldSeedWord$index" } - - val paperKey = BuildConfig.SCREENGRAB_PAPERKEY - - editTextTags.zip(paperKey).forEachIndexed { index, (textFieldTag, value) -> - uiDevice.waitForIdle() - val textForTyping = value.dropLast(1) - composeTestRule.onNodeWithTag(textFieldTag).onChild().performTextInput(textForTyping) - composeTestRule.onNodeWithText(value).performClick() - } - - Screengrab.screenshot("3_input_words_screen_2") - - composeTestRule.onNodeWithTag("buttonRestore").performScrollTo() - composeTestRule.onNodeWithTag("buttonRestore").assertExists() - composeTestRule.onNodeWithTag("buttonRestore").performClick() - - uiDevice.waitForIdle() - - composeTestRule.waitUntilAtLeastOneExists(hasTestTag("keypad1")) - - repeat(4) { - uiDevice.waitForIdle() - composeTestRule.onNodeWithTag("keypad1").assertExists() - composeTestRule.onNodeWithTag("keypad1").performClick() - } - - Screengrab.screenshot("4_set_passcode_screen") - - composeTestRule.waitUntilAtLeastOneExists(hasTestTag("keypad1")) - - repeat(4) { - uiDevice.waitForIdle() - composeTestRule.onNodeWithTag("keypad1").assertExists() - composeTestRule.onNodeWithTag("keypad1").performClick() - } - - Screengrab.screenshot("5_set_passcode_confirm_screen") - - uiDevice.waitForIdle() - - Thread.sleep(1000) - - Screengrab.screenshot("6_main_screen") - - // setting drawer - onView(withId(R.id.menuBut)).perform(click()) - - Screengrab.screenshot("7_setting_drawer_open") - - composeTestRule.onNodeWithTag("settingSecurity").assertExists() - composeTestRule.onNodeWithTag("settingSecurity").performClick() - - composeTestRule.waitForIdle() - - Screengrab.screenshot("8_setting_drawer_open_security") - - composeTestRule.waitForIdle() - - composeTestRule.onNodeWithTag("settingLanguage").assertExists() - composeTestRule.onNodeWithTag("settingLanguage").performClick() - - composeTestRule.waitForIdle() - - Screengrab.screenshot("9_setting_drawer_open_language") - - composeTestRule.waitForIdle() - - composeTestRule.onNodeWithTag("settingCurrency").assertExists() - composeTestRule.onNodeWithTag("settingCurrency").performClick() - - composeTestRule.waitForIdle() - - Screengrab.screenshot("10_setting_drawer_open_currency") - - composeTestRule.waitForIdle() - - composeTestRule.onNodeWithTag("settingGames").assertExists() - composeTestRule.onNodeWithTag("settingGames").performClick() - - composeTestRule.waitForIdle() - - Screengrab.screenshot("11_setting_drawer_open_games") - - composeTestRule.waitForIdle() - - composeTestRule.onNodeWithTag("lazyColumnSetting").performScrollToNode(hasTestTag("settingBlockchain")) - composeTestRule.onNodeWithTag("settingBlockchain").assertExists() - composeTestRule.onNodeWithTag("settingBlockchain").performClick() - - composeTestRule.waitForIdle() - - Screengrab.screenshot("12_setting_drawer_open_blockchain") - - composeTestRule.waitForIdle() - - composeTestRule.onNodeWithTag("lazyColumnSetting").performScrollToNode(hasTestTag("settingLock")) - composeTestRule.onNodeWithTag("settingLock").assertExists() - composeTestRule.onNodeWithTag("settingLock").performClick() - - composeTestRule.waitForIdle() - - Screengrab.screenshot("13_setting_drawer_lock") - - repeat(4) { - uiDevice.waitForIdle() - composeTestRule.onNodeWithTag("keypad1").assertExists() - composeTestRule.onNodeWithTag("keypad1").performClick() - } - - uiDevice.waitForIdle() - Thread.sleep(1000) - - // tx send ui - onView(withId(R.id.nav_send)).perform(click()) - - onView(withId(R.id.amount_edit)).perform(click()) - - Screengrab.screenshot("14_transaction_send") - - onView(withId(R.id.close_button)).perform(click()) - - // tx buy/receive ui - onView(withId(R.id.nav_receive)).perform(click()) - - Screengrab.screenshot("15_transaction_buy_receive") - - composeTestRule.onNodeWithTag("buttonClose").performClick() - - uiDevice.waitForIdle() - } - - private fun waitUntilReady() { - var attempts = 0 - val maxAttempts = 5 - - while (attempts < maxAttempts) { - try { - composeTestRule.waitUntil(timeoutMillis = 60_000) { - try { - composeTestRule.onRoot().fetchSemanticsNode() - true - } catch (e: Exception) { - Timber.e("[waitForComposeHierarchy] Error1 : ${e.message}", e) - false - } - } - return - } catch (e: Exception) { - Timber.e("[waitForComposeHierarchy] Error2 : ${e.message}", e) - attempts++ - if (attempts >= maxAttempts) throw e - Thread.sleep(1000) - } - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts index 66f85cdc..22a2a4eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,4 +15,8 @@ plugins { tasks.register("clean", Delete::class) { delete(rootProject.layout.buildDirectory) -} \ No newline at end of file + delete(layout.projectDirectory.dir("bw-gdlib/core/build")) + delete(layout.projectDirectory.dir("bw-gdlib/android/build")) + dependsOn(gradle.includedBuild("bw-gdlib").task(":core:clean")) + dependsOn(gradle.includedBuild("bw-gdlib").task(":android:clean")) +} diff --git a/bw-gdlib b/bw-gdlib new file mode 160000 index 00000000..c8ca278c --- /dev/null +++ b/bw-gdlib @@ -0,0 +1 @@ +Subproject commit c8ca278c2142c63322ec176835a4dd321bb8930a diff --git a/gradle.properties b/gradle.properties index 16129a19..3cae24e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,6 @@ org.gradle.parallel=true # Configuration on demand (optional, less useful in multi-module builds now). org.gradle.configureondemand=true - # ============================== # ? Android-Specific Tweaks # ============================== @@ -59,7 +58,7 @@ gradle.dependency.verification=on android.enableResourceOptimizations=true # Enable safe configuration caching (massive speedups on AGP 8.2+) -org.gradle.configuration-cache=true +org.gradle.configuration-cache=false org.gradle.configuration-cache.problems=warn # Use Gradle?s new file system watching for faster incremental builds. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f74782b1..08d7e543 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,6 +34,8 @@ foundation = "1.10.6" animation = "1.10.6" play-services-games = "24.0.0" benchmark-traceprocessor = "1.4.1" +kotlin = "2.3.10" +ui-graphics = "1.11.2" [libraries] androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } @@ -109,6 +111,7 @@ androidx-foundation = { group = "androidx.compose.foundation", name = "foundatio androidx-animation = { group = "androidx.compose.animation", name = "animation", version.ref = "animation" } play-services-games = { group = "com.google.android.gms", name = "play-services-games", version.ref = "play-services-games" } androidx-benchmark-traceprocessor = { group = "androidx.benchmark", name = "benchmark-traceprocessor", version.ref = "benchmark-traceprocessor" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "ui-graphics" } [bundles] androidx-lifecycle = ["androidx-lifecycle-runtime", "androidx-lifecycle-viewmodel", "androidx-lifecycle-viewmodel-compose"] @@ -125,3 +128,6 @@ squareup-okhttp = ["squareup-okhttp", "squareup-okhttp-logging-interceptor"] [plugins] google-services = { id = "com.google.gms.google-services", version = "4.4.2" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.2" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/modules/private-general-purpose b/modules/private-general-purpose index 5d8e9f99..2fe93eb1 160000 --- a/modules/private-general-purpose +++ b/modules/private-general-purpose @@ -1 +1 @@ -Subproject commit 5d8e9f9967ffc396082f84dc6b5c6749a81c7e41 +Subproject commit 2fe93eb1b79fb16d1d7975aedbe66d33d5c8fb35 diff --git a/settings.gradle.kts b/settings.gradle.kts index bbbebf96..7298605b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,7 @@ +import org.gradle.kotlin.dsl.project pluginManagement { includeBuild("android-build-logic") + repositories { google { content { @@ -10,14 +12,25 @@ pluginManagement { } mavenCentral() gradlePluginPortal() + mavenLocal() + maven { url = uri("https://jitpack.io") } + maven { url = uri("https://central.sonatype.com/repository/maven-snapshots/") } + } +} +includeBuild("bw-gdlib") { + dependencySubstitution { + substitute(module("ltd.grunt.brainwallet.gdx:core")).using(project(":android")) } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { + mavenLocal() google() mavenCentral() + maven { url = uri("https://jitpack.io") } + maven { url = uri("https://central.sonatype.com/repository/maven-snapshots/") } } versionCatalogs { create("grunt") { @@ -29,8 +42,11 @@ dependencyResolutionManagement { rootProject.name = "Brainwallet Android" include(":app") include(":install_time_asset_pack") -include(":core", ":games", ":iap", ":general-purpose-app") +include(":core",":iap",":general-purpose-app") project(":general-purpose-app").projectDir = file("modules/private-general-purpose/app") project(":core").projectDir = file("modules/private-general-purpose/core") -project(":games").projectDir = file("modules/private-general-purpose/games") project(":iap").projectDir = file("modules/private-general-purpose/iap") + + + +