diff --git a/app/src/main/java/one/mixin/android/ui/wallet/AllOrdersFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/AllOrdersFragment.kt index 2f8daafa2d..e16c70ec51 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/AllOrdersFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/AllOrdersFragment.kt @@ -4,25 +4,25 @@ import android.os.Bundle import android.view.Gravity import android.view.View import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.TextView import androidx.appcompat.widget.ListPopupWindow import androidx.core.content.ContextCompat import androidx.core.util.Pair import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import androidx.paging.PagedList import androidx.recyclerview.widget.LinearLayoutManager -import android.widget.ArrayAdapter -import android.widget.TextView -import android.view.ViewGroup -import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.CalendarConstraints import com.google.android.material.datepicker.DateValidatorPointBackward +import com.google.android.material.datepicker.MaterialDatePicker import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersDecoration import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import one.mixin.android.R import one.mixin.android.databinding.FragmentAllOrdersBinding import one.mixin.android.db.web3.vo.Web3TokenItem @@ -38,8 +38,8 @@ import one.mixin.android.ui.home.web3.trade.OrderDetailFragment import one.mixin.android.ui.wallet.adapter.OrderPagedAdapter import one.mixin.android.util.analytics.AnalyticsTracker import one.mixin.android.util.viewBinding -import one.mixin.android.widget.SpacesItemDecoration import one.mixin.android.vo.route.OrderItem +import one.mixin.android.widget.SpacesItemDecoration @AndroidEntryPoint class AllOrdersFragment : BaseTransactionsFragment>(R.layout.fragment_all_orders) { diff --git a/app/src/main/java/one/mixin/android/ui/wallet/ClassicWalletFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/ClassicWalletFragment.kt index 12ee9dad86..ad7cea193f 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/ClassicWalletFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/ClassicWalletFragment.kt @@ -27,12 +27,14 @@ import kotlinx.coroutines.withContext import one.mixin.android.Constants import one.mixin.android.R import one.mixin.android.RxBus +import one.mixin.android.crypto.CryptoWalletHelper import one.mixin.android.databinding.FragmentPrivacyWalletBinding import one.mixin.android.databinding.ViewWalletFragmentHeaderBinding import one.mixin.android.db.web3.vo.Web3TokenItem import one.mixin.android.db.web3.vo.isImported import one.mixin.android.db.web3.vo.isWatch import one.mixin.android.event.QuoteColorEvent +import one.mixin.android.event.WalletRefreshedEvent import one.mixin.android.extension.dp import one.mixin.android.extension.dpToPx import one.mixin.android.extension.mainThread @@ -161,64 +163,32 @@ class ClassicWalletFragment : BaseFragment(R.layout.fragment_privacy_wallet), He ViewWalletFragmentHeaderBinding.bind(layoutInflater.inflate(R.layout.view_wallet_fragment_header, coinsRv, false)).apply { sendReceiveView.enableBuy() sendReceiveView.buy.setOnClickListener { - lifecycleScope.launch { - val wallet = web3ViewModel.findWalletById(walletId) - val chainId = web3ViewModel.getAddresses(walletId).first().chainId - if (wallet?.isImported() == true && !wallet.hasLocalPrivateKey) { - ImportKeyBottomSheetDialogFragment.newInstance( - if (wallet.category == WalletCategory.IMPORTED_MNEMONIC.value) ImportKeyBottomSheetDialogFragment.PopupType.ImportMnemonicPhrase else ImportKeyBottomSheetDialogFragment.PopupType.ImportPrivateKey, - walletId = walletId, chainId = chainId - ).showNow(parentFragmentManager, ImportKeyBottomSheetDialogFragment.TAG) - return@launch - } - WalletActivity.showBuy(requireActivity(), true, null, null, walletId) - } + WalletActivity.showBuy(requireActivity(), true, null, null, walletId) } sendReceiveView.send.setOnClickListener { - lifecycleScope.launch { - val wallet = web3ViewModel.findWalletById(walletId) - val chainId = web3ViewModel.getAddresses(walletId).first().chainId - if (wallet?.isImported() == true && !wallet.hasLocalPrivateKey) { - ImportKeyBottomSheetDialogFragment.newInstance( - if (wallet.category == WalletCategory.IMPORTED_MNEMONIC.value) ImportKeyBottomSheetDialogFragment.PopupType.ImportMnemonicPhrase else ImportKeyBottomSheetDialogFragment.PopupType.ImportPrivateKey, - walletId = walletId, chainId = chainId - ).showNow(parentFragmentManager, ImportKeyBottomSheetDialogFragment.TAG) - return@launch - } - Web3TokenListBottomSheetDialogFragment.newInstance(walletId = walletId).apply { - setOnClickListener { token -> - this@ClassicWalletFragment.lifecycleScope.launch { - if (walletId.isEmpty()) { - toast(R.string.Data_error) - return@launch - } - val wallet = web3ViewModel.findWalletById(walletId) - val address = web3ViewModel.getAddressesByChainId(walletId, token.chainId) - if (address == null || wallet == null) { - toast(R.string.Data_error) - return@launch - } - val chain = web3ViewModel.web3TokenItemById(token.walletId, token.chainId) ?: return@launch - Timber.e("chain ${chain.name} ${token.chainId} ${chain.chainId}") - WalletActivity.navigateToWalletActivity(this@ClassicWalletFragment.requireActivity(), address.destination, token, chain, wallet) + Web3TokenListBottomSheetDialogFragment.newInstance(walletId = walletId).apply { + setOnClickListener { token -> + this@ClassicWalletFragment.lifecycleScope.launch { + if (walletId.isEmpty()) { + toast(R.string.Data_error) + return@launch } - dismissNow() + val wallet = web3ViewModel.findWalletById(walletId) + val address = web3ViewModel.getAddressesByChainId(walletId, token.chainId) + if (address == null || wallet == null) { + toast(R.string.Data_error) + return@launch + } + val chain = web3ViewModel.web3TokenItemById(token.walletId, token.chainId) ?: return@launch + Timber.e("chain ${chain.name} ${token.chainId} ${chain.chainId}") + WalletActivity.navigateToWalletActivity(this@ClassicWalletFragment.requireActivity(), address.destination, token, chain, wallet) } - }.show(parentFragmentManager, Web3TokenListBottomSheetDialogFragment.TAG) - } + dismissNow() + } + }.show(parentFragmentManager, Web3TokenListBottomSheetDialogFragment.TAG) } sendReceiveView.receive.setOnClickListener { - lifecycleScope.launch { - val wallet = web3ViewModel.findWalletById(walletId) - if (wallet?.isImported() == true && !wallet.hasLocalPrivateKey) { - val chainId = web3ViewModel.getAddresses(walletId).first().chainId - ImportKeyBottomSheetDialogFragment.newInstance( - if (wallet.category == WalletCategory.IMPORTED_MNEMONIC.value) ImportKeyBottomSheetDialogFragment.PopupType.ImportMnemonicPhrase else ImportKeyBottomSheetDialogFragment.PopupType.ImportPrivateKey, - walletId = walletId, chainId - ).showNow(parentFragmentManager, ImportKeyBottomSheetDialogFragment.TAG) - return@launch - } - if (!Session.saltExported() && Session.isAnonymous()) { + if (!Session.saltExported() && Session.isAnonymous()) { BackupMnemonicPhraseWarningBottomSheetDialogFragment.newInstance() .apply { laterCallback = { @@ -226,25 +196,13 @@ class ClassicWalletFragment : BaseFragment(R.layout.fragment_privacy_wallet), He } } .show(parentFragmentManager, BackupMnemonicPhraseWarningBottomSheetDialogFragment.TAG) - } else { - showReceiveAssetList() - } + } else { + showReceiveAssetList() } } sendReceiveView.swap.setOnClickListener { - lifecycleScope.launch { - val wallet = web3ViewModel.findWalletById(walletId) - if (wallet?.isImported() == true && !wallet.hasLocalPrivateKey) { - val chainId = web3ViewModel.getAddresses(walletId).first().chainId - ImportKeyBottomSheetDialogFragment.newInstance( - if (wallet.category == WalletCategory.IMPORTED_MNEMONIC.value) ImportKeyBottomSheetDialogFragment.PopupType.ImportMnemonicPhrase else ImportKeyBottomSheetDialogFragment.PopupType.ImportPrivateKey, - walletId = walletId, chainId = chainId - ).showNow(parentFragmentManager, ImportKeyBottomSheetDialogFragment.TAG) - return@launch - } - AnalyticsTracker.trackTradeStart(TradeWallet.WEB3, TradeSource.WALLET_HOME) - SwapActivity.show(requireActivity(), inMixin = false, walletId = walletId) - } + AnalyticsTracker.trackTradeStart(TradeWallet.WEB3, TradeSource.WALLET_HOME) + SwapActivity.show(requireActivity(), inMixin = false, walletId = walletId) } } _headBinding?.pendingView?.isVisible = false @@ -282,25 +240,18 @@ class ClassicWalletFragment : BaseFragment(R.layout.fragment_privacy_wallet), He } _walletId.observe(viewLifecycleOwner) { id -> if (id.isNotEmpty()) { - lifecycleScope.launch { - val wallet = web3ViewModel.findWalletById(id) - _headBinding?.sendReceiveView?.isVisible = wallet?.isWatch() == false - _headBinding?.watchLayout?.isVisible = wallet?.isWatch() == true - - if (wallet?.isWatch() == true) { - val addresses = web3ViewModel.getAddressesGroupedByDestination(id) - if (addresses.isNotEmpty()) { - if (addresses.size == 1) { - val address = addresses.first().destination - _headBinding?.watchTv?.text = getString(R.string.watching_address, "${address.take(6)}..${address.takeLast(4)}") - } else { - _headBinding?.watchTv?.text = getString(R.string.watching_addresses, addresses.size) - } - } - } - } + updateWalletUI(id) } } + + RxBus.listen(WalletRefreshedEvent::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(destroyScope) + .subscribe { event -> + if (event.walletId == walletId) { + updateWalletUI(walletId) + } + } _headBinding?.web3PendingView?.observePendingCount(viewLifecycleOwner, pendingTxCountLiveData) tokensLiveData.observe(viewLifecycleOwner, observer) @@ -518,6 +469,50 @@ class ClassicWalletFragment : BaseFragment(R.layout.fragment_privacy_wallet), He _headBinding?.pieItemContainer?.addView(item) } + private fun updateWalletUI(id: String) { + lifecycleScope.launch { + val wallet = web3ViewModel.findWalletById(id) + val isWatch = wallet?.isWatch() == true + val isMissingKey = wallet?.isImported() == true && !wallet.hasLocalPrivateKey + + _headBinding?.viewAnimator?.displayedChild = when { + isMissingKey -> 1 + else -> 0 + } + + _headBinding?.watchLayout?.isVisible = isWatch + + if (isMissingKey) { + val isMnemonic = wallet.category == WalletCategory.IMPORTED_MNEMONIC.value + _headBinding?.missingKeyView?.setMissingKey(isMnemonic) { + lifecycleScope.launch { + val chainId = web3ViewModel.getAddresses(id).firstOrNull()?.chainId + if (chainId != null) { + val mode = if (isMnemonic) { + WalletSecurityActivity.Mode.RE_IMPORT_MNEMONIC + } else { + WalletSecurityActivity.Mode.RE_IMPORT_PRIVATE_KEY + } + WalletSecurityActivity.show(requireActivity(), mode, walletId = id, chainId = chainId) + } + } + } + } + + if (isWatch) { + val addresses = web3ViewModel.getAddressesGroupedByDestination(id) + if (addresses.isNotEmpty()) { + if (addresses.size == 1) { + val address = addresses.first().destination + _headBinding?.watchTv?.text = getString(R.string.watching_address, "${address.take(6)}..${address.takeLast(4)}") + } else { + _headBinding?.watchTv?.text = getString(R.string.watching_addresses, addresses.size) + } + } + } + } + } + private fun showReceiveAssetList() { Web3TokenListBottomSheetDialogFragment.newInstance(walletId = walletId, TYPE_FROM_RECEIVE).apply { setOnClickListener { token -> diff --git a/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt index 65b29759d3..42daf91d8c 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt @@ -7,11 +7,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height diff --git a/app/src/main/java/one/mixin/android/ui/wallet/LimitTransferBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/LimitTransferBottomSheetDialogFragment.kt index 18cec333df..b88bce988a 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/LimitTransferBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/LimitTransferBottomSheetDialogFragment.kt @@ -11,9 +11,6 @@ import android.view.View import android.view.ViewGroup import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -25,14 +22,15 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -41,7 +39,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -49,93 +46,80 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.core.view.doOnPreDraw import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope -import com.google.android.material.bottomsheet.BottomSheetBehavior import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import one.mixin.android.Constants import one.mixin.android.R import one.mixin.android.api.MixinResponse import one.mixin.android.api.ResponseError +import one.mixin.android.api.request.web3.EstimateFeeRequest +import one.mixin.android.api.request.web3.Web3RawTransactionRequest import one.mixin.android.api.response.CreateLimitOrderResponse import one.mixin.android.api.response.web3.SwapToken import one.mixin.android.compose.CoilImage import one.mixin.android.compose.theme.MixinAppTheme +import one.mixin.android.db.web3.vo.Web3TokenItem +import one.mixin.android.db.web3.vo.buildTransaction +import one.mixin.android.db.web3.vo.getChainFromName +import one.mixin.android.extension.base64Encode import one.mixin.android.extension.booleanFromAttribute import one.mixin.android.extension.composeDp +import one.mixin.android.extension.defaultSharedPreferences import one.mixin.android.extension.getParcelableCompat -import one.mixin.android.extension.getSafeAreaInsetsBottom import one.mixin.android.extension.getSafeAreaInsetsTop import one.mixin.android.extension.isNightMode -import one.mixin.android.extension.navigationBarHeight -import one.mixin.android.extension.realSize -import one.mixin.android.extension.roundTopOrBottom +import one.mixin.android.extension.putLong import one.mixin.android.extension.screenHeight -import one.mixin.android.extension.statusBarHeight import one.mixin.android.extension.updatePinCheck import one.mixin.android.extension.withArgs import one.mixin.android.session.Session +import one.mixin.android.tip.getTipExceptionMsg +import one.mixin.android.tip.isTipNodeException +import one.mixin.android.tip.wc.internal.Chain +import one.mixin.android.tip.wc.internal.TipGas +import one.mixin.android.tip.wc.internal.buildTipGas import one.mixin.android.ui.common.BottomSheetViewModel +import one.mixin.android.ui.common.MixinComposeBottomSheetDialogFragment import one.mixin.android.ui.common.PinInputBottomSheetDialogFragment import one.mixin.android.ui.common.UtxoConsolidationBottomSheetDialogFragment import one.mixin.android.ui.common.biometric.BiometricInfo import one.mixin.android.ui.common.biometric.buildTransferBiometricItem import one.mixin.android.ui.common.biometric.getUtxoExceptionMsg import one.mixin.android.ui.common.biometric.isUtxoException +import one.mixin.android.ui.home.web3.Web3ViewModel import one.mixin.android.ui.home.web3.components.ActionBottom import one.mixin.android.ui.tip.wc.compose.ItemContent import one.mixin.android.ui.tip.wc.compose.ItemWalletContent import one.mixin.android.ui.url.UrlInterpreterActivity -import one.mixin.android.ui.wallet.components.WalletLabel -import one.mixin.android.ui.common.MixinComposeBottomSheetDialogFragment import one.mixin.android.util.ErrorHandler import one.mixin.android.util.SystemUIManager import one.mixin.android.util.analytics.AnalyticsTracker import one.mixin.android.util.getMixinErrorStringByCode import one.mixin.android.util.reportException +import one.mixin.android.util.tickerFlow import one.mixin.android.vo.User import one.mixin.android.vo.toUser -import timber.log.Timber -import java.math.BigDecimal -import java.util.UUID -import one.mixin.android.api.request.web3.EstimateFeeRequest -import one.mixin.android.api.request.web3.Web3RawTransactionRequest -import one.mixin.android.db.web3.vo.Web3TokenItem -import one.mixin.android.db.web3.vo.buildTransaction -import one.mixin.android.db.web3.vo.getChainFromName -import one.mixin.android.extension.base64Encode -import one.mixin.android.extension.defaultSharedPreferences -import one.mixin.android.extension.putLong -import one.mixin.android.tip.getTipExceptionMsg -import one.mixin.android.tip.isTipNodeException -import one.mixin.android.tip.wc.internal.Chain -import one.mixin.android.tip.wc.internal.TipGas -import one.mixin.android.tip.wc.internal.buildTipGas -import one.mixin.android.ui.home.web3.Web3ViewModel -import one.mixin.android.ui.tip.wc.sessionrequest.FeeInfo import one.mixin.android.web3.Rpc import one.mixin.android.web3.js.JsSignMessage import one.mixin.android.web3.js.Web3Signer import org.sol4kt.VersionedTransactionCompat import org.web3j.utils.Convert import org.web3j.utils.Numeric -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.withContext -import one.mixin.android.util.tickerFlow -import kotlin.time.Duration.Companion.seconds -import javax.inject.Inject -import kotlin.math.max -import one.mixin.android.ui.wallet.AssetChanges -import one.mixin.android.ui.wallet.ItemPriceContent -import one.mixin.android.ui.wallet.ItemUserContent +import timber.log.Timber +import java.math.BigDecimal import java.math.RoundingMode import java.time.Duration import java.time.Instant +import java.util.UUID +import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds @AndroidEntryPoint class LimitTransferBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragment() { diff --git a/app/src/main/java/one/mixin/android/ui/wallet/MultiSelectWeb3TokenListBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/MultiSelectWeb3TokenListBottomSheetDialogFragment.kt index a9dd24fb82..0779e9d86d 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/MultiSelectWeb3TokenListBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/MultiSelectWeb3TokenListBottomSheetDialogFragment.kt @@ -24,14 +24,12 @@ import one.mixin.android.db.web3.vo.Web3TokenItem import one.mixin.android.extension.appCompatActionBarHeight import one.mixin.android.extension.getSafeAreaInsetsTop import one.mixin.android.extension.hideKeyboard -import one.mixin.android.session.Session import one.mixin.android.ui.common.MixinBottomSheetDialogFragment import one.mixin.android.ui.wallet.adapter.SelectableWeb3TokenAdapter import one.mixin.android.ui.wallet.adapter.SelectedWeb3TokenAdapter import one.mixin.android.ui.wallet.adapter.WalletSearchWeb3TokenItemCallback import one.mixin.android.util.viewBinding import one.mixin.android.widget.BottomSheet -import timber.log.Timber import java.util.concurrent.TimeUnit @SuppressLint("NotifyDataSetChanged") diff --git a/app/src/main/java/one/mixin/android/ui/wallet/OrderFilterParams.kt b/app/src/main/java/one/mixin/android/ui/wallet/OrderFilterParams.kt index 7e263bb509..6c8c9b0ddc 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/OrderFilterParams.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/OrderFilterParams.kt @@ -1,8 +1,8 @@ package one.mixin.android.ui.wallet import androidx.sqlite.db.SimpleSQLiteQuery -import one.mixin.android.tip.wc.SortOrder import one.mixin.android.db.web3.vo.Web3TokenItem +import one.mixin.android.tip.wc.SortOrder import org.threeten.bp.Instant class OrderFilterParams( diff --git a/app/src/main/java/one/mixin/android/ui/wallet/OrdersViewModel.kt b/app/src/main/java/one/mixin/android/ui/wallet/OrdersViewModel.kt index 25ff46cb8a..7cb235f5a9 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/OrdersViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/OrdersViewModel.kt @@ -2,17 +2,16 @@ package one.mixin.android.ui.wallet import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import one.mixin.android.Constants -import one.mixin.android.vo.route.OrderItem +import one.mixin.android.api.service.RouteService import one.mixin.android.db.WalletDatabase import one.mixin.android.db.provider.LimitOrderDataProvider -import one.mixin.android.api.service.RouteService -import one.mixin.android.vo.route.Order +import one.mixin.android.vo.route.OrderItem import javax.inject.Inject @HiltViewModel diff --git a/app/src/main/java/one/mixin/android/ui/wallet/SwapTransferBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/SwapTransferBottomSheetDialogFragment.kt index e92b393ace..fb2509da97 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/SwapTransferBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/SwapTransferBottomSheetDialogFragment.kt @@ -73,7 +73,6 @@ import one.mixin.android.api.request.web3.Web3RawTransactionRequest import one.mixin.android.api.response.web3.SwapResponse import one.mixin.android.api.response.web3.SwapToken import one.mixin.android.compose.CoilImage - import one.mixin.android.compose.theme.MixinAppTheme import one.mixin.android.db.web3.vo.Web3TokenItem import one.mixin.android.db.web3.vo.buildTransaction @@ -83,7 +82,6 @@ import one.mixin.android.extension.booleanFromAttribute import one.mixin.android.extension.composeDp import one.mixin.android.extension.defaultSharedPreferences import one.mixin.android.extension.getParcelableCompat -import one.mixin.android.extension.getSafeAreaInsetsBottom import one.mixin.android.extension.getSafeAreaInsetsTop import one.mixin.android.extension.isNightMode import one.mixin.android.extension.notNullWithElse diff --git a/app/src/main/java/one/mixin/android/ui/wallet/WalletMultiSelectBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/WalletMultiSelectBottomSheetDialogFragment.kt index 0a76194a1b..e7cb02f80a 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/WalletMultiSelectBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/WalletMultiSelectBottomSheetDialogFragment.kt @@ -3,9 +3,7 @@ package one.mixin.android.ui.wallet import android.content.Context import android.view.View import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -13,17 +11,9 @@ 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.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults -import androidx.compose.material.Checkbox -import androidx.compose.material.CheckboxDefaults -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -35,12 +25,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.fragment.app.viewModels import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.FlowPreview @@ -56,14 +42,13 @@ import one.mixin.android.extension.getSafeAreaInsetsTop import one.mixin.android.extension.screenHeight import one.mixin.android.extension.withArgs import one.mixin.android.ui.common.MixinComposeBottomSheetDialogFragment -import one.mixin.android.ui.common.NoKeyWarningBottomSheetDialogFragment +import one.mixin.android.ui.home.web3.components.ActionBottom import one.mixin.android.ui.wallet.components.KEY_HIDE_COMMON_WALLET_INFO import one.mixin.android.ui.wallet.components.KEY_HIDE_PRIVACY_WALLET_INFO import one.mixin.android.ui.wallet.components.PREF_NAME import one.mixin.android.ui.wallet.components.WalletCard import one.mixin.android.ui.wallet.components.WalletDestination import one.mixin.android.ui.wallet.components.WalletInfoCard -import one.mixin.android.ui.home.web3.components.ActionBottom @AndroidEntryPoint class WalletMultiSelectBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragment() { diff --git a/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCard.kt b/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCard.kt index 3d17d77e41..dc358630e8 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCard.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCard.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Colors import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable diff --git a/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferBottomSheetDialogFragment.kt index 06975c52ac..e068782303 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferBottomSheetDialogFragment.kt @@ -71,8 +71,8 @@ import one.mixin.android.ui.common.biometric.displayAddress import one.mixin.android.ui.common.biometric.getUtxoExceptionMsg import one.mixin.android.ui.common.showUserBottom import one.mixin.android.ui.setting.SettingActivity -import one.mixin.android.ui.wallet.WithdrawalSuspendedBottomSheet import one.mixin.android.ui.wallet.CrossWalletFeeFreeBottomSheetDialogFragment +import one.mixin.android.ui.wallet.WithdrawalSuspendedBottomSheet import one.mixin.android.ui.wallet.transfer.data.TransferStatus import one.mixin.android.ui.wallet.transfer.data.TransferType import one.mixin.android.util.BiometricUtil diff --git a/app/src/main/java/one/mixin/android/ui/wallet/transfer/widget/TransferContent.kt b/app/src/main/java/one/mixin/android/ui/wallet/transfer/widget/TransferContent.kt index b76cc28d94..bd4a130d0e 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/transfer/widget/TransferContent.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/transfer/widget/TransferContent.kt @@ -9,9 +9,6 @@ import one.mixin.android.Constants import one.mixin.android.R import one.mixin.android.api.response.SafeAccount import one.mixin.android.databinding.ViewTransferContentBinding -import one.mixin.android.extension.base64RawURLEncode -import one.mixin.android.extension.hexString -import one.mixin.android.extension.isByteArrayValidUtf8 import one.mixin.android.extension.numberFormat2 import one.mixin.android.extension.numberFormat8 import one.mixin.android.session.Session diff --git a/app/src/main/java/one/mixin/android/web3/details/Web3TransactionsFragment.kt b/app/src/main/java/one/mixin/android/web3/details/Web3TransactionsFragment.kt index 9eb11a79b7..01f82e6cc5 100644 --- a/app/src/main/java/one/mixin/android/web3/details/Web3TransactionsFragment.kt +++ b/app/src/main/java/one/mixin/android/web3/details/Web3TransactionsFragment.kt @@ -12,14 +12,18 @@ import androidx.core.view.updateLayoutParams import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import com.uber.autodispose.autoDispose import dagger.hilt.android.AndroidEntryPoint +import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch import one.mixin.android.Constants import one.mixin.android.R +import one.mixin.android.RxBus import one.mixin.android.api.response.web3.StakeAccount +import one.mixin.android.event.WalletRefreshedEvent import one.mixin.android.databinding.FragmentWeb3TransactionsBinding import one.mixin.android.databinding.ViewWalletWeb3TokenBottomBinding import one.mixin.android.db.web3.vo.Web3TokenItem @@ -63,8 +67,8 @@ import one.mixin.android.ui.home.web3.stake.StakingFragment import one.mixin.android.ui.home.web3.stake.ValidatorsFragment import one.mixin.android.ui.home.web3.trade.TradeFragment import one.mixin.android.ui.wallet.AllWeb3TransactionsFragment -import one.mixin.android.ui.wallet.ImportKeyBottomSheetDialogFragment import one.mixin.android.ui.wallet.MarketDetailsFragment.Companion.ARGS_ASSET_ID +import one.mixin.android.ui.wallet.WalletSecurityActivity import one.mixin.android.ui.wallet.MarketDetailsFragment.Companion.ARGS_MARKET import one.mixin.android.ui.wallet.WalletActivity import one.mixin.android.ui.wallet.Web3FilterParams @@ -126,20 +130,20 @@ class Web3TransactionsFragment : BaseFragment(R.layout.fragment_web3_transaction savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - lifecycleScope.launch { - val wallet = web3ViewModel.findWalletById(token.walletId) - binding.sendReceiveView.isVisible = wallet?.isWatch() != true - binding.empty.isVisible = wallet?.isWatch() == true - if (token.isNativeSolToken() && wallet != null && (wallet.category == WalletCategory.CLASSIC.value || (wallet.isImported() && wallet.hasLocalPrivateKey))) { - binding.stake.root.visibility = View.VISIBLE - getStakeAccounts(address) - } else{ - binding.stake.root.visibility = View.GONE + updateWalletUI() + + RxBus.listen(WalletRefreshedEvent::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(destroyScope) + .subscribe { event -> + if (event.walletId == token.walletId) { + updateWalletUI() + } } - } jobManager.addJobInBackground(RefreshPriceJob(token.assetId)) refreshToken(token.assetId) + val scrollY = web3ViewModel.scrollOffset binding.titleView.apply { titleTv.setTextOnly(token.name) leftIb.setOnClickListener { @@ -148,9 +152,10 @@ class Web3TransactionsFragment : BaseFragment(R.layout.fragment_web3_transaction rightIb.setOnClickListener { showBottom() } + } - binding.apply { - value.text = try { + binding.apply { + value.text = try { if (token.priceFiat().toFloat() == 0f) { getString(R.string.NA) } else { @@ -210,13 +215,6 @@ class Web3TransactionsFragment : BaseFragment(R.layout.fragment_web3_transaction lifecycleScope.launch { val chain = web3ViewModel.web3TokenItemById(token.walletId, token.chainId) val wallet = web3ViewModel.findWalletById(token.walletId) - if (wallet?.isImported() == true && !wallet.hasLocalPrivateKey) { - ImportKeyBottomSheetDialogFragment.newInstance( - if (wallet.category == WalletCategory.IMPORTED_MNEMONIC.value) ImportKeyBottomSheetDialogFragment.PopupType.ImportMnemonicPhrase else ImportKeyBottomSheetDialogFragment.PopupType.ImportPrivateKey, - walletId = wallet.id, chainId = token.chainId - ).showNow(parentFragmentManager, ImportKeyBottomSheetDialogFragment.TAG) - return@launch - } if (chain == null) { refreshToken(token.chainId) toast(R.string.Please_wait_a_bit) @@ -234,45 +232,26 @@ class Web3TransactionsFragment : BaseFragment(R.layout.fragment_web3_transaction } } sendReceiveView.receive.setOnClickListener { - lifecycleScope.launch { - val wallet = web3ViewModel.findWalletById(token.walletId) - if (wallet?.isImported() == true && !wallet.hasLocalPrivateKey) { - ImportKeyBottomSheetDialogFragment.newInstance( - if (wallet.category == WalletCategory.IMPORTED_MNEMONIC.value) ImportKeyBottomSheetDialogFragment.PopupType.ImportMnemonicPhrase else ImportKeyBottomSheetDialogFragment.PopupType.ImportPrivateKey, - walletId = wallet.id, chainId = token.chainId - ).showNow(parentFragmentManager, ImportKeyBottomSheetDialogFragment.TAG) - return@launch + requireView().navigate( + R.id.action_web3_transactions_to_web3_address, + Bundle().apply { + putString("address", address) + putParcelable("web3_token", token) } - requireView().navigate( - R.id.action_web3_transactions_to_web3_address, - Bundle().apply { - putString("address", address) - putParcelable("web3_token", token) - } - ) - } + ) } sendReceiveView.swap.setOnClickListener { - lifecycleScope.launch { - val wallet = web3ViewModel.findWalletById(token.walletId) - if (wallet?.isImported() == true && !wallet.hasLocalPrivateKey) { - ImportKeyBottomSheetDialogFragment.newInstance( - if (wallet.category == WalletCategory.IMPORTED_MNEMONIC.value) ImportKeyBottomSheetDialogFragment.PopupType.ImportMnemonicPhrase else ImportKeyBottomSheetDialogFragment.PopupType.ImportPrivateKey, - walletId = wallet.id, chainId = token.chainId - ).showNow(parentFragmentManager, ImportKeyBottomSheetDialogFragment.TAG) - return@launch + AnalyticsTracker.trackTradeStart(TradeWallet.WEB3, TradeSource.ASSET_DETAIL) + requireView().navigate( + R.id.action_web3_transactions_to_swap, + Bundle().apply { + putString(TradeFragment.ARGS_INPUT, token.assetId) + putBoolean(TradeFragment.ARGS_IN_MIXIN, false) + putString(TradeFragment.ARGS_WALLET_ID, token.walletId) } - AnalyticsTracker.trackTradeStart(TradeWallet.WEB3, TradeSource.ASSET_DETAIL) - requireView().navigate( - R.id.action_web3_transactions_to_swap, - Bundle().apply { - putString(TradeFragment.ARGS_INPUT, token.assetId) - putBoolean(TradeFragment.ARGS_IN_MIXIN, false) - putString(TradeFragment.ARGS_WALLET_ID, token.walletId) - } - ) - } + ) } + transactionsTitleLl.setOnClickListener { view.navigate( @@ -304,9 +283,8 @@ class Web3TransactionsFragment : BaseFragment(R.layout.fragment_web3_transaction ) } } - marketView.setContent { - Market(token.assetId) - } + marketView.setContent { + Market(token.assetId) } } @@ -529,4 +507,38 @@ class Web3TransactionsFragment : BaseFragment(R.layout.fragment_web3_transaction override fun onScrollChanged() { if (isAdded) web3ViewModel.scrollOffset = binding.scrollView.scrollY } + + private fun updateWalletUI() { + lifecycleScope.launch { + val wallet = web3ViewModel.findWalletById(token.walletId) + val isWatch = wallet?.isWatch() == true + val isMissingKey = wallet?.isImported() == true && !wallet.hasLocalPrivateKey + + binding.viewAnimator.displayedChild = when { + isMissingKey -> 1 + else -> 0 + } + + binding.empty.isVisible = isWatch + + if (isMissingKey) { + val isMnemonic = wallet?.category == WalletCategory.IMPORTED_MNEMONIC.value + binding.missingKeyView.setMissingKey(isMnemonic) { + val mode = if (isMnemonic) { + WalletSecurityActivity.Mode.RE_IMPORT_MNEMONIC + } else { + WalletSecurityActivity.Mode.RE_IMPORT_PRIVATE_KEY + } + WalletSecurityActivity.show(requireActivity(), mode, walletId = token.walletId, chainId = token.chainId) + } + } + + if (token.isNativeSolToken() && wallet != null && (wallet.category == WalletCategory.CLASSIC.value || (wallet.isImported() && wallet.hasLocalPrivateKey))) { + binding.stake.root.visibility = View.VISIBLE + getStakeAccounts(address) + } else{ + binding.stake.root.visibility = View.GONE + } + } + } } diff --git a/app/src/main/java/one/mixin/android/widget/MissingKeyView.kt b/app/src/main/java/one/mixin/android/widget/MissingKeyView.kt new file mode 100644 index 0000000000..d6822558ae --- /dev/null +++ b/app/src/main/java/one/mixin/android/widget/MissingKeyView.kt @@ -0,0 +1,42 @@ +package one.mixin.android.widget + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.LinearLayout +import one.mixin.android.R +import one.mixin.android.databinding.ViewMissingKeyBinding +import one.mixin.android.extension.highlightStarTag + +class MissingKeyView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + + private val binding: ViewMissingKeyBinding + + init { + binding = ViewMissingKeyBinding.inflate(LayoutInflater.from(context), this) + orientation = VERTICAL + } + + fun setMissingKey(isMnemonic: Boolean, onImportClick: () -> Unit) { + binding.importKeyBtn.text = context.getString( + if (isMnemonic) R.string.Import_Mnemonic_Phrase else R.string.import_private_key + ) + + val learn = context.getString(R.string.Learn_More) + val info = context.getString( + if (isMnemonic) R.string.Import_Mnemonic_Phrase_Desc else R.string.Import_Private_Key_Desc, + "**$learn**" + ) + val learnUrl = context.getString( + if (isMnemonic) R.string.import_mnemonic_phrase_url else R.string.import_private_key_url + ) + + binding.missingKeyTv.highlightStarTag(info, arrayOf(learnUrl)) + binding.importKeyBtn.setOnClickListener { onImportClick() } + } + +} diff --git a/app/src/main/res/drawable/bg_rnd_rect_blue_btn.xml b/app/src/main/res/drawable/bg_rnd_rect_blue_btn.xml new file mode 100644 index 0000000000..74e57e67ab --- /dev/null +++ b/app/src/main/res/drawable/bg_rnd_rect_blue_btn.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_round_blue_btn.xml b/app/src/main/res/drawable/bg_round_blue_btn.xml index 74e57e67ab..b54f28395e 100644 --- a/app/src/main/res/drawable/bg_round_blue_btn.xml +++ b/app/src/main/res/drawable/bg_round_blue_btn.xml @@ -5,13 +5,13 @@ - + - + diff --git a/app/src/main/res/layout/fragment_web3_transactions.xml b/app/src/main/res/layout/fragment_web3_transactions.xml index 34c9b3358d..a912c770d7 100644 --- a/app/src/main/res/layout/fragment_web3_transactions.xml +++ b/app/src/main/res/layout/fragment_web3_transactions.xml @@ -129,14 +129,30 @@ - + android:layout_height="wrap_content"> + + + + + + + + +