diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index d4c3c6eca3..f5feed18d7 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -12,7 +12,7 @@ import com.tangem.tap.features.details.ui.details.DetailsFragment import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment -import com.tangem.tap.features.details.ui.walletconnect.WalletConnectSessionsFragment +import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment import com.tangem.tap.features.home.HomeFragment import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment @@ -101,7 +101,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.AddTokens -> AddTokensFragment() AppScreen.AddCustomToken -> AddCustomTokenFragment() AppScreen.WalletDetails -> WalletDetailsFragment() - AppScreen.WalletConnectSessions -> WalletConnectSessionsFragment() + AppScreen.WalletConnectSessions -> WalletConnectFragment() AppScreen.QrScan -> QrScanFragment() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 471ccaa44c..988207c5e6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.details.redux.walletconnect -import android.app.Activity import com.tangem.blockchain.common.Blockchain import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.redux.NotificationAction @@ -19,7 +18,7 @@ sealed class WalletConnectAction : Action { object RestoreSessions : WalletConnectAction() data class StartWalletConnect( - val activity: Activity, + val copiedUri: String?, ) : WalletConnectAction() data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 7f7a88748a..61bfaf5e79 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -5,7 +5,6 @@ import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -51,7 +50,7 @@ class WalletConnectMiddleware { } } is WalletConnectAction.StartWalletConnect -> { - val uri = action.activity.getFromClipboard()?.toString() + val uri = action.copiedUri if (uri != null && WalletConnectManager.isCorrectWcUri(uri)) { store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri)) } else { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index a0117d0fdc..4c5cd02e86 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -31,6 +31,7 @@ import com.tangem.wallet.R fun SettingsScreensScaffold( content: @Composable () -> Unit, background: @Composable (() -> Unit)? = null, + fab: @Composable (() -> Unit)? = null, titleRes: Int, onBackClick: () -> Unit, modifier: Modifier = Modifier, @@ -41,6 +42,7 @@ fun SettingsScreensScaffold( topBar = { EmptyTopBarWithNavigation(onBackClick = onBackClick) }, modifier = modifier.systemBarsPadding(), backgroundColor = colorResource(id = R.color.background_primary), + floatingActionButton = { fab?.invoke() }, ) { Box(modifier = modifier.fillMaxSize()) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt new file mode 100644 index 0000000000..34147c0b72 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt @@ -0,0 +1,66 @@ +package com.tangem.tap.features.details.ui.walletconnect + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import androidx.transition.TransitionInflater +import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState +import com.tangem.tap.store +import org.rekotlin.StoreSubscriber + +class WalletConnectFragment : Fragment(), StoreSubscriber { + private val viewModel = WalletConnectViewModel(store) + private var screenState: MutableState = + mutableStateOf(viewModel.updateState(store.state.walletConnectState)) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return ComposeView(requireContext()).apply { + setContent { + isTransitionGroup = true + AppCompatTheme { + WalletConnectScreen( + state = screenState.value, + onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) }, + ) + } + } + } + } + + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state.skipRepeats { oldState, newState -> + oldState.walletConnectState == newState.walletConnectState + }.select { it.walletConnectState } + } + } + + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun newState(state: WalletConnectState) { + if (activity == null || view == null) return + screenState.value = viewModel.updateState(state) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt index 0d77c0e50c..086c2ae8b8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -1,21 +1,182 @@ package com.tangem.tap.features.details.ui.walletconnect +import androidx.compose.foundation.Image +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.FloatingActionButton +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.tap.common.compose.TangemTypography +import com.tangem.tap.common.extensions.getFromClipboard +import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold +import com.tangem.wallet.R @Composable fun WalletConnectScreen( - + state: WalletConnectScreenState, + onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { + val context = LocalContext.current + + SettingsScreensScaffold( + content = { + if (state.sessions.isEmpty()) { + EmptyScreen(state, modifier) + } else { + WalletConnectSessions(state, modifier) + } + }, + fab = { + AddSessionFab( + onAddSession = { + state.onAddSession(context.getFromClipboard()?.toString()) + }, + ) + }, + titleRes = R.string.wallet_connect_title, + onBackClick = onBackPressed, + ) } +@Composable +private fun AddSessionFab( + onAddSession: () -> Unit, + modifier: Modifier = Modifier, +) { + FloatingActionButton( + onClick = onAddSession, + backgroundColor = colorResource(id = R.color.button_primary), + contentColor = colorResource(id = R.color.icon_primary_2), + shape = RoundedCornerShape(16.dp), + modifier = modifier, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_plus), + contentDescription = "", + ) + } +} -//data class WalletConnectScreenState( -// sessions: WcSessionScreenState -//) -// -//data class WcSessionScreenState( -// val icon: Bitmap, -// -//) \ No newline at end of file +@Composable +private fun EmptyScreen(state: WalletConnectScreenState, modifier: Modifier = Modifier) { + if (state.isLoading) { + LinearProgressIndicator( + modifier = modifier.fillMaxWidth(), + color = colorResource(id = R.color.icon_accent), + ) + } + Column( + modifier = modifier + .fillMaxSize() + .padding(bottom = 64.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = painterResource(id = R.drawable.ic_walletconnect), + contentDescription = "", + colorFilter = ColorFilter.tint(colorResource(id = R.color.icon_inactive)), + contentScale = ContentScale.FillWidth, + modifier = modifier + .width(width = 100.dp), + ) + Spacer(modifier = modifier.size(24.dp)) + Text( + text = stringResource(id = R.string.wallet_connect_subtitle), + style = TangemTypography.body2, + color = colorResource(id = R.color.text_tertiary), + ) + } +} + +@Composable +private fun WalletConnectSessions( + state: WalletConnectScreenState, + modifier: Modifier = Modifier, +) { + if (state.isLoading) { + LinearProgressIndicator( + modifier = modifier + .fillMaxWidth() + .height(2.dp), + color = colorResource(id = R.color.icon_accent), + ) + } else { + Spacer(modifier = modifier.height(2.dp)) + } + + LazyColumn( + modifier = modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + items(state.sessions) { session -> + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = modifier) { + Text( + text = session.description, + style = TangemTypography.subtitle1, + color = colorResource(id = R.color.text_primary_1), + ) + } + IconButton( + onClick = { state.onRemoveSession(session.sessionId) }, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_cross_rounded_24), + contentDescription = "", + tint = colorResource(id = R.color.icon_warning), + ) + } + } + } + } +} + +@Composable +@Preview +fun WalletConnectScreenPreview() { + WalletConnectScreen( + state = WalletConnectScreenState( + sessions = listOf( + WcSessionForScreen( + description = "session from some dApp", + cardId = "12312312321", + sessionId = "", + ), + ), + isLoading = true, + ), + {}, + ) +} diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt new file mode 100644 index 0000000000..44792f9623 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.features.details.ui.walletconnect + +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession + +data class WalletConnectScreenState( + val sessions: List, + val isLoading: Boolean = false, + val onRemoveSession: (String) -> Unit = {}, + val onAddSession: (String?) -> Unit = {}, +) + +data class WcSessionForScreen( + val description: String, + val cardId: String, + val sessionId: String, +) { + companion object { + fun fromSession(session: WalletConnectSession): WcSessionForScreen { + return WcSessionForScreen( + description = session.peerMeta.name, + cardId = session.wallet.cardId, + sessionId = session.session.toUri(), + ) + } + } +} + diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt deleted file mode 100644 index b019dc95e7..0000000000 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.tangem.tap.features.details.ui.walletconnect - -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.transition.TransitionInflater -import by.kirich1409.viewbindingdelegate.viewBinding -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState -import com.tangem.tap.store -import com.tangem.wallet.R -import com.tangem.wallet.databinding.FragmentWalletConnectSessionsBinding -import org.rekotlin.StoreSubscriber - - -class WalletConnectSessionsFragment : Fragment(R.layout.fragment_wallet_connect_sessions), - StoreSubscriber { - - private lateinit var walletConnectSessionsAdapter: WalletConnectSessionsAdapter - private val binding: FragmentWalletConnectSessionsBinding by viewBinding( - FragmentWalletConnectSessionsBinding::bind - ) - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback( - this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - } - }) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - override fun onStart() { - super.onStart() - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.walletConnectState == newState.walletConnectState - }.select { it.walletConnectState } - } - } - - override fun onStop() { - super.onStop() - store.unsubscribe(this) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.toolbar.setNavigationOnClickListener { - store.dispatch(NavigationAction.PopBackTo()) - } - setOnClickListeners() - setupTransactionsRecyclerView() - } - - private fun setOnClickListeners() { - binding.fabOpenSession.setOnClickListener { - store.dispatch(WalletConnectAction.StartWalletConnect(activity = requireActivity())) - } - } - - private fun setupTransactionsRecyclerView() = with(binding) { - walletConnectSessionsAdapter = WalletConnectSessionsAdapter() - rvWalletConnectSessions.layoutManager = LinearLayoutManager(requireContext()) - rvWalletConnectSessions.adapter = walletConnectSessionsAdapter - walletConnectSessionsAdapter.submitList(store.state.walletConnectState.sessions) - } - - override fun newState(state: WalletConnectState) { - if (activity == null || view == null) return - - binding.fabOpenSession.show(!state.loading) - binding.pbWalletConnect.show(state.loading) - - showUpdatedList(state.sessions) - } - - private fun showUpdatedList(sessions: List) = with(binding) { - walletConnectSessionsAdapter.submitList(sessions) - rvWalletConnectSessions.show(sessions.isNotEmpty()) - tvNoSessions.show(sessions.isEmpty()) - tvNoSessionsTitle.show(sessions.isEmpty()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt new file mode 100644 index 0000000000..6bbde4c1ae --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.features.details.ui.walletconnect + +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState +import org.rekotlin.Store + +class WalletConnectViewModel(private val store: Store) { + fun updateState(state: WalletConnectState): WalletConnectScreenState { + return WalletConnectScreenState( + state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) }, + isLoading = state.loading, + onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions) }, + onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) }, + ) + } + + private fun onRemoveSession(sessionUri: String, sessions: List) { + sessions + .firstOrNull { it.session.toUri() == sessionUri } + ?.let { baseSession -> + store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session)) + } + } +} \ No newline at end of file