Updated on 2026-08-14
This commit is contained in:
commit
19092b22db
26 changed files with 708 additions and 92 deletions
|
|
@ -79,6 +79,8 @@
|
|||
android:name="android.nfc.action.TECH_DISCOVERED"
|
||||
android:resource="@xml/nfc_tech_filter" />
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.tangem.CardFilter
|
||||
import com.tangem.Config
|
||||
|
|
@ -76,4 +79,15 @@ class MainActivity : AppCompatActivity() {
|
|||
store.dispatch(NavigationAction.ActivityDestroyed)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
||||
fun setTranslucent(activity: Activity, translucent: Boolean) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val w = activity.getWindow();
|
||||
if (translucent) {
|
||||
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
} else {
|
||||
w.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,10 @@ package com.tangem.tap
|
|||
import android.app.Application
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.appReducer
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
val store = Store(
|
||||
reducer = ::appReducer,
|
||||
|
|
@ -11,4 +14,13 @@ val store = Store(
|
|||
state = AppState()
|
||||
)
|
||||
|
||||
class TapApplication : Application()
|
||||
class TapApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
NetworkConnectivity(store, this)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import androidx.fragment.app.FragmentActivity
|
|||
import androidx.fragment.app.FragmentManager
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.features.home.HomeFragment
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
import com.tangem.wallet.R
|
||||
|
||||
|
|
@ -34,5 +35,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
|
|||
return when (screen) {
|
||||
AppScreen.Home -> HomeFragment()
|
||||
AppScreen.Wallet -> WalletFragment()
|
||||
AppScreen.Send -> SendFragment()
|
||||
}
|
||||
}
|
||||
|
|
@ -29,14 +29,17 @@ fun View.show(show: Boolean) {
|
|||
}
|
||||
|
||||
fun View.show() {
|
||||
if (this.visibility == View.VISIBLE) return
|
||||
this.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
fun View.hide() {
|
||||
if (this.visibility == View.GONE) return
|
||||
this.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun View.makeInvisible() {
|
||||
if (this.visibility == View.INVISIBLE) return
|
||||
this.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
|
|
@ -105,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)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,19 @@ package com.tangem.tap.common.redux
|
|||
|
||||
import com.tangem.tap.common.redux.global.globalReducer
|
||||
import com.tangem.tap.common.redux.navigation.navigationReducer
|
||||
import com.tangem.tap.features.send.redux.SendReducer
|
||||
import com.tangem.tap.features.wallet.redux.walletReducer
|
||||
import org.rekotlin.Action
|
||||
|
||||
fun appReducer(action: Action, state: AppState?): AppState {
|
||||
requireNotNull(state)
|
||||
if (action is AppAction.RestoreState) return action.state
|
||||
|
||||
return AppState(
|
||||
navigationState = navigationReducer(action, state),
|
||||
globalState = globalReducer(action, state),
|
||||
walletState = walletReducer(action, state),
|
||||
navigationState = navigationReducer(action, state),
|
||||
globalState = globalReducer(action, state),
|
||||
walletState = walletReducer(action, state),
|
||||
sendState = SendReducer.reduce(action, state.sendState)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,22 +4,25 @@ import com.tangem.tap.common.redux.global.GlobalState
|
|||
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.sendMiddleware
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.walletMiddleware
|
||||
import org.rekotlin.Middleware
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class AppState(
|
||||
val navigationState: NavigationState = NavigationState(),
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val walletState: WalletState = WalletState()
|
||||
val navigationState: NavigationState = NavigationState(),
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val walletState: WalletState = WalletState(),
|
||||
val sendState: SendState = SendState(),
|
||||
) : StateType {
|
||||
|
||||
companion object {
|
||||
fun getMiddleware(): List<Middleware<AppState>> {
|
||||
return listOf(
|
||||
navigationMiddleware, notificationsMiddleware,
|
||||
homeMiddleware, walletMiddleware,
|
||||
logMiddleware, navigationMiddleware, notificationsMiddleware,
|
||||
homeMiddleware, walletMiddleware, sendMiddleware
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.tap.common.redux
|
||||
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
val logMiddleware: Middleware<AppState> = { dispatch, appState ->
|
||||
{ nextDispatch ->
|
||||
{ action ->
|
||||
Timber.d("$action")
|
||||
nextDispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,4 +9,4 @@ data class NavigationState(
|
|||
val activity: WeakReference<FragmentActivity>? = null
|
||||
) : StateType
|
||||
|
||||
enum class AppScreen { Home, Wallet }
|
||||
enum class AppScreen { Home, Wallet, Send }
|
||||
|
|
@ -51,6 +51,8 @@ class PayIdManager {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val payIdRegExp = "^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$".toRegex()
|
||||
|
||||
val payIdSupported: EnumSet<Blockchain> = EnumSet.of(
|
||||
Blockchain.XRP,
|
||||
Blockchain.Ethereum,
|
||||
|
|
@ -63,6 +65,8 @@ class PayIdManager {
|
|||
Blockchain.Binance,
|
||||
Blockchain.RSK,
|
||||
)
|
||||
|
||||
fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.tap.features.send
|
||||
|
||||
import android.os.Bundle
|
||||
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.store
|
||||
import kotlinx.android.synthetic.main.fragment_wallet.*
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
|
||||
|
||||
abstract fun subscribeToStore()
|
||||
|
||||
protected val storeSubscribersList = mutableListOf<StoreSubscriber<*>>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
toolbar.setNavigationOnClickListener { store.dispatch(NavigationAction.PopBackTo()) }
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
subscribeToStore()
|
||||
}
|
||||
|
||||
|
||||
override fun onStop() {
|
||||
storeSubscribersList.forEach { store.unsubscribe(it) }
|
||||
super.onStop()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
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.PayIdManager
|
||||
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 (PayIdManager.isPayId(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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
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, sendState: SendState): SendState = internalReduce(action, sendState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, sendState: SendState): SendState {
|
||||
if (action is ReleaseSendState) return SendState()
|
||||
val sendAction = action as? SendScreenAction ?: return sendState
|
||||
|
||||
return when (sendAction) {
|
||||
is AddressPayIdActionUI -> handleAddressPayIdAction(sendAction, sendState, sendState.addressPayIDState)
|
||||
is FeeActionUI -> handleFeeLayoutAction(sendAction, sendState, sendState.feeLayoutState)
|
||||
else -> 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 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 ChangeSelectedFee -> {
|
||||
val result = state.copy(selectedFeeId = action.id)
|
||||
updateLastState(sendState.copy(feeLayoutState = result), result)
|
||||
}
|
||||
is ChangeIncludeFee -> {
|
||||
val result = state.copy(includeFeeIsChecked = action.isChecked)
|
||||
updateLastState(sendState.copy(feeLayoutState = result), result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLastState(sendState: SendState, state: StateType): SendState {
|
||||
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 ReleaseSendState : 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
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
|
||||
|
||||
/**
|
||||
[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
|
||||
|
||||
data class FeeLayoutState(
|
||||
val visibility: Int = View.GONE,
|
||||
val selectedFeeId: Int = R.id.chipNormal,
|
||||
val includeFeeIsChecked: Boolean = false
|
||||
) : StateType
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
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.AddressPayIdActionUI.SetAddressOrPayId
|
||||
import com.tangem.tap.features.send.redux.FeeActionUI.*
|
||||
import com.tangem.tap.features.send.redux.ReleaseSendState
|
||||
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.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
||||
|
||||
private val sendSubscriber = SendStateSubscriber(this)
|
||||
private val walletSubscriber = WalletStateSubscriber(this)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
flExpandCollapse.setOnClickListener {
|
||||
store.dispatch(ToggleFeeLayoutVisibility)
|
||||
}
|
||||
chipGroup.setOnCheckedChangeListener { group, checkedId ->
|
||||
store.dispatch(ChangeSelectedFee(checkedId))
|
||||
}
|
||||
swIncludeFee.setOnCheckedChangeListener { btn, isChecked ->
|
||||
store.dispatch(ChangeIncludeFee(isChecked))
|
||||
}
|
||||
|
||||
imvPaste.setOnClickListener {
|
||||
store.dispatch(SetAddressOrPayId(requireContext().getFromClipboard()))
|
||||
}
|
||||
imvQrCode.setOnClickListener {
|
||||
requireActivity().startActivity(Intent(requireContext(), ScanQrCodeActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(walletSubscriber) { appState ->
|
||||
appState.skipRepeats { oldState, newState -> false }.select { it.walletState }
|
||||
}
|
||||
store.subscribe(sendSubscriber) { appState ->
|
||||
appState.skipRepeats { oldState, newState -> false }.select { it.sendState }
|
||||
}
|
||||
|
||||
storeSubscribersList.add(walletSubscriber)
|
||||
storeSubscribersList.add(sendSubscriber)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
store.dispatch(ReleaseSendState)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.features.send.ui.stateSubscribers
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.rekotlin.StateType
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class FragmentStateSubscriber<S : StateType>(fragment: Fragment) : StoreSubscriber<S> {
|
||||
private val weakFragment: WeakReference<Fragment> = WeakReference(fragment)
|
||||
|
||||
abstract fun updateWithNewState(fg: Fragment, state: S)
|
||||
|
||||
override fun newState(state: S) {
|
||||
val fg = weakFragment.get() ?: return
|
||||
|
||||
updateWithNewState(fg, state)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
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.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SendStateSubscriber(fragment: Fragment) : FragmentStateSubscriber<SendState>(fragment) {
|
||||
|
||||
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) {
|
||||
if (fg.llFeeContainer.visibility != layoutState.visibility) {
|
||||
val rotationAngle = if (fg.imvExpandCollapse.rotation == 0f) 180f else 0f
|
||||
fg.imvExpandCollapse.rotation = rotationAngle
|
||||
|
||||
(fg.llFeeContainer.parent?.parent as? ViewGroup)?.let { TransitionManager.beginDelayedTransition(it) }
|
||||
fg.llFeeContainer.visibility = layoutState.visibility
|
||||
}
|
||||
|
||||
if (fg.swIncludeFee.isChecked != layoutState.includeFeeIsChecked) {
|
||||
fg.swIncludeFee.isChecked = layoutState.includeFeeIsChecked
|
||||
}
|
||||
|
||||
if (fg.chipGroup.checkedChipId != layoutState.selectedFeeId) {
|
||||
fg.chipGroup.check(layoutState.selectedFeeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.features.send.ui.stateSubscribers
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WalletStateSubscriber(fragment: Fragment) : FragmentStateSubscriber<WalletState>(fragment) {
|
||||
override fun updateWithNewState(fg: Fragment, state: WalletState) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import androidx.activity.OnBackPressedCallback
|
|||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.redux.PayIdState
|
||||
|
|
@ -55,6 +56,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
btn_scan.setOnClickListener {
|
||||
store.dispatch(WalletAction.Scan)
|
||||
}
|
||||
btn_main.setOnClickListener {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
|
||||
}
|
||||
}
|
||||
|
||||
override fun newState(state: WalletState) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.tap.network
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.ConnectivityManager
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class NetworkConnectivity(
|
||||
private val store: Store<*>,
|
||||
context: Context
|
||||
) {
|
||||
private val connectivityManager = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
private val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(c: Context?, intent: Intent?) {
|
||||
store.dispatch(NetworkStateChanged(isOnlineOrConnecting()))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val intentFilter = IntentFilter()
|
||||
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||
context.registerReceiver(receiver, intentFilter)
|
||||
store.dispatch(NetworkStateChanged(isOnlineOrConnecting()))
|
||||
}
|
||||
|
||||
|
||||
fun isOnlineOrConnecting(): Boolean {
|
||||
val netInfo = connectivityManager.activeNetworkInfo
|
||||
return netInfo != null && netInfo.isConnectedOrConnecting
|
||||
}
|
||||
}
|
||||
|
||||
data class NetworkStateChanged(val isOnline: Boolean) : Action
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 24% opacity -->
|
||||
<item android:alpha="0.24" android:color="?attr/colorSecondary" android:state_enabled="true" android:state_selected="true" />
|
||||
<item android:alpha="0.24" android:color="?attr/colorSecondary" android:state_checked="true" android:state_enabled="true" />
|
||||
<!-- 12% of 87% opacity -->
|
||||
<item android:alpha="0.1" android:color="?attr/colorSecondary" android:state_enabled="true" android:state_selected="true" />
|
||||
<item android:alpha="0.1" android:color="?attr/colorSecondary" android:state_checked="true" android:state_enabled="true" />
|
||||
|
||||
<item android:color="@android:color/transparent" android:state_enabled="true" />
|
||||
<item android:color="@android:color/transparent" />
|
||||
|
||||
|
|
|
|||
|
|
@ -3,49 +3,70 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="24dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
layout="@layout/layout_send_address_payid"
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp" />
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
|
||||
app:title="@string/send_title" />
|
||||
|
||||
<include
|
||||
layout="@layout/layout_send_currency"
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
android:id="@+id/clNetworkFee"
|
||||
layout="@layout/layout_send_network_fee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
<include
|
||||
layout="@layout/layout_send_address_payid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/clNetworkFee"
|
||||
layout="@layout/layout_send_total"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="24dp" />
|
||||
<include
|
||||
layout="@layout/layout_send_currency"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/TapButtonWithIcon"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:fontFamily="@font/saira_semi_condensed_regular"
|
||||
android:text="@string/send_btn_send"
|
||||
app:icon="@drawable/ic_arrow_right" />
|
||||
<include
|
||||
android:id="@+id/clNetworkFee"
|
||||
layout="@layout/layout_send_network_fee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<include
|
||||
android:id="@+id/clNetworkFee"
|
||||
layout="@layout/layout_send_total"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/TapButtonWithIcon"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:fontFamily="@font/saira_semi_condensed_regular"
|
||||
android:text="@string/send_btn_send"
|
||||
app:icon="@drawable/ic_arrow_right" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -11,14 +11,14 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="@string/send_network_fee"
|
||||
android:text="@string/send_fee"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/include"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/flExpandCollapse"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/include" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<include
|
||||
android:id="@+id/include"
|
||||
android:id="@+id/flExpandCollapse"
|
||||
layout="@layout/btn_expand_collapse"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -26,51 +26,58 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/chipGroup"
|
||||
<LinearLayout
|
||||
android:id="@+id/llFeeContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:translationY="-8dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/include"
|
||||
app:singleLine="true"
|
||||
app:singleSelection="true">
|
||||
app:layout_constraintTop_toBottomOf="@+id/flExpandCollapse">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipLow"
|
||||
style="@style/ChipNetworkFee"
|
||||
android:layout_width="wrap_content"
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/chipGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_network_chip_fee_low" />
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:translationY="-8dp"
|
||||
app:singleLine="true"
|
||||
app:singleSelection="true">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipNormal"
|
||||
style="@style/ChipNetworkFee"
|
||||
android:layout_width="wrap_content"
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipLow"
|
||||
style="@style/ChipNetworkFee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_fee_low" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipNormal"
|
||||
style="@style/ChipNetworkFee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_fee_normal" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipPriority"
|
||||
style="@style/ChipNetworkFee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_fee_priority" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/swIncludeFee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_network_chip_fee_normal" />
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chipPriority"
|
||||
style="@style/ChipNetworkFee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_network_chip_fee_priority" />
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/switch1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. At tristique ornare auctor in."
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipGroup" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -36,11 +36,13 @@
|
|||
<string name="error_creating_payid">Error response while creating PayID.</string>
|
||||
|
||||
|
||||
<string name="send_title">Send</string>
|
||||
<string name="send_address_or_payid">Address or PayID</string>
|
||||
<string name="send_network_fee">Network fee</string>
|
||||
<string name="send_network_chip_fee_low">Low</string>
|
||||
<string name="send_network_chip_fee_normal">Normal</string>
|
||||
<string name="send_network_chip_fee_priority">Priority</string>
|
||||
<string name="send_fee">Network fee</string>
|
||||
<string name="send_fee_include">Include fee</string>
|
||||
<string name="send_fee_low">Low</string>
|
||||
<string name="send_fee_normal">Normal</string>
|
||||
<string name="send_fee_priority">Priority</string>
|
||||
<string name="send_btn_send">Send</string>
|
||||
<string name="send_total">Total</string>
|
||||
<string name="send_total_amount">Amount</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue