Updated on 2026-08-14
This commit is contained in:
parent
44f6871822
commit
0802aa6b25
13 changed files with 271 additions and 33 deletions
|
|
@ -108,6 +108,14 @@ fun Context.copyToClipboard(value: Any, label: String = "") {
|
|||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
|
||||
fun Context.getFromClipboard(default: CharSequence? = null): CharSequence? {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return default
|
||||
val clipData = clipboard.primaryClip ?: return default
|
||||
if (clipData.itemCount == 0) return default
|
||||
|
||||
return clipData.getItemAt(0).text
|
||||
}
|
||||
|
||||
fun Context.shareText(text: String) {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.tap.common.qrCodeScan
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.zxing.Result
|
||||
import com.otaliastudios.cameraview.CameraView.PERMISSION_REQUEST_CODE
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUI
|
||||
import com.tangem.tap.store
|
||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
|
||||
private lateinit var mScannerView: ZXingScannerView
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
mScannerView = ZXingScannerView(this)
|
||||
setContentView(mScannerView)
|
||||
|
||||
if (!permissionIsGranted()) requestPermission()
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
mScannerView.setResultHandler(this)
|
||||
mScannerView.startCamera()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
mScannerView.stopCamera()
|
||||
}
|
||||
|
||||
override fun handleResult(result: Result) {
|
||||
store.dispatch(AddressPayIdActionUI.SetAddressOrPayId(result.text))
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
if (requestCode != PERMISSION_REQUEST_CODE) return
|
||||
|
||||
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun permissionIsGranted(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||
cameraPermission == PackageManager.PERMISSION_GRANTED
|
||||
} else true
|
||||
}
|
||||
|
||||
private fun requestPermission() {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import com.tangem.tap.common.redux.navigation.NavigationState
|
|||
import com.tangem.tap.common.redux.navigation.navigationMiddleware
|
||||
import com.tangem.tap.features.home.redux.homeMiddleware
|
||||
import com.tangem.tap.features.send.redux.SendState
|
||||
import com.tangem.tap.features.send.redux.logMiddleware
|
||||
import com.tangem.tap.features.send.redux.sendMiddleware
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.walletMiddleware
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -22,7 +22,7 @@ data class AppState(
|
|||
fun getMiddleware(): List<Middleware<AppState>> {
|
||||
return listOf(
|
||||
logMiddleware, navigationMiddleware, notificationsMiddleware,
|
||||
homeMiddleware, walletMiddleware,
|
||||
homeMiddleware, walletMiddleware, sendMiddleware
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.features.send.redux
|
||||
package com.tangem.tap.common.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -5,6 +5,7 @@ import android.view.View
|
|||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.send.redux.SendRelease
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.android.synthetic.main.fragment_wallet.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -42,4 +43,9 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
|
|||
storeSubscribersList.forEach { store.unsubscribe(it) }
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
store.dispatch(SendRelease)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.features.send.redux
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SendAction : Action
|
||||
|
||||
sealed class FeeLayout : SendAction {
|
||||
object ToggleFeeLayoutVisibility : FeeLayout()
|
||||
data class ChangeSelectedFee(val id: Int) : FeeLayout()
|
||||
class ChangeIncludeFee(val isChecked: Boolean) : FeeLayout()
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.tap.features.send.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.isPayIdSupported
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUI.SetAddressOrPayId
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
val sendMiddleware: Middleware<AppState> = { dispatch, appState ->
|
||||
{ nextDispatch ->
|
||||
{ action ->
|
||||
handleSendAction(action)
|
||||
nextDispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendAction(action: Action) {
|
||||
val sendAction = action as? SendScreenActionUI ?: return
|
||||
|
||||
when (sendAction) {
|
||||
is AddressPayIdActionUI -> {
|
||||
when (sendAction) {
|
||||
is SetAddressOrPayId -> AddressPayIdHandler().handle(sendAction.data?.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class AddressPayIdHandler {
|
||||
fun handle(data: String?) {
|
||||
val walletManager = store.state.globalState.walletManager ?: return
|
||||
val clipboardData = data ?: return
|
||||
|
||||
if (AddressPayIDState.isPayIDAddress(clipboardData)) {
|
||||
if (walletManager.wallet.blockchain.isPayIdSupported()) {
|
||||
store.dispatch(AddressPayIdAction.Verification.PayIdNotSupportedByBlockchain)
|
||||
} else {
|
||||
scope.launch {
|
||||
val response = verifyPayID(walletManager, clipboardData)
|
||||
if (response == null) {
|
||||
store.dispatch(AddressPayIdAction.Verification.Failed)
|
||||
} else {
|
||||
val address = "some address D:" // extract from response
|
||||
store.dispatch(AddressPayIdAction.Verification.Success(address))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun verifyPayID(walletManager: WalletManager, payID: String?): String? {
|
||||
val cardId = walletManager.cardId
|
||||
val publicKey = store.state.globalState.card?.cardPublicKey
|
||||
|
||||
// val result = PayIdManager().getPayId(cardId, publicKey.toHexString())
|
||||
// withContext(Dispatchers.Main) {
|
||||
// when (result) {
|
||||
// is Result.Success -> {
|
||||
// val payId = result.data
|
||||
// if (payId == null) {
|
||||
// store.dispatch(WalletAction.LoadPayId.NotCreated)
|
||||
// } else {
|
||||
// store.dispatch(WalletAction.LoadPayId.Success(payId))
|
||||
// }
|
||||
// }
|
||||
// is Result.Failure -> store.dispatch(WalletAction.LoadPayId.Failure)
|
||||
// }
|
||||
// }
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun verifyWalletAddress(payID: String?): Boolean {
|
||||
val isRealAddress = true
|
||||
return isRealAddress
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +1,59 @@
|
|||
package com.tangem.tap.features.send.redux
|
||||
|
||||
import android.view.View
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUI.SetAddressOrPayId
|
||||
import com.tangem.tap.features.send.redux.FeeActionUI.*
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.StateType
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SendReducer {
|
||||
companion object {
|
||||
fun reduce(action: Action, state: SendState): SendState {
|
||||
val sendAction = action as? SendAction ?: return state
|
||||
fun reduce(action: Action, sendState: SendState): SendState {
|
||||
if (action is SendRelease) return SendState()
|
||||
val sendAction = action as? SendScreenAction ?: return sendState
|
||||
|
||||
return when (sendAction) {
|
||||
is FeeLayout -> handleFeeLayoutAction(sendAction, state, state.feeLayoutState)
|
||||
else -> state
|
||||
is AddressPayIdActionUI -> handleAddressPayIdAction(sendAction, sendState, sendState.addressPayIDState)
|
||||
is FeeActionUI -> handleFeeLayoutAction(sendAction, sendState, sendState.feeLayoutState)
|
||||
else -> sendState
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFeeLayoutAction(action: FeeLayout, sendState: SendState, state: FeeLayoutState): SendState {
|
||||
private fun handleAddressPayIdAction(
|
||||
action: AddressPayIdActionUI,
|
||||
sendState: SendState,
|
||||
state: AddressPayIDState
|
||||
): SendState {
|
||||
var state = state
|
||||
|
||||
when (action) {
|
||||
is SetAddressOrPayId -> {
|
||||
state = state.copy(value = action.data?.toString())
|
||||
return updateLastState(sendState.copy(addressPayIDState = state), state)
|
||||
}
|
||||
}
|
||||
|
||||
return sendState
|
||||
}
|
||||
|
||||
private fun handleFeeLayoutAction(action: FeeActionUI, sendState: SendState, state: FeeLayoutState): SendState {
|
||||
return when (action) {
|
||||
is FeeLayout.ToggleFeeLayoutVisibility -> {
|
||||
is ToggleFeeLayoutVisibility -> {
|
||||
val visibility = if (state.visibility == View.VISIBLE) View.GONE
|
||||
else View.VISIBLE
|
||||
|
||||
val result = state.copy(visibility = visibility)
|
||||
updateLastState(sendState.copy(feeLayoutState = result), result)
|
||||
}
|
||||
is FeeLayout.ChangeSelectedFee -> {
|
||||
is ChangeSelectedFee -> {
|
||||
val result = state.copy(selectedFeeId = action.id)
|
||||
updateLastState(sendState.copy(feeLayoutState = result), result)
|
||||
}
|
||||
is FeeLayout.ChangeIncludeFee -> {
|
||||
is ChangeIncludeFee -> {
|
||||
val result = state.copy(includeFeeIsChecked = action.isChecked)
|
||||
updateLastState(sendState.copy(feeLayoutState = result), result)
|
||||
}
|
||||
|
|
@ -39,7 +61,9 @@ class SendReducer {
|
|||
}
|
||||
|
||||
private fun updateLastState(sendState: SendState, state: StateType): SendState {
|
||||
return sendState.copy(lastChangedStateType = state)
|
||||
val sendState = sendState.copy(lastChangedStateType = state)
|
||||
Timber.d("$sendState")
|
||||
return sendState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.tap.features.send.redux
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SendScreenAction : Action
|
||||
interface SendScreenActionUI : SendScreenAction
|
||||
|
||||
object SendRelease : Action
|
||||
|
||||
sealed class FeeActionUI : SendScreenActionUI {
|
||||
object ToggleFeeLayoutVisibility : FeeActionUI()
|
||||
data class ChangeSelectedFee(val id: Int) : FeeActionUI()
|
||||
class ChangeIncludeFee(val isChecked: Boolean) : FeeActionUI()
|
||||
}
|
||||
|
||||
sealed class AddressPayIdActionUI : SendScreenActionUI {
|
||||
data class SetAddressOrPayId(val data: CharSequence?) : AddressPayIdActionUI()
|
||||
}
|
||||
|
||||
sealed class AddressPayIdAction : SendScreenAction {
|
||||
object Verification : AddressPayIdAction() {
|
||||
object PayIdNotSupportedByBlockchain : AddressPayIdAction()
|
||||
object Failed : AddressPayIdAction()
|
||||
data class Success(val payIdWalletAddress: String) : AddressPayIdAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.send.redux
|
||||
|
||||
import android.view.View
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StateType
|
||||
|
||||
|
|
@ -8,12 +9,26 @@ import org.rekotlin.StateType
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class SendState(
|
||||
val walletManager: WalletManager? = null,
|
||||
val lastChangedStateType: StateType = NoneState(),
|
||||
val addressPayIDState: AddressPayIDState = AddressPayIDState(),
|
||||
val feeLayoutState: FeeLayoutState = FeeLayoutState()
|
||||
) : StateType
|
||||
|
||||
class NoneState : StateType
|
||||
|
||||
data class AddressPayIDState(
|
||||
val value: String? = null,
|
||||
val payIDWalletAddress: String? = null,
|
||||
val error: String? = null,
|
||||
) : StateType {
|
||||
fun isPayIDAddress(): Boolean = isPayIDAddress(value)
|
||||
|
||||
companion object {
|
||||
fun isPayIDAddress(value: String?): Boolean = value?.contains("\$payid.tangem.com") ?: false
|
||||
}
|
||||
}
|
||||
|
||||
data class FeeLayoutState(
|
||||
val visibility: Int = View.GONE,
|
||||
val selectedFeeId: Int = R.id.chipNormal,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
package com.tangem.tap.features.send.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity
|
||||
import com.tangem.tap.features.send.BaseStoreFragment
|
||||
import com.tangem.tap.features.send.redux.FeeLayout
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUI.SetAddressOrPayId
|
||||
import com.tangem.tap.features.send.redux.FeeActionUI.*
|
||||
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
|
||||
import com.tangem.tap.features.send.ui.stateSubscribers.WalletStateSubscriber
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.btn_paste.*
|
||||
import kotlinx.android.synthetic.main.btn_qr_code.*
|
||||
import kotlinx.android.synthetic.main.layout_send_network_fee.*
|
||||
|
||||
/**
|
||||
|
|
@ -22,13 +28,20 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
flExpandCollapse.setOnClickListener {
|
||||
store.dispatch(FeeLayout.ToggleFeeLayoutVisibility)
|
||||
store.dispatch(ToggleFeeLayoutVisibility)
|
||||
}
|
||||
chipGroup.setOnCheckedChangeListener { group, checkedId ->
|
||||
store.dispatch(FeeLayout.ChangeSelectedFee(checkedId))
|
||||
store.dispatch(ChangeSelectedFee(checkedId))
|
||||
}
|
||||
swIncludeFee.setOnCheckedChangeListener { btn, isChecked ->
|
||||
store.dispatch(FeeLayout.ChangeIncludeFee(isChecked))
|
||||
store.dispatch(ChangeIncludeFee(isChecked))
|
||||
}
|
||||
|
||||
imvPaste.setOnClickListener {
|
||||
store.dispatch(SetAddressOrPayId(requireContext().getFromClipboard()))
|
||||
}
|
||||
imvQrCode.setOnClickListener {
|
||||
requireActivity().startActivity(Intent(requireContext(), ScanQrCodeActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ package com.tangem.tap.features.send.ui.stateSubscribers
|
|||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionManager
|
||||
import com.tangem.tap.features.send.redux.AddressPayIDState
|
||||
import com.tangem.tap.features.send.redux.FeeLayoutState
|
||||
import com.tangem.tap.features.send.redux.SendState
|
||||
import kotlinx.android.synthetic.main.btn_expand_collapse.*
|
||||
import kotlinx.android.synthetic.main.layout_send_address_payid.*
|
||||
import kotlinx.android.synthetic.main.layout_send_network_fee.*
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -17,11 +18,15 @@ class SendStateSubscriber(fragment: Fragment) : FragmentStateSubscriber<SendStat
|
|||
override fun updateWithNewState(fg: Fragment, state: SendState) {
|
||||
when (state.lastChangedStateType) {
|
||||
is FeeLayoutState -> handleFeeLayoutState(fg, state.feeLayoutState)
|
||||
is AddressPayIDState -> handleAddressPayIdState(fg, state.addressPayIDState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddressPayIdState(fg: Fragment, state: AddressPayIDState) {
|
||||
fg.etAddressOrPayId.setText(state.value)
|
||||
}
|
||||
|
||||
private fun handleFeeLayoutState(fg: Fragment, layoutState: FeeLayoutState) {
|
||||
Timber.d("handleFeeLayoutState")
|
||||
if (fg.llFeeContainer.visibility != layoutState.visibility) {
|
||||
val rotationAngle = if (fg.imvExpandCollapse.rotation == 0f) 180f else 0f
|
||||
fg.imvExpandCollapse.rotation = rotationAngle
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue