Updated on 2026-08-14

This commit is contained in:
Tangem 2022-08-04 16:20:04 +04:00
parent 43387c838d
commit efaf04920f
9 changed files with 295 additions and 109 deletions

View file

@ -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()
}
}

View file

@ -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()

View file

@ -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 {

View file

@ -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()) {

View file

@ -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<WalletConnectState> {
private val viewModel = WalletConnectViewModel(store)
private var screenState: MutableState<WalletConnectScreenState> =
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)
}
}

View file

@ -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,
//
//)
@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,
),
{},
)
}

View file

@ -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<WcSessionForScreen>,
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(),
)
}
}
}

View file

@ -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<WalletConnectState> {
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<WalletConnectSession>) = with(binding) {
walletConnectSessionsAdapter.submitList(sessions)
rvWalletConnectSessions.show(sessions.isNotEmpty())
tvNoSessions.show(sessions.isEmpty())
tvNoSessionsTitle.show(sessions.isEmpty())
}
}

View file

@ -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<AppState>) {
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<WalletConnectSession>) {
sessions
.firstOrNull { it.session.toUri() == sessionUri }
?.let { baseSession ->
store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session))
}
}
}