diff --git a/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletFragment.kt b/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletFragment.kt index 2253bdd03..711736eae 100644 --- a/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletFragment.kt +++ b/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletFragment.kt @@ -5,27 +5,24 @@ import android.accounts.AccountManager import android.content.Intent import android.net.Uri import android.os.Bundle -import android.util.Log -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast -import androidx.activity.viewModels import androidx.core.view.isVisible +import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.fragment.NavHostFragment.Companion.findNavController import androidx.navigation.fragment.findNavController -import com.walletconnect.wcmodal.client.Modal import com.walletconnect.wcmodal.client.WalletConnectModal import com.walletconnect.wcmodal.ui.openWalletConnectModal import kotlinx.coroutines.launch import org.xmtp.android.example.MainActivity import org.xmtp.android.example.R import org.xmtp.android.example.databinding.FragmentConnectWalletBinding +import timber.log.Timber class ConnectWalletFragment : Fragment() { @@ -39,7 +36,7 @@ class ConnectWalletFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { _binding = FragmentConnectWalletBinding.inflate(inflater, container, false) return binding.root } @@ -53,6 +50,12 @@ class ConnectWalletFragment : Fragment() { } } + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.showWalletState.collect(::showWalletState) + } + } + binding.generateButton.setOnClickListener { viewModel.generateWallet() } @@ -61,19 +64,12 @@ class ConnectWalletFragment : Fragment() { binding.connectButton.isEnabled = isConnectWalletAvailable binding.connectError.isVisible = !isConnectWalletAvailable binding.connectButton.setOnClickListener { - binding.connectButton.start(viewModel.walletConnectKit, ::onConnected, ::onDisconnected) + WalletConnectModal.setSessionParams(viewModel.getSessionParams()) + findNavController().openWalletConnectModal(id = R.id.action_to_bottomSheet) } } - private fun onConnected(address: String) { - viewModel.connectWallet() - } - - private fun onDisconnected() { - // No-op currently. - } - private fun isConnectAvailable(): Boolean { val wcIntent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(WC_URI_SCHEME) @@ -89,10 +85,24 @@ class ConnectWalletFragment : Fragment() { uiState.address, uiState.encodedKeyData ) + ConnectWalletViewModel.ConnectUiState.Unknown -> Unit } } + private fun showWalletState(walletState: ConnectWalletViewModel.ShowWalletForSigningState) { + if (walletState.showWallet) { + try { + val intent = Intent(Intent.ACTION_VIEW, walletState.uri) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + requireActivity().startActivity(intent) + viewModel.clearShowWalletState() + } catch (e: Exception) { + Timber.tag(FRAGMENT_LOG_TAG).e("Activity not found: $e") + } + } + } + private fun signIn(address: String, encodedKey: String) { val accountManager = AccountManager.get(requireContext()) Account(address, resources.getString(R.string.account_type)).also { account -> @@ -124,6 +134,7 @@ class ConnectWalletFragment : Fragment() { companion object { private const val WC_URI_SCHEME = "wc://wc?uri=" + private const val FRAGMENT_LOG_TAG = "ConnectWalletFragment" } } \ No newline at end of file diff --git a/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletViewModel.kt b/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletViewModel.kt index ef4a10580..5255f0d21 100644 --- a/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletViewModel.kt +++ b/example/src/main/java/org/xmtp/android/example/connect/ConnectWalletViewModel.kt @@ -1,35 +1,82 @@ package org.xmtp.android.example.connect -import android.app.Application +import android.net.Uri import androidx.annotation.UiThread -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dev.pinkroom.walletconnectkit.WalletConnectKit -import dev.pinkroom.walletconnectkit.WalletConnectKitConfig +import com.walletconnect.wcmodal.client.Modal import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.xmtp.android.example.ClientManager -import org.xmtp.android.example.account.WalletConnectAccount +import org.xmtp.android.example.account.WalletConnectV2Account import org.xmtp.android.library.Client import org.xmtp.android.library.XMTPException import org.xmtp.android.library.messages.PrivateKeyBuilder import org.xmtp.android.library.messages.PrivateKeyBundleV1Builder -class ConnectWalletViewModel(application: Application) : AndroidViewModel(application) { +class ConnectWalletViewModel : ViewModel() { + + private val chains: List = + Chains.values().map { it.toChainUiState() } + + private val _showWalletState = MutableStateFlow(ShowWalletForSigningState(showWallet = false)) + val showWalletState: StateFlow + get() = _showWalletState.asStateFlow() private val _uiState = MutableStateFlow(ConnectUiState.Unknown) val uiState: StateFlow = _uiState - private val walletConnectKitConfig = WalletConnectKitConfig( - context = application, - bridgeUrl = "https://safe-walletconnect.safe.global/", - appUrl = "https://xmtp.org", - appName = "XMTP Example", - appDescription = "Example app using the xmtp-android SDK" + init { + DappDelegate.wcEventModels + .filterNotNull() + .onEach { walletEvent -> + when (walletEvent) { + is Modal.Model.ApprovedSession -> { + connectWallet(walletEvent) + } + + else -> Unit + } + + }.launchIn(viewModelScope) + } + + fun getSessionParams() = Modal.Params.SessionParams( + requiredNamespaces = getNamespaces(), + optionalNamespaces = getOptionalNamespaces() ) - val walletConnectKit = WalletConnectKit.Builder(walletConnectKitConfig).build() + + private fun getNamespaces(): Map { + val namespaces: Map = + chains + .groupBy { it.chainNamespace } + .map { (key: String, selectedChains: List) -> + key to Modal.Model.Namespace.Proposal( + chains = selectedChains.map { it.chainId }, + methods = selectedChains.flatMap { it.methods }.distinct(), + events = selectedChains.flatMap { it.events }.distinct() + ) + }.toMap() + + + return namespaces.toMutableMap() + } + + private fun getOptionalNamespaces() = chains + .groupBy { it.chainId } + .map { (key: String, selectedChains: List) -> + key to Modal.Model.Namespace.Proposal( + methods = selectedChains.flatMap { it.methods }.distinct(), + events = selectedChains.flatMap { it.events }.distinct() + ) + }.toMap() @UiThread fun generateWallet() { @@ -49,11 +96,18 @@ class ConnectWalletViewModel(application: Application) : AndroidViewModel(applic } @UiThread - fun connectWallet() { + fun connectWallet(approvedSession: Modal.Model.ApprovedSession) { viewModelScope.launch(Dispatchers.IO) { _uiState.value = ConnectUiState.Loading try { - val wallet = WalletConnectAccount(walletConnectKit) + val wallet = WalletConnectV2Account( + approvedSession, + Chains.ETHEREUM_MAIN.chainNamespace + ) { uri -> + _showWalletState.update { + it.copy(showWallet = true, uri = uri) + } + } val client = Client().create(wallet, ClientManager.CLIENT_OPTIONS) _uiState.value = ConnectUiState.Success( wallet.address, @@ -65,10 +119,18 @@ class ConnectWalletViewModel(application: Application) : AndroidViewModel(applic } } + fun clearShowWalletState() { + _showWalletState.update { + it.copy(showWallet = false) + } + } + sealed class ConnectUiState { object Unknown : ConnectUiState() object Loading : ConnectUiState() data class Success(val address: String, val encodedKeyData: String) : ConnectUiState() data class Error(val message: String) : ConnectUiState() } + + data class ShowWalletForSigningState(val showWallet: Boolean, val uri: Uri? = null) }