diff --git a/AndroidDemo/app/src/androidTest/java/io/ton/walletkit/demo/e2e/dapp/JsDAppController.kt b/AndroidDemo/app/src/androidTest/java/io/ton/walletkit/demo/e2e/dapp/JsDAppController.kt index 2f48afec..3c77c9fb 100644 --- a/AndroidDemo/app/src/androidTest/java/io/ton/walletkit/demo/e2e/dapp/JsDAppController.kt +++ b/AndroidDemo/app/src/androidTest/java/io/ton/walletkit/demo/e2e/dapp/JsDAppController.kt @@ -24,9 +24,11 @@ package io.ton.walletkit.demo.e2e.dapp import android.content.ClipboardManager import android.util.Log import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextClearance +import androidx.compose.ui.test.performTextInput import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice @@ -120,19 +122,27 @@ class JsDAppController { fun openBrowser(url: String = DAPP_URL, injectTonConnect: Boolean = false) { jsBridge.clearCache() // Clear any cached WebView reference - if (injectTonConnect) { - composeTestRule.onNodeWithContentDescription("Open TonConnect Browser") - .performClick() - } else { - composeTestRule.onNodeWithTag(TestTags.BROWSER_NO_INJECT_BUTTON) - .performClick() + val target = InstrumentationRegistry.getArguments().getString("browserUrl") + ?.takeIf { it.isNotBlank() } ?: url + + // Modern path: gear -> Investigation -> dApp Browser -> type URL -> open (inject / no-inject). + composeTestRule.onNodeWithTag(TestTags.INVESTIGATION_BUTTON).performClick() + composeTestRule.waitUntil(ELEMENT_TIMEOUT) { + composeTestRule.onAllNodesWithTag(TestTags.INVESTIGATION_BROWSER_ROW).fetchSemanticsNodes().isNotEmpty() } + composeTestRule.onNodeWithTag(TestTags.INVESTIGATION_BROWSER_ROW).performClick() - composeTestRule.waitForIdle() + composeTestRule.waitUntil(ELEMENT_TIMEOUT) { + composeTestRule.onAllNodesWithTag(TestTags.BROWSER_URL_FIELD).fetchSemanticsNodes().isNotEmpty() + } + composeTestRule.onNodeWithTag(TestTags.BROWSER_URL_FIELD).performTextClearance() + composeTestRule.onNodeWithTag(TestTags.BROWSER_URL_FIELD).performTextInput(target) - // Wait for WebView to load + val buttonTag = if (injectTonConnect) TestTags.BROWSER_INJECT_BUTTON else TestTags.BROWSER_NO_INJECT_BUTTON + composeTestRule.onNodeWithTag(buttonTag).performClick() + composeTestRule.waitForIdle() - Log.d("JsDAppController", "Browser opened, waiting for WebView...") + Log.d("JsDAppController", "Browser opened ($target, inject=$injectTonConnect), waiting for WebView...") } /** diff --git a/AndroidDemo/app/src/androidTest/java/io/ton/walletkit/demo/e2e/wallet/WalletController.kt b/AndroidDemo/app/src/androidTest/java/io/ton/walletkit/demo/e2e/wallet/WalletController.kt index ace58129..7d65bc45 100644 --- a/AndroidDemo/app/src/androidTest/java/io/ton/walletkit/demo/e2e/wallet/WalletController.kt +++ b/AndroidDemo/app/src/androidTest/java/io/ton/walletkit/demo/e2e/wallet/WalletController.kt @@ -83,28 +83,11 @@ class WalletController(composeTestRule: ComposeTestRule? = null) { // =========================================== /** - * Check if we're on the legacy wallet home screen (the only screen the e2e suite knows how - * to drive). The legacy toolbar exposes [TestTags.BROWSER_NO_INJECT_BUTTON]; the modern - * [WalletScreen] does not. Returns true only when the legacy home is visible AND the - * [AddWalletSheet] is not in front of it. + * Whether the wallet home is visible. The home exposes [TestTags.WALLET_BALANCE]; returns true + * only when it is visible AND the [AddWalletSheet] is not in front of it. */ - fun isOnHomeScreen(): Boolean { - if (!isOnLegacyHome()) return false - return !isAddWalletSheetShowing() - } + fun isOnHomeScreen(): Boolean = isOnModernHome() && !isAddWalletSheetShowing() - private fun isOnLegacyHome(): Boolean = try { - composeTestRule.onNodeWithTag(TestTags.BROWSER_NO_INJECT_BUTTON).assertExists() - true - } catch (e: AssertionError) { - false - } - - /** - * The modern [WalletScreen] is the default for new installs. It carries no legacy testTags - * but exposes [TestTags.WALLET_BALANCE] on the balance area, which is also the 5-tap secret - * gesture target that toggles back to the legacy screen. - */ private fun isOnModernHome(): Boolean = try { composeTestRule.onNodeWithTag(TestTags.WALLET_BALANCE).assertExists() true @@ -293,8 +276,8 @@ class WalletController(composeTestRule: ComposeTestRule? = null) { enterMnemonicOnImportScreen(mnemonic) } isAddWalletSheetShowing() -> { - Log.d("WalletController", "Legacy AddWalletSheet visible - using paste-all field") - importWalletViaLegacySheet(mnemonic) + Log.d("WalletController", "AddWalletSheet visible - using paste-all field") + importWalletViaAddSheet(mnemonic) } else -> { Log.w("WalletController", "importWallet: no recognised import UI - aborting") @@ -302,15 +285,14 @@ class WalletController(composeTestRule: ComposeTestRule? = null) { } } - // Wait for wallet home (either modern or legacy) to appear after import succeeds. + // Wait for the wallet home to appear after import succeeds. composeTestRule.waitForIdle() try { - composeTestRule.waitUntil(15_000L) { isOnLegacyHome() || isOnModernHome() } + composeTestRule.waitUntil(15_000L) { isOnModernHome() } Log.d("WalletController", "Wallet loaded successfully in UI") } catch (e: Exception) { Log.e("WalletController", "Wallet home still not visible after timeout: ${e.message}") } - Log.d("WalletController", "After import - legacy: ${isOnLegacyHome()}, modern: ${isOnModernHome()}") } /** Modern import flow: paste the full phrase into the first tagged word field, tap Continue. */ @@ -336,8 +318,8 @@ class WalletController(composeTestRule: ComposeTestRule? = null) { composeTestRule.waitForIdle() } - /** Legacy AddWalletSheet flow (only reachable when the dev legacy-screen toggle is on). */ - private fun importWalletViaLegacySheet(mnemonic: List) { + /** AddWalletSheet flow: paste the full phrase into the sheet's paste-all field. */ + private fun importWalletViaAddSheet(mnemonic: List) { val mnemonicString = mnemonic.joinToString(" ") composeTestRule.waitUntil(5_000L) { @@ -406,35 +388,16 @@ class WalletController(composeTestRule: ComposeTestRule? = null) { Log.d("WalletController", "=== setupWallet called with ${mnemonic.size} word mnemonic ===") composeTestRule.waitForIdle() - // Check if we're already on the legacy home screen (wallet exists from a previous run - // and the dev toggle is already flipped). + // Already on the wallet home — wallet exists from a previous run. if (isOnHomeScreen()) { - Log.d("WalletController", "Already on legacy home screen - wallet exists, skipping setup") + Log.d("WalletController", "Already on home screen - wallet exists, skipping setup") return } - // Check if AddWalletSheet is already showing (password was set previously, dev toggle on) - if (isAddWalletSheetShowing()) { - Log.d("WalletController", "AddWalletSheet already showing - importing wallet directly") + // A pre-wallet surface is showing — import directly. + if (isAddWalletSheetShowing() || isOnOnboardingScreen() || isOnImportScreen()) { + Log.d("WalletController", "Pre-wallet surface visible - importing wallet") importWallet(mnemonic) - ensureLegacyScreen() - return - } - - // Modern first-run lands on the onboarding screen (or the import screen if the user - // already tapped through). Both are valid pre-wallet states. - if (isOnOnboardingScreen() || isOnImportScreen()) { - Log.d("WalletController", "Modern onboarding visible - importing wallet") - importWallet(mnemonic) - ensureLegacyScreen() - return - } - - // The post-import / post-unlock app lands on the modern WalletScreen by default. - // Tests target legacy UI, so toggle if needed. - if (isOnModernHome()) { - Log.d("WalletController", "On modern WalletScreen - wallet exists, toggling to legacy") - ensureLegacyScreen() return } @@ -450,86 +413,24 @@ class WalletController(composeTestRule: ComposeTestRule? = null) { for (i in 1..10) { composeTestRule.waitForIdle() - // If on legacy home screen, wallet already exists and toggle was on if (isOnHomeScreen()) { - Log.d("WalletController", "On legacy home after auth (check $i) - wallet exists, skipping import") + Log.d("WalletController", "On home after auth (check $i) - wallet exists, skipping import") return } - // If AddWalletSheet is showing, import wallet - if (isAddWalletSheetShowing()) { - Log.d("WalletController", "AddWalletSheet showing after auth (check $i) - importing wallet") + if (isAddWalletSheetShowing() || isOnOnboardingScreen() || isOnImportScreen()) { + Log.d("WalletController", "Import surface after auth (check $i) - importing wallet") importWallet(mnemonic) - ensureLegacyScreen() - return - } - - // Modern first-run onboarding (or already on the import screen). - if (isOnOnboardingScreen() || isOnImportScreen()) { - Log.d("WalletController", "Onboarding visible after auth (check $i) - importing wallet") - importWallet(mnemonic) - ensureLegacyScreen() - return - } - - // Default landing for an existing-wallet app: the modern WalletScreen with the balance - // tile visible. Switch to legacy so the rest of the suite can drive it. - if (isOnModernHome()) { - Log.d("WalletController", "Modern home after auth (check $i) - toggling to legacy") - ensureLegacyScreen() return } - Log.d("WalletController", "Waiting for home/addWallet screen (check $i)...") + Log.d("WalletController", "Waiting for home/import surface (check $i)...") Thread.sleep(200) // Small delay between checks } // Fallback: try to import wallet anyway (this may fail) Log.w("WalletController", "No expected screen detected after 10 checks, attempting import as fallback") importWallet(mnemonic) - ensureLegacyScreen() - } - - /** - * Make sure we end up on the legacy wallet home. The new auth flow drops the user on - * [WalletScreen] by default; e2e tests were written against [LegacyWalletScreen], which is - * reached by the dev-only 5-tap secret on the balance tile. No-op if already on legacy. - */ - @Step("Switch to legacy wallet screen") - fun ensureLegacyScreen() { - composeTestRule.waitForIdle() - if (isOnLegacyHome()) { - Log.d("WalletController", "ensureLegacyScreen: already on legacy home") - return - } - - // Wait for the modern home (with the WALLET_BALANCE tile) to be ready before tapping. - try { - composeTestRule.waitUntil(UI_IDLE_TIMEOUT) { isOnModernHome() } - } catch (e: Exception) { - Log.w("WalletController", "ensureLegacyScreen: timed out waiting for modern home: ${e.message}") - } - - if (!isOnModernHome()) { - Log.w("WalletController", "ensureLegacyScreen: modern home not visible, skipping toggle") - return - } - - // DevToggleTaps requires 5 taps inside a 3-second window. performClick runs on the test - // thread back-to-back so they always land inside that window. - Log.d("WalletController", "ensureLegacyScreen: performing 5 quick taps on WALLET_BALANCE") - repeat(5) { - composeTestRule.onNodeWithTag(TestTags.WALLET_BALANCE).performClick() - } - composeTestRule.waitForIdle() - - try { - composeTestRule.waitUntil(UI_IDLE_TIMEOUT) { isOnLegacyHome() } - Log.d("WalletController", "ensureLegacyScreen: legacy home is now visible") - } catch (e: Exception) { - Log.w("WalletController", "ensureLegacyScreen: legacy home did not appear: ${e.message}") - } - composeTestRule.waitForIdle() } // =========================================== @@ -544,43 +445,34 @@ class WalletController(composeTestRule: ComposeTestRule? = null) { */ @Step("Connect by TonConnect URL") fun connectByUrl(url: String) { - // Wait for wallet home screen with Handle URL button + // Modern path: gear -> Investigation -> TonConnect -> Connect to dApp -> URL dialog. + composeTestRule.onNodeWithTag(TestTags.INVESTIGATION_BUTTON).performClick() composeTestRule.waitUntil(5000) { - composeTestRule.onAllNodesWithTag(TestTags.HANDLE_URL_BUTTON) + composeTestRule.onAllNodesWithTag(TestTags.INVESTIGATION_TONCONNECT_ROW) .fetchSemanticsNodes().isNotEmpty() } + composeTestRule.onNodeWithTag(TestTags.INVESTIGATION_TONCONNECT_ROW).performClick() - // Click "Handle URL" button to open dialog - composeTestRule.onNodeWithTag(TestTags.HANDLE_URL_BUTTON) - .performClick() - - composeTestRule.waitForIdle() + composeTestRule.waitUntil(5000) { + composeTestRule.onAllNodesWithTag(TestTags.INVESTIGATION_CONNECT_ROW) + .fetchSemanticsNodes().isNotEmpty() + } + composeTestRule.onNodeWithTag(TestTags.INVESTIGATION_CONNECT_ROW).performClick() - // Wait for the URL input field in the dialog composeTestRule.waitUntil(3000) { composeTestRule.onAllNodesWithTag(TestTags.TONCONNECT_URL_FIELD) .fetchSemanticsNodes().isNotEmpty() } - - // Enter the TonConnect URL - composeTestRule.onNodeWithTag(TestTags.TONCONNECT_URL_FIELD) - .performTextInput(url) - - // Click the process button - composeTestRule.onNodeWithTag(TestTags.TONCONNECT_PROCESS_BUTTON) - .performClick() - + composeTestRule.onNodeWithTag(TestTags.TONCONNECT_URL_FIELD).performTextInput(url) + composeTestRule.onNodeWithTag(TestTags.TONCONNECT_PROCESS_BUTTON).performClick() composeTestRule.waitForIdle() } - /** - * Wait for the (legacy) wallet home screen to be visible. The e2e suite runs against the - * legacy screen, so [setupWallet] ensures we are toggled there before this is called. - */ + /** Wait for the wallet home screen (balance tile) to be visible. */ @Step("Wait for wallet home screen") fun waitForWalletHome() { composeTestRule.waitUntil(SHEET_APPEAR_TIMEOUT) { - composeTestRule.onAllNodesWithTag(TestTags.WALLET_ADDRESS) + composeTestRule.onAllNodesWithTag(TestTags.WALLET_BALANCE) .fetchSemanticsNodes().isNotEmpty() } } diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/MainActivity.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/MainActivity.kt index d282e280..0f1602c9 100644 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/MainActivity.kt +++ b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/MainActivity.kt @@ -45,9 +45,7 @@ import io.ton.walletkit.demo.designsystem.theme.TonTheme import io.ton.walletkit.demo.presentation.actions.WalletActionsImpl import io.ton.walletkit.demo.presentation.dev.DevPreferences import io.ton.walletkit.demo.presentation.state.CreateWalletFlow -import io.ton.walletkit.demo.presentation.state.SheetState import io.ton.walletkit.demo.presentation.ui.screen.CreatePinScreen -import io.ton.walletkit.demo.presentation.ui.screen.LegacyWalletScreen import io.ton.walletkit.demo.presentation.ui.screen.UnlockPinScreen import io.ton.walletkit.demo.presentation.ui.screen.WalletScreen import io.ton.walletkit.demo.presentation.ui.screen.onboarding.CreateWalletOnboardingScreen @@ -132,7 +130,6 @@ private fun AppNavigation( else -> { walletKit.value?.let { kit -> - val useLegacyMainScreen by DevPreferences.useLegacyMainScreen.collectAsState() val createFlow by viewModel.createWalletFlow.collectAsState() val walletsBootstrapped = state.walletsBootstrapped @@ -144,14 +141,10 @@ private fun AppNavigation( } } - LaunchedEffect(hasWallet, useLegacyMainScreen, walletsBootstrapped) { + LaunchedEffect(hasWallet, walletsBootstrapped) { if (!walletsBootstrapped) return@LaunchedEffect if (hasWallet) return@LaunchedEffect - if (useLegacyMainScreen) { - if (state.sheetState !is SheetState.AddWallet) { - viewModel.openAddWalletSheet() - } - } else if (createFlow is CreateWalletFlow.Idle) { + if (createFlow is CreateWalletFlow.Idle) { viewModel.showCreateWalletOnboarding() } } @@ -160,23 +153,13 @@ private fun AppNavigation( flow = createFlow, viewModel = viewModel, fallback = { - if (useLegacyMainScreen) { - LegacyWalletScreen( - state = state, - walletKit = kit, - nftsViewModel = nftsViewModel, - swapViewModel = swapViewModel, - actions = walletActions, - ) - } else { - WalletScreen( - state = state, - walletKit = kit, - nftsViewModel = nftsViewModel, - swapViewModel = swapViewModel, - actions = walletActions, - ) - } + WalletScreen( + state = state, + walletKit = kit, + nftsViewModel = nftsViewModel, + swapViewModel = swapViewModel, + actions = walletActions, + ) }, ) } diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/dev/DevPreferences.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/dev/DevPreferences.kt index 07fbc176..340ec8c1 100644 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/dev/DevPreferences.kt +++ b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/dev/DevPreferences.kt @@ -22,52 +22,17 @@ package io.ton.walletkit.demo.presentation.dev import android.content.Context -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow object DevPreferences { private const val PREFS_NAME = "dev_preferences" - private const val KEY_USE_LEGACY_MAIN_SCREEN = "use_legacy_main_screen" - private val _useLegacyMainScreen = MutableStateFlow(false) - val useLegacyMainScreen: StateFlow = _useLegacyMainScreen.asStateFlow() + fun ensureLoaded(context: Context) = Unit - @Volatile - private var loaded = false - - fun ensureLoaded(context: Context) { - if (loaded) return - synchronized(this) { - if (loaded) return - val prefs = context.applicationContext - .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - _useLegacyMainScreen.value = prefs.getBoolean(KEY_USE_LEGACY_MAIN_SCREEN, false) - loaded = true - } - } - - fun toggleLegacyMainScreen(context: Context): Boolean { - ensureLoaded(context) - val next = !_useLegacyMainScreen.value + fun reset(context: Context) { context.applicationContext .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) .edit() - .putBoolean(KEY_USE_LEGACY_MAIN_SCREEN, next) - .apply() - _useLegacyMainScreen.value = next - return next - } - - fun reset(context: Context) { - synchronized(this) { - context.applicationContext - .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - .edit() - .clear() - .commit() - _useLegacyMainScreen.value = false - loaded = false - } + .clear() + .commit() } } diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/dev/DevToggleTaps.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/dev/DevToggleTaps.kt deleted file mode 100644 index 00fc879a..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/dev/DevToggleTaps.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.dev - -import android.os.SystemClock -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.pointer.pointerInput - -fun Modifier.devToggleTaps( - requiredTaps: Int = 5, - windowMs: Long = 3_000L, - onTrigger: () -> Unit, -): Modifier = this.then( - Modifier.pointerInput(requiredTaps, windowMs) { - var count = 0 - var firstAt = 0L - detectTapGestures(onTap = { - val now = SystemClock.elapsedRealtime() - if (count == 0 || now - firstAt > windowMs) { - count = 1 - firstAt = now - } else { - count++ - } - if (count >= requiredTaps) { - count = 0 - onTrigger() - } - }) - }, -) diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/QuickActionsCard.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/QuickActionsCard.kt deleted file mode 100644 index 417b4459..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/QuickActionsCard.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.ui.components - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import io.ton.walletkit.demo.R -import io.ton.walletkit.demo.presentation.util.TestTags - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun QuickActionsCard( - onHandleUrl: () -> Unit, - onAddWallet: () -> Unit, - onRefresh: () -> Unit, - onSwap: () -> Unit = {}, -) { - ElevatedCard { - Column( - modifier = Modifier.padding(ACTION_CARD_PADDING), - verticalArrangement = Arrangement.spacedBy(ACTION_CARD_SPACING), - ) { - Text(stringResource(R.string.quick_actions_title), style = MaterialTheme.typography.titleMedium) - FlowRow(horizontalArrangement = Arrangement.spacedBy(ACTION_BUTTON_SPACING)) { - FilledTonalButton( - onClick = onHandleUrl, - modifier = Modifier.testTag(TestTags.HANDLE_URL_BUTTON), - ) { Text(stringResource(R.string.action_handle_url)) } - FilledTonalButton(onClick = onAddWallet) { Text(stringResource(R.string.action_add_wallet)) } - FilledTonalButton(onClick = onRefresh) { Text(stringResource(R.string.action_refresh)) } - FilledTonalButton(onClick = onSwap) { Text("Swap") } - } - } - } -} - -private val ACTION_CARD_PADDING = 20.dp -private val ACTION_CARD_SPACING = 16.dp -private val ACTION_BUTTON_SPACING = 12.dp - -@Preview(showBackground = true) -@Composable -private fun QuickActionsCardPreview() { - QuickActionsCard(onHandleUrl = {}, onAddWallet = {}, onRefresh = {}) -} diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/StatusHeader.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/StatusHeader.kt deleted file mode 100644 index d13e3385..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/StatusHeader.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.ui.components - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import io.ton.walletkit.demo.R -import io.ton.walletkit.demo.presentation.state.WalletUiState -import io.ton.walletkit.demo.presentation.ui.preview.PreviewData - -@Composable -fun StatusHeader(state: WalletUiState) { - val clipboardManager = LocalClipboardManager.current - - // Auto-copy to clipboard when clipboardContent is set - LaunchedEffect(state.clipboardContent) { - state.clipboardContent?.let { content -> - clipboardManager.setText(AnnotatedString(content)) - } - } - - Column(verticalArrangement = Arrangement.spacedBy(STATUS_HEADER_SPACING)) { - Text( - text = state.status.ifBlank { "" }, - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold, - ) - if (state.lastUpdated != null) { - Text( - text = stringResource(R.string.status_updated_now), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } -} - -private val STATUS_HEADER_SPACING = 8.dp - -@Preview(showBackground = true) -@Composable -private fun StatusHeaderPreview() { - StatusHeader(state = PreviewData.uiState) -} diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/WalletCard.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/WalletCard.kt deleted file mode 100644 index 5a0c4d66..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/WalletCard.kt +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.ui.components - -import android.content.ClipData -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.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Send -import androidx.compose.material.icons.filled.Refresh -import androidx.compose.material3.Button -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -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.platform.ClipEntry -import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import io.ton.walletkit.demo.R -import io.ton.walletkit.demo.presentation.model.WalletSummary -import io.ton.walletkit.demo.presentation.ui.icons.ContentCopy -import io.ton.walletkit.demo.presentation.ui.preview.PreviewData -import io.ton.walletkit.demo.presentation.util.TestTags -import io.ton.walletkit.demo.presentation.util.abbreviated -import kotlinx.coroutines.launch - -@Composable -fun WalletCard( - wallet: WalletSummary, - onDetails: () -> Unit, - onSend: () -> Unit = {}, - onStake: () -> Unit = {}, - isStreamingConnected: Boolean? = null, - onRefresh: () -> Unit = {}, -) { - val clipboard = LocalClipboard.current - val coroutineScope = rememberCoroutineScope() - ElevatedCard { - Column( - modifier = Modifier.padding(WALLET_CARD_PADDING), - verticalArrangement = Arrangement.spacedBy(WALLET_CARD_SECTION_SPACING), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth(), - ) { - Column(verticalArrangement = Arrangement.spacedBy(WALLET_CARD_LABEL_SPACING)) { - Text(wallet.name, style = MaterialTheme.typography.titleMedium) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(STREAMING_DOT_SPACING), - ) { - NetworkBadge(wallet.network) - if (isStreamingConnected != null) { - Box( - modifier = Modifier - .size(STREAMING_DOT_SIZE) - .background( - color = if (isStreamingConnected) STREAMING_DOT_CONNECTED else STREAMING_DOT_DISCONNECTED, - shape = CircleShape, - ), - ) - } - } - } - TextButton(onClick = onDetails) { Text(stringResource(R.string.action_details)) } - } - - Column(verticalArrangement = Arrangement.spacedBy(WALLET_CARD_CONTENT_SPACING)) { - Text( - stringResource(R.string.label_wallet_address), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = wallet.address.abbreviated(), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.weight(1f).testTag(TestTags.WALLET_ADDRESS), - ) - IconButton( - onClick = { - coroutineScope.launch { - clipboard.setClipEntry( - ClipEntry(ClipData.newPlainText(CLIPBOARD_WALLET_ADDRESS_LABEL, wallet.address)), - ) - } - }, - ) { - Icon(Icons.Default.ContentCopy, contentDescription = stringResource(R.string.action_copy_address)) - } - } - } - - Column(verticalArrangement = Arrangement.spacedBy(WALLET_CARD_LABEL_SPACING)) { - Text( - stringResource(R.string.label_balance), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth(), - ) { - val balanceText = wallet.balance ?: stringResource(R.string.wallet_balance_placeholder) - Text(balanceText, style = MaterialTheme.typography.headlineSmall) - IconButton(onClick = onRefresh) { - Icon( - Icons.Default.Refresh, - contentDescription = stringResource(R.string.action_refresh), - ) - } - } - } - - Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(WALLET_CARD_BUTTON_SPACING), - ) { - Button( - onClick = onSend, - modifier = Modifier - .weight(1f) - .testTag(TestTags.WALLET_SEND_BUTTON), - ) { - Icon( - Icons.AutoMirrored.Filled.Send, - contentDescription = null, - modifier = Modifier.padding(end = SEND_ICON_PADDING), - ) - Text(stringResource(R.string.action_send)) - } - OutlinedButton( - onClick = onStake, - modifier = Modifier - .weight(1f) - .testTag(TestTags.WALLET_STAKE_BUTTON), - ) { - Text(stringResource(R.string.action_stake)) - } - } - } - } - } -} - -private val WALLET_CARD_PADDING = 20.dp -private val WALLET_CARD_SECTION_SPACING = 12.dp -private val WALLET_CARD_CONTENT_SPACING = 8.dp -private val WALLET_CARD_LABEL_SPACING = 4.dp -private val WALLET_CARD_BUTTON_SPACING = 8.dp -private val SEND_ICON_PADDING = 4.dp -private val STREAMING_DOT_SIZE = 8.dp -private val STREAMING_DOT_SPACING = 6.dp -private val STREAMING_DOT_CONNECTED = Color(0xFF4CAF50) -private val STREAMING_DOT_DISCONNECTED = Color(0xFFBDBDBD) -private const val CLIPBOARD_WALLET_ADDRESS_LABEL = "wallet_address" - -@Preview(showBackground = true) -@Composable -private fun WalletCardPreview() { - WalletCard(wallet = PreviewData.wallet, onDetails = {}, onSend = {}, onStake = {}) -} diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/wallet/home/WalletHomeBalance.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/wallet/home/WalletHomeBalance.kt index 050422d7..858493c0 100644 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/wallet/home/WalletHomeBalance.kt +++ b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/wallet/home/WalletHomeBalance.kt @@ -34,7 +34,6 @@ import io.ton.walletkit.demo.designsystem.components.text.TonText import io.ton.walletkit.demo.designsystem.icons.TonIcon import io.ton.walletkit.demo.designsystem.icons.TonIconImage import io.ton.walletkit.demo.designsystem.theme.TonTheme -import io.ton.walletkit.demo.presentation.dev.devToggleTaps import io.ton.walletkit.demo.presentation.util.TestTags @Composable @@ -45,7 +44,6 @@ fun WalletHomeBalance( truncatedAddress: String, onCopyAddress: () -> Unit, modifier: Modifier = Modifier, - onSecretTap: (() -> Unit)? = null, ) { val animated = rememberCountUp(totalBalance) val formatted = formatCountUp(animated, maxFractionDigits) @@ -53,14 +51,8 @@ fun WalletHomeBalance( val integerPart = if (dotIndex < 0) formatted else formatted.substring(0, dotIndex) val fractionPart = if (dotIndex < 0) "" else formatted.substring(dotIndex) - val gestureModifier = if (onSecretTap != null) { - Modifier.devToggleTaps(onTrigger = onSecretTap) - } else { - Modifier - } Column( modifier = modifier - .then(gestureModifier) .testTag(TestTags.WALLET_BALANCE), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp), diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/wallet/home/WalletHomeContent.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/wallet/home/WalletHomeContent.kt index 2fa9886b..2206b001 100644 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/wallet/home/WalletHomeContent.kt +++ b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/components/wallet/home/WalletHomeContent.kt @@ -57,7 +57,6 @@ fun WalletHomeContent( onShowAllNFTs: () -> Unit, onNFTTap: (WalletHomeNFTPreview) -> Unit, modifier: Modifier = Modifier, - onBalanceSecretTap: (() -> Unit)? = null, ) { val scrollState = rememberScrollState() @@ -77,7 +76,6 @@ fun WalletHomeContent( .fillMaxWidth() .padding(horizontal = 16.dp) .padding(top = 8.dp, bottom = 8.dp), - onSecretTap = onBalanceSecretTap, ) WalletHomeActionsRow( diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/LegacyWalletScreen.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/LegacyWalletScreen.kt deleted file mode 100644 index 16a12bfc..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/LegacyWalletScreen.kt +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.ui.screen - -import android.widget.Toast -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -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.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material3.Card -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SheetValue -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import io.ton.walletkit.ITONWalletKit -import io.ton.walletkit.api.generated.TONNFT -import io.ton.walletkit.demo.R -import io.ton.walletkit.demo.designsystem.theme.TonTheme -import io.ton.walletkit.demo.presentation.actions.WalletActions -import io.ton.walletkit.demo.presentation.dev.DevPreferences -import io.ton.walletkit.demo.presentation.dev.devToggleTaps -import io.ton.walletkit.demo.presentation.model.NFTDetails -import io.ton.walletkit.demo.presentation.state.SheetState -import io.ton.walletkit.demo.presentation.state.WalletUiState -import io.ton.walletkit.demo.presentation.ui.components.QuickActionsCard -import io.ton.walletkit.demo.presentation.ui.components.StatusHeader -import io.ton.walletkit.demo.presentation.ui.components.WalletSwitcher -import io.ton.walletkit.demo.presentation.ui.dialog.SignerConfirmationDialog -import io.ton.walletkit.demo.presentation.ui.dialog.UrlPromptDialog -import io.ton.walletkit.demo.presentation.ui.icons.Language -import io.ton.walletkit.demo.presentation.ui.sections.EventLogSection -import io.ton.walletkit.demo.presentation.ui.sections.JettonsSection -import io.ton.walletkit.demo.presentation.ui.sections.MasterchainInfoSection -import io.ton.walletkit.demo.presentation.ui.sections.NFTsSection -import io.ton.walletkit.demo.presentation.ui.sections.SessionsSection -import io.ton.walletkit.demo.presentation.ui.sections.WalletsSection -import io.ton.walletkit.demo.presentation.ui.sheet.AddWalletSheet -import io.ton.walletkit.demo.presentation.ui.sheet.BrowserSession -import io.ton.walletkit.demo.presentation.ui.sheet.BrowserSheet -import io.ton.walletkit.demo.presentation.ui.sheet.ConnectRequestSheet -import io.ton.walletkit.demo.presentation.ui.sheet.JettonDetailsSheet -import io.ton.walletkit.demo.presentation.ui.sheet.SignDataSheet -import io.ton.walletkit.demo.presentation.ui.sheet.SignMessageRequestSheet -import io.ton.walletkit.demo.presentation.ui.sheet.StakingSheet -import io.ton.walletkit.demo.presentation.ui.sheet.SwapSheet -import io.ton.walletkit.demo.presentation.ui.sheet.TransactionDetailSheet -import io.ton.walletkit.demo.presentation.ui.sheet.TransactionRequestSheet -import io.ton.walletkit.demo.presentation.ui.sheet.TransferJettonSheet -import io.ton.walletkit.demo.presentation.ui.sheet.WalletDetailsSheet -import io.ton.walletkit.demo.presentation.util.TestTags -import io.ton.walletkit.demo.presentation.viewmodel.NFTsListViewModel -import io.ton.walletkit.demo.presentation.viewmodel.SwapViewModel - -// Pre-redesign main screen, kept behind a dev toggle. 5 taps on the title bar -// flips back to the new home screen. -private const val DEFAULT_DAPP_URL = "https://allure-test-runner.vercel.app/e2e" - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun LegacyWalletScreen( - state: WalletUiState, - walletKit: ITONWalletKit, - nftsViewModel: NFTsListViewModel?, - swapViewModel: SwapViewModel?, - actions: WalletActions, -) { - val scrollState = rememberScrollState() - val snackbarHostState = remember { SnackbarHostState() } - val context = LocalContext.current - - val onRefreshAll: () -> Unit = { - actions.onRefresh() - actions.onRefreshJettons() - nftsViewModel?.refresh() - } - - var selectedNFT by remember { mutableStateOf(null) } - val nftDetailsSheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true, - confirmValueChange = { it != SheetValue.Hidden }, - ) - - val injectedSession = remember { BrowserSession(injectTonConnect = true) } - val plainSession = remember { BrowserSession(injectTonConnect = false) } - - DisposableEffect(Unit) { - onDispose { - injectedSession.destroyAllWebViews() - plainSession.destroyAllWebViews() - } - } - - LaunchedEffect(state.error) { - val error = state.error ?: return@LaunchedEffect - snackbarHostState.showSnackbar(error) - } - - val sheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true, - confirmValueChange = { it != SheetValue.Hidden }, - ) - val sheet = state.sheetState - val showSheet = sheet !is SheetState.None - LaunchedEffect(state.sheetState) { - if (state.sheetState is SheetState.None && sheetState.isVisible) { - sheetState.hide() - } - val browserSheet = state.sheetState as? SheetState.Browser ?: return@LaunchedEffect - val session = if (browserSheet.injectTonConnect) injectedSession else plainSession - if (session.tabs.isEmpty()) session.openTab(browserSheet.url) - } - val activeWallet = state.wallets.firstOrNull { it.walletId == state.activeWalletId } - ?: state.wallets.firstOrNull() - if (showSheet) { - ModalBottomSheet( - onDismissRequest = actions::onDismissSheet, - sheetState = sheetState, - containerColor = TonTheme.colors.bgPrimary, - dragHandle = null, - ) { - when (sheet) { - SheetState.AddWallet -> AddWalletSheet( - onDismiss = actions::onDismissSheet, - onImportWallet = actions::onImportWallet, - onGenerateWallet = actions::onGenerateWallet, - walletCount = state.wallets.size, - ) - - is SheetState.Connect -> ConnectRequestSheet( - request = sheet.request, - wallets = state.wallets, - onApprove = actions::onApproveConnect, - onReject = actions::onRejectConnect, - ) - - is SheetState.Transaction -> TransactionRequestSheet( - request = sheet.request, - onApprove = { actions.onApproveTransaction(sheet.request) }, - onReject = { actions.onRejectTransaction(sheet.request) }, - wallet = state.wallets.firstOrNull { it.address == sheet.request.walletAddress } - ?: activeWallet, - ) - - is SheetState.SignData -> SignDataSheet( - request = sheet.request, - onApprove = { actions.onApproveSignData(sheet.request) }, - onReject = { actions.onRejectSignData(sheet.request) }, - wallet = state.wallets.firstOrNull { it.address == sheet.request.walletAddress } - ?: activeWallet, - ) - - is SheetState.SignMessage -> SignMessageRequestSheet( - request = sheet.request, - onApprove = { actions.onApproveSignMessage(sheet.request) }, - onReject = { actions.onRejectSignMessage(sheet.request) }, - wallet = state.wallets.firstOrNull { it.address == sheet.request.walletAddress } - ?: activeWallet, - ) - - is SheetState.WalletDetails -> WalletDetailsSheet( - wallet = sheet.wallet, - onDismiss = actions::onDismissSheet, - ) - - is SheetState.SendTransaction -> SendTransactionScreen( - wallet = sheet.wallet, - walletKit = walletKit, - onBack = actions::onDismissSheet, - ) - - is SheetState.Staking -> StakingSheet( - wallet = sheet.wallet, - walletKit = walletKit, - sheetKey = sheet.openedAt, - onDismiss = actions::onDismissSheet, - ) - - is SheetState.TransactionDetail -> TransactionDetailSheet( - transaction = sheet.transaction, - onDismiss = actions::onDismissSheet, - ) - - is SheetState.Browser -> BrowserSheet( - session = if (sheet.injectTonConnect) injectedSession else plainSession, - onClose = actions::onDismissSheet, - walletKit = walletKit, - ) - - is SheetState.JettonDetails -> { - JettonDetailsSheet( - jetton = sheet.jetton, - onDismiss = actions::onDismissSheet, - onTransfer = { actions.onShowTransferJetton(sheet.jetton) }, - ) - } - - is SheetState.TransferJetton -> { - TransferJettonSheet( - jetton = sheet.jetton, - onDismiss = actions::onDismissSheet, - onTransfer = { recipient, amount, comment -> - actions.onTransferJetton(sheet.jetton.jettonAddress ?: "", recipient, amount, comment) - }, - isLoading = false, - ) - } - - is SheetState.Swap -> { - swapViewModel?.let { vm -> - SwapSheet(viewModel = vm, onDismiss = actions::onDismissSheet) - } - } - - SheetState.None -> Unit - } - } - } - - if (state.isUrlPromptVisible) { - UrlPromptDialog( - onDismiss = actions::onDismissUrlPrompt, - onConfirm = actions::onHandleUrl, - ) - } - - state.pendingSignerConfirmation?.let { request -> - SignerConfirmationDialog( - request = request, - onConfirm = actions::onConfirmSignerApproval, - onCancel = actions::onCancelSignerApproval, - ) - } - - Scaffold( - topBar = { - TopAppBar( - title = { - Text( - text = stringResource(R.string.wallet_screen_title), - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold, - modifier = Modifier.devToggleTaps { - val nowLegacy = DevPreferences.toggleLegacyMainScreen(context) - Toast.makeText( - context, - if (nowLegacy) "Legacy main screen ON" else "Legacy main screen OFF", - Toast.LENGTH_SHORT, - ).show() - }, - ) - }, - actions = { - IconButton(onClick = { actions.onOpenBrowser(DEFAULT_DAPP_URL) }) { - Icon(painterResource(R.drawable.ic_ton), contentDescription = "Open TonConnect Browser") - } - IconButton( - onClick = { actions.onOpenBrowser(DEFAULT_DAPP_URL, injectTonConnect = false) }, - modifier = Modifier.testTag(TestTags.BROWSER_NO_INJECT_BUTTON), - ) { - Icon(Icons.Default.Language, contentDescription = "Open Plain Browser") - } - }, - ) - }, - snackbarHost = { SnackbarHost(snackbarHostState) }, - ) { padding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .verticalScroll(scrollState) - .padding(horizontal = 20.dp, vertical = 16.dp), - verticalArrangement = Arrangement.spacedBy(24.dp), - ) { - Box(modifier = Modifier.fillMaxWidth().height(4.dp)) { - if (state.isLoadingWallets || state.isLoadingSessions) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) - } - } - StatusHeader(state) - - QuickActionsCard( - onHandleUrl = actions::onUrlPromptClick, - onAddWallet = actions::onAddWalletClick, - onRefresh = onRefreshAll, - onSwap = actions::onSwapClick, - ) - - if (state.wallets.size > 1) { - WalletSwitcher( - wallets = state.wallets, - activeWalletId = state.activeWalletId, - isExpanded = state.isWalletSwitcherExpanded, - onToggle = actions::onToggleWalletSwitcher, - onSwitchWallet = actions::onSwitchWallet, - onRemoveWallet = actions::onRemoveWallet, - onRenameWallet = actions::onRenameWallet, - ) - } - - WalletsSection( - activeWallet = activeWallet, - totalWallets = state.wallets.size, - onWalletSelected = actions::onWalletDetails, - onSendFromWallet = actions::onSendFromWallet, - onStakeFromWallet = actions::onStakeFromWallet, - isStreamingConnected = state.isStreamingConnected, - onRefresh = actions::onRefresh, - ) - - if (nftsViewModel != null) { - NFTsSection( - viewModel = nftsViewModel, - onNFTClick = { nft -> selectedNFT = nft }, - ) - } - - JettonsSection( - jettons = state.jettons, - isLoading = state.isLoadingJettons, - error = state.jettonsError, - canLoadMore = state.canLoadMoreJettons, - onJettonClick = actions::onShowJettonDetails, - onLoadMore = actions::onLoadMoreJettons, - onRefresh = actions::onRefreshJettons, - ) - - if (activeWallet != null) { - MasterchainInfoSection(network = activeWallet.network) - } - - Card( - modifier = Modifier.fillMaxWidth(), - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text( - text = "Transaction History", - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "Coming Soon", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - - SessionsSection( - sessions = state.sessions, - onDisconnect = actions::onDisconnectSession, - ) - - if (state.events.isNotEmpty()) { - EventLogSection(events = state.events) - } - - Spacer(modifier = Modifier.height(48.dp)) - } - } - - selectedNFT?.let { nft -> - ModalBottomSheet( - onDismissRequest = { selectedNFT = null }, - sheetState = nftDetailsSheetState, - containerColor = TonTheme.colors.bgPrimary, - dragHandle = null, - ) { - activeWallet?.let { wallet -> - val nftDetails = NFTDetails.from(nft) - NFTDetailsScreen( - walletId = wallet.walletId, - walletKit = walletKit, - nftDetails = nftDetails, - onClose = { selectedNFT = null }, - onTransferSuccess = { - val nftAddress = selectedNFT?.address?.value - nftAddress?.let { addr -> nftsViewModel?.removeNft(addr) } - nftsViewModel?.refreshWithDelay() - }, - ) - } - } - } -} diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/WalletKitInvestigationScreen.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/WalletKitInvestigationScreen.kt index 15f119f1..59dfac7a 100644 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/WalletKitInvestigationScreen.kt +++ b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/WalletKitInvestigationScreen.kt @@ -19,13 +19,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -@file:SuppressLint("SetJavaScriptEnabled") - package io.ton.walletkit.demo.presentation.ui.screen -import android.annotation.SuppressLint -import android.view.ViewGroup -import android.webkit.WebView import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.compose.foundation.background @@ -35,6 +30,9 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -43,21 +41,30 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView import io.ton.walletkit.ITONWalletKit +import io.ton.walletkit.api.ChainIds +import io.ton.walletkit.api.MAINNET +import io.ton.walletkit.api.TESTNET +import io.ton.walletkit.api.TETRA +import io.ton.walletkit.api.WalletVersions +import io.ton.walletkit.api.generated.TONNetwork import io.ton.walletkit.demo.R +import io.ton.walletkit.demo.designsystem.components.button.TonButton +import io.ton.walletkit.demo.designsystem.components.button.TonButtonConfig +import io.ton.walletkit.demo.designsystem.components.segmentedcontrol.TonSegmentedControl import io.ton.walletkit.demo.designsystem.components.text.TonText import io.ton.walletkit.demo.designsystem.theme.SmoothCornerShape import io.ton.walletkit.demo.designsystem.theme.TonTheme +import io.ton.walletkit.demo.domain.model.WalletInterfaceType import io.ton.walletkit.demo.presentation.ui.dialog.UrlPromptDialog -import io.ton.walletkit.demo.presentation.ui.screen.iframesec.RealBridgeIframeCase import io.ton.walletkit.demo.presentation.ui.screen.iframesec.WalletKitRealBridgeIframeScreen import io.ton.walletkit.demo.presentation.util.QrScanner -import io.ton.walletkit.extensions.injectTonConnect +import io.ton.walletkit.demo.presentation.util.TestTags -private enum class InvestigationPage { Tonconnect, DappWebView, RealBridge } +private enum class InvestigationPage { Tonconnect, Browser, AddWallet, RealBridge } /** * Developer "Wallet Kit Investigation" screen: a list of debug tools reached from the wallet-home @@ -68,6 +75,9 @@ private enum class InvestigationPage { Tonconnect, DappWebView, RealBridge } fun WalletKitInvestigationScreen( onBack: () -> Unit, onConnect: (String) -> Unit, + onOpenBrowser: (String, Boolean) -> Unit, + onImportWallet: (String, TONNetwork, List, String, String, WalletInterfaceType) -> Unit, + onGenerateWallet: (String, TONNetwork, String, WalletInterfaceType) -> Unit, walletKit: ITONWalletKit, modifier: Modifier = Modifier, ) { @@ -79,9 +89,25 @@ fun WalletKitInvestigationScreen( WalletKitTonconnectScreen(onBack = { page = null }, onConnect = onConnect, modifier = modifier) return } - InvestigationPage.DappWebView -> { + InvestigationPage.Browser -> { BackHandler { page = null } - DappWebViewScreen(onBack = { page = null }, walletKit = walletKit, modifier = modifier) + BrowserLauncherScreen(onBack = { page = null }, onOpenBrowser = onOpenBrowser, modifier = modifier) + return + } + InvestigationPage.AddWallet -> { + BackHandler { page = null } + TestAddWalletScreen( + onBack = { page = null }, + onImport = { name, network, mnemonic, secretKey, version, interfaceType -> + onImportWallet(name, network, mnemonic, secretKey, version, interfaceType) + page = null + }, + onGenerate = { name, network, version, interfaceType -> + onGenerateWallet(name, network, version, interfaceType) + page = null + }, + modifier = modifier, + ) return } InvestigationPage.RealBridge -> { @@ -107,8 +133,18 @@ fun WalletKitInvestigationScreen( InvestigationRow( title = stringResource(R.string.investigation_tonconnect), onClick = { page = InvestigationPage.Tonconnect }, + modifier = Modifier.testTag(TestTags.INVESTIGATION_TONCONNECT_ROW), + ) + InvestigationRow( + title = "dApp Browser", + onClick = { page = InvestigationPage.Browser }, + modifier = Modifier.testTag(TestTags.INVESTIGATION_BROWSER_ROW), + ) + InvestigationRow( + title = "Add Wallet", + onClick = { page = InvestigationPage.AddWallet }, + modifier = Modifier.testTag(TestTags.INVESTIGATION_ADD_WALLET_ROW), ) - InvestigationRow(title = "dApp WebView", onClick = { page = InvestigationPage.DappWebView }) InvestigationRow( title = "Iframe Security — Real dApp Bridge", onClick = { page = InvestigationPage.RealBridge }, @@ -142,6 +178,7 @@ private fun WalletKitTonconnectScreen( InvestigationRow( title = stringResource(R.string.investigation_connect_to_dapp), onClick = { showPrompt = true }, + modifier = Modifier.testTag(TestTags.INVESTIGATION_CONNECT_ROW), ) InvestigationRow( title = stringResource(R.string.investigation_scan_qr), @@ -169,45 +206,179 @@ private fun WalletKitTonconnectScreen( } } -/** Minimal dApp WebView with the real WalletKit injection — mirrors the iOS investigation entry. */ +private const val DEFAULT_DAPP_URL = "https://tonconnect-demo-dapp-with-react-ui.vercel.app/" + @Composable -private fun DappWebViewScreen( +private fun BrowserLauncherScreen( onBack: () -> Unit, - walletKit: ITONWalletKit, + onOpenBrowser: (String, Boolean) -> Unit, modifier: Modifier = Modifier, ) { + var url by remember { mutableStateOf(DEFAULT_DAPP_URL) } Column( modifier = modifier .fillMaxSize() .background(TonTheme.colors.bgSecondary), ) { - SubScreenTopBar(title = "dApp WebView", onBack = onBack) - AndroidView( - modifier = Modifier.fillMaxSize(), - factory = { ctx -> - WebView(ctx).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - ) - settings.javaScriptEnabled = true - settings.domStorageEnabled = true - injectTonConnect(walletKit) - loadUrl(RealBridgeIframeCase.DAPP_URL) - } - }, - ) + SubScreenTopBar(title = "dApp Browser", onBack = onBack) + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + TextField( + value = url, + onValueChange = { url = it }, + singleLine = true, + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.BROWSER_URL_FIELD), + ) + InvestigationRow( + title = "Open (injected)", + onClick = { onOpenBrowser(url, true) }, + modifier = Modifier.testTag(TestTags.BROWSER_INJECT_BUTTON), + ) + InvestigationRow( + title = "Open (no-inject)", + onClick = { onOpenBrowser(url, false) }, + modifier = Modifier.testTag(TestTags.BROWSER_NO_INJECT_BUTTON), + ) + } + } +} + +@Composable +private fun TestAddWalletScreen( + onBack: () -> Unit, + onImport: (String, TONNetwork, List, String, String, WalletInterfaceType) -> Unit, + onGenerate: (String, TONNetwork, String, WalletInterfaceType) -> Unit, + modifier: Modifier = Modifier, +) { + var name by remember { mutableStateOf("Test wallet") } + var network by remember { mutableStateOf(TONNetwork.MAINNET) } + var version by remember { mutableStateOf(WalletVersions.V5R1) } + var interfaceType by remember { mutableStateOf(WalletInterfaceType.MNEMONIC) } + var mnemonic by remember { mutableStateOf("") } + var secretKey by remember { mutableStateOf("") } + + Column( + modifier = modifier + .fillMaxSize() + .background(TonTheme.colors.bgSecondary), + ) { + SubScreenTopBar(title = "Add Wallet", onBack = onBack) + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + AddWalletFieldLabel("Name") + TextField( + value = name, + onValueChange = { name = it }, + singleLine = true, + modifier = Modifier.fillMaxWidth().testTag(TestTags.ADD_WALLET_NAME_FIELD), + ) + + AddWalletFieldLabel("Network") + TonSegmentedControl( + selection = network, + items = listOf(TONNetwork.MAINNET, TONNetwork.TESTNET, TONNetwork.TETRA), + title = ::networkLabel, + onSelect = { network = it }, + modifier = Modifier.fillMaxWidth(), + ) + + AddWalletFieldLabel("Version") + TonSegmentedControl( + selection = version, + items = listOf(WalletVersions.V5R1, WalletVersions.V4R2), + title = { it }, + onSelect = { version = it }, + modifier = Modifier.fillMaxWidth(), + ) + + AddWalletFieldLabel("Interface") + TonSegmentedControl( + selection = interfaceType, + items = WalletInterfaceType.entries, + title = ::interfaceLabel, + onSelect = { interfaceType = it }, + modifier = Modifier.fillMaxWidth(), + ) + + if (interfaceType == WalletInterfaceType.SECRET_KEY) { + AddWalletFieldLabel("Secret key (hex)") + TextField( + value = secretKey, + onValueChange = { secretKey = it.trim() }, + singleLine = true, + modifier = Modifier.fillMaxWidth().testTag(TestTags.ADD_WALLET_SECRET_KEY_FIELD), + ) + } else { + AddWalletFieldLabel("Recovery phrase") + TextField( + value = mnemonic, + onValueChange = { mnemonic = it }, + minLines = 2, + modifier = Modifier.fillMaxWidth().testTag(TestTags.ADD_WALLET_MNEMONIC_FIELD), + ) + } + + TonButton( + text = "Import", + onClick = { + val words = mnemonic.trim().lowercase().split(Regex("\\s+")).filter { it.isNotBlank() } + onImport(name, network, words, secretKey, version, interfaceType) + }, + config = TonButtonConfig.Primary, + modifier = Modifier.fillMaxWidth().testTag(TestTags.ADD_WALLET_IMPORT_BUTTON), + ) + TonButton( + text = "Generate", + onClick = { onGenerate(name, network, version, interfaceType) }, + config = TonButtonConfig.Secondary, + enabled = interfaceType != WalletInterfaceType.SECRET_KEY, + modifier = Modifier.fillMaxWidth().testTag(TestTags.ADD_WALLET_GENERATE_BUTTON), + ) + } } } @Composable -private fun InvestigationRow(title: String, onClick: () -> Unit) { +private fun AddWalletFieldLabel(text: String) { + TonText( + text = text, + style = TonTheme.typography.subheadline2, + color = TonTheme.colors.textSecondary, + ) +} + +private fun networkLabel(network: TONNetwork): String = when (network.chainId) { + ChainIds.MAINNET -> "Mainnet" + ChainIds.TESTNET -> "Testnet" + ChainIds.TETRA -> "Tetra" + else -> "Unknown" +} + +private fun interfaceLabel(type: WalletInterfaceType): String = when (type) { + WalletInterfaceType.MNEMONIC -> "Mnemonic" + WalletInterfaceType.SECRET_KEY -> "Secret Key" + WalletInterfaceType.SIGNER -> "Signer" +} + +@Composable +private fun InvestigationRow(title: String, onClick: () -> Unit, modifier: Modifier = Modifier) { val shape = SmoothCornerShape(12.dp) TonText( text = title, style = TonTheme.typography.body, color = TonTheme.colors.textPrimary, - modifier = Modifier + modifier = modifier .fillMaxWidth() .clip(shape) .background(TonTheme.colors.bgPrimary) diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/WalletScreen.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/WalletScreen.kt index dc90d2b1..24bd4680 100644 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/WalletScreen.kt +++ b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/screen/WalletScreen.kt @@ -88,7 +88,6 @@ import io.ton.walletkit.demo.designsystem.icons.TonIconImage import io.ton.walletkit.demo.designsystem.theme.TonTheme import io.ton.walletkit.demo.domain.model.WalletInterfaceType import io.ton.walletkit.demo.presentation.actions.WalletActions -import io.ton.walletkit.demo.presentation.dev.DevPreferences import io.ton.walletkit.demo.presentation.model.ConnectRequestUi import io.ton.walletkit.demo.presentation.model.JettonDetails import io.ton.walletkit.demo.presentation.model.JettonSummary @@ -98,7 +97,6 @@ import io.ton.walletkit.demo.presentation.model.TransactionRequestUi import io.ton.walletkit.demo.presentation.model.WalletSummary import io.ton.walletkit.demo.presentation.state.SheetState import io.ton.walletkit.demo.presentation.state.WalletUiState -import io.ton.walletkit.demo.presentation.ui.components.QuickActionsCard import io.ton.walletkit.demo.presentation.ui.components.wallet.home.WalletHomeAssetIcon import io.ton.walletkit.demo.presentation.ui.components.wallet.home.WalletHomeAssetItem import io.ton.walletkit.demo.presentation.ui.components.wallet.home.WalletHomeContent @@ -121,13 +119,10 @@ import io.ton.walletkit.demo.presentation.ui.sheet.WalletDetailsSheet import io.ton.walletkit.demo.presentation.ui.sheet.WalletsBottomSheet import io.ton.walletkit.demo.presentation.util.JettonFormatters import io.ton.walletkit.demo.presentation.util.QrScanner +import io.ton.walletkit.demo.presentation.util.TestTags import io.ton.walletkit.demo.presentation.viewmodel.NFTsListViewModel import io.ton.walletkit.demo.presentation.viewmodel.SwapViewModel -// URL for the TonConnect E2E test runner dApp -// This is the same dApp used by web demo-wallet E2E tests -private const val DEFAULT_DAPP_URL = "https://allure-test-runner.vercel.app/e2e" - private const val MAX_ASSETS = 3 private const val MAX_NFTS = 5 private const val MAX_FRACTION_DIGITS = 5 @@ -451,7 +446,22 @@ fun WalletScreen( HomeSubScreen.Investigation -> { WalletKitInvestigationScreen( onBack = { subScreen = HomeSubScreen.None }, - onConnect = actions::onHandleUrl, + onConnect = { url -> + subScreen = HomeSubScreen.None + actions.onHandleUrl(url) + }, + onOpenBrowser = { url, inject -> + subScreen = HomeSubScreen.None + actions.onOpenBrowser(url, inject) + }, + onImportWallet = { name, network, mnemonic, secretKey, version, interfaceType -> + subScreen = HomeSubScreen.None + actions.onImportWallet(name, network, mnemonic, secretKey, version, interfaceType) + }, + onGenerateWallet = { name, network, version, interfaceType -> + subScreen = HomeSubScreen.None + actions.onGenerateWallet(name, network, version, interfaceType) + }, walletKit = walletKit, ) } @@ -515,7 +525,10 @@ fun WalletScreen( } }, actions = { - IconButton(onClick = { subScreen = HomeSubScreen.Investigation }) { + IconButton( + onClick = { subScreen = HomeSubScreen.Investigation }, + modifier = Modifier.testTag(TestTags.INVESTIGATION_BUTTON), + ) { TonIconImage( icon = TonIcon.SettingsControl, size = 24.dp, @@ -535,7 +548,11 @@ fun WalletScreen( // Always reserve space for progress indicator to prevent content shift. Box(modifier = Modifier.fillMaxWidth().height(4.dp)) { if (state.isLoadingWallets || state.isLoadingSessions) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + color = TonTheme.colors.bgBrand, + trackColor = TonTheme.colors.bgBrandSubtle, + ) } } @@ -558,14 +575,6 @@ fun WalletScreen( onNFTTap = { preview -> nftsList.firstOrNull { it.address.value == preview.address }?.let { selectedNFT = it } }, - onBalanceSecretTap = { - val nowLegacy = DevPreferences.toggleLegacyMainScreen(context) - Toast.makeText( - context, - if (nowLegacy) "Legacy main screen ON" else "Legacy main screen OFF", - Toast.LENGTH_SHORT, - ).show() - }, ) } } diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/EventLogSection.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/EventLogSection.kt deleted file mode 100644 index 91625301..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/EventLogSection.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.ui.sections - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import io.ton.walletkit.demo.R - -@Composable -fun EventLogSection(events: List) { - Column(verticalArrangement = Arrangement.spacedBy(EVENT_SECTION_SPACING)) { - Text(stringResource(R.string.events_recent_title), style = MaterialTheme.typography.titleMedium) - events.forEach { event -> - HorizontalDivider() - Text(event, style = MaterialTheme.typography.bodySmall) - } - } -} -private val EVENT_SECTION_SPACING = 8.dp - -@Preview(showBackground = true) -@Composable -private fun EventLogSectionPreview() { - EventLogSection(events = listOf("Handled TON Connect URL", "Approved transaction")) -} diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/JettonsSection.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/JettonsSection.kt deleted file mode 100644 index 93d50f91..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/JettonsSection.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.ui.sections - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import io.ton.walletkit.demo.presentation.model.JettonSummary -import io.ton.walletkit.demo.presentation.ui.components.JettonListItem - -/** - * Section displaying jettons owned by the wallet. - * - * Mirrors iOS WalletJettonsListView for cross-platform consistency. - */ -@Composable -fun JettonsSection( - jettons: List, - isLoading: Boolean, - error: String?, - canLoadMore: Boolean, - onJettonClick: (JettonSummary) -> Unit, - onLoadMore: () -> Unit, - onRefresh: () -> Unit, - modifier: Modifier = Modifier, -) { - // Auto-load jettons when section is first displayed - LaunchedEffect(Unit) { - onRefresh() - } - - Column( - modifier = modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - // Header - Text( - text = "Jettons", - style = MaterialTheme.typography.headlineSmall, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(horizontal = 16.dp), - ) - - when { - // Loading state - isLoading && jettons.isEmpty() -> { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(32.dp), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator() - } - } - - // Error state - error != null && jettons.isEmpty() -> { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(32.dp), - contentAlignment = Alignment.Center, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - Text( - text = "Failed to load jettons", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.error, - ) - Button(onClick = onRefresh) { - Text("Retry") - } - } - } - } - - // Empty state - jettons.isEmpty() -> { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(32.dp), - contentAlignment = Alignment.Center, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - Text( - text = "No Jettons found", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Button(onClick = onRefresh) { - Text("Try Again") - } - } - } - } - - // Success state - display jettons - else -> { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - // Display each jetton - jettons.forEach { jetton -> - JettonListItem( - jetton = jetton, - onClick = onJettonClick, - modifier = Modifier.padding(horizontal = 16.dp), - ) - } - - // Load More button - if (canLoadMore) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - contentAlignment = Alignment.Center, - ) { - Button( - onClick = onLoadMore, - enabled = !isLoading, - ) { - if (isLoading) { - CircularProgressIndicator( - modifier = Modifier.padding(horizontal = 8.dp), - ) - } else { - Text("Load more") - } - } - } - } - } - } - } - } -} diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/MasterchainInfoSection.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/MasterchainInfoSection.kt deleted file mode 100644 index 7c7a4de6..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/MasterchainInfoSection.kt +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.ui.sections - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Button -import androidx.compose.material3.Card -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -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.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import io.ton.walletkit.api.generated.TONMasterchainInfo -import io.ton.walletkit.api.generated.TONNetwork -import io.ton.walletkit.api.isTetra -import io.ton.walletkit.demo.core.TonAPIClient -import io.ton.walletkit.demo.core.ToncenterAPIClient -import kotlinx.coroutines.launch - -/** - * Section demonstrating getMasterchainInfo() API call. - */ -@Composable -fun MasterchainInfoSection( - network: TONNetwork, - modifier: Modifier = Modifier, -) { - var info by remember(network) { mutableStateOf(null) } - var isLoading by remember(network) { mutableStateOf(false) } - var error by remember(network) { mutableStateOf(null) } - val scope = rememberCoroutineScope() - - Column(modifier = modifier.fillMaxWidth()) { - Text( - text = "Masterchain Info", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold, - ) - Spacer(modifier = Modifier.height(8.dp)) - Card(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier.padding(16.dp).fillMaxWidth(), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - Button( - onClick = { - scope.launch { - isLoading = true - error = null - try { - val client = if (network.isTetra) TonAPIClient.tetra() else ToncenterAPIClient(network) - info = client.getMasterchainInfo() - } catch (e: Exception) { - error = e.message ?: "Unknown error" - } finally { - isLoading = false - } - } - }, - enabled = !isLoading, - ) { - Text("Fetch") - } - if (isLoading) { - Spacer(modifier = Modifier.width(12.dp)) - CircularProgressIndicator( - modifier = Modifier.height(24.dp).width(24.dp), - strokeWidth = 2.dp, - ) - } - } - - error?.let { - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "Error: $it", - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodySmall, - ) - } - - info?.let { mc -> - Spacer(modifier = Modifier.height(12.dp)) - InfoRow("Seqno", mc.seqno.toString()) - InfoRow("Workchain", mc.workchain.toString()) - InfoRow("Shard", mc.shard) - InfoRow("Root Hash", mc.rootHash.value) - InfoRow("File Hash", mc.fileHash.value) - } - } - } - } -} - -@Composable -private fun InfoRow(label: String, value: String) { - Row(modifier = Modifier.padding(vertical = 2.dp)) { - Text( - text = "$label: ", - style = MaterialTheme.typography.bodySmall, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Text( - text = value, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface, - ) - } -} diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/SessionsSection.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/SessionsSection.kt deleted file mode 100644 index c721373e..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/SessionsSection.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.ui.sections - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.width -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import io.ton.walletkit.demo.R -import io.ton.walletkit.demo.presentation.model.SessionSummary -import io.ton.walletkit.demo.presentation.ui.components.EmptyStateCard -import io.ton.walletkit.demo.presentation.ui.components.SessionCard -import io.ton.walletkit.demo.presentation.ui.preview.PreviewData - -@Composable -fun SessionsSection(sessions: List, onDisconnect: (String) -> Unit) { - Column(verticalArrangement = Arrangement.spacedBy(SESSIONS_SECTION_SPACING)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - stringResource(R.string.sessions_title), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - Spacer(modifier = Modifier.width(SESSIONS_TITLE_SPACING)) - Text( - "${sessions.size}", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.primary, - ) - } - if (sessions.isEmpty()) { - EmptyStateCard( - title = stringResource(R.string.sessions_empty_title), - description = stringResource(R.string.sessions_empty_description), - ) - } else { - Column(verticalArrangement = Arrangement.spacedBy(SESSIONS_LIST_SPACING)) { - sessions.forEach { session -> - SessionCard( - session = session, - onDisconnect = { onDisconnect(session.sessionId) }, - ) - } - } - } - } -} - -private val SESSIONS_SECTION_SPACING = 12.dp -private val SESSIONS_TITLE_SPACING = 8.dp -private val SESSIONS_LIST_SPACING = 12.dp - -@Preview(showBackground = true) -@Composable -private fun SessionsSectionPreview() { - SessionsSection(sessions = listOf(PreviewData.session), onDisconnect = {}) -} diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/WalletsSection.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/WalletsSection.kt deleted file mode 100644 index 21c3945e..00000000 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/ui/sections/WalletsSection.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2025 TonTech - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package io.ton.walletkit.demo.presentation.ui.sections - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.width -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.pluralStringResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import io.ton.walletkit.demo.R -import io.ton.walletkit.demo.presentation.model.WalletSummary -import io.ton.walletkit.demo.presentation.ui.components.EmptyStateCard -import io.ton.walletkit.demo.presentation.ui.components.WalletCard -import io.ton.walletkit.demo.presentation.ui.preview.PreviewData - -@Composable -fun WalletsSection( - activeWallet: WalletSummary?, - totalWallets: Int, - onWalletSelected: (String) -> Unit, - onSendFromWallet: (String) -> Unit = {}, - onStakeFromWallet: (String) -> Unit = {}, - isStreamingConnected: Boolean? = null, - onRefresh: () -> Unit = {}, -) { - Column(verticalArrangement = Arrangement.spacedBy(WALLETS_SECTION_SPACING)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - stringResource(R.string.wallets_title), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - Spacer(modifier = Modifier.width(WALLETS_TITLE_SPACING)) - Text( - pluralStringResource(R.plurals.wallets_count, totalWallets, totalWallets), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.primary, - ) - } - if (activeWallet == null) { - EmptyStateCard( - title = stringResource(R.string.wallets_empty_title), - description = stringResource(R.string.wallets_empty_description), - ) - } else { - WalletCard( - wallet = activeWallet, - onDetails = { onWalletSelected(activeWallet.walletId) }, - onSend = { onSendFromWallet(activeWallet.walletId) }, - onStake = { onStakeFromWallet(activeWallet.walletId) }, - isStreamingConnected = isStreamingConnected, - onRefresh = onRefresh, - ) - if (totalWallets > 1) { - Text( - text = stringResource(R.string.wallets_switcher_hint), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - } -} - -private val WALLETS_SECTION_SPACING = 12.dp -private val WALLETS_TITLE_SPACING = 8.dp - -@Preview(showBackground = true) -@Composable -private fun WalletsSectionPreview() { - WalletsSection( - activeWallet = PreviewData.wallet, - totalWallets = 3, - onWalletSelected = {}, - onSendFromWallet = {}, - onStakeFromWallet = {}, - ) -} diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/util/TestTags.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/util/TestTags.kt index 0a201a54..64242e4b 100644 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/util/TestTags.kt +++ b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/util/TestTags.kt @@ -60,6 +60,18 @@ object TestTags { const val ADD_WALLET_BUTTON = "add-wallet" const val REFRESH_BUTTON = "refresh" const val BROWSER_NO_INJECT_BUTTON = "browser-no-inject" + const val BROWSER_INJECT_BUTTON = "browser-inject" + const val BROWSER_URL_FIELD = "browser-url" + const val INVESTIGATION_BUTTON = "investigation" + const val INVESTIGATION_TONCONNECT_ROW = "investigation-tonconnect" + const val INVESTIGATION_BROWSER_ROW = "investigation-browser" + const val INVESTIGATION_CONNECT_ROW = "investigation-connect-to-dapp" + const val INVESTIGATION_ADD_WALLET_ROW = "investigation-add-wallet" + const val ADD_WALLET_NAME_FIELD = "add-wallet-name" + const val ADD_WALLET_MNEMONIC_FIELD = "add-wallet-mnemonic" + const val ADD_WALLET_SECRET_KEY_FIELD = "add-wallet-secret-key" + const val ADD_WALLET_IMPORT_BUTTON = "add-wallet-import" + const val ADD_WALLET_GENERATE_BUTTON = "add-wallet-generate" // ConnectRequestSheet const val CONNECT_REQUEST_SHEET = "request" diff --git a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/viewmodel/WalletKitViewModel.kt b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/viewmodel/WalletKitViewModel.kt index e8e3934a..64cea8ab 100644 --- a/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/viewmodel/WalletKitViewModel.kt +++ b/AndroidDemo/app/src/main/java/io/ton/walletkit/demo/presentation/viewmodel/WalletKitViewModel.kt @@ -45,7 +45,6 @@ import io.ton.walletkit.demo.data.storage.DemoAppStorage import io.ton.walletkit.demo.data.storage.WalletRecord import io.ton.walletkit.demo.domain.model.WalletInterfaceType import io.ton.walletkit.demo.domain.model.WalletMetadata -import io.ton.walletkit.demo.presentation.dev.DevPreferences import io.ton.walletkit.demo.presentation.model.ConnectPermissionUi import io.ton.walletkit.demo.presentation.model.ConnectRequestUi import io.ton.walletkit.demo.presentation.model.JettonDetails @@ -1790,14 +1789,6 @@ class WalletKitViewModel @Inject constructor( // Wait until the initial wallet load cycle in bootstrap() has fully completed // before deciding whether to open the "add wallet" sheet. _state.first { it.walletsBootstrapped } - - // Modern flow uses CreateWalletOnboardingScreen, orchestrated by MainActivity - // when there's no wallet — auto-opening AddWalletSheet here would race and - // leave a stale sheet behind for the user to bump into later. Only fire for - // the legacy main screen, which still relies on the bottom-sheet flow. - if (_state.value.wallets.isEmpty() && DevPreferences.useLegacyMainScreen.value) { - uiCoordinator.openAddWalletSheet() - } } catch (e: Exception) { Log.e(LOG_TAG, "Failed to setup password", e) val reason = e.message ?: uiString(R.string.wallet_error_unknown) @@ -1831,11 +1822,6 @@ class WalletKitViewModel @Inject constructor( // Wait until the initial wallet load cycle in bootstrap() has fully completed // before deciding whether to open the "add wallet" sheet. _state.first { it.walletsBootstrapped } - // See [setupPassword] — modern main screen drives onboarding from MainActivity, - // so skip the legacy AddWalletSheet auto-open unless we're explicitly on legacy. - if (_state.value.wallets.isEmpty() && DevPreferences.useLegacyMainScreen.value) { - uiCoordinator.openAddWalletSheet() - } } }