Updated on 2026-08-14

This commit is contained in:
Tangem 2022-02-24 12:19:07 +00:00
commit 3a8b7851cc
81 changed files with 2145 additions and 1381 deletions

View file

@ -1,22 +1,22 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
android {
compileSdkVersion 30
compileSdkVersion 31
defaultConfig {
applicationId "com.tangem.wallet"
versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 1
versionName project.hasProperty('versionName') ? project.property('versionName') : "1.0.0.-SNAPSHOT"
minSdkVersion 21
targetSdkVersion 30
targetSdkVersion 31
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
androidExtensions {
experimental = true
buildFeatures {
compose true
viewBinding true
}
buildTypes {
release {
@ -47,12 +47,14 @@ android {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled false
}
composeOptions {
kotlinCompilerExtensionVersion '1.1.0'
}
packagingOptions {
exclude 'lib/x86_64/darwin/libscrypt.dylib'
exclude 'lib/x86_64/freebsd/libscrypt.so'
@ -67,13 +69,13 @@ repositories {
dependencies {
implementation fileTree(include: ['*.aar'], dir: 'libs')
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'com.google.android.material:material:1.4.0'
implementation "androidx.core:core-ktx:1.6.0"
implementation 'com.google.android.play:core:1.10.2'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.google.android.material:material:1.5.0'
implementation "androidx.core:core-ktx:1.7.0"
implementation 'com.google.android.play:core:1.10.3'
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
@ -138,6 +140,15 @@ dependencies {
// Google Pay
implementation 'com.google.android.gms:play-services-wallet:19.1.0'
//Compose
implementation 'androidx.activity:activity-compose:1.4.0'
implementation 'androidx.compose.material:material:1.1.0'
implementation 'androidx.compose.animation:animation:1.1.0'
implementation 'androidx.compose.ui:ui-tooling:1.1.0'
implementation "com.google.accompanist:accompanist-appcompat-theme:0.23.0"
implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6'
testImplementation 'junit:junit:4.13.2'
testImplementation "com.google.truth:truth:1.1.3"
androidTestImplementation 'androidx.test.ext:junit:1.1.3'

View file

@ -44,6 +44,7 @@
android:value="true" />
<activity
android:exported="true"
android:name="com.tangem.tap.MainActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"

View file

@ -6,6 +6,7 @@ import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.Snackbar
import com.tangem.TangemSdk
import com.tangem.operations.backup.BackupService
@ -25,7 +26,7 @@ import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.shop.redux.ShopAction
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_main.*
import com.tangem.wallet.databinding.ActivityMainBinding
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -63,6 +64,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
private var snackbar: Snackbar? = null
private val dialogManager = DialogManager()
private val intentHandler = IntentHandler()
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -102,7 +104,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
override fun onResume() {
super.onResume()
notificationsHandler = NotificationsHandler(fragment_container)
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
val isScannedBefore = store.state.globalState.scanResponse != null
@ -139,7 +141,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
if (snackbar != null) return
snackbar = Snackbar.make(
fragment_container, getString(text), Snackbar.LENGTH_INDEFINITE
binding.fragmentContainer, getString(text), Snackbar.LENGTH_INDEFINITE
)
if (buttonTitle != null && action != null) {
snackbar?.setAction(getString(buttonTitle), action)

View file

@ -37,6 +37,11 @@ fun Context.getColorCompat(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(this, colorRes)
}
@ColorInt
fun View.getColor(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(context, colorRes)
}
fun View.getString(@StringRes id: Int): String {
return context.getString(id)
}
@ -169,4 +174,8 @@ fun Context.safeStartActivity(
} finally {
finally?.invoke()
}
}
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
return context.getString(resId, formatArgs)
}

View file

@ -5,15 +5,18 @@ import android.view.View
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidgetState
import com.tangem.tap.domain.twins.TwinsCardWidget
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.test_leapfrog_fragment.*
import com.tangem.wallet.databinding.TestLeapfrogFragmentBinding
class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) {
private lateinit var twinsCardWidget: TwinsCardWidget
private val binding: TestLeapfrogFragmentBinding by viewBinding(TestLeapfrogFragmentBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -28,30 +31,29 @@ class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) {
val leapfrog = LeapfrogWidget(leapfrogContainer)
twinsCardWidget = TwinsCardWidget(leapfrog) { 200f }
btn_twin_welcome.setOnClickListener {
binding.btnTwinWelcome.setOnClickListener {
twinsCardWidget.toWelcome()
}
btn_twin_to_leapfrog.setOnClickListener {
binding.btnTwinToLeapfrog.setOnClickListener {
twinsCardWidget.toLeapfrog()
}
btn_twin_activate.setOnClickListener {
binding.btnTwinActivate.setOnClickListener {
twinsCardWidget.toActivate()
}
btn_lp_init.setOnClickListener {
binding.btnLpInit.setOnClickListener {
leapfrog.initViews()
}
btn_lp_unfold.setOnClickListener {
binding.btnLpUnfold.setOnClickListener {
leapfrog.unfold()
}
btn_lp_fold.setOnClickListener {
binding.btnLpFold.setOnClickListener {
leapfrog.fold()
}
btn_lp_leap.setOnClickListener {
binding.btnLpLeap.setOnClickListener {
leapfrog.leap()
}
btn_lp_leap_back.setOnClickListener {
binding.btnLpLeapBack.setOnClickListener {
leapfrog.leapBack()
}
}

View file

@ -5,6 +5,7 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.getDrawable
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.details.redux.ConfirmScreenState
@ -12,12 +13,14 @@ import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_details_confirm.*
import com.tangem.wallet.databinding.FragmentDetailsConfirmBinding
import org.rekotlin.StoreSubscriber
class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm),
StoreSubscriber<DetailsState> {
private val binding: FragmentDetailsConfirmBinding by viewBinding(FragmentDetailsConfirmBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
@ -47,38 +50,39 @@ class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar.setNavigationOnClickListener {
binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
}
override fun newState(state: DetailsState) {
if (activity == null) return
if (activity == null || view == null) return
setState(state)
}
private fun setState(state: DetailsState) = with(binding) {
when (state.confirmScreenState) {
ConfirmScreenState.EraseWallet -> {
toolbar.title = getString(R.string.details_row_title_reset_factory_settings)
tv_warning_description.text = getString(R.string.details_row_title_reset_factory_settings_warning)
btn_confirm.text = getString(R.string.details_row_title_reset_factory_settings)
btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
null, null, getDrawable(R.drawable.ic_send), null
tvWarningDescription.text = getString(R.string.details_row_title_reset_factory_settings_warning)
btnConfirm.text = getString(R.string.details_row_title_reset_factory_settings)
btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
null, null, getDrawable(R.drawable.ic_send), null
)
btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) }
btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) }
}
ConfirmScreenState.LongTap, ConfirmScreenState.AccessCode,
ConfirmScreenState.PassCode -> {
toolbar.title = getString(R.string.details_manage_security_title)
tv_warning_description.text = getString(R.string.details_security_management_warning)
btn_confirm.text = getString(R.string.common_save_changes)
btn_confirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
null, null, getDrawable(R.drawable.ic_save), null
tvWarningDescription.text = getString(R.string.details_security_management_warning)
btnConfirm.text = getString(R.string.common_save_changes)
btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
null, null, getDrawable(R.drawable.ic_save), null
)
btn_confirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) }
btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) }
}
}
}
}

View file

@ -6,6 +6,7 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@ -18,13 +19,13 @@ import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.feedback.FeedbackEmail
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_details.*
import kotlinx.android.synthetic.main.fragment_wallet.toolbar
import com.tangem.wallet.databinding.FragmentDetailsBinding
import org.rekotlin.StoreSubscriber
class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<DetailsState> {
private var currencySelectionDialog = CurrencySelectionDialog()
private val binding: FragmentDetailsBinding by viewBinding(FragmentDetailsBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -55,15 +56,18 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar.setNavigationOnClickListener {
binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
}
override fun newState(state: DetailsState) {
if (activity == null) return
if (activity == null || view == null) return
setState(state)
}
private fun setState(state: DetailsState) = with (binding){
if (state.cardInfo != null) {
val cardId = if (state.isTangemTwins) {
@ -71,61 +75,61 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
} else {
state.cardInfo.cardId
}
tv_card_id.text = cardId
tv_issuer.text = state.cardInfo.issuer
tv_signed_hashes.text = getString(
tvCardId.text = cardId
tvIssuer.text = state.cardInfo.issuer
tvSignedHashes.text = getString(
R.string.details_row_subtitle_signed_hashes_format,
state.cardInfo.signedHashes.toString()
)
}
tv_signed_hashes.show(!state.isTangemTwins)
tv_signed_hashes_title.show(!state.isTangemTwins)
tvSignedHashes.show(!state.isTangemTwins)
tvSignedHashesTitle.show(!state.isTangemTwins)
tv_disclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
tvDisclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
tv_card_tou.show(state.cardTermsOfUseUrl != null)
tv_card_tou.setOnClickListener {
tvCardTou.show(state.cardTermsOfUseUrl != null)
tvCardTou.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = state.cardTermsOfUseUrl
startActivity(intent)
}
if (state.isTangemTwins) {
tv_reset_to_factory.show()
tv_reset_to_factory.text = getText(R.string.details_row_title_twins_recreate)
tv_reset_to_factory.setOnClickListener {
tvResetToFactory.show()
tvResetToFactory.text = getText(R.string.details_row_title_twins_recreate)
tvResetToFactory.setOnClickListener {
store.dispatch(DetailsAction.ReCreateTwinsWallet(state.twinCardsState.cardNumber!!))
}
} else {
tv_reset_to_factory.show()
tv_reset_to_factory.text = getText(R.string.details_row_title_reset_factory_settings)
tv_reset_to_factory.setOnClickListener {
tvResetToFactory.show()
tvResetToFactory.text = getText(R.string.details_row_title_reset_factory_settings)
tvResetToFactory.setOnClickListener {
store.dispatch(DetailsAction.ResetToFactory.Check)
store.dispatch(DetailsAction.ResetToFactory.Proceed)
}
}
tv_create_backup.show(state.createBackupAllowed)
tv_create_backup.setOnClickListener {
tvCreateBackup.show(state.createBackupAllowed)
tvCreateBackup.setOnClickListener {
store.dispatch(DetailsAction.CreateBackup)
}
tv_app_currency.text = state.appCurrencyState.fiatCurrencyName
tvAppCurrency.text = state.appCurrencyState.fiatCurrencyName
tv_app_currency_title.setOnClickListener {
tvAppCurrencyTitle.setOnClickListener {
store.dispatch(DetailsAction.AppCurrencyAction.ChooseAppCurrency)
}
tv_send_feedback.setOnClickListener {
tvSendFeedback.setOnClickListener {
store.dispatch(GlobalAction.SendFeedback(FeedbackEmail()))
}
tv_wallet_connect.show(state.scanResponse?.card?.isMultiwalletAllowed == true)
tv_wallet_connect.setOnClickListener {
tvWalletConnect.show(state.scanResponse?.card?.isMultiwalletAllowed == true)
tvWalletConnect.setOnClickListener {
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
}
ll_manage_security.setOnClickListener {
llManageSecurity.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.CheckCurrentSecurityOption(state.scanResponse!!.card))
}
@ -135,7 +139,7 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
SecurityOption.AccessCode -> R.string.details_manage_security_access_code
null -> null
}
currentSecurity?.let { tv_security.text = getString(it) }
currentSecurity?.let { tvSecurity.text = getString(it) }
if (state.appCurrencyState.showAppCurrencyDialog &&
!state.appCurrencyState.fiatCurrencies.isNullOrEmpty()) {
@ -147,7 +151,6 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
} else {
currencySelectionDialog.clear()
}
}
}

View file

@ -5,6 +5,7 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.common.card.Card
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction
@ -13,14 +14,17 @@ import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_details_confirm.toolbar
import kotlinx.android.synthetic.main.fragment_details_security.*
import com.tangem.wallet.databinding.FragmentDetailsSecurityBinding
import org.rekotlin.StoreSubscriber
import java.util.*
class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
StoreSubscriber<DetailsState> {
private val binding: FragmentDetailsSecurityBinding by viewBinding(
FragmentDetailsSecurityBinding::bind
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
@ -49,14 +53,14 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar.setNavigationOnClickListener {
binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
setOnClickListeners()
}
private fun setOnClickListeners() {
btn_save_changes.setOnClickListener {
binding.btnSaveChanges.setOnClickListener {
store.state.detailsState.securityScreenState?.selectedOption?.let {
store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection(it))
}
@ -64,7 +68,8 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
}
override fun newState(state: DetailsState) {
if (activity == null) return
if (activity == null || view == null) return
selectSecurityOption(state.securityScreenState?.selectedOption)
for (option in SecurityOption.values()) {
setupOption(
@ -73,18 +78,18 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
?: EnumSet.noneOf(SecurityOption::class.java)
)
}
tv_access_code_unavailable_disclaimer.show(
binding.tvAccessCodeUnavailableDisclaimer.show(
state.scanResponse?.card?.backupStatus == Card.BackupStatus.NoBackup
)
}
private fun selectSecurityOption(securityOption: SecurityOption?) {
radiobutton_long_tap.isChecked =
securityOption == SecurityOption.LongTap && radiobutton_long_tap.isEnabled
radiobutton_passcode.isChecked =
securityOption == SecurityOption.PassCode && radiobutton_passcode.isEnabled
radiobutton_access_code.isChecked =
securityOption == SecurityOption.AccessCode && radiobutton_access_code.isEnabled
private fun selectSecurityOption(securityOption: SecurityOption?) = with(binding) {
radiobuttonLongTap.isChecked =
securityOption == SecurityOption.LongTap && radiobuttonLongTap.isEnabled
radiobuttonPasscode.isChecked =
securityOption == SecurityOption.PassCode && radiobuttonPasscode.isEnabled
radiobuttonAccessCode.isChecked =
securityOption == SecurityOption.AccessCode && radiobuttonAccessCode.isEnabled
}
private fun setupOption(option: SecurityOption, allowedOptions: EnumSet<SecurityOption>) {
@ -95,46 +100,46 @@ class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
}
}
private fun enableLongTap(enable: Boolean) {
group_long_tap.show(enable)
private fun enableLongTap(enable: Boolean) = with(binding) {
groupLongTap.show(enable)
val alpha = if (enable) 1f else 0.5f
tv_long_tap_description.alpha = alpha
tv_long_tap_title.alpha = alpha
radiobutton_long_tap.alpha = alpha
radiobutton_long_tap.isEnabled = enable
v_long_tap.isClickable = enable
tvLongTapDescription.alpha = alpha
tvLongTapTitle.alpha = alpha
radiobuttonLongTap.alpha = alpha
radiobuttonLongTap.isEnabled = enable
vLongTap.isClickable = enable
if (enable) {
v_long_tap.setOnClickListener {
vLongTap.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.LongTap))
}
}
}
private fun enablePasscode(enable: Boolean) {
group_passcode.show(enable)
private fun enablePasscode(enable: Boolean) = with(binding) {
groupPasscode.show(enable)
val alpha = if (enable) 1f else 0.5f
tv_passcode_description.alpha = alpha
tv_passcode_title.alpha = alpha
radiobutton_passcode.alpha = alpha
radiobutton_passcode.isEnabled = enable
v_passcode.isClickable = enable
tvPasscodeDescription.alpha = alpha
tvPasscodeTitle.alpha = alpha
radiobuttonPasscode.alpha = alpha
radiobuttonPasscode.isEnabled = enable
vPasscode.isClickable = enable
if (enable) {
v_passcode.setOnClickListener {
vPasscode.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.PassCode))
}
}
}
private fun enableAccessCode(enable: Boolean) {
group_access_code.show(enable)
private fun enableAccessCode(enable: Boolean) = with(binding) {
groupAccessCode.show(enable)
val alpha = if (enable) 1f else 0.5f
tv_access_code_description.alpha = alpha
tv_access_code_title.alpha = alpha
radiobutton_access_code.alpha = alpha
radiobutton_access_code.isEnabled = enable
v_access_code.isClickable = enable
tvAccessCodeDescription.alpha = alpha
tvAccessCodeTitle.alpha = alpha
radiobuttonAccessCode.alpha = alpha
radiobuttonAccessCode.isEnabled = enable
vAccessCode.isClickable = enable
if (enable) {
v_access_code.setOnClickListener {
vAccessCode.setOnClickListener {
store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.AccessCode))
}
}

View file

@ -3,14 +3,15 @@ package com.tangem.tap.features.details.ui.walletconnect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.item_wallet_connect_session.view.*
class WalletConnectSessionsAdapter
: ListAdapter<WalletConnectSession, WalletConnectSessionsAdapter.SessionsViewHolder>(
@ -41,15 +42,14 @@ class WalletConnectSessionsAdapter
RecyclerView.ViewHolder(view) {
fun bind(session: WalletConnectSession) {
view.tv_card_id.text = view.context.getString(
view.findViewById<TextView>(R.id.tv_card_id).text = view.context.getString(
R.string.wallet_connect_card_number, session.wallet.cardId
)
view.tv_d_app_name.text = session.peerMeta.name
view.findViewById<TextView>(R.id.tv_d_app_name).text = session.peerMeta.name
view.btn_disconnect.setOnClickListener {
view.findViewById<Chip>(R.id.btn_disconnect).setOnClickListener {
store.dispatch(WalletConnectAction.DisconnectSession(session.session))
}
}
}
}

View file

@ -1,19 +1,20 @@
package com.tangem.tap.features.details.ui.walletconnect
import android.app.Dialog
import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_wallet_connect_sessions.*
import com.tangem.wallet.databinding.FragmentWalletConnectSessionsBinding
import org.rekotlin.StoreSubscriber
@ -21,7 +22,9 @@ class WalletConnectSessionsFragment : Fragment(R.layout.fragment_wallet_connect_
StoreSubscriber<WalletConnectState> {
private lateinit var walletConnectSessionsAdapter: WalletConnectSessionsAdapter
private var dialog: Dialog? = null
private val binding: FragmentWalletConnectSessionsBinding by viewBinding(
FragmentWalletConnectSessionsBinding::bind
)
override fun onCreate(savedInstanceState: Bundle?) {
@ -53,7 +56,7 @@ class WalletConnectSessionsFragment : Fragment(R.layout.fragment_wallet_connect_
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar.setNavigationOnClickListener {
binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
setOnClickListeners()
@ -61,27 +64,31 @@ class WalletConnectSessionsFragment : Fragment(R.layout.fragment_wallet_connect_
}
private fun setOnClickListeners() {
fab_open_session.setOnClickListener {
binding.fabOpenSession.setOnClickListener {
store.dispatch(WalletConnectAction.StartWalletConnect(activity = requireActivity()))
}
}
private fun setupTransactionsRecyclerView() {
private fun setupTransactionsRecyclerView() = with(binding) {
walletConnectSessionsAdapter = WalletConnectSessionsAdapter()
rv_wallet_connect_sessions.layoutManager = LinearLayoutManager(requireContext())
rv_wallet_connect_sessions.adapter = walletConnectSessionsAdapter
rvWalletConnectSessions.layoutManager = LinearLayoutManager(requireContext())
rvWalletConnectSessions.adapter = walletConnectSessionsAdapter
walletConnectSessionsAdapter.submitList(store.state.walletConnectState.sessions)
}
override fun newState(state: WalletConnectState) {
if (activity == null) return
if (activity == null || view == null) return
fab_open_session.show(!state.loading)
pb_wallet_connect.show(state.loading)
binding.fabOpenSession.show(!state.loading)
binding.pbWalletConnect.show(state.loading)
walletConnectSessionsAdapter.submitList(state.sessions)
rv_wallet_connect_sessions.show(state.sessions.isNotEmpty())
tv_no_sessions.show(state.sessions.isEmpty())
tv_no_sessions_title.show(state.sessions.isEmpty())
showUpdatedList(state.sessions)
}
private fun showUpdatedList(sessions: List<WalletConnectSession>) = with(binding) {
walletConnectSessionsAdapter.submitList(sessions)
rvWalletConnectSessions.show(sessions.isNotEmpty())
tvNoSessions.show(sessions.isEmpty())
tvNoSessionsTitle.show(sessions.isEmpty())
}
}

View file

@ -5,18 +5,21 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_disclaimer.*
import com.tangem.wallet.databinding.FragmentDisclaimerBinding
import org.rekotlin.StoreSubscriber
class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer),
StoreSubscriber<DisclaimerState> {
private val binding: FragmentDisclaimerBinding by viewBinding(FragmentDisclaimerBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
@ -45,22 +48,22 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar.setNavigationOnClickListener {
binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo())
}
setOnClickListeners()
}
private fun setOnClickListeners() {
btn_accept.setOnClickListener { store.dispatch(DisclaimerAction.AcceptDisclaimer) }
binding.btnAccept.setOnClickListener { store.dispatch(DisclaimerAction.AcceptDisclaimer) }
}
override fun newState(state: DisclaimerState) {
if (activity == null) return
if (activity == null || view == null) return
if (state.accepted) {
half_transparent_overlay.hide()
btn_accept.hide()
binding.halfTransparentOverlay.hide()
binding.btnAccept.hide()
}
}
}

View file

@ -2,35 +2,25 @@ package com.tangem.tap.features.home
import android.os.Bundle
import android.view.View
import androidx.transition.TransitionInflater
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.common.redux.navigation.ShareElement
import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget
import com.tangem.tap.common.toggleWidget.ViewStateWidget
import com.tangem.tap.common.transitions.FrontCardEnterTransition
import com.tangem.tap.common.transitions.FrontCardExitTransition
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import com.google.accompanist.appcompattheme.AppCompatTheme
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_onboarding_main.*
import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
import kotlinx.android.synthetic.main.layout_onboarding_home_top.*
import kotlinx.android.synthetic.main.view_bg_home.*
import org.rekotlin.StoreSubscriber
class HomeFragment : BaseOnboardingFragment<HomeState>() {
class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState> {
private lateinit var btnScanCard: ViewStateWidget
override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_home_top
var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val inflater = TransitionInflater.from(requireContext())
exitTransition = inflater.inflateTransition(R.transition.fade)
store.dispatch(HomeAction.Init)
}
@ -39,50 +29,36 @@ class HomeFragment : BaseOnboardingFragment<HomeState>() {
store.dispatch(BackupAction.CheckForUnfinishedBackup)
toolbar.hide()
val shareTransition = FragmentShareTransition(
listOf(
ShareElement(imv_front_card, ShareElement.imvFrontCard),
ShareElement(imv_back_card, ShareElement.imvBackCard),
ShareElement(bg_circle_large),
ShareElement(bg_circle_medium),
ShareElement(bg_circle_min),
),
FrontCardEnterTransition(),
FrontCardExitTransition()
)
store.dispatch(HomeAction.SetFragmentShareTransition(shareTransition))
tv_header.setText(R.string.home_welcome_header)
tv_body.setText(R.string.home_welcome_body)
btn_main_action.setText(R.string.home_button_scan)
btn_main_action.setOnClickListener { store.dispatch(HomeAction.ReadCard) }
btn_alternative_action.setText(R.string.home_button_get_new_card)
btn_alternative_action.setOnClickListener { store.dispatch(HomeAction.GoToShop) }
btnScanCard = IndeterminateProgressButtonWidget(btn_main_action, progress)
getView()?.findViewById<ComposeView>(R.id.cv_stories)?.setContent {
AppCompatTheme {
StoriesScreen(
homeState,
onScanButtonClick = { store.dispatch(HomeAction.ReadCard) },
onShopButtonClick = { store.dispatch(HomeAction.GoToShop) }
)
}
}
}
override fun subscribeToStore() {
override fun onStart() {
super.onStart()
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.homeState == newState.homeState
}.select { it.homeState }
}
storeSubscribersList.add(this)
}
override fun onStop() {
super.onStop()
store.unsubscribe(this)
}
override fun newState(state: HomeState) {
if (activity == null) return
if (activity == null || view == null) return
btnScanCard.changeState(state.btnScanState.progressState)
btnScanCard.mainView.isEnabled = state.btnScanState.enabled
homeState.value = state
}
override fun onDestroyView() {
store.dispatch(HomeAction.SetFragmentShareTransition(null))
super.onDestroyView()
}
}

View file

@ -0,0 +1,146 @@
package com.tangem.tap.features.home.compose
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.scaleIn
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.wallet.R
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun FirstStoriesContent(
paused: Boolean, duration: Int = 8_000,
hideContent: (Boolean) -> Unit
) {
val screenState = remember { mutableStateOf(StartingScreenState.INIT) }
val progress = remember { Animatable(0f) }
LaunchedEffect(paused) {
if (paused) {
progress.stop()
} else {
progress.animateTo(
targetValue = 2f,
animationSpec = tween(
durationMillis = duration,
easing = LinearEasing
)
)
}
}
when (progress.value) {
in 0f..0.2f -> screenState.value = StartingScreenState.INIT
in 0.2f..0.3f -> screenState.value = StartingScreenState.BUY
in 0.3f..0.4f -> screenState.value = StartingScreenState.STORE
in 0.4f..0.5f -> screenState.value = StartingScreenState.SEND
in 0.5f..0.6f -> screenState.value = StartingScreenState.PAY
in 0.6f..0.7f -> screenState.value = StartingScreenState.EXCHANGE
in 0.7f..0.8f -> screenState.value = StartingScreenState.BORROW
in 0.8f..1f -> screenState.value = StartingScreenState.LEND
in 1f..1.2f -> screenState.value = StartingScreenState.SHOW_CARD
in 1.2f..2f -> screenState.value = StartingScreenState.MEET_TANGEM
}
if (screenState.value == StartingScreenState.INIT) hideContent(true)
if (screenState.value == StartingScreenState.BUY) hideContent(false)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier =
Modifier
.fillMaxSize(),
) {
val text = when (screenState.value) {
StartingScreenState.INIT -> null
StartingScreenState.BUY -> R.string.story_meet_buy
StartingScreenState.STORE -> R.string.story_meet_store
StartingScreenState.SEND -> R.string.story_meet_send
StartingScreenState.PAY -> R.string.story_meet_pay
StartingScreenState.EXCHANGE -> R.string.story_meet_exchange
StartingScreenState.BORROW -> R.string.story_meet_borrow
StartingScreenState.LEND -> R.string.story_meet_lend
StartingScreenState.SHOW_CARD -> R.string.story_meet_title
StartingScreenState.MEET_TANGEM -> R.string.story_meet_title
}
if (screenState.value != StartingScreenState.MEET_TANGEM) {
Text(
text = text?.let { stringResource(text) } ?: "",
fontSize = 60.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(start = 40.dp, end = 40.dp)
.alpha(if (screenState.value == StartingScreenState.SHOW_CARD) 0f else 1f),
color = Color.White,
textAlign = TextAlign.Center,
)
}
AnimatedVisibility(
visible = screenState.value == StartingScreenState.MEET_TANGEM,
enter = slideInVertically() { it / 2 }
) {
Text(
text = text?.let { stringResource(text) } ?: "",
fontSize = 60.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(start = 40.dp, end = 40.dp),
color = Color.White,
textAlign = TextAlign.Center
)
}
AnimatedVisibility(
visible = screenState.value == StartingScreenState.SHOW_CARD ||
screenState.value == StartingScreenState.MEET_TANGEM,
enter = scaleIn(initialScale = 3f)
) {
Image(
painter = painterResource(
id = R.drawable.meet_tangem
),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)
}
}
}
enum class StartingScreenState {
INIT, BUY, STORE, SEND, PAY, EXCHANGE, BORROW, LEND, SHOW_CARD, MEET_TANGEM
}
@Preview
@Composable
fun Stories1Preview() {
FirstStoriesContent(false, 7_000) {}
}

View file

@ -0,0 +1,66 @@
package com.tangem.tap.features.home.compose
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.wallet.R
import androidx.compose.foundation.layout.fillMaxWidth as fillMaxWidth1
@Composable
fun HomeButtons(
isDarkBackground: Boolean, modifier: Modifier = Modifier,
onScanButtonClick: () -> Unit,
onShopButtonClick: () -> Unit
) {
val darkColorBackground = Color(0xFF26292E)
val darkColorButton = Color(0xFF080C10)
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = modifier.fillMaxWidth1()
) {
Spacer(modifier = Modifier.size(16.dp))
Button(
modifier = Modifier
.weight(1f)
.height(42.dp),
onClick = onScanButtonClick,
colors = ButtonDefaults.textButtonColors(
backgroundColor = if (isDarkBackground) darkColorBackground else Color.White,
contentColor = if (isDarkBackground) Color.White else darkColorButton
)
) {
Text(
text = stringResource(id = R.string.home_button_scan),
fontWeight = FontWeight.Medium,
fontSize = 16.sp
)
}
Spacer(modifier = Modifier.size(8.dp))
Button(
modifier = Modifier
.weight(1f)
.height(42.dp),
onClick = onShopButtonClick,
colors = ButtonDefaults.textButtonColors(
backgroundColor = if (isDarkBackground) Color.White else darkColorBackground,
contentColor = if (isDarkBackground) darkColorButton else Color.White
)
) {
Text(
text = stringResource(id = R.string.home_button_order),
fontWeight = FontWeight.Medium,
fontSize = 16.sp
)
}
Spacer(modifier = Modifier.size(16.dp))
}
}

View file

@ -0,0 +1,145 @@
package com.tangem.tap.features.home.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.wallet.R
import kotlin.math.max
@Composable
fun StoriesScreen(
homeState: MutableState<HomeState> = mutableStateOf(HomeState()),
onScanButtonClick: () -> Unit,
onShopButtonClick: () -> Unit
) {
val steps = 6
val stepDuration = 8_000
val currentStep = remember { mutableStateOf(1) }
val isDarkBackground = currentStep.value !in 3..5
val goToPreviousScreen = {
currentStep.value = max(1, currentStep.value - 1)
}
val goToNextScreen = {
currentStep.value = if (currentStep.value < steps) currentStep.value + 1 else 1
}
val isPressed = remember { mutableStateOf(false) }
val needsToBePaused =
remember(homeState) { mutableStateOf(homeState.value.btnScanState.progressState == ProgressState.Loading) }
val pause = isPressed.value || needsToBePaused.value
val hideContent = remember { mutableStateOf(true) }
Box(
Modifier
.fillMaxSize()
.background(Color(0xFF090E13))
.pointerInput(Unit) {
val maxWidth = this.size.width
detectTapGestures(
onPress = {
val pressStartTime = System.currentTimeMillis()
isPressed.value = true
this.tryAwaitRelease()
val pressEndTime = System.currentTimeMillis()
val totalPressTime = pressEndTime - pressStartTime
if (totalPressTime < 200) {
val isTapOnRightTwoTiers = (it.x > (maxWidth / 2))
if (isTapOnRightTwoTiers) {
goToNextScreen()
} else {
goToPreviousScreen()
}
}
isPressed.value = false
},
)
}
) {
if (!isDarkBackground) {
Image(
painter = painterResource(id = R.drawable.ic_overlay),
contentDescription = null,
modifier = Modifier
.fillMaxSize(),
contentScale = ContentScale.FillBounds
)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
Modifier
.fillMaxSize()
) {
Spacer(modifier = Modifier.size(24.dp))
StoriesProgressBar(
steps = steps,
currentStep = currentStep.value,
// paused = isPressed.value,
stepDuration = stepDuration,
paused = pause,
onStepFinished = goToNextScreen,
)
Image(
painter = painterResource(id = R.drawable.ic_tangem_logo),
contentDescription = null,
contentScale = ContentScale.FillHeight,
modifier = Modifier
.padding(start = 16.dp, top = 10.dp)
.height(17.dp)
.alpha(if (hideContent.value) 0f else 1f)
.align(Alignment.Start),
colorFilter = if (isDarkBackground) null else ColorFilter.tint(Color.Black)
)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
when (currentStep.value) {
1 -> FirstStoriesContent(pause, stepDuration) { hideContent.value = it }
2 -> StoriesRevolutionaryWallet()
3 -> StoriesUltraSecureBackup()
4 -> StoriesThousandsOfCurrencies()
5 -> StoriesWeb3()
6 -> StoriesWalletForEveryone()
}
}
HomeButtons(
isDarkBackground = isDarkBackground,
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(37.dp),
onScanButtonClick = onScanButtonClick,
onShopButtonClick = onShopButtonClick
)
}
}
@Preview
@Composable
fun InstagramScreenPreview() {
StoriesScreen(onScanButtonClick = {}, onShopButtonClick = {})
}

View file

@ -0,0 +1,120 @@
package com.tangem.tap.features.home.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
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 com.tangem.wallet.R
@Composable
fun StoriesGeneralContent(
titleText: String,
subtitleText: String,
imageSource: Int?,
isDarkBackground: Boolean,
imageComposable: (() -> Unit)? = null,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier =
Modifier
.fillMaxWidth()
.fillMaxHeight(),
) {
Text(
text = titleText,
fontSize = 32.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(start = 40.dp, end = 40.dp),
color = if (isDarkBackground) Color.White else Color(0xFF090E13),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.size(16.dp))
Text(
text = subtitleText,
fontSize = 20.sp,
fontWeight = FontWeight.Normal,
modifier = Modifier
.padding(start = 40.dp, end = 40.dp),
color = Color(0xFFA6AAAD),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.size(25.dp))
if (imageSource != null) {
Image(
painter = painterResource(id = imageSource),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)
}
imageComposable?.invoke()
}
}
@Composable
fun StoriesRevolutionaryWallet() {
StoriesGeneralContent(
titleText = stringResource(id = R.string.story_awe_title),
subtitleText = stringResource(id = R.string.story_awe_description),
imageSource = R.drawable.revolutionary_wallet,
isDarkBackground = true
)
}
@Composable
fun StoriesUltraSecureBackup() {
StoriesGeneralContent(
titleText = stringResource(id = R.string.story_backup_title),
subtitleText = stringResource(id = R.string.story_backup_description),
imageSource = R.drawable.floating_cards,
isDarkBackground = false
)
}
@Composable
fun StoriesThousandsOfCurrencies() {
StoriesGeneralContent(
titleText = stringResource(id = R.string.story_currencies_title),
subtitleText = stringResource(id = R.string.story_currencies_description),
imageSource = R.drawable.thousands_of_currencies,
isDarkBackground = false
)
}
@Composable
fun StoriesWeb3() {
StoriesGeneralContent(
titleText = stringResource(id = R.string.story_web3_title),
subtitleText = stringResource(id = R.string.story_web3_description),
imageSource = R.drawable.web_3,
isDarkBackground = false
)
}
@Composable
fun StoriesWalletForEveryone() {
StoriesGeneralContent(
titleText = stringResource(id = R.string.story_finish_title),
subtitleText = stringResource(id = R.string.story_finish_description),
imageSource = R.drawable.wallet_for_everyone,
isDarkBackground = true
)
}

View file

@ -0,0 +1,80 @@
package com.tangem.tap.features.home.compose
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun StoriesProgressBar(
steps: Int,
currentStep: Int,
paused: Boolean = false,
stepDuration: Int = 8_000,
onStepFinished: () -> Unit = {},
) {
val progress = remember(currentStep) { Animatable(0f) }
LaunchedEffect(paused, currentStep) {
if (paused) {
progress.stop()
} else {
progress.animateTo(
targetValue = 1f,
animationSpec = tween(
durationMillis = (stepDuration * (1f - progress.value)).toInt(),
easing = LinearEasing
)
)
progress.snapTo(0f)
onStepFinished()
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
// .height()
.padding(start = 9.dp, end = 9.dp, top = 16.dp),
) {
for (index in 1..steps) {
Row(
modifier = Modifier
.height(2.dp)
.weight(1f)
.background(Color.White.copy(alpha = 0.4f))
) {
Box(
modifier = Modifier
.background(Color.White)
.fillMaxHeight().let {
when (index) {
currentStep -> it.fillMaxWidth(progress.value)
in 0..currentStep -> it.fillMaxWidth(1f)
else -> it
}
},
) {}
}
if (index != steps) {
Spacer(modifier = Modifier.width(4.dp))
}
}
}
}
@Preview
@Composable
fun StoriesProgressBarPreview() {
StoriesProgressBar(steps = 3, currentStep = 2, paused = false) { }
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.home.redux
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import org.rekotlin.Action
sealed class HomeAction : Action {
@ -10,10 +9,9 @@ sealed class HomeAction : Action {
object GoToShop : HomeAction()
// internal
data class ShouldScanCardOnResume(val shouldScanCard: Boolean):HomeAction()
data class ShouldScanCardOnResume(val shouldScanCard: Boolean) : HomeAction()
object Init : HomeAction()
data class SetFragmentShareTransition(val shareTransition: FragmentShareTransition?) : HomeAction()
data class SetTermsOfUseState(val isDisclaimerAccepted: Boolean) : HomeAction()
data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction()

View file

@ -73,14 +73,14 @@ private fun handleReadCard() {
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
val navigateTo = OnboardingHelper.whereToNavigate(scanResponse)
store.dispatch(GlobalAction.Onboarding.Start(scanResponse))
navigateTo(navigateTo, store.state.homeState.shareTransition)
navigateTo(navigateTo)
} else {
scope.launch {
store.onCardScanned(scanResponse)
withMainContext {
if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly))
navigateTo(AppScreen.OnboardingTwins, store.state.homeState.shareTransition)
navigateTo(AppScreen.OnboardingTwins)
} else {
navigateTo(AppScreen.Wallet, null)
}
@ -98,7 +98,7 @@ private fun changeButtonState(state: ButtonState) {
store.dispatch(HomeAction.ChangeScanCardButtonState(btnState))
}
private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition?) {
private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition? = null) {
post(DELAY_SDK_DIALOG_CLOSE) {
changeButtonState(ButtonState.ENABLED)
store.dispatch(NavigationAction.NavigateTo(screen, transition))

View file

@ -15,9 +15,6 @@ private fun internalReduce(action: Action, state: AppState): HomeState {
var state = state.homeState
when (action) {
is HomeAction.SetFragmentShareTransition -> {
state = state.copy(shareTransition = action.shareTransition)
}
is HomeAction.ShouldScanCardOnResume -> {
state = state.copy(shouldScanCardOnResume = action.shouldScanCard)
}

View file

@ -1,12 +1,10 @@
package com.tangem.tap.features.home.redux
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.features.send.redux.states.ButtonState
import org.rekotlin.StateType
data class HomeState(
val shouldScanCardOnResume: Boolean = false,
val shareTransition: FragmentShareTransition? = null,
val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED),
) : StateType

View file

@ -1,6 +1,8 @@
package com.tangem.tap.features.onboarding
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.common.extensions.copyToClipboard
@ -8,11 +10,11 @@ import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchShare
import com.tangem.tap.common.extensions.dispatchToastNotification
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.dialog_onboarding_address_info.*
import kotlinx.android.synthetic.main.layout_pseudo_toolbar.*
import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
/**
[REDACTED_AUTHOR]
@ -22,30 +24,40 @@ class AddressInfoBottomSheetDialog(
context: Context
) : BottomSheetDialog(context) {
init {
this.setContentView(R.layout.dialog_onboarding_address_info)
var binding: DialogOnboardingAddressInfoBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DialogOnboardingAddressInfoBinding
.inflate(LayoutInflater.from(context))
setContentView(binding!!.root)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
setOnCancelListener { store.dispatchDialogHide() }
setOnCancelListener {
store.dispatchDialogHide()
binding = null
}
}
override fun show() {
super.show()
val data = stateDialog.addressData
showData(data = stateDialog.addressData)
}
imv_close.setOnClickListener {
private fun showData(data: AddressData) = with(binding!!) {
pseudoToolbar.imvClose.setOnClickListener {
dismissWithAnimation = true
cancel()
}
imv_qr_code.setImageBitmap(data.qrCode)
tv_address.text = data.address
btn_fl_copy_address.setOnClickListener {
imvQrCode.setImageBitmap(data.qrCode)
tvAddress.text = data.address
btnFlCopyAddress.setOnClickListener {
context.copyToClipboard(data.address)
store.dispatchToastNotification(R.string.copy_toast_msg)
}
btn_fl_share.setOnClickListener {
btnFlShare.setOnClickListener {
store.dispatchShare(data.shareUrl)
}
tv_receive_message.text = getQRReceiveMessage(tv_receive_message.context, stateDialog.currency)
tvReceiveMessage.text = getQRReceiveMessage(tvReceiveMessage.context, stateDialog.currency)
}
}

View file

@ -1,14 +1,12 @@
package com.tangem.tap.features.onboarding.products
import android.os.Bundle
import android.view.View
import com.tangem.tap.common.extensions.inflate
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.transitions.HomeToOnboardingTransition
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_onboarding_main.*
import kotlinx.android.synthetic.main.view_confetti.*
import com.tangem.wallet.databinding.FragmentOnboardingMainBinding
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
import org.rekotlin.StoreSubscriber
/**
@ -16,26 +14,22 @@ import org.rekotlin.StoreSubscriber
*/
abstract class BaseOnboardingFragment<T> : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber<T> {
protected val binding: FragmentOnboardingMainBinding by viewBinding(FragmentOnboardingMainBinding::bind)
protected val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
override fun configureTransitions() {
enterTransition = HomeToOnboardingTransition()
exitTransition = HomeToOnboardingTransition()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onboarding_top_container.inflate(getOnboardingTopContainerId(), true)
}
abstract fun getOnboardingTopContainerId(): Int
protected fun showConfetti(show: Boolean) {
lav_confetti.show(show)
protected fun showConfetti(show: Boolean) = with(binding.vConfetti) {
lavConfetti.show(show)
if (show) {
lav_confetti.playAnimation()
lavConfetti.playAnimation()
} else {
lav_confetti.cancelAnimation()
lavConfetti.cancelAnimation()
}
}
}

View file

@ -26,11 +26,6 @@ import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteStat
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteStep
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_onboarding_main.*
import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
import kotlinx.android.synthetic.main.view_onboarding_progress.*
import kotlinx.android.synthetic.main.view_onboarding_tv_balance.*
/**
[REDACTED_AUTHOR]
@ -41,8 +36,6 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
@ -52,11 +45,11 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
super.onViewCreated(view, savedInstanceState)
addBackPressHandler(this)
imv_front_card.transitionName = ShareElement.imvFrontCard
binding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
startPostponedEnterTransition()
toolbar.setTitle(R.string.onboarding_title)
btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container)
binding.toolbar.setTitle(R.string.onboarding_title)
btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer)
store.dispatch(OnboardingNoteAction.LoadCardArtwork)
store.dispatch(OnboardingNoteAction.DetermineStepOfScreen)
@ -72,19 +65,19 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
}
override fun newState(state: OnboardingNoteState) {
if (activity == null) return
if (activity == null || view == null) return
Picasso.get()
.load(state.cardArtworkUrl)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(imv_front_card)
.load(state.cardArtworkUrl)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(binding.onboardingTopContainer.imvFrontCard)
pb_state.max = state.steps.size - 1
pb_state.progress = state.progress
pbBinding.pbState.max = state.steps.size - 1
pbBinding.pbState.progress = state.progress
when (state.currentStep) {
OnboardingNoteStep.None -> setupNoneState(state)
OnboardingNoteStep.None -> setupNoneState()
OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state)
OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state)
OnboardingNoteStep.Done -> setupDoneState(state)
@ -97,77 +90,82 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
private fun setBalance(state: OnboardingNoteState) {
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
if (state.balanceCriticalError == null) {
val balanceValue = state.walletBalance.value.stripZeroPlainString()
tv_balance_value.text = balanceValue
tv_balance_currency.text = state.walletBalance.currency.currencySymbol
} else {
tv_balance_value.text = ""
tv_balance_currency.text = ""
with (binding.onboardingTopContainer.onboardingTvBalance) {
if (state.balanceCriticalError == null) {
val balanceValue = state.walletBalance.value.stripZeroPlainString()
tvBalanceValue.text = balanceValue
tvBalanceCurrency.text = state.walletBalance.currency.currencySymbol
} else {
tvBalanceValue.text = ""
tvBalanceCurrency.text = ""
}
}
}
private fun setupNoneState(state: OnboardingNoteState) {
btn_main_action.isVisible = false
btn_alternative_action.isVisible = false
private fun setupNoneState() = with(binding.onboardingActionContainer) {
btnMainAction.isVisible = false
btnAlternativeAction.isVisible = false
btnRefreshBalanceWidget.show(false)
tv_header.isVisible = false
tv_body.isVisible = false
tvHeader.isVisible = false
tvBody.isVisible = false
btn_main_action.text = ""
btn_alternative_action.text = ""
tv_header.text = ""
tv_body.text = ""
btnMainAction.text = ""
btnAlternativeAction.text = ""
tvHeader.text = ""
tvBody.text = ""
}
private fun setupCreateWalletState(state: OnboardingNoteState) {
btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) }
btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean)
btn_alternative_action.setOnClickListener { }
private fun setupCreateWalletState(state: OnboardingNoteState) =
with(binding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) }
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction.setOnClickListener { }
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
tv_header.setText(R.string.onboarding_create_wallet_header)
tv_body.setText(R.string.onboarding_create_wallet_body)
tvHeader.setText(R.string.onboarding_create_wallet_header)
tvBody.setText(R.string.onboarding_create_wallet_body)
imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle))
updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet)
binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
requireContext().getDrawableCompat(R.drawable.shape_circle)
)
updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet)
btn_alternative_action.isVisible = false // temporary
}
btnAlternativeAction.isVisible = false // temporary
}
private fun setupTopUpWalletState(state: OnboardingNoteState) {
private fun setupTopUpWalletState(state: OnboardingNoteState) = with(binding.onboardingActionContainer) {
if (state.isBuyAllowed) {
btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto)
btn_main_action.setOnClickListener {
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
btnMainAction.setOnClickListener {
store.dispatch(OnboardingNoteAction.TopUp)
}
btn_alternative_action.isVisible = true
btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address)
btn_alternative_action.setOnClickListener {
btnAlternativeAction.isVisible = true
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
btnAlternativeAction.setOnClickListener {
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
}
} else {
btn_main_action.setText(R.string.onboarding_button_receive_crypto)
btn_main_action.setOnClickListener {
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
btnMainAction.setOnClickListener {
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
}
btn_alternative_action.isVisible = false
btnAlternativeAction.isVisible = false
}
tv_header.setText(R.string.onboarding_top_up_header)
tvHeader.setText(R.string.onboarding_top_up_header)
if (state.balanceNonCriticalError == null) {
tv_body.setText(R.string.onboarding_top_up_body)
tvBody.setText(R.string.onboarding_top_up_body)
} else {
state.walletBalance.amountToCreateAccount?.let { amount ->
val tvBodyMessage = getString(
R.string.onboarding_top_up_body_no_account_error,
amount, state.walletBalance.currency.currencySymbol
)
tv_body.text = tvBodyMessage
tvBody.text = tvBodyMessage
}
}
@ -177,38 +175,43 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
store.dispatch(OnboardingNoteAction.Balance.Update)
}
}
imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
binding.onboardingTopContainer.imvCardBackground
.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet)
}
private fun setupDoneState(state: OnboardingNoteState) {
btn_main_action.setText(R.string.onboarding_done_button_continue)
btn_main_action.setOnClickListener {
private fun setupDoneState(state: OnboardingNoteState) = with(binding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_done_button_continue)
btnMainAction.setOnClickListener {
showConfetti(false)
store.dispatch(OnboardingNoteAction.Done)
}
btn_alternative_action.isVisible = false
btn_alternative_action.setText("")
btn_alternative_action.setOnClickListener { }
btnAlternativeAction.isVisible = false
btnAlternativeAction.setText("")
btnAlternativeAction.setOnClickListener { }
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
tv_header.setText(R.string.onboarding_done_header)
tv_body.setText(R.string.onboarding_done_body)
tvHeader.setText(R.string.onboarding_done_header)
tvBody.setText(R.string.onboarding_done_body)
imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
)
updateConstraints(state.currentStep, R.layout.lp_onboarding_done_activation)
}
private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) {
if (this.previousStep == currentStep) return
val constraintSet = ConstraintSet()
constraintSet.clone(requireContext(), layoutId)
constraintSet.applyTo(onboarding_main_container)
val transition = InternalNoteLayoutTransition()
transition.interpolator = OvershootInterpolator()
TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
with(binding.onboardingTopContainer) {
val constraintSet = ConstraintSet()
constraintSet.clone(requireContext(), layoutId)
constraintSet.applyTo(onboardingMainContainer)
val transition = InternalNoteLayoutTransition()
transition.interpolator = OvershootInterpolator()
TransitionManager.beginDelayedTransition(onboardingMainContainer, transition)
}
}
}

View file

@ -18,18 +18,12 @@ import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOt
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsStep
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_onboarding_main.*
import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
import kotlinx.android.synthetic.main.view_onboarding_progress.*
/**
[REDACTED_AUTHOR]
*/
class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCardsState>() {
override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
@ -39,10 +33,10 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
super.onViewCreated(view, savedInstanceState)
addBackPressHandler(this)
imv_front_card.transitionName = ShareElement.imvFrontCard
binding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
startPostponedEnterTransition()
toolbar.setTitle(R.string.onboarding_title)
binding.toolbar.setTitle(R.string.onboarding_title)
store.dispatch(OnboardingOtherCardsAction.LoadCardArtwork)
store.dispatch(OnboardingOtherCardsAction.DetermineStepOfScreen)
}
@ -57,64 +51,75 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
}
override fun newState(state: OnboardingOtherCardsState) {
if (activity == null) return
if (activity == null || view == null) return
if (state.currentStep == OnboardingOtherCardsStep.None) return
Picasso.get()
.load(state.cardArtworkUrl)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(imv_front_card)
.load(state.cardArtworkUrl)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(binding.onboardingTopContainer.imvFrontCard)
pb_state.max = state.steps.size - 1
pb_state.progress = state.progress
pbBinding.pbState.max = state.steps.size - 1
pbBinding.pbState.progress = state.progress
when (state.currentStep) {
OnboardingOtherCardsStep.CreateWallet -> setupCreateWalletState(state)
OnboardingOtherCardsStep.Done -> setupDoneState(state)
OnboardingOtherCardsStep.CreateWallet -> setupCreateWalletState()
OnboardingOtherCardsStep.Done -> setupDoneState()
}
showConfetti(state.showConfetti)
}
private fun setupCreateWalletState(state: OnboardingOtherCardsState) {
btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
btn_main_action.setOnClickListener { store.dispatch(OnboardingOtherCardsAction.CreateWallet) }
btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean)
btn_alternative_action.setOnClickListener { }
private fun setupCreateWalletState() = with(binding) {
tv_header.setText(R.string.onboarding_create_wallet_header)
tv_body.setText(R.string.onboarding_create_wallet_body)
with(onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction.setOnClickListener { store.dispatch(OnboardingOtherCardsAction.CreateWallet) }
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction.setOnClickListener { }
imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle))
tvHeader.setText(R.string.onboarding_create_wallet_header)
tvBody.setText(R.string.onboarding_create_wallet_body)
btnAlternativeAction.isVisible = false // temporary
}
binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
requireContext().getDrawableCompat(R.drawable.shape_circle)
)
updateConstraints(R.layout.lp_onboarding_create_wallet)
btn_alternative_action.isVisible = false // temporary
}
private fun setupDoneState(state: OnboardingOtherCardsState) {
btn_main_action.setText(R.string.onboarding_done_button_continue)
btn_main_action.setOnClickListener {
private fun setupDoneState() = with(binding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_done_button_continue)
btnMainAction.setOnClickListener {
showConfetti(false)
store.dispatch(OnboardingOtherCardsAction.Done)
}
btn_alternative_action.isVisible = false
btn_alternative_action.setText("")
btn_alternative_action.setOnClickListener { }
btnAlternativeAction.isVisible = false
btnAlternativeAction.setText("")
btnAlternativeAction.setOnClickListener { }
tv_header.setText(R.string.onboarding_done_header)
tv_body.setText(R.string.onboarding_done_body)
tvHeader.setText(R.string.onboarding_done_header)
tvBody.setText(R.string.onboarding_done_body)
imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
binding.onboardingTopContainer.imvCardBackground?.setBackgroundDrawable(
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
)
updateConstraints(R.layout.lp_onboarding_done)
}
private fun updateConstraints(@LayoutRes layoutId: Int) {
val constraintSet = ConstraintSet()
constraintSet.clone(requireContext(), layoutId)
constraintSet.applyTo(onboarding_main_container)
val transition = InternalNoteLayoutTransition()
transition.interpolator = OvershootInterpolator()
TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
with(binding.onboardingTopContainer) {
val constraintSet = ConstraintSet()
constraintSet.clone(requireContext(), layoutId)
constraintSet.applyTo(onboardingMainContainer)
val transition = InternalNoteLayoutTransition()
transition.interpolator = OvershootInterpolator()
TransitionManager.beginDelayedTransition(onboardingMainContainer, transition)
}
}
}

View file

@ -32,13 +32,7 @@ import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_onboarding_main.*
import kotlinx.android.synthetic.main.layout_onboarding_container_bottom.*
import kotlinx.android.synthetic.main.layout_onboarding_container_top.*
import kotlinx.android.synthetic.main.view_bg_twins_welcome.*
import kotlinx.android.synthetic.main.view_onboarding_progress.*
import kotlinx.android.synthetic.main.view_onboarding_tv_balance.*
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
@ -49,12 +43,11 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
private val assetReader: AssetReader by lazy {
object : AssetReader {
override fun readAssetAsString(name: String): String = requireContext().readAssetAsString(name)
override fun readAssetAsString(name: String): String =
requireContext().readAssetAsString(name)
}
}
override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_container_top
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
@ -76,43 +69,44 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
super.onViewCreated(view, savedInstanceState)
addBackPressHandler(this)
reconfigureLayoutForTwins()
reconfigureLayoutForTwins(binding.onboardingTopContainer)
val typedValue = TypedValue()
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
val deviceScaleFactorForWelcomeState = typedValue.float
twinsWidget = TwinsCardWidget(LeapfrogWidget(cards_container), deviceScaleFactorForWelcomeState) {
twinsWidget = TwinsCardWidget(LeapfrogWidget(binding.onboardingTopContainer.cardsContainer), deviceScaleFactorForWelcomeState) {
285f * deviceScaleFactorForWelcomeState
}
btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container)
btnRefreshBalanceWidget = RefreshBalanceWidget(binding.onboardingTopContainer.onboardingMainContainer)
toolbar.title = getText(R.string.twins_recreate_toolbar)
binding.toolbar.title = getText(R.string.twins_recreate_toolbar)
Picasso.get()
.load(Artwork.TWIN_CARD_1)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(imv_twin_front_card)
.load(Artwork.TWIN_CARD_1)
.error(R.drawable.card_placeholder_black)
.placeholder(R.drawable.card_placeholder_black)
?.into(binding.onboardingTopContainer.imvTwinFrontCard)
Picasso.get()
.load(Artwork.TWIN_CARD_2)
.error(R.drawable.card_placeholder_white)
.placeholder(R.drawable.card_placeholder_white)
?.into(imv_twin_back_card)
.load(Artwork.TWIN_CARD_2)
.error(R.drawable.card_placeholder_white)
.placeholder(R.drawable.card_placeholder_white)
?.into(binding.onboardingTopContainer.imvTwinBackCard)
}
private fun reconfigureLayoutForTwins() {
imv_front_card.hide()
imv_card_background.hide()
cards_container.show()
private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) =
with(containerBinding) {
imvFrontCard.hide()
imvCardBackground.hide()
cardsContainer.show()
imv_twin_front_card.transitionName = ShareElement.imvFrontCard
imv_twin_back_card.transitionName = ShareElement.imvBackCard
imvTwinFrontCard.transitionName = ShareElement.imvFrontCard
imvTwinBackCard.transitionName = ShareElement.imvBackCard
// if don't this, the bg_circle_... is overflow the app_bar
app_bar.bringToFront()
}
// if don't this, the bg_circle_... is overflow the app_bar
binding.appBar.bringToFront()
}
override fun subscribeToStore() {
store.subscribe(this) { state ->
@ -124,10 +118,10 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
}
override fun newState(state: TwinCardsState) {
if (activity == null) return
if (activity == null || view == null) return
pb_state.max = state.steps.size - 1
pb_state.progress = state.progress
pbBinding.pbState.max = state.steps.size - 1
pbBinding.pbState.progress = state.progress
when (state.currentStep) {
TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state)
@ -148,13 +142,15 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
private fun setBalance(state: TwinCardsState) {
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
if (state.balanceCriticalError == null) {
val balanceValue = state.walletBalance.value.stripZeroPlainString()
tv_balance_value.text = balanceValue
tv_balance_currency.text = state.walletBalance.currency.currencySymbol
} else {
tv_balance_value.text = ""
tv_balance_currency.text = ""
with(binding.onboardingTopContainer.onboardingTvBalance) {
if (state.balanceCriticalError == null) {
val balanceValue = state.walletBalance.value.stripZeroPlainString()
tvBalanceValue.text = balanceValue
tvBalanceCurrency.text = state.walletBalance.currency.currencySymbol
} else {
tvBalanceValue.text = ""
tvBalanceCurrency.text = ""
}
}
}
@ -169,203 +165,235 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
setupWelcomeState(state) { store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet)) }
}
private fun setupWelcomeState(state: TwinCardsState, mainAction: VoidCallback) {
private fun setupWelcomeState(state: TwinCardsState, mainAction: VoidCallback) =
with(binding.onboardingActionContainer) {
twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.show()
pbBinding.pbState.hide()
tvHeader.setText(R.string.twins_onboarding_subtitle)
tvBody.text = getString(
R.string.twins_onboarding_description_format,
state.cardNumber?.pairIndexNumber()
)
btnMainAction.setText(R.string.common_continue)
btnMainAction.setOnClickListener { mainAction() }
}
private fun setupWarningState(state: TwinCardsState) = with(binding.onboardingActionContainer) {
twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
onboarding_twins_welcome_bg.show()
pb_state.hide()
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
pbBinding.pbState.hide()
chbUnderstand.show()
tv_header.setText(R.string.twins_onboarding_subtitle)
tv_body.text = getString(R.string.twins_onboarding_description_format, state.cardNumber?.pairIndexNumber())
tvHeader.setText(R.string.common_warning)
tvBody.setText(R.string.twins_recreate_warning)
btn_main_action.setText(R.string.common_continue)
btn_main_action.setOnClickListener { mainAction() }
}
private fun setupWarningState(state: TwinCardsState) {
twinsWidget.toWelcome(false) { startPostponedEnterTransition() }
onboarding_twins_welcome_bg.hide()
pb_state.hide()
chb_understand.show()
tv_header.setText(R.string.common_warning)
tv_body.setText(R.string.twins_recreate_warning)
chb_understand.setOnCheckedChangeListener { buttonView, isChecked ->
chbUnderstand.setOnCheckedChangeListener { buttonView, isChecked ->
store.dispatch(TwinCardsAction.SetUserUnderstand(isChecked))
}
btn_main_action.isEnabled = state.userWasUnderstandIfWalletRecreate
btn_main_action.setText(R.string.common_continue)
btn_main_action.setOnClickListener {
btnMainAction.isEnabled = state.userWasUnderstandIfWalletRecreate
btnMainAction.setText(R.string.common_continue)
btnMainAction.setOnClickListener {
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
}
}
private fun setupCreateFirstWalletState(state: TwinCardsState) {
onboarding_twins_welcome_bg.hide()
bg_circle_large.hide()
bg_circle_medium.hide()
bg_circle_min.hide()
chb_understand.hide()
pb_state.show()
private fun setupCreateFirstWalletState(state: TwinCardsState) =
with(binding.onboardingActionContainer) {
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide()
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide()
binding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide()
chbUnderstand.hide()
pbBinding.pbState.show()
onboarding_main_container.beginDelayedTransition()
binding.onboardingTopContainer.onboardingMainContainer.beginDelayedTransition()
when (previousStep) {
TwinCardsStep.None -> {
twinsWidget.toLeapfrog(false) {
when (state.cardNumber) {
TwinCardNumber.First -> startPostponedEnterTransition()
TwinCardNumber.Second -> {
switchToCard(state.cardNumber, false) { startPostponedEnterTransition() }
when (previousStep) {
TwinCardsStep.None -> {
twinsWidget.toLeapfrog(false) {
when (state.cardNumber) {
TwinCardNumber.First -> startPostponedEnterTransition()
TwinCardNumber.Second -> {
switchToCard(
state.cardNumber,
false
) { startPostponedEnterTransition() }
}
}
}
}
TwinCardsStep.Welcome, TwinCardsStep.Warning -> {
twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) })
}
}
TwinCardsStep.Welcome, TwinCardsStep.Warning -> {
twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) })
val twinIndexNumber = state.cardNumber?.indexNumber()
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
btnMainAction.setOnClickListener {
store.dispatch(
TwinCardsAction.Wallet.LaunchFirstStep(
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
assetReader
)
)
}
btnAlternativeAction.isVisible = false
btnAlternativeAction.isClickable = false
}
private fun setupCreateSecondWalletState(state: TwinCardsState) =
with(binding.onboardingActionContainer) {
switchToCard(state.cardNumber?.pairNumber())
val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
btnMainAction.text =
getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
btnMainAction.setOnClickListener {
store.dispatch(
TwinCardsAction.Wallet.LaunchSecondStep(
Message(
getString(
R.string.twins_recreate_title_format,
twinPairIndexNumber
)
),
Message(getString(R.string.twins_recreate_title_preparing)),
Message(getString(R.string.twins_recreate_title_creating_wallet)),
)
)
}
}
val twinIndexNumber = state.cardNumber?.indexNumber()
tv_header.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
tv_body.setText(R.string.onboarding_twins_interrupt_warning)
private fun setupCreateThirdWalletState(state: TwinCardsState) =
with(binding.onboardingActionContainer) {
switchToCard(state.cardNumber)
btn_main_action.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
btn_main_action.setOnClickListener {
store.dispatch(TwinCardsAction.Wallet.LaunchFirstStep(
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
assetReader
))
val twinIndexNumber = state.cardNumber?.indexNumber()
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
btnMainAction.setOnClickListener {
store.dispatch(
TwinCardsAction.Wallet.LaunchThirdStep(
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber))
)
)
}
}
btn_alternative_action.isVisible = false
btn_alternative_action.isClickable = false
}
private fun setupCreateSecondWalletState(state: TwinCardsState) {
switchToCard(state.cardNumber?.pairNumber())
val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
tv_header.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
tv_body.setText(R.string.onboarding_twins_interrupt_warning)
btn_main_action.text = getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
btn_main_action.setOnClickListener {
store.dispatch(TwinCardsAction.Wallet.LaunchSecondStep(
Message(getString(R.string.twins_recreate_title_format, twinPairIndexNumber)),
Message(getString(R.string.twins_recreate_title_preparing)),
Message(getString(R.string.twins_recreate_title_creating_wallet)),
))
}
}
private fun setupCreateThirdWalletState(state: TwinCardsState) {
switchToCard(state.cardNumber)
val twinIndexNumber = state.cardNumber?.indexNumber()
tv_header.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
tv_body.setText(R.string.onboarding_twins_interrupt_warning)
btn_main_action.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
btn_main_action.setOnClickListener {
store.dispatch(TwinCardsAction.Wallet.LaunchThirdStep(
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber))
))
}
}
private fun setupTopUpWalletState(state: TwinCardsState) {
when (previousStep) {
TwinCardsStep.None -> {
when (state.cardNumber) {
TwinCardNumber.First -> {
twinsWidget.leapfrogWidget.unfold(false) {
twinsWidget.toActivate(false) {
private fun setupTopUpWalletState(state: TwinCardsState) =
with(binding.onboardingActionContainer) {
when (previousStep) {
TwinCardsStep.None -> {
when (state.cardNumber) {
TwinCardNumber.First -> {
twinsWidget.leapfrogWidget.unfold(false) {
twinsWidget.toActivate(false) {
startPostponedEnterTransition()
}
}
}
TwinCardNumber.Second -> {
switchToCard(state.cardNumber, false) {
twinsWidget.toActivate(false)
startPostponedEnterTransition()
}
}
}
TwinCardNumber.Second -> {
switchToCard(state.cardNumber, false) {
twinsWidget.toActivate(false)
startPostponedEnterTransition()
}
}
}
TwinCardsStep.CreateThirdWallet -> {
twinsWidget.toActivate()
}
}
TwinCardsStep.CreateThirdWallet -> {
twinsWidget.toActivate()
}
}
if (state.isBuyAllowed) {
btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto)
btn_main_action.setOnClickListener {
store.dispatch(TwinCardsAction.TopUp)
if (state.isBuyAllowed) {
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
btnMainAction.setOnClickListener {
store.dispatch(TwinCardsAction.TopUp)
}
btnAlternativeAction.isVisible = true
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
btnAlternativeAction.setOnClickListener {
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
}
} else {
btnAlternativeAction.setText(R.string.onboarding_button_receive_crypto)
btnAlternativeAction.setOnClickListener {
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
}
btnAlternativeAction.isVisible = false
}
btn_alternative_action.isVisible = true
btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address)
btn_alternative_action.setOnClickListener {
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
tvHeader.setText(R.string.onboarding_top_up_header)
tvBody.setText(R.string.onboarding_top_up_body)
btnRefreshBalanceWidget.changeState(state.walletBalance.state)
if (btnRefreshBalanceWidget.isShowing != true) {
btnRefreshBalanceWidget.mainView.setOnClickListener {
store.dispatch(TwinCardsAction.Balance.Update)
}
}
} else {
btn_main_action.setText(R.string.onboarding_button_receive_crypto)
btn_main_action.setOnClickListener {
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
binding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
)
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins)
}
private fun setupDoneState(state: TwinCardsState) =
with(binding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_done_button_continue)
btnMainAction.setOnClickListener {
store.dispatch(TwinCardsAction.Confetti.Hide)
store.dispatch(TwinCardsAction.Done)
}
btn_alternative_action.isVisible = false
}
btnAlternativeAction.isVisible = false
btnAlternativeAction.isClickable = false
tv_header.setText(R.string.onboarding_top_up_header)
tv_body.setText(R.string.onboarding_top_up_body)
tvHeader.setText(R.string.onboarding_done_header)
tvBody.setText(R.string.onboarding_done_body)
btnRefreshBalanceWidget.changeState(state.walletBalance.state)
if (btnRefreshBalanceWidget.isShowing != true) {
btnRefreshBalanceWidget.mainView.setOnClickListener {
store.dispatch(TwinCardsAction.Balance.Update)
val layout = when (state.mode) {
CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
}
updateConstraints(state.currentStep, layout)
}
imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8))
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins)
}
private fun setupDoneState(state: TwinCardsState) {
btn_main_action.setText(R.string.onboarding_done_button_continue)
btn_main_action.setOnClickListener {
store.dispatch(TwinCardsAction.Confetti.Hide)
store.dispatch(TwinCardsAction.Done)
}
btn_alternative_action.isVisible = false
btn_alternative_action.isClickable = false
tv_header.setText(R.string.onboarding_done_header)
tv_body.setText(R.string.onboarding_done_body)
val layout = when (state.mode) {
CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
}
updateConstraints(state.currentStep, layout)
}
private fun updateConstraints(currentStep: TwinCardsStep, @LayoutRes layoutId: Int) {
if (this.previousStep == currentStep) return
val constraintSet = ConstraintSet()
constraintSet.clone(requireContext(), layoutId)
constraintSet.applyTo(onboarding_main_container)
val transition = InternalNoteLayoutTransition()
transition.interpolator = OvershootInterpolator()
TransitionManager.beginDelayedTransition(onboarding_main_container, transition)
with(binding.onboardingTopContainer) {
val constraintSet = ConstraintSet()
constraintSet.clone(requireContext(), layoutId)
constraintSet.applyTo(onboardingMainContainer)
val transition = InternalNoteLayoutTransition()
transition.interpolator = OvershootInterpolator()
TransitionManager.beginDelayedTransition(onboardingMainContainer, transition)
}
}
private fun switchToCard(cardNumber: TwinCardNumber?, animate: Boolean = true, onEnd: VoidCallback = {}) {
private fun switchToCard(
cardNumber: TwinCardNumber?,
animate: Boolean = true,
onEnd: VoidCallback = {}
) {
val position = cardNumber?.number ?: return
if (twinsWidget.leapfrogWidget.getViewPositionByIndex(1) != position - 1) {

View file

@ -8,7 +8,6 @@ import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.GetCardSourceParams
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
@ -218,13 +217,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
}
}
is BackupAction.GoToShop -> {
backupState.buyAdditionalCardsUrl?.let { url ->
store.dispatchOpenUrl(url)
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.GET_CARD,
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.ONBOARDING.param)
)
}
store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.GET_CARD,
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.ONBOARDING.param)
)
}
is BackupAction.FinishAddingBackupCards -> {
if (backupService.addedBackupCardsCount == 1) {
@ -236,9 +233,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
}
is BackupAction.CheckAccessCode -> {
if (action.accessCode.length < 4) {
store.dispatch(BackupAction.SetAccessCodeError(
AccessCodeError.CodeTooShort
))
store.dispatch(
BackupAction.SetAccessCodeError(
AccessCodeError.CodeTooShort
)
)
} else {
store.dispatch(BackupAction.SetAccessCodeError(null))
store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode))
@ -250,9 +249,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
backupService.setAccessCode(action.accessCodeConfirmation)
store.dispatch(BackupAction.PrepareToWritePrimaryCard)
} else {
store.dispatch(BackupAction.SetAccessCodeError(
AccessCodeError.CodesDoNotMatch
))
store.dispatch(
BackupAction.SetAccessCodeError(
AccessCodeError.CodesDoNotMatch
)
)
}
}
is BackupAction.WritePrimaryCard -> {

View file

@ -1,28 +1,30 @@
package com.tangem.tap.features.onboarding.products.wallet.ui
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
import com.tangem.tap.common.extensions.getString
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.item_backup_info_adapter.view.*
import com.tangem.wallet.databinding.ItemBackupInfoAdapterBinding
class BackupInfoAdapter : RecyclerView.Adapter<BackupInfoViewHolder>() {
private val data = backupInfoSnippets
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupInfoViewHolder =
BackupInfoViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_backup_info_adapter, parent, false))
BackupInfoViewHolder(
ItemBackupInfoAdapterBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: BackupInfoViewHolder, position: Int) =
holder.itemView.run {
tv_header.text = getString(data[position].header)
tv_body.text = getString(data[position].body)
holder.binding.run {
tvHeader.text = root.getString(data[position].header)
tvBody.text = root.getString(data[position].body)
}
}
@ -51,4 +53,5 @@ private val backupInfoSnippets = listOf(
)
class BackupInfoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class BackupInfoViewHolder(val binding: ItemBackupInfoAdapterBinding) :
RecyclerView.ViewHolder(binding.root)

View file

@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import androidx.transition.TransitionManager
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayoutMediator
import com.squareup.picasso.Picasso
@ -23,11 +24,8 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.*
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_onboarding_wallet.*
import kotlinx.android.synthetic.main.layout_onboarding_buttons_add_cards.*
import kotlinx.android.synthetic.main.layout_onboarding_buttons_common.*
import kotlinx.android.synthetic.main.view_confetti.*
import kotlinx.android.synthetic.main.view_onboarding_progress.*
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
import org.rekotlin.StoreSubscriber
class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
@ -35,6 +33,12 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
private var accessCodeDialog: AccessCodeDialog? = null
private lateinit var cardsWidget: WalletCardsWidget
private val binding: FragmentOnboardingWalletBinding by viewBinding(
FragmentOnboardingWalletBinding::bind
)
private val pbBinding: ViewOnboardingProgressBinding by viewBinding(
ViewOnboardingProgressBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -56,19 +60,22 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
val leapfrogCalculator = PropertyCalculator(
yTranslationFactor = 25f * deviceScaleFactor,
)
val leapfrog = LeapfrogWidget(fl_cards_container, leapfrogCalculator)
val leapfrog = LeapfrogWidget(binding.flCardsContainer, leapfrogCalculator)
cardsWidget = WalletCardsWidget(leapfrog, deviceScaleFactor) { 200f * deviceScaleFactor }
startPostponedEnterTransition()
view_pager_backup_info.adapter = BackupInfoAdapter()
TabLayoutMediator(tab_layout_backup_info, view_pager_backup_info) { tab, position ->
binding.viewPagerBackupInfo.adapter = BackupInfoAdapter()
TabLayoutMediator(
binding.tabLayoutBackupInfo,
binding.viewPagerBackupInfo
) { tab, position ->
//Some implementation
}.attach()
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
store.dispatch(OnboardingWalletAction.Init)
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
store.dispatch(OnboardingWalletAction.LoadArtwork)
@ -90,16 +97,18 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
}
override fun newState(state: OnboardingWalletState) {
if (activity == null) return
if (activity == null || view == null) return
requireActivity().invalidateOptionsMenu()
loadImageIntoImageView(state.cardArtworkUrl, imv_front_card)
loadImageIntoImageView(state.cardArtworkUrl, imv_first_backup_card)
loadImageIntoImageView(state.cardArtworkUrl, imv_second_backup_card)
with(binding) {
loadImageIntoImageView(state.cardArtworkUrl, imvFrontCard)
loadImageIntoImageView(state.cardArtworkUrl, imvFirstBackupCard)
loadImageIntoImageView(state.cardArtworkUrl, imvSecondBackupCard)
}
pb_state.max = 6
pb_state.progress = state.getProgressStep()
pbBinding.pbState.max = 6
pbBinding.pbState.progress = state.getProgressStep()
when (state.step) {
OnboardingWalletStep.None -> {
@ -120,15 +129,15 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
?.into(view)
}
private fun setupCreateWalletState() {
btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
btn_main_action.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) }
btn_alternative_action.hide()
private fun setupCreateWalletState() = with(binding) {
layoutButtonsCommon.btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) }
layoutButtonsCommon.btnAlternativeAction.hide()
toolbar.title = getText(R.string.onboarding_getting_started)
tv_header.setText(R.string.onboarding_create_wallet_header)
tv_body.setText(R.string.onboarding_create_wallet_body)
tvHeader.setText(R.string.onboarding_create_wallet_header)
tvBody.setText(R.string.onboarding_create_wallet_body)
cardsWidget.toFolded(false) { startPostponedEnterTransition() }
}
@ -137,7 +146,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
private fun setBackupState(state: BackupState) {
when (state.backupStep) {
BackupStep.InitBackup -> showBackupIntro(state)
BackupStep.ScanOriginCard -> showScanOriginCard(state)
BackupStep.ScanOriginCard -> showScanOriginCard()
BackupStep.AddBackupCards -> showAddBackupCards(state)
BackupStep.SetAccessCode -> showSetAccessCode()
BackupStep.EnterAccessCode -> showEnterAccessCode(state)
@ -148,65 +157,70 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
}
}
private fun showBackupIntro(state: BackupState) {
imv_first_backup_card.show()
imv_second_backup_card.show()
private fun showBackupIntro(state: BackupState) = with(binding) {
imvFirstBackupCard.show()
imvSecondBackupCard.show()
cardsWidget.toWelcome()
tv_header.hide()
tv_body.hide()
view_pager_backup_info.show()
tab_layout_backup_info.show()
tvHeader.hide()
tvBody.hide()
viewPagerBackupInfo.show()
tabLayoutBackupInfo.show()
with(layoutButtonsCommon) {
btnMainAction.text = getText(R.string.onboarding_button_backup_now)
btnAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
btnAlternativeAction.show(state.canSkipBackup)
btnMainAction.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
btnAlternativeAction.setOnClickListener { store.dispatch(BackupAction.DismissBackup) }
}
btn_main_action.text = getText(R.string.onboarding_button_backup_now)
btn_alternative_action.text = getText(R.string.onboarding_button_skip_backup)
btn_alternative_action.show(state.canSkipBackup)
btn_main_action.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
btn_alternative_action.setOnClickListener { store.dispatch(BackupAction.DismissBackup) }
startPostponedEnterTransition()
}
private fun showScanOriginCard(state: BackupState) {
private fun showScanOriginCard() = with(binding) {
prepareBackupView()
cardsWidget.toFolded()
tv_header.text = getText(R.string.onboarding_title_scan_origin_card)
tv_body.text = getString(
tvHeader.text = getText(R.string.onboarding_title_scan_origin_card)
tvBody.text = getString(
R.string.onboarding_subtitle_scan_origin_card,
)
btn_main_action.text = getString(R.string.onboarding_button_scan_origin_card)
btn_alternative_action.hide()
btn_main_action.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
with(layoutButtonsCommon) {
btnMainAction.text = getString(R.string.onboarding_button_scan_origin_card)
btnAlternativeAction.hide()
btnMainAction.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
}
}
private fun prepareBackupView() {
private fun prepareBackupView() = with(binding) {
toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup)
tv_header.show()
tv_body.show()
view_pager_backup_info.hide()
tab_layout_backup_info.hide()
tvHeader.show()
tvBody.show()
viewPagerBackupInfo.hide()
tabLayoutBackupInfo.hide()
imv_first_backup_card.show()
imv_second_backup_card.show()
imvSecondBackupCard.show()
imvSecondBackupCard.show()
}
private fun showAddBackupCards(state: BackupState) {
private fun showAddBackupCards(state: BackupState) = with(binding) {
prepareBackupView()
accessCodeDialog?.dismiss()
accessCodeDialog = null
layout_buttons_add_cards.show()
layout_buttons_common.hide()
btn_add_card.text = getText(R.string.onboarding_button_add_backup_card)
layoutButtonsAddCards.root.show()
layoutButtonsCommon.root.hide()
layoutButtonsAddCards.btnAddCard.text = getText(R.string.onboarding_button_add_backup_card)
if (state.backupCardsNumber < state.maxBackupCards) {
btn_add_card.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) }
layoutButtonsAddCards.btnAddCard.setOnClickListener { store.dispatch(BackupAction.AddBackupCard) }
} else {
btn_add_card.isEnabled = false
layoutButtonsAddCards.btnAddCard.isEnabled = false
}
when (state.backupCardsNumber) {
@ -215,27 +229,27 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
cardsWidget.getFirstBackupCardView().animate().alpha(0.6f).setDuration(200)
cardsWidget.getSecondBackupCardView().animate().alpha(0.2f).setDuration(200)
}
tv_header.text = getText(R.string.onboarding_title_no_backup_cards)
tv_body.text = getText(R.string.onboarding_subtitle_no_backup_cards)
tvHeader.text = getText(R.string.onboarding_title_no_backup_cards)
tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards)
}
1 -> {
tv_header.text = getText(R.string.onboarding_title_one_backup_card)
tv_body.text = getText(R.string.onboarding_subtitle_one_backup_card)
tvHeader.text = getText(R.string.onboarding_title_one_backup_card)
tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card)
cardsWidget.getFirstBackupCardView().animate().alpha(1f).setDuration(400)
cardsWidget.getSecondBackupCardView().alpha = 0.2f
}
2 -> {
tv_header.text = getText(R.string.onboarding_title_two_backup_cards)
tv_body.text = getText(R.string.onboarding_subtitle_two_backup_cards)
tvHeader.text = getText(R.string.onboarding_title_two_backup_cards)
tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards)
cardsWidget.getFirstBackupCardView().alpha = 1f
cardsWidget.getSecondBackupCardView().animate().alpha(1f).setDuration(400)
}
}
btn_continue.text = getText(R.string.onboarding_button_finalize_backup)
btn_continue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) }
btn_continue.isEnabled = state.backupCardsNumber != 0
layoutButtonsAddCards.btnContinue.text = getText(R.string.onboarding_button_finalize_backup)
layoutButtonsAddCards.btnContinue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) }
layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0
}
private fun showSetAccessCode() {
@ -266,7 +280,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
accessCodeDialog?.showError(state.accessCodeError)
}
private fun showWritePrimaryCard(state: BackupState) {
private fun showWritePrimaryCard(state: BackupState) = with(binding) {
accessCodeDialog?.dismiss()
prepareViewForFinalizeStep()
@ -278,33 +292,33 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
}
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
tv_header.text = getText(R.string.onboarding_title_prepare_origin)
tv_body.text = getString(
tvHeader.text = getText(R.string.onboarding_title_prepare_origin)
tvBody.text = getString(
R.string.onboarding_subtitle_scan_primary_card_format,
state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }
)
btn_main_action.text = getText(R.string.onboarding_button_backup_origin)
btn_main_action.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_button_backup_origin)
layoutButtonsCommon.btnMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
}
private fun prepareViewForFinalizeStep() {
layout_buttons_add_cards.hide()
layout_buttons_common.show()
private fun prepareViewForFinalizeStep() = with(binding) {
layoutButtonsAddCards.root.hide()
layoutButtonsCommon.root.show()
imv_first_backup_card.show()
imv_second_backup_card.show()
imvFirstBackupCard.show()
imvSecondBackupCard.show()
toolbar.title = getText(R.string.onboarding_button_finalize_backup)
imv_card_background.hide()
imvCardBackground.hide()
cardsWidget.toLeapfrog()
btn_alternative_action.hide()
layoutButtonsCommon.btnAlternativeAction.hide()
}
private fun showWriteBackupCard(state: BackupState) {
private fun showWriteBackupCard(state: BackupState) = with(binding) {
prepareViewForFinalizeStep()
cardsWidget.getSecondBackupCardView().show(state.backupCardsNumber == 2)
@ -324,46 +338,47 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
}
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
tv_header.text =
tvHeader.text =
getString(R.string.onboarding_title_backup_card_format, cardNumber)
tv_body.text = getString(
tvBody.text = getString(
R.string.onboarding_subtitle_scan_backup_card_format,
cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1])
)
btn_main_action.text = getString(
layoutButtonsCommon.btnMainAction.text = getString(
R.string.onboarding_button_backup_card_format,
cardNumber
)
btn_main_action.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) }
layoutButtonsCommon.btnMainAction.setOnClickListener {
store.dispatch(BackupAction.WriteBackupCard(cardNumber))
}
}
private fun showSuccess() {
tv_header.text = getText(R.string.onboarding_done_header)
private fun showSuccess() = with(binding) {
tvHeader.text = getText(R.string.onboarding_done_header)
tv_header.show()
tv_body.show()
view_pager_backup_info.hide()
tab_layout_backup_info.hide()
tvHeader.show()
tvBody.show()
viewPagerBackupInfo.hide()
tabLayoutBackupInfo.hide()
tv_body.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
btn_main_action.text = getText(R.string.onboarding_button_continue_wallet)
btn_alternative_action.hide()
btn_main_action.setOnClickListener {
lav_confetti.cancelAnimation()
lav_confetti.hide()
tvBody.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
layoutButtonsCommon.btnMainAction.text = getText(R.string.onboarding_button_continue_wallet)
layoutButtonsCommon.btnAlternativeAction.hide()
layoutButtonsCommon.btnMainAction.setOnClickListener {
vConfetti.lavConfetti.cancelAnimation()
vConfetti.lavConfetti.hide()
store.dispatch(OnboardingWalletAction.FinishOnboarding)
}
cardsWidget.leapfrogWidget.fold {
fl_cards_container?.hide()
imv_card_background?.hide()
lav_confetti?.show()
lav_confetti?.playAnimation()
imv_success?.alpha = 0f
imv_success?.show()
flCardsContainer.hide()
imvCardBackground.hide()
vConfetti.lavConfetti.show()
vConfetti.lavConfetti.playAnimation()
imvSuccess.alpha = 0f
imvSuccess.show()
imv_success?.animate()
imvSuccess.animate()
?.alpha(1f)
?.setDuration(400)
}

View file

@ -1,8 +1,9 @@
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.common.extensions.hide
@ -11,64 +12,69 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.AccessCodeError
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.layout_backup_access_code.*
import kotlinx.android.synthetic.main.layout_backup_access_code_info.*
import kotlinx.android.synthetic.main.layout_backup_access_code_submit.*
import com.tangem.wallet.databinding.LayoutBackupAccessCodeBinding
class AccessCodeDialog(context: Context) : BottomSheetDialog(context) {
var binding: LayoutBackupAccessCodeBinding? = null
init {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
val dialogView = LayoutInflater.from(context).inflate(R.layout.layout_backup_access_code, null)
setContentView(dialogView)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutBackupAccessCodeBinding
.inflate(LayoutInflater.from(context))
setContentView(binding!!.root)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
override fun setContentView(view: View) {
super.setContentView(view)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
override fun setOnDismissListener(listener: DialogInterface.OnDismissListener?) {
super.setOnDismissListener(listener)
binding = null
}
fun showInfoScreen() {
fun showInfoScreen() = with(binding!!) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
access_code_title.text = context.getText(R.string.onboarding_access_code_intro_title)
layout_backup_access_code_info.show()
layout_backup_access_code_submit.hide()
btn_access_code_create.setOnClickListener {
accessCodeTitle.text = context.getText(R.string.onboarding_access_code_intro_title)
layoutBackupAccessCodeInfo.root.show()
layoutBackupAccessCodeSubmit.root.hide()
layoutBackupAccessCodeInfo.btnAccessCodeCreate.setOnClickListener {
store.dispatch(BackupAction.ShowEnterAccessCodeScreen)
}
}
fun showEnterAccessCode() {
layout_backup_access_code_info.hide()
layout_backup_access_code_submit.show()
access_code_title.text = context.getText(R.string.onboarding_access_code_intro_title)
btn_access_code_submit.text = context.getText(R.string.common_continue)
btn_access_code_submit.setOnClickListener {
store.dispatch(BackupAction.CheckAccessCode(et_access_code.text.toString()))
fun showEnterAccessCode() = with(binding!!) {
layoutBackupAccessCodeInfo.root.hide()
layoutBackupAccessCodeSubmit.root.show()
accessCodeTitle.text = context.getText(R.string.onboarding_access_code_intro_title)
with(layoutBackupAccessCodeSubmit) {
btnAccessCodeSubmit.text = context.getText(R.string.common_continue)
btnAccessCodeSubmit.setOnClickListener {
store.dispatch(BackupAction.CheckAccessCode(etAccessCode.text.toString()))
}
}
}
fun showReenterAccessCode() {
layout_backup_access_code_info.hide()
layout_backup_access_code_submit.show()
access_code_title.text = context.getText(R.string.onboarding_access_code_repeat_code_title)
et_access_code.setText("")
btn_access_code_submit.text = context.getText(R.string.common_submit)
btn_access_code_submit.setOnClickListener {
store.dispatch(BackupAction.SaveAccessCodeConfirmation(et_access_code.text.toString()))
fun showReenterAccessCode() = with(binding!!) {
layoutBackupAccessCodeInfo.root.hide()
layoutBackupAccessCodeSubmit.root.show()
accessCodeTitle.text = context.getText(R.string.onboarding_access_code_repeat_code_title)
with(layoutBackupAccessCodeSubmit) {
etAccessCode.setText("")
btnAccessCodeSubmit.text = context.getText(R.string.common_submit)
btnAccessCodeSubmit.setOnClickListener {
store.dispatch(BackupAction.SaveAccessCodeConfirmation(etAccessCode.text.toString()))
}
}
}
fun showError(error: AccessCodeError?) {
fun showError(error: AccessCodeError?) = with(binding!!.layoutBackupAccessCodeSubmit) {
when (error) {
AccessCodeError.CodeTooShort ->
til_access_code.error = context.getString(R.string.onboarding_access_code_too_short)
tilAccessCode.error = context.getString(R.string.onboarding_access_code_too_short)
AccessCodeError.CodesDoNotMatch ->
til_access_code.error = context.getString(R.string.onboarding_access_codes_doesnt_match)
null -> til_access_code.error = null
tilAccessCode.error = context.getString(R.string.onboarding_access_codes_doesnt_match)
null -> tilAccessCode.error = null
}
}
}

View file

@ -11,6 +11,7 @@ import androidx.core.view.postDelayed
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.textfield.TextInputEditText
import com.tangem.Message
import com.tangem.tangem_sdk_new.extensions.dpToPx
@ -40,10 +41,7 @@ import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_send.*
import kotlinx.android.synthetic.main.layout_send_address_payid.*
import kotlinx.android.synthetic.main.layout_send_amount.*
import kotlinx.android.synthetic.main.layout_send_fee.*
import com.tangem.wallet.databinding.FragmentSendBinding
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
@ -62,6 +60,8 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
private val sendSubscriber = SendStateSubscriber(this)
private lateinit var keyboardObserver: KeyboardObserver
val binding: FragmentSendBinding by viewBinding(FragmentSendBinding::bind)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
addBackPressHandler(this)
@ -77,7 +77,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
store.dispatch(SendActionUi.CheckIfTransactionDataWasProvided)
}
private fun initSendButtonStates() {
private fun initSendButtonStates() = with(binding) {
btnSend.setOnClickListener {
store.dispatch(SendActionUi.SendAmountToRecipient(
Message(getString(R.string.initial_message_sign_header))
@ -86,7 +86,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
sendBtn = IndeterminateProgressButtonWidget(btnSend, progress)
}
private fun setupAddressOrPayIdLayout() {
private fun setupAddressOrPayIdLayout() = with(binding.lSendAddressPayid) {
store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") })
store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString()))
@ -113,7 +113,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
private fun setupTransactionExtrasLayout() {
private fun setupTransactionExtrasLayout() = with(binding.lSendAddressPayid) {
etXlmMemo.inputtedTextAsFlow()
.debounce(400)
.filter {
@ -151,9 +151,9 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
// If do not use the delay, then etAmount error field is not displayed when
// inserting an incorrect amount by shareUri
imvQrCode.postDelayed({
binding.lSendAddressPayid.imvQrCode.postDelayed({
store.dispatch(PasteAddressPayId(scannedCode))
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused))
}, 200)
}
@ -165,7 +165,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
val decimalSeparator = DecimalFormatSymbols.getInstance().decimalSeparator.toString()
store.dispatch(AmountAction.SetDecimalSeparator(decimalSeparator))
tvAmountCurrency.setOnClickListener {
binding.lSendAmount.tvAmountCurrency.setOnClickListener {
store.dispatch(ToggleMainCurrency)
store.dispatch(ReceiptAction.RefreshReceipt)
store.dispatch(SendAction.ChangeSendButtonState(store.state.sendState.getButtonState()))
@ -223,8 +223,8 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
private fun setupFeeLayout() {
flExpandCollapse.setOnClickListener {
private fun setupFeeLayout() = with(binding.clNetworkFee) {
flExpandCollapse.flExpandCollapse.setOnClickListener {
store.dispatch(ToggleControlsVisibility)
}
chipGroup.check(FeeUiHelper.toId(FeeType.NORMAL))
@ -240,12 +240,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
private fun setupWarningMessages() {
private fun setupWarningMessages() = with(binding) {
warningsAdapter = WarningMessagesAdapter()
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
rv_warning_messages.layoutManager = layoutManager
rv_warning_messages.addItemDecoration(SpacesItemDecoration(rv_warning_messages.dpToPx(16f).toInt()))
rv_warning_messages.adapter = warningsAdapter
rvWarningMessages.layoutManager = layoutManager
rvWarningMessages.addItemDecoration(SpacesItemDecoration(rvWarningMessages.dpToPx(16f).toInt()))
rvWarningMessages.adapter = warningsAdapter
store.dispatch(SendAction.Warnings.Update)
}

View file

@ -26,32 +26,30 @@ import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.btn_expand_collapse.*
import kotlinx.android.synthetic.main.fragment_send.*
import kotlinx.android.synthetic.main.fragment_send.clReceiptContainer
import kotlinx.android.synthetic.main.layout_receipt_total.*
import kotlinx.android.synthetic.main.layout_receipt_total.view.*
import kotlinx.android.synthetic.main.layout_send_address_payid.*
import kotlinx.android.synthetic.main.layout_send_amount.*
import kotlinx.android.synthetic.main.layout_send_fee.*
import kotlinx.android.synthetic.main.layout_send_receipt.*
/**
[REDACTED_AUTHOR]
*/
class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber<SendState>(fragment) {
class SendStateSubscriber(fragment: BaseStoreFragment) :
FragmentStateSubscriber<SendState>(fragment) {
private var dialog: Dialog? = null
override fun updateWithNewState(fg: BaseStoreFragment, state: SendState) {
fg.view ?: return
if (fg !is SendFragment) return
val lastChangedStates = state.lastChangedStates.toList()
state.lastChangedStates.clear()
fg.main_send_container.beginDelayedTransition()
(fg as? SendFragment)?.binding?.mainSendContainer?.beginDelayedTransition()
lastChangedStates.forEach {
when (it) {
StateId.SEND_SCREEN -> handleSendScreen(fg, state)
StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState)
StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(fg, state.transactionExtrasState)
StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(
fg,
state.transactionExtrasState
)
StateId.AMOUNT -> handleAmountState(fg, state.amountState)
StateId.FEE -> handleFeeState(fg, state.feeState)
StateId.RECEIPT -> handleReceiptState(fg, state.receiptState)
@ -59,57 +57,58 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
}
}
private fun handleTransactionExtrasState(fg: BaseStoreFragment, infoState: TransactionExtrasState) {
fun showView(view: View, info: Any?) {
view.show(info != null)
}
showView(fg.xlmMemoContainer, infoState.xlmMemo)
showView(fg.xrpDestinationTagContainer, infoState.xrpDestinationTag)
showView(fg.binanceMemoContainer, infoState.binanceMemo)
private fun handleTransactionExtrasState(fg: SendFragment, infoState: TransactionExtrasState) =
with(fg.binding.lSendAddressPayid) {
infoState.xlmMemo?.let {
fg.etXlmMemo.inputType = when (it.selectedMemoType) {
XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
fun showView(view: View, info: Any?) {
view.show(info != null)
}
if (!it.viewFieldValue.isFromUserInput) fg.etXlmMemo.setText(it.viewFieldValue.value)
if (it.error != null) {
if (it.error == TransactionExtraError.INVALID_XLM_MEMO) {
fg.tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
}
} else {
fg.tilXlmMemo.error = null
}
}
infoState.xrpDestinationTag?.let {
if (infoState.xrpDestinationTag.error != null) {
if (infoState.xrpDestinationTag.error == TransactionExtraError.INVALID_DESTINATION_TAG) {
fg.tilDestinationTag.error = fg.getText(R.string.send_error_invalid_destination_tag)
}
} else {
fg.tilDestinationTag.error = null
}
if (!it.viewFieldValue.isFromUserInput) {
fg.etDestinationTag.setText(it.viewFieldValue.value)
}
}
infoState.binanceMemo?.let {
if (infoState.binanceMemo.error != null) {
if (infoState.binanceMemo.error == TransactionExtraError.INVALID_BINANCE_MEMO) {
fg.tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
}
} else {
fg.tilBinanceMemo.error = null
}
if (!it.viewFieldValue.isFromUserInput) {
fg.etBinanceMemo.setText(it.viewFieldValue.value)
}
}
}
showView(xlmMemoContainer, infoState.xlmMemo)
showView(xrpDestinationTagContainer, infoState.xrpDestinationTag)
showView(binanceMemoContainer, infoState.binanceMemo)
private fun handleSendScreen(fg: BaseStoreFragment, state: SendState) {
val sendFragment = (fg as? SendFragment) ?: return
infoState.xlmMemo?.let {
etXlmMemo.inputType = when (it.selectedMemoType) {
XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
}
if (!it.viewFieldValue.isFromUserInput) etXlmMemo.setText(it.viewFieldValue.value)
if (it.error != null) {
if (it.error == TransactionExtraError.INVALID_XLM_MEMO) {
tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
}
} else {
tilXlmMemo.error = null
}
}
infoState.xrpDestinationTag?.let {
if (infoState.xrpDestinationTag.error != null) {
if (infoState.xrpDestinationTag.error == TransactionExtraError.INVALID_DESTINATION_TAG) {
tilDestinationTag.error =
fg.getText(R.string.send_error_invalid_destination_tag)
}
} else {
tilDestinationTag.error = null
}
if (!it.viewFieldValue.isFromUserInput) {
etDestinationTag.setText(it.viewFieldValue.value)
}
}
infoState.binanceMemo?.let {
if (infoState.binanceMemo.error != null) {
if (infoState.binanceMemo.error == TransactionExtraError.INVALID_BINANCE_MEMO) {
tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
}
} else {
tilBinanceMemo.error = null
}
if (!it.viewFieldValue.isFromUserInput) {
etBinanceMemo.setText(it.viewFieldValue.value)
}
}
}
private fun handleSendScreen(fg: SendFragment, state: SendState) = with(fg.binding) {
when (state.dialog) {
is SendAction.Dialog.TezosWarningDialog -> {
if (dialog == null) {
@ -129,147 +128,158 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
}
}
sendFragment.sendBtn.changeState(state.sendButtonState.progressState)
sendFragment.sendBtn.mainView.isEnabled = state.sendButtonState.enabled
fg.sendBtn.changeState(state.sendButtonState.progressState)
fg.sendBtn.mainView.isEnabled = state.sendButtonState.enabled
val rv = fg.rv_warning_messages
val rv = rvWarningMessages
val adapter = rv.adapter as? WarningMessagesAdapter ?: return
adapter.submitList(state.sendWarningsList)
rv.show(state.sendWarningsList.isNotEmpty())
fg.toolbar.title = fg.getString(
toolbar.title = fg.getString(
R.string.send_title_currency_format,
state.amountState.mainCurrency.currencySymbol
)
}
private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) {
fun parseError(context: Context, error: Error?): String? {
val resId = when (error) {
Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_error_payid_unsupported_by_blockchain
Error.PAY_ID_NOT_REGISTERED -> R.string.send_error_payid_not_registered
Error.PAY_ID_REQUEST_FAILED -> R.string.send_error_payid_request_failed
Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_validation_invalid_address
Error.ADDRESS_SAME_AS_WALLET -> R.string.send_error_address_same_as_wallet
else -> null
private fun handleAddressPayIdState(fg: SendFragment, state: AddressPayIdState) =
with(fg.binding.lSendAddressPayid) {
fun parseError(context: Context, error: Error?): String? {
val resId = when (error) {
Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_error_payid_unsupported_by_blockchain
Error.PAY_ID_NOT_REGISTERED -> R.string.send_error_payid_not_registered
Error.PAY_ID_REQUEST_FAILED -> R.string.send_error_payid_request_failed
Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_validation_invalid_address
Error.ADDRESS_SAME_AS_WALLET -> R.string.send_error_address_same_as_wallet
else -> null
}
return if (resId == null) null else context.getString(resId, "", "")
}
return if (resId == null) null else context.getString(resId, "", "")
imvPaste.isEnabled = state.pasteIsEnabled
val et = etAddressOrPayId
val til = tilAddressOrPayId
val parsedError = parseError(til.context, state.error)
til.isEnabled = state.inputIsEnabled
imvPaste.show(state.inputIsEnabled)
imvQrCode.show(state.inputIsEnabled)
flPaste.show(state.inputIsEnabled)
flQrCode.show(state.inputIsEnabled)
val hintResId = if (state.sendingToPayIdEnabled) {
R.string.send_destination_hint_address_payid
} else {
R.string.send_destination_hint_address
}
til.hint = til.getString(hintResId)
til.error = parsedError
til.isErrorEnabled = parsedError != null
til.helperText = state.destinationWalletAddress
til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
}
fg.imvPaste.isEnabled = state.pasteIsEnabled
val et = fg.etAddressOrPayId
val til = fg.tilAddressOrPayId
val parsedError = parseError(til.context, state.error)
til.isEnabled = state.inputIsEnabled
fg.imvPaste.show(state.inputIsEnabled)
fg.imvQrCode.show(state.inputIsEnabled)
fg.flPaste.show(state.inputIsEnabled)
fg.flQrCode.show(state.inputIsEnabled)
val hintResId = if (state.sendingToPayIdEnabled) {
R.string.send_destination_hint_address_payid
} else {
R.string.send_destination_hint_address
}
til.hint = til.getString(hintResId)
til.error = parsedError
til.isErrorEnabled = parsedError != null
til.helperText = state.destinationWalletAddress
til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
}
private fun handleAmountState(fg: BaseStoreFragment, state: AmountState) {
private fun handleAmountState(fg: SendFragment, state: AmountState) = with(fg.binding.lSendAmount) {
if (state.error != null) {
val context = fg.requireContext()
val message = when (state.error) {
is MultiMessageError -> {
val multiError = state.error as MultiMessageError
val messageList = multiError.assembleErrors().map { getMessageString(context, it.first, it.second) }
val messageList = multiError.assembleErrors()
.map { getMessageString(context, it.first, it.second) }
multiError.builder(messageList)
}
else -> context.getString(state.error.messageResource)
}
fg.tilAmountToSend.enableError(true, message)
tilAmountToSend.enableError(true, message)
} else {
fg.tilAmountToSend.enableError(false)
tilAmountToSend.enableError(false)
}
val filter = DecimalDigitsInputFilter(12, state.maxLengthOfAmount, state.decimalSeparator)
fg.etAmountToSend.filters = arrayOf(filter)
etAmountToSend.filters = arrayOf(filter)
val amountToSend = state.viewAmountValue
if (!amountToSend.isFromUserInput) fg.etAmountToSend.update(amountToSend.value)
if (!amountToSend.isFromUserInput) etAmountToSend.update(amountToSend.value)
fg.tvAmountCurrency.update(state.mainCurrency.currencySymbol)
tvAmountCurrency.update(state.mainCurrency.currencySymbol)
(fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type)
val balanceText = when (state.mainCurrency.type) {
MainCurrencyType.FIAT -> fg.getString(R.string.send_balance_subtitle_format,
state.viewBalanceValue, state.mainCurrency.currencySymbol).remove(":")
MainCurrencyType.CRYPTO -> fg.getString(R.string.send_balance_subtitle_format,
state.mainCurrency.currencySymbol, state.viewBalanceValue)
MainCurrencyType.FIAT -> fg.getString(
R.string.send_balance_subtitle_format,
state.viewBalanceValue, state.mainCurrency.currencySymbol
).remove(":")
MainCurrencyType.CRYPTO -> fg.getString(
R.string.send_balance_subtitle_format,
state.mainCurrency.currencySymbol, state.viewBalanceValue
)
}
fg.tvBalance.update(balanceText)
tvBalance.update(balanceText)
fg.tilAmountToSend.isEnabled = state.inputIsEnabled
tilAmountToSend.isEnabled = state.inputIsEnabled
val imageRes = if (state.inputIsEnabled) R.drawable.ic_arrows_up_down else 0
fg.tvAmountCurrency.setCompoundDrawablesWithIntrinsicBounds(0, 0, imageRes, 0)
tvAmountCurrency.setCompoundDrawablesWithIntrinsicBounds(0, 0, imageRes, 0)
val textColor = if (state.inputIsEnabled) R.color.blue else R.color.textGray
fg.tvAmountCurrency.setTextColor(fg.getColor(textColor))
tvAmountCurrency.setTextColor(fg.getColor(textColor))
}
private fun handleFeeState(fg: BaseStoreFragment, state: FeeState) {
fg.chipGroup.fitChipsByGroupWidth()
private fun handleFeeState(fg: SendFragment, state: FeeState) = with(fg.binding.clNetworkFee) {
chipGroup.fitChipsByGroupWidth()
fg.view?.findViewById<ViewGroup>(R.id.clNetworkFee)?.show(state.mainLayoutIsVisible)
flExpandCollapse.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f
llFeeControlsContainer.show(state.controlsLayoutIsVisible)
chipGroup.show(state.feeChipGroupIsVisible)
fg.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f
fg.llFeeControlsContainer.show(state.controlsLayoutIsVisible)
fg.chipGroup.show(state.feeChipGroupIsVisible)
fg.swIncludeFee.isEnabled = state.includeFeeSwitcherIsEnabled
if (fg.swIncludeFee.isChecked != state.feeIsIncluded) {
fg.swIncludeFee.isChecked = state.feeIsIncluded
swIncludeFee.isEnabled = state.includeFeeSwitcherIsEnabled
if (swIncludeFee.isChecked != state.feeIsIncluded) {
swIncludeFee.isChecked = state.feeIsIncluded
}
if (state.error == FeeAction.Error.REQUEST_FAILED) {
fg.showRetrySnackbar(fg.requireContext().getString(R.string.send_error_fee_request_failed)) {
fg.showRetrySnackbar(
fg.requireContext().getString(R.string.send_error_fee_request_failed)
) {
store.dispatch(FeeAction.RequestFee)
}
}
val chipId = FeeUiHelper.toId(state.selectedFeeType)
if (fg.chipGroup.checkedChipId != chipId && chipId != View.NO_ID) fg.chipGroup.check(chipId)
if (chipGroup.checkedChipId != chipId && chipId != View.NO_ID) chipGroup.check(chipId)
}
private fun handleReceiptState(fg: BaseStoreFragment, state: ReceiptState) {
val mainLayout = fg.clReceiptContainer as ViewGroup
val totalLayout = fg.llTotal as ViewGroup
val totalTokenLayout = fg.flTotalTokenCrypto as ViewGroup
private fun handleReceiptState(fg: SendFragment, state: ReceiptState) = with(fg.binding.clReceiptContainer) {
val mainLayout = clReceiptContainer as ViewGroup
val totalLayout = llTotalContainer.llTotal as ViewGroup
val totalTokenLayout = llTotalContainer.flTotalTokenCrypto as ViewGroup
fun getString(id: Int, vararg formatStrings: String): String =
mainLayout.context.getString(id, *formatStrings)
mainLayout.context.getString(id, *formatStrings)
val rough = getString(R.string.sign_rough)
fun roughOrEmpty(value: String): String = if (value == ReceiptReducer.EMPTY) value else "$rough $value"
fun roughOrEmpty(value: String): String =
if (value == ReceiptReducer.EMPTY) value else "$rough $value"
when (state.visibleTypeOfReceipt) {
ReceiptLayoutType.FIAT -> {
val receipt = state.fiat ?: return
llTotalContainer.tvTotalValue
totalLayout.show(true)
totalTokenLayout.show(false)
fg.tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
fg.tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
llTotalContainer.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
val willSent = getString(R.string.send_total_subtitle_format,
receipt.willSentCrypto, receipt.symbols.crypto)
totalLayout.tvWillBeSentValue.update(willSent)
val willSent = getString(
R.string.send_total_subtitle_format,
receipt.willSentCrypto, receipt.symbols.crypto
)
llTotalContainer.tvWillBeSentValue.update(willSent)
}
ReceiptLayoutType.CRYPTO -> {
@ -277,34 +287,34 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalLayout.show(true)
totalTokenLayout.show(false)
fg.tvReceiptAmountValue.update("${receipt.amountCrypto} ${receipt.symbols.crypto}")
fg.tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}")
totalLayout.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}")
tvReceiptAmountValue.update("${receipt.amountCrypto} ${receipt.symbols.crypto}")
tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}")
llTotalContainer.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}")
val willSent = SpannableStringBuilder()
.bold {
append(roughOrEmpty(receipt.willSentFiat)).append(" ")
append(receipt.symbols.fiat)
append(" (fee: ${receipt.feeFiat} ")
append(receipt.symbols.fiat).append(")")
}
totalLayout.tvWillBeSentValue.update(willSent.toString())
.bold {
append(roughOrEmpty(receipt.willSentFiat)).append(" ")
append(receipt.symbols.fiat)
append(" (fee: ${receipt.feeFiat} ")
append(receipt.symbols.fiat).append(")")
}
llTotalContainer.tvWillBeSentValue.update(willSent.toString())
}
ReceiptLayoutType.TOKEN_FIAT -> {
val receipt = state.tokenFiat ?: return
totalLayout.show(true)
totalTokenLayout.show(false)
fg.tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
fg.tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
totalLayout.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
llTotalContainer.tvTotalValue.update("${roughOrEmpty(receipt.totalFiat)} ${receipt.symbols.fiat}")
val willSent = getString(
R.string.send_total_subtitle_asset_format,
receipt.symbols.token ?: "", receipt.willSentToken,
receipt.symbols.crypto, receipt.willSentFeeCoin
)
totalLayout.tvWillBeSentValue.update(willSent)
llTotalContainer.tvWillBeSentValue.update(willSent)
}
ReceiptLayoutType.TOKEN_CRYPTO -> {
val receipt = state.tokenCrypto ?: return
@ -312,15 +322,15 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalLayout.show(false)
totalTokenLayout.show(true)
fg.tvReceiptAmountValue.update("${receipt.amountToken} ${receipt.symbols.token}")
fg.tvReceiptFeeValue.update("${receipt.feeCoin} ${receipt.symbols.crypto}")
tvReceiptAmountValue.update("${receipt.amountToken} ${receipt.symbols.token}")
tvReceiptFeeValue.update("${receipt.feeCoin} ${receipt.symbols.crypto}")
val willSent = SpannableStringBuilder()
.bold {
append(roughOrEmpty(receipt.totalFiat)).append(" ")
append(receipt.symbols.fiat)
}
totalTokenLayout.tvTotalTokenCryptoValue.update(willSent.toString())
.bold {
append(roughOrEmpty(receipt.totalFiat)).append(" ")
append(receipt.symbols.fiat)
}
llTotalContainer.tvTotalTokenCryptoValue.update(willSent.toString())
}
}
}

View file

@ -9,6 +9,7 @@ import android.view.View.OnFocusChangeListener
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.activity.OnBackPressedCallback
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.KeyboardObserver
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction
@ -18,12 +19,15 @@ import com.tangem.tap.features.shop.redux.ShopAction
import com.tangem.tap.features.shop.redux.ShopState
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_shop.*
import com.tangem.wallet.databinding.FragmentShopBinding
import org.rekotlin.StoreSubscriber
class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<ShopState> {
private val binding: FragmentShopBinding by viewBinding(FragmentShopBinding::bind)
lateinit var keyboardObserver: KeyboardObserver
override fun subscribeToStore() {
store.subscribe(this) { state ->
@ -44,6 +48,11 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<
})
}
override fun onDestroyView() {
super.onDestroyView()
keyboardObserver.unregisterListener()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -51,41 +60,41 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<
setupProductSelection()
setupPromoCodeEditText()
toolbar.setNavigationOnClickListener {
binding.toolbar.setNavigationOnClickListener {
requireActivity().onBackPressed()
}
val keyboardObserver = KeyboardObserver(requireActivity())
keyboardObserver.registerListener { isVisible ->
fl_cards.show(!isVisible)
keyboardObserver = KeyboardObserver(requireActivity()).apply {
registerListener { isVisible ->
binding.flCards.show(!isVisible)
}
}
}
private fun setupCardsImages() {
imv_second.animate()
binding.imvSecond.animate()
.translationY(70f)
.scaleX(0.9f)
.scaleY(0.9f)
.start()
imv_third.animate()
binding.imvThird.animate()
.translationY(140f)
.scaleX(0.8f)
.scaleY(0.8f)
.start()
}
private fun setupProductSelection() {
chip_product_1.setOnCheckedChangeListener { _, isChecked ->
private fun setupProductSelection() = with(binding) {
chipProduct1.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_3_CARDS))
}
chip_product_2.setOnCheckedChangeListener { _, isChecked ->
chipProduct2.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_2_CARDS))
}
}
private fun setupPromoCodeEditText() {
et_promo_code.setOnEditorActionListener { view, actionId, event ->
private fun setupPromoCodeEditText() = with(binding) {
etPromoCode.setOnEditorActionListener { view, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
val imm: InputMethodManager =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
@ -96,15 +105,15 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<
return@setOnEditorActionListener false
}
et_promo_code.onFocusChangeListener = OnFocusChangeListener { _, hasFocus ->
etPromoCode.onFocusChangeListener = OnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
store.dispatch(ShopAction.ApplyPromoCode(et_promo_code.text.toString()))
store.dispatch(ShopAction.ApplyPromoCode(etPromoCode.text.toString()))
}
}
}
override fun newState(state: ShopState) {
if (activity == null) return
if (activity == null || view == null) return
animateProductSelection(state.selectedProduct)
handlePriceState(state)
@ -120,43 +129,43 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<
showOrHideThirdCardWithAnimation(show)
}
private fun showOrHideThirdCardWithAnimation(show: Boolean) {
private fun showOrHideThirdCardWithAnimation(show: Boolean) = with(binding) {
val translationY = if (show) 140f else 0f
if (show) imv_third.show()
imv_third.animate()
if (show) imvThird.show()
imvThird.animate()
.translationY(translationY)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
imv_third?.show(show)
imvThird?.show(show)
}
})
}
private fun handlePriceState(state: ShopState) {
tv_total.text = state.total
tv_total_before_discount.text = state.priceBeforeDiscount
private fun handlePriceState(state: ShopState) = with(binding) {
tvTotal.text = state.total
tvTotalBeforeDiscount.text = state.priceBeforeDiscount
pb_price.show(state.total == null)
pbPrice.show(state.total == null)
}
private fun handlePromoCodeState(state: ShopState) {
if (state.promoCode == null && !et_promo_code.hasFocus()) {
et_promo_code.setText("")
private fun handlePromoCodeState(state: ShopState) = with(binding) {
if (state.promoCode == null && !etPromoCode.hasFocus()) {
etPromoCode.setText("")
}
pb_promo_code.show(state.promoCodeLoading)
pbPromoCode.show(state.promoCodeLoading)
}
private fun handleButtonsState(state: ShopState) {
btn_pay_google_pay.show(state.isGooglePayAvailable)
btn_alternative_payment.show(state.isGooglePayAvailable)
btn_main_action.show(!state.isGooglePayAvailable)
private fun handleButtonsState(state: ShopState) = with(binding) {
btnPayGooglePay.root.show(state.isGooglePayAvailable)
btnAlternativePayment.show(state.isGooglePayAvailable)
btnMainAction.show(!state.isGooglePayAvailable)
if (state.total != null) {
btn_alternative_payment.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
btn_main_action.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
btn_pay_google_pay.setOnClickListener { store.dispatch(ShopAction.BuyWithGooglePay) }
btnAlternativePayment.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
btnMainAction.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
btnPayGooglePay.root.setOnClickListener { store.dispatch(ShopAction.BuyWithGooglePay) }
}
}

View file

@ -11,6 +11,7 @@ import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.tokens.redux.TokensAction
@ -19,7 +20,7 @@ import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter
import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_add_tokens.*
import com.tangem.wallet.databinding.FragmentAddTokensBinding
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import org.rekotlin.StoreSubscriber
@ -29,6 +30,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
StoreSubscriber<TokensState> {
private lateinit var viewAdapter: CurrenciesAdapter
private val binding: FragmentAddTokensBinding by viewBinding(FragmentAddTokensBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -57,29 +59,29 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
store.unsubscribe(this)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) {
super.onViewCreated(view, savedInstanceState)
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
setupPopularTokensRecyclerView()
btn_tokens_save_changes.setOnClickListener {
btnTokensSaveChanges.setOnClickListener {
store.dispatch(TokensAction.SaveChanges(viewAdapter.getAddedItems()))
}
}
private fun setupPopularTokensRecyclerView() {
private fun setupPopularTokensRecyclerView() = with(binding) {
viewAdapter = CurrenciesAdapter()
viewAdapter.setOnItemAddListener {
btn_tokens_save_changes.isEnabled = viewAdapter.getAddedItems().isNotEmpty()
btnTokensSaveChanges.isEnabled = viewAdapter.getAddedItems().isNotEmpty()
}
rv_popular_tokens.layoutManager = LinearLayoutManager(context)
rv_popular_tokens.adapter = viewAdapter
rvPopularTokens.layoutManager = LinearLayoutManager(context)
rvPopularTokens.adapter = viewAdapter
}
override fun newState(state: TokensState) {
if (activity == null) return
if (activity == null || view == null) return
viewAdapter.addedCurrencies = state.addedCurrencies
viewAdapter.submitUnfilteredList(state.shownCurrencies)

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.tokens.ui.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@ -20,8 +19,8 @@ import com.tangem.tap.features.tokens.redux.CurrencyListItem
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.item_currency_subtitle.view.*
import kotlinx.android.synthetic.main.item_popular_token.view.*
import com.tangem.wallet.databinding.ItemCurrencySubtitleBinding
import com.tangem.wallet.databinding.ItemPopularTokenBinding
import java.util.*
class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>(DiffUtilCallback) {
@ -62,18 +61,18 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
0 -> TitleViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_currency_subtitle, parent, false)
binding = ItemCurrencySubtitleBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
)
1 -> CurrenciesViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_popular_token, parent, false),
vhOnItemAddListener
binding = ItemPopularTokenBinding
.inflate(LayoutInflater.from(parent.context), parent, false),
onItemAddListener = vhOnItemAddListener
)
else -> CurrenciesViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_popular_token, parent, false),
vhOnItemAddListener
binding = ItemPopularTokenBinding
.inflate(LayoutInflater.from(parent.context), parent, false),
onItemAddListener = vhOnItemAddListener
)
}
}
@ -109,15 +108,15 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
is CurrencyListItem.BlockchainListItem -> {
element.blockchain.currency.toLowerCase(Locale.US)
.contains(queryNormalized) ||
element.blockchain.fullName.toLowerCase(Locale.US)
.contains(queryNormalized)
element.blockchain.fullName.toLowerCase(Locale.US)
.contains(queryNormalized)
}
is CurrencyListItem.TokenListItem -> {
element.token.name.toLowerCase(Locale.US)
.contains(queryNormalized) ||
element.token.symbol.toLowerCase(Locale.US)
.contains(queryNormalized)
element.token.symbol.toLowerCase(Locale.US)
.contains(queryNormalized)
}
is CurrencyListItem.TitleListItem -> true
}
@ -129,9 +128,9 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
}
class CurrenciesViewHolder(
private val view: View,
private val binding: ItemPopularTokenBinding,
private val onItemAddListener: ((CurrencyListItem) -> Unit),
) : RecyclerView.ViewHolder(view) {
) : RecyclerView.ViewHolder(binding.root) {
fun bind(
currency: CurrencyListItem,
@ -141,12 +140,14 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
when (currency) {
is CurrencyListItem.BlockchainListItem -> {
val addedBlockchains = addedCurrencies?.blockchains ?: listOf()
val currentlyAdded = currentlyAddedItems.filterIsInstance<CurrencyListItem.BlockchainListItem>()
val currentlyAdded =
currentlyAddedItems.filterIsInstance<CurrencyListItem.BlockchainListItem>()
bindBlockchain(currency, addedBlockchains, currentlyAdded)
}
is CurrencyListItem.TokenListItem -> {
val addedTokens = addedCurrencies?.tokens ?: listOf()
val currentlyAdded = currentlyAddedItems.filterIsInstance<CurrencyListItem.TokenListItem>()
val currentlyAdded =
currentlyAddedItems.filterIsInstance<CurrencyListItem.TokenListItem>()
bindToken(currency, addedTokens, currentlyAdded)
}
}
@ -156,13 +157,13 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
currency: CurrencyListItem.BlockchainListItem,
addedBlockchains: List<Blockchain>,
currentlyAdded: List<CurrencyListItem.BlockchainListItem>
) {
) = with(binding) {
val blockchain = currency.blockchain
Picasso.get().loadCurrenciesIcon(view.iv_currency, view.tv_token_letter, null, blockchain)
Picasso.get().loadCurrenciesIcon(ivCurrency, tvTokenLetter, null, blockchain)
view.tv_currency_name.text = blockchain.fullName
view.tv_currency_symbol.text = blockchain.currency
view.btn_add_token.setOnClickListener {
tvCurrencyName.text = blockchain.fullName
tvCurrencySymbol.text = blockchain.currency
btnAddToken.setOnClickListener {
onItemAddListener.invoke(currency)
currency.isAdded = true
modifyAddTokenButton(currency)
@ -178,16 +179,17 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
currency: CurrencyListItem.TokenListItem,
addedTokens: List<Token>,
currentlyAdded: List<CurrencyListItem.TokenListItem>
) {
) = with(binding) {
val token = currency.token
Picasso.get().loadCurrenciesIcon(view.iv_currency, view.tv_token_letter, token, token.blockchain)
Picasso.get()
.loadCurrenciesIcon(ivCurrency, tvTokenLetter, token, token.blockchain)
view.tv_currency_name.text = token.name
view.tv_currency_symbol.text = view.context.getString(
tvCurrencyName.text = token.name
tvCurrencySymbol.text = tvCurrencySymbol.getString(
R.string.token_symbol_address_format,
token.symbol, token.contractAddress
)
view.btn_add_token.setOnClickListener {
btnAddToken.setOnClickListener {
onItemAddListener.invoke(currency)
currency.isAdded = true
modifyAddTokenButton(currency)
@ -200,48 +202,52 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
modifyAddTokenButton(currency)
}
private fun modifyAddTokenButton(currency: CurrencyListItem) {
private fun modifyAddTokenButton(currency: CurrencyListItem) = with(binding) {
if (currency.isLocked) {
view.btn_add_token.setText(R.string.common_add)
view.btn_add_token.isEnabled = false
btnAddToken.setText(R.string.common_add)
btnAddToken.isEnabled = false
} else {
val text = if (currency.isAdded) R.string.add_token_added else R.string.common_add
view.btn_add_token.setText(text)
view.btn_add_token.isEnabled = !currency.isAdded
btnAddToken.setText(text)
btnAddToken.isEnabled = !currency.isAdded
}
}
}
class TitleViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(title: CurrencyListItem.TitleListItem) {
class TitleViewHolder(val binding: ItemCurrencySubtitleBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(title: CurrencyListItem.TitleListItem) = with(binding) {
if (title.blockchain != null) {
val subtitle = view.getString(title.titleResId, title.blockchain.getTokensName())
view.tv_subtitle.text = subtitle.uppercase()
view.cl_subtitle_container.setOnClickListener {
val subtitle = root.getString(title.titleResId, title.blockchain.getTokensName())
tvSubtitle.text = subtitle.uppercase()
clSubtitleContainer.setOnClickListener {
val rotation = if (title.isContentShown) -90f else 90f
store.dispatch(TokensAction.ToggleShowTokensForBlockchain(
title.isContentShown, title.blockchain
))
view.iv_toggle_sublist_visibility.animate().rotation(rotation).withEndAction {
showArrow(view, title.isContentShown)
store.dispatch(
TokensAction.ToggleShowTokensForBlockchain(
title.isContentShown, title.blockchain
)
)
ivToggleSublistVisibility.animate().rotation(rotation).withEndAction {
showArrow(title.isContentShown)
}
}
showArrow(view, title.isContentShown)
showArrow(title.isContentShown)
} else {
view.tv_subtitle.text = view.getString(title.titleResId).uppercase()
view.iv_toggle_sublist_visibility.hide()
tvSubtitle.text = root.getString(title.titleResId).uppercase()
ivToggleSublistVisibility.hide()
}
}
private fun showArrow(view: View, isContentShown: Boolean) {
view.iv_toggle_sublist_visibility.show()
private fun showArrow(isContentShown: Boolean) = with(binding) {
ivToggleSublistVisibility.show()
val drawable = if (isContentShown) {
R.drawable.ic_arrow_angle_down
} else {
R.drawable.ic_arrow_angle_right
}
view.iv_toggle_sublist_visibility.setImageResource(drawable)
view.iv_toggle_sublist_visibility.rotation = 0f
ivToggleSublistVisibility.setImageResource(drawable)
ivToggleSublistVisibility.rotation = 0f
}
}
}

View file

@ -1,13 +1,10 @@
package com.tangem.tap.features.wallet.ui
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.card_balance.*
import kotlinx.android.synthetic.main.layout_balance.*
import kotlinx.android.synthetic.main.layout_balance_error.*
import com.tangem.wallet.databinding.CardBalanceBinding
import java.math.BigDecimal
enum class BalanceStatus {
@ -34,30 +31,34 @@ data class BalanceWidgetData(
)
data class TokenData(
val amount: String,
val tokenSymbol: String,
val fiatAmount: String? = null,
val fiatRateString: String? = null,
val fiatRate: BigDecimal? = null,
val amount: String,
val tokenSymbol: String,
val fiatAmount: String? = null,
val fiatRateString: String? = null,
val fiatRate: BigDecimal? = null,
)
class BalanceWidget(
val fragment: Fragment,
val data: BalanceWidgetData,
private val isTwinCard: Boolean,
private val binding: CardBalanceBinding,
private val fragment: WalletFragment,
private val data: BalanceWidgetData,
private val isTwinCard: Boolean,
) {
fun setup() {
when (data.status) {
BalanceStatus.Loading -> {
fragment.l_balance.show()
fragment.l_balance_error.hide()
fragment.tv_fiat_amount.hide()
with(binding) {
lBalance.root.show()
lBalanceError.root.hide()
lBalance.tvFiatAmount.hide()
lBalance.tvCurrency.text = data.currency
lBalance.tvAmount.text = ""
}
fragment.tv_currency.text = data.currency
fragment.tv_amount.text = ""
showStatus(R.id.tv_status_loading)
@ -67,18 +68,18 @@ class BalanceWidget(
showBalanceWithoutToken(data, false)
}
}
BalanceStatus.VerifiedOnline, BalanceStatus.TransactionInProgress -> {
fragment.l_balance.show()
fragment.l_balance_error.hide()
BalanceStatus.VerifiedOnline, BalanceStatus.TransactionInProgress -> with(binding.lBalance) {
root.show()
binding.lBalanceError.root.hide()
val statusView = if (data.status == BalanceStatus.VerifiedOnline) {
R.id.tv_status_verified
} else {
fragment.tv_status_error.text =
fragment.getText(R.string.wallet_balance_tx_in_progress)
tvStatusError.text =
fragment.getText(R.string.wallet_balance_tx_in_progress)
R.id.group_error
}
showStatus(statusView)
fragment.tv_status_error_message.hide()
tvStatusErrorMessage.hide()
if (data.token != null) {
showBalanceWithToken(data, true)
@ -86,79 +87,83 @@ class BalanceWidget(
showBalanceWithoutToken(data, true)
}
}
BalanceStatus.Unreachable -> {
fragment.l_balance.show()
fragment.l_balance_error.hide()
fragment.tv_fiat_amount.hide()
fragment.group_base_currency.hide()
BalanceStatus.Unreachable -> with(binding.lBalance) {
root.show()
binding.lBalanceError.root.hide()
tvFiatAmount.hide()
groupBaseCurrency.hide()
val currency = if (data.token != null) data.token.tokenSymbol else data.currency
fragment.tv_currency.text = currency
fragment.tv_amount.text = ""
tvCurrency.text = currency
tvAmount.text = ""
fragment.tv_status_error_message.text = data.errorMessage
fragment.tv_status_error.text =
fragment.getString(R.string.wallet_balance_blockchain_unreachable)
tvStatusErrorMessage.text = data.errorMessage
tvStatusError.text =
fragment.getString(R.string.wallet_balance_blockchain_unreachable)
showStatus(R.id.group_error)
fragment.tv_status_error_message.show(!data.errorMessage.isNullOrBlank())
tvStatusErrorMessage.show(!data.errorMessage.isNullOrBlank())
}
BalanceStatus.EmptyCard -> {
fragment.l_balance.hide()
fragment.l_balance_error.show()
BalanceStatus.EmptyCard -> with(binding.lBalanceError) {
binding.lBalance.root.hide()
binding.lBalanceError.root.show()
if (isTwinCard) {
fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_twin_card)
fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_twin_card_subtitle)
tvErrorTitle.text = fragment.getText(R.string.wallet_error_empty_twin_card)
tvErrorDescriptions.text =
fragment.getText(R.string.wallet_error_empty_twin_card_subtitle)
} else {
fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_empty_card)
fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_error_empty_card_subtitle)
tvErrorTitle.text = fragment.getText(R.string.wallet_error_empty_card)
tvErrorDescriptions.text =
fragment.getText(R.string.wallet_error_empty_card_subtitle)
}
}
BalanceStatus.NoAccount -> {
fragment.l_balance.hide()
fragment.l_balance_error.show()
fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_no_account)
fragment.tv_error_descriptions.text =
fragment.getString(
R.string.wallet_error_no_account_subtitle_format,
data.amountToCreateAccount, data.currencySymbol
)
BalanceStatus.NoAccount -> with(binding.lBalanceError) {
binding.lBalance.root.hide()
binding.lBalanceError.root.show()
tvErrorTitle.text = fragment.getText(R.string.wallet_error_no_account)
tvErrorDescriptions.text =
fragment.getString(
R.string.wallet_error_no_account_subtitle_format,
data.amountToCreateAccount, data.currencySymbol
)
}
BalanceStatus.UnknownBlockchain -> {
fragment.l_balance.hide()
fragment.l_balance_error.show()
fragment.tv_error_title.text = fragment.getText(R.string.wallet_error_unsupported_blockchain)
fragment.tv_error_descriptions.text =
fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
BalanceStatus.UnknownBlockchain -> with(binding.lBalanceError) {
binding.lBalance.root.hide()
binding.lBalanceError.root.show()
tvErrorTitle.text =
fragment.getText(R.string.wallet_error_unsupported_blockchain)
tvErrorDescriptions.text =
fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
}
}
}
private fun showStatus(@IdRes viewRes: Int) {
fragment.group_error.show(viewRes == R.id.group_error)
fragment.tv_status_loading.show(viewRes == R.id.tv_status_loading)
fragment.tv_status_verified.show(viewRes == R.id.tv_status_verified)
private fun showStatus(@IdRes viewRes: Int) = with(binding.lBalance) {
groupError.show(viewRes == R.id.group_error)
tvStatusLoading.show(viewRes == R.id.tv_status_loading)
tvStatusVerified.show(viewRes == R.id.tv_status_verified)
}
private fun showBalanceWithToken(data: BalanceWidgetData, showAmount: Boolean) {
fragment.group_base_currency.show()
fragment.tv_currency.text = data.token?.tokenSymbol
fragment.tv_base_currency.text = data.currency
fragment.tv_amount.text = if (showAmount) data.token?.amount else ""
fragment.tv_base_amount.text = if (showAmount) data.amount else ""
private fun showBalanceWithToken(data: BalanceWidgetData, showAmount: Boolean) = with(binding.lBalance) {
groupBaseCurrency.show()
tvCurrency.text = data.token?.tokenSymbol
tvBaseCurrency.text = data.currency
tvAmount.text = if (showAmount) data.token?.amount else ""
tvBaseAmount.text = if (showAmount) data.amount else ""
if (showAmount) {
fragment.tv_fiat_amount.show()
fragment.tv_fiat_amount.text = data.token?.fiatAmount
tvFiatAmount.show()
tvFiatAmount.text = data.token?.fiatAmount
}
}
private fun showBalanceWithoutToken(data: BalanceWidgetData, showAmount: Boolean) {
fragment.group_base_currency.hide()
fragment.tv_currency.text = data.currency
fragment.tv_amount.text = if (showAmount) data.amount else ""
if (showAmount) {
fragment.tv_fiat_amount.show()
fragment.tv_fiat_amount.text = data.fiatAmountFormatted
private fun showBalanceWithoutToken(data: BalanceWidgetData, showAmount: Boolean) =
with(binding.lBalance) {
groupBaseCurrency.hide()
tvCurrency.text = data.currency
tvAmount.text = if (showAmount) data.amount else ""
if (showAmount) {
tvFiatAmount.show()
tvFiatAmount.text = data.fiatAmountFormatted
}
}
}
}

View file

@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.squareup.picasso.Picasso
import com.tangem.common.extensions.guard
import com.tangem.tap.common.SnackbarHandler
@ -22,19 +23,17 @@ import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_wallet_details.*
import kotlinx.android.synthetic.main.layout_balance_error.*
import kotlinx.android.synthetic.main.layout_balance_wallet_details.*
import kotlinx.android.synthetic.main.layout_wallet_details.*
import kotlinx.android.synthetic.main.layout_wallet_details.card_balance
import kotlinx.android.synthetic.main.layout_wallet_details_rent.*
import com.tangem.wallet.databinding.FragmentWalletDetailsBinding
import org.rekotlin.StoreSubscriber
class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreSubscriber<WalletState> {
class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
StoreSubscriber<WalletState> {
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
private var dialog: Dialog? = null
private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
@ -65,8 +64,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
setupTransactionsRecyclerView()
@ -74,91 +73,98 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
}
private fun setupTransactionsRecyclerView() {
private fun setupTransactionsRecyclerView() = with(binding) {
pendingTransactionAdapter = PendingTransactionsAdapter()
rv_pending_transaction.layoutManager = LinearLayoutManager(requireContext())
rv_pending_transaction.adapter = pendingTransactionAdapter
rvPendingTransaction.layoutManager = LinearLayoutManager(requireContext())
rvPendingTransaction.adapter = pendingTransactionAdapter
}
private fun setupButtons() {
btn_confirm.text = getString(R.string.wallet_button_send)
btn_confirm.setOnClickListener { store.dispatch(WalletAction.Send()) }
private fun setupButtons() = with(binding) {
btnConfirm.text = getString(R.string.wallet_button_send)
btnConfirm.setOnClickListener { store.dispatch(WalletAction.Send()) }
btn_share.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
binding.lWalletDetails.btnShare.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
btn_trade.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) }
btnTrade.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Buy) }
btn_sell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
btnSell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
}
override fun newState(state: WalletState) {
if (activity == null) return
if (activity == null || view == null) return
if (state.selectedWallet == null) return
val selectedWallet = state.getSelectedWalletData() ?: return
tv_currency_title.text = selectedWallet.currencyData.currency
val currency = selectedWallet.currency
if (currency is Currency.Token) {
tv_currency_subtitle.text = currency.blockchain.tokenDisplayName()
tv_currency_subtitle.show()
} else {
tv_currency_subtitle.hide()
}
showPendingTransactionsIfPresent(selectedWallet.pendingTransactions)
setupCurrency(selectedWallet.currencyData, selectedWallet.currency)
setupAddressCard(selectedWallet)
setupNoInternetHandling(state)
setupBalanceData(selectedWallet.currencyData)
btn_confirm.isEnabled = selectedWallet.mainButton.enabled
btn_copy.setOnClickListener {
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.CopyAddress(addressString, requireContext()))
}
}
btn_share.setOnClickListener {
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.ShareAddress(addressString, requireContext()))
}
}
setupButtons(selectedWallet)
handleDialogs(state.walletDialog)
handleCurrencyIcon(selectedWallet)
handleWalletRent(selectedWallet.walletRent)
srl_wallet_details.setOnRefreshListener {
binding.srlWalletDetails.setOnRefreshListener {
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
store.dispatch(WalletAction.LoadWallet(
blockchain = selectedWallet.currency.blockchain
))
store.dispatch(
WalletAction.LoadWallet(
blockchain = selectedWallet.currency.blockchain
)
)
}
}
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
srl_wallet_details.isRefreshing = false
binding.srlWalletDetails.isRefreshing = false
}
btn_trade.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed
btn_sell.show(selectedWallet.tradeCryptoState.sellingAllowed)
}
private fun handleWalletRent(rent: WalletRent?) {
private fun setupCurrency(currencyData: BalanceWidgetData, currency: Currency) = with(binding) {
tvCurrencyTitle.text = currencyData.currency
if (currency is Currency.Token) {
binding.tvCurrencySubtitle.text = currency.blockchain.tokenDisplayName()
binding.tvCurrencySubtitle.show()
} else {
binding.tvCurrencySubtitle.hide()
}
}
private fun setupButtons(selectedWallet: WalletData) = with(binding) {
btnConfirm.isEnabled = selectedWallet.mainButton.enabled
lWalletDetails.btnCopy.setOnClickListener {
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.CopyAddress(addressString, requireContext()))
}
}
lWalletDetails.btnShare.setOnClickListener {
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.ShareAddress(addressString, requireContext()))
}
}
btnTrade.isEnabled = selectedWallet.tradeCryptoState.buyingAllowed
btnSell.show(selectedWallet.tradeCryptoState.sellingAllowed)
}
private fun handleWalletRent(rent: WalletRent?) = with(binding) {
val rent = rent.guard {
l_rent_warning.hide()
lRentWarning.root.hide()
return
}
val warningMessage = requireContext().getString(
R.string.solana_rent_warning, rent.minRentValue, rent.rentExemptValue)
tv_rent_warning_message.text = warningMessage
l_rent_warning.show()
R.string.solana_rent_warning, rent.minRentValue, rent.rentExemptValue
)
lRentWarning.tvRentWarningMessage.text = warningMessage
lRentWarning.root.show()
}
private fun handleCurrencyIcon(wallet: WalletData) {
private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) {
Picasso.get().loadCurrenciesIcon(
imageView = iv_currency,
textView = tv_token_letter,
imageView = ivCurrency,
textView = tvTokenLetter,
blockchain = wallet.currency.blockchain,
token = (wallet.currency as? Currency.Token)?.token
)
@ -167,42 +173,48 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
pendingTransactionAdapter.submitList(pendingTransactions)
rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
binding.rvPendingTransaction?.show(pendingTransactions.isNotEmpty())
}
private fun setupAddressCard(state: WalletData) {
private fun setupAddressCard(state: WalletData) = with(binding.lWalletDetails) {
if (state.walletAddresses != null) {
if (state.shouldShowMultipleAddress() && state.currency is Currency.Blockchain) {
(card_balance as? ViewGroup)?.beginDelayedTransition()
chip_group_address_type.show()
chip_group_address_type.fitChipsByGroupWidth()
(cardBalance as? ViewGroup)?.beginDelayedTransition()
chipGroupAddressType.show()
chipGroupAddressType.fitChipsByGroupWidth()
val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
val checkedId =
MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
chipGroupAddressType.setOnCheckedChangeListener { group, checkedId ->
if (checkedId == -1) return@setOnCheckedChangeListener
val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
val type =
MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
}
} else {
chip_group_address_type.hide()
chipGroupAddressType.hide()
}
tv_address.text = state.walletAddresses.selectedAddress.address
tv_explore?.setOnClickListener {
store.dispatch(WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,
requireContext()))
tvAddress.text = state.walletAddresses.selectedAddress.address
tvExplore?.setOnClickListener {
store.dispatch(
WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,
requireContext()
)
)
}
iv_qr_code.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode())
tv_receive_message.text = getQRReceiveMessage(tv_receive_message.context, state.currency)
ivQrCode.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode())
tvReceiveMessage.text =
getQRReceiveMessage(tvReceiveMessage.context, state.currency)
}
}
private fun setupNoInternetHandling(state: WalletState) {
if (state.state == ProgressState.Error) {
if (state.error == ErrorType.NoInternetConnection) {
srl_wallet_details?.isRefreshing = false
binding.srlWalletDetails.isRefreshing = false
(activity as? SnackbarHandler)?.showSnackbar(
text = R.string.wallet_notification_no_internet,
buttonTitle = R.string.common_retry
@ -213,73 +225,77 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS
}
}
private fun setupBalanceData(data: BalanceWidgetData) {
private fun setupBalanceData(data: BalanceWidgetData) = with(binding.lWalletDetails) {
when (data.status) {
BalanceStatus.Loading -> {
l_balance.show()
l_balance_error.hide()
tv_fiat_amount.hide()
tv_amount.text = ""
lBalance.root.show()
lBalanceError.root.hide()
lBalance.tvFiatAmount.hide()
lBalance.tvAmount.text = ""
showStatus(R.id.tv_status_loading)
showBalanceWithoutToken(data, false)
}
BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress,
BalanceStatus.TransactionInProgress -> {
l_balance.show()
l_balance_error.hide()
lBalance.root.show()
lBalanceError.root.hide()
val statusView = if (data.status == BalanceStatus.VerifiedOnline ||
data.status == BalanceStatus.SameCurrencyTransactionInProgress) {
data.status == BalanceStatus.SameCurrencyTransactionInProgress
) {
R.id.tv_status_verified
} else {
tv_status_error.text =
getText(R.string.wallet_balance_tx_in_progress)
lBalance.tvStatusError.text =
getText(R.string.wallet_balance_tx_in_progress)
R.id.group_error
}
showStatus(statusView)
tv_status_error_message.hide()
lBalance.tvStatusErrorMessage.hide()
showBalanceWithoutToken(data, true)
}
BalanceStatus.Unreachable -> {
l_balance.show()
l_balance_error.hide()
tv_fiat_amount.hide()
lBalance.root.show()
lBalanceError.root.hide()
lBalance.tvFiatAmount.hide()
tv_amount.text = ""
lBalance.tvAmount.text = ""
tv_status_error_message.text = data.errorMessage
tv_status_error.text =
getString(R.string.wallet_balance_blockchain_unreachable)
lBalance.tvStatusErrorMessage.text = data.errorMessage
lBalance.tvStatusError.text =
getString(R.string.wallet_balance_blockchain_unreachable)
showStatus(R.id.group_error)
tv_status_error_message.show(!data.errorMessage.isNullOrBlank())
lBalance.tvStatusErrorMessage.show(!data.errorMessage.isNullOrBlank())
}
BalanceStatus.NoAccount -> {
l_balance.hide()
l_balance_error.show()
tv_error_title.text = getText(R.string.wallet_error_no_account)
tv_error_descriptions.text =
getString(
R.string.wallet_error_no_account_subtitle_format,
data.amountToCreateAccount, data.currencySymbol
)
lBalance.root.hide()
lBalanceError.root.show()
lBalanceError.tvErrorTitle.text = getText(R.string.wallet_error_no_account)
lBalanceError.tvErrorDescriptions.text =
getString(
R.string.wallet_error_no_account_subtitle_format,
data.amountToCreateAccount, data.currencySymbol
)
}
}
card_pending_transaction_warning.show(data.status == BalanceStatus.SameCurrencyTransactionInProgress)
binding.cardPendingTransactionWarning.show(data.status == BalanceStatus.SameCurrencyTransactionInProgress)
}
private fun showStatus(@IdRes viewRes: Int) {
group_error.show(viewRes == R.id.group_error)
tv_status_loading.show(viewRes == R.id.tv_status_loading)
tv_status_verified.show(viewRes == R.id.tv_status_verified)
with(binding.lWalletDetails.lBalance) {
groupError.show(viewRes == R.id.group_error)
tvStatusLoading.show(viewRes == R.id.tv_status_loading)
tvStatusVerified.show(viewRes == R.id.tv_status_verified)
}
}
private fun showBalanceWithoutToken(data: BalanceWidgetData, showAmount: Boolean) {
tv_amount.text = if (showAmount) data.amount else ""
if (showAmount) {
tv_fiat_amount.show()
tv_fiat_amount.text = data.fiatAmountFormatted
with(binding.lWalletDetails.lBalance) {
tvAmount.text = if (showAmount) data.amount else ""
if (showAmount) {
tvFiatAmount.show()
tvFiatAmount.text = data.fiatAmountFormatted
}
}
}

View file

@ -11,6 +11,7 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.squareup.picasso.Picasso
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.MainActivity
@ -29,9 +30,7 @@ import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
import com.tangem.tap.features.wallet.ui.wallet.WalletView
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_wallet.*
import kotlinx.android.synthetic.main.fragment_wallet.toolbar
import kotlinx.android.synthetic.main.fragment_wallet_details.*
import com.tangem.wallet.databinding.FragmentWalletBinding
import org.rekotlin.StoreSubscriber
@ -39,6 +38,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
private lateinit var warningsAdapter: WarningMessagesAdapter
private val binding: FragmentWalletBinding by viewBinding(FragmentWalletBinding::bind)
private var walletView: WalletView = SingleWalletView()
override fun onCreate(savedInstanceState: Bundle?) {
@ -61,7 +62,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
oldState.walletState == newState.walletState
}.select { it.walletState }
}
walletView.setFragment(this)
walletView.setFragment(this, binding)
store.dispatch(WalletAction.UpdateWallet())
}
@ -73,43 +74,45 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
toolbar.setNavigationOnClickListener {
binding.toolbar.setNavigationOnClickListener {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
setupWarningsRecyclerView()
walletView.changeWalletView(this)
walletView.changeWalletView(this, binding)
}
private fun setupWarningsRecyclerView() {
warningsAdapter = WarningMessagesAdapter()
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
rv_warning_messages.layoutManager = layoutManager
rv_warning_messages.addItemDecoration(SpacesItemDecoration(rv_warning_messages.dpToPx(16f).toInt()))
rv_warning_messages.adapter = warningsAdapter
with(binding) {
rvWarningMessages.layoutManager = layoutManager
rvWarningMessages.addItemDecoration(SpacesItemDecoration(rvWarningMessages.dpToPx(16f).toInt()))
rvWarningMessages.adapter = warningsAdapter
}
}
override fun newState(state: WalletState) {
if (activity == null) return
if (activity == null || view == null) return
if (state.isMultiwalletAllowed &&
state.primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
walletView is SingleWalletView
) {
walletView = MultiWalletView()
walletView.changeWalletView(this)
walletView.changeWalletView(this, binding)
} else if (!state.isMultiwalletAllowed && walletView is MultiWalletView) {
walletView = SingleWalletView()
walletView.changeWalletView(this)
walletView.changeWalletView(this, binding)
}
walletView.onNewState(state)
if (!state.shouldShowDetails) {
toolbar.menu.removeItem(R.id.details_menu)
} else if (toolbar.menu.findItem(R.id.details_menu) == null) {
toolbar.inflateMenu(R.menu.wallet)
binding.toolbar.menu.removeItem(R.id.details_menu)
} else if (binding.toolbar.menu.findItem(R.id.details_menu) == null) {
binding.toolbar.inflateMenu(R.menu.wallet)
}
setupNoInternetHandling(state)
@ -117,26 +120,26 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
showWarningsIfPresent(state.mainWarningsList)
srl_wallet.setOnRefreshListener {
binding.srlWallet.setOnRefreshListener {
if (state.state != ProgressState.Loading) {
store.dispatch(WalletAction.LoadData)
}
}
if (state.state != ProgressState.Loading) {
srl_wallet.isRefreshing = false
binding.srlWallet.isRefreshing = false
}
}
private fun showWarningsIfPresent(warnings: List<WarningMessage>) {
warningsAdapter.submitList(warnings)
rv_warning_messages.show(warnings.isNotEmpty())
binding.rvWarningMessages.show(warnings.isNotEmpty())
}
private fun setupNoInternetHandling(state: WalletState) {
if (state.state == ProgressState.Error) {
if (state.error == ErrorType.NoInternetConnection) {
srl_wallet_details?.isRefreshing = false
binding.srlWallet.isRefreshing = false
(activity as? MainActivity)?.showSnackbar(
text = R.string.wallet_notification_no_internet,
buttonTitle = R.string.common_retry
@ -152,7 +155,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
.load(cardImage?.artworkId)
.placeholder(R.drawable.card_placeholder_black)
?.error(R.drawable.card_placeholder_black)
?.into(iv_card)
?.into(binding.ivCard)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {

View file

@ -1,25 +1,26 @@
package com.tangem.tap.features.wallet.ui.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.PendingTransactionType
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.item_pending_transaction.view.*
import com.tangem.wallet.databinding.ItemPendingTransactionBinding
class PendingTransactionsAdapter
: ListAdapter<PendingTransaction, PendingTransactionsAdapter.TransactionsViewHolder>(DiffUtilCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionsViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_pending_transaction, parent, false)
return TransactionsViewHolder(layout)
val binding = ItemPendingTransactionBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return TransactionsViewHolder(binding)
}
override fun onBindViewHolder(holder: TransactionsViewHolder, position: Int) {
@ -36,13 +37,13 @@ class PendingTransactionsAdapter
) = oldItem == newItem
}
class TransactionsViewHolder(val view: View) :
RecyclerView.ViewHolder(view) {
class TransactionsViewHolder(val binding: ItemPendingTransactionBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(transaction: PendingTransaction) {
if (transaction.type == PendingTransactionType.Unknown) {
view.hide()
binding.root.hide()
}
val transactionDescriptionRes = when (transaction.type) {
@ -60,17 +61,16 @@ class PendingTransactionsAdapter
PendingTransactionType.Outgoing -> R.drawable.ic_arrow_right_20
PendingTransactionType.Unknown -> return
}
view.tv_pending_transaction.text = view.context.getString(transactionDescriptionRes)
binding.tvPendingTransaction.text = binding.root.getString(transactionDescriptionRes)
transaction.amountUi?.let { view.tv_pending_transaction_amount.text = "$it " }
view.tv_pending_transaction_currency.text = "${transaction.currency}"
transaction.amountUi?.let { binding.tvPendingTransactionAmount.text = "$it " }
binding.tvPendingTransactionCurrency.text = "${transaction.currency}"
if (transaction.address != null) {
view.tv_pending_transaction_address.text =
view.context.getString(transactionAddressRes, transaction.address)
binding.tvPendingTransactionAddress.text =
binding.root.getString(transactionAddressRes, transaction.address)
}
view.iv_pending_transaction.setImageDrawable(view.context.getDrawableCompat(image))
binding.ivPendingTransaction.setImageDrawable(binding.root.context.getDrawableCompat(image))
}
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.wallet.ui.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@ -9,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.loadCurrenciesIcon
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.wallet.redux.Currency
@ -17,7 +17,7 @@ import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.item_currency_wallet.view.*
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
class WalletAdapter
: ListAdapter<WalletData, WalletAdapter.WalletsViewHolder>(DiffUtilCallback) {
@ -32,8 +32,9 @@ class WalletAdapter
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_currency_wallet, parent, false)
val layout = ItemCurrencyWalletBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return WalletsViewHolder(layout)
}
@ -51,24 +52,24 @@ class WalletAdapter
) = oldItem == newItem
}
class WalletsViewHolder(val view: View) :
RecyclerView.ViewHolder(view) {
class WalletsViewHolder(val binding: ItemCurrencyWalletBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(wallet: WalletData) {
view.tv_currency.text = wallet.currencyData.currency
view.tv_amount.text = wallet.currencyData.amount?.takeWhile { !it.isWhitespace() }
view.tv_currency_symbol.text = wallet.currencyData.amount?.takeLastWhile { !it.isWhitespace() }
view.tv_amount_fiat.text = wallet.currencyData.fiatAmountFormatted
view.tv_exchange_rate.text = wallet.fiatRateString
view.card_wallet.setOnClickListener {
fun bind(wallet: WalletData) = with(binding) {
tvCurrency.text = wallet.currencyData.currency
tvAmount.text = wallet.currencyData.amount?.takeWhile { !it.isWhitespace() }
tvCurrencySymbol.text = wallet.currencyData.amount?.takeLastWhile { !it.isWhitespace() }
tvAmountFiat.text = wallet.currencyData.fiatAmountFormatted
tvExchangeRate.text = wallet.fiatRateString
cardWallet.setOnClickListener {
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
}
val blockchain = wallet.currency.blockchain
val token = (wallet.currency as? Currency.Token)?.token
Picasso.get().loadCurrenciesIcon(
imageView = view.iv_currency,
textView = view.tv_token_letter,
imageView = ivCurrency,
textView = tvTokenLetter,
token = token, blockchain = blockchain,
)
@ -77,16 +78,16 @@ class WalletAdapter
BalanceStatus.Loading -> {
hideWarning()
if (wallet.currencyData.amount == null) {
view.tv_exchange_rate.text = view.context.getText(R.string.wallet_balance_loading)
tvExchangeRate.text = root.getString(R.string.wallet_balance_loading)
}
}
BalanceStatus.TransactionInProgress ->
showWarning(view.context.getString(R.string.wallet_balance_tx_in_progress))
showWarning(root.getString(R.string.wallet_balance_tx_in_progress))
BalanceStatus.Unreachable ->
showWarning(view.context.getString(R.string.wallet_balance_blockchain_unreachable))
showWarning(root.getString(R.string.wallet_balance_blockchain_unreachable))
BalanceStatus.NoAccount ->
showWarning(view.context.getString(R.string.wallet_error_no_account))
showWarning(root.getString(R.string.wallet_error_no_account))
else -> {
}
}
@ -94,7 +95,7 @@ class WalletAdapter
private fun showWarning(message: String) {
toggleWarning(true)
view.tv_status_error_message.text = message
binding.tvStatusErrorMessage.text = message
}
private fun hideWarning() {
@ -102,8 +103,8 @@ class WalletAdapter
}
private fun toggleWarning(show: Boolean) {
view.tv_exchange_rate.show(!show)
view.tv_status_error_message.show(show)
binding.tvExchangeRate.show(!show)
binding.tvStatusErrorMessage.show(show)
}
}
}

View file

@ -10,25 +10,23 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import com.google.android.play.core.review.ReviewManagerFactory
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.extensions.getActivity
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.features.feedback.RateCanBeBetterEmail
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.layout_warning.view.*
import com.tangem.wallet.databinding.LayoutWarningBinding
import timber.log.Timber
class WarningMessagesAdapter : ListAdapter<WarningMessage, WarningMessageVH>(DiffUtilCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH {
val inflater = LayoutInflater.from(parent.context)
val layout = inflater.inflate(R.layout.layout_warning, parent, false)
return WarningMessageVH(layout)
val binding = LayoutWarningBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return WarningMessageVH(binding)
}
override fun onBindViewHolder(holder: WarningMessageVH, position: Int) {
@ -44,7 +42,7 @@ class WarningMessagesAdapter : ListAdapter<WarningMessage, WarningMessageVH>(Dif
}
}
class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(warning: WarningMessage) {
setBgColor(warning.priority)
@ -52,12 +50,12 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
setupControlButtons(warning)
}
private fun setText(warning: WarningMessage) {
private fun setText(warning: WarningMessage) = with(binding) {
fun getString(resId: Int?, default: String, formatArgs: String? = null) =
if (resId == null) default else view.context.getString(resId, formatArgs)
if (resId == null) default else root.getString(resId, formatArgs)
view.tv_title.text = getString(warning.titleResId, warning.title)
view.tv_message.text = getString(
tvTitle.text = getString(warning.titleResId, warning.title)
tvMessage.text = getString(
resId = warning.messageResId, default = warning.message, formatArgs = warning.messageFormatArg
)
}
@ -68,17 +66,17 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
WarningMessage.Priority.Warning -> R.color.warning_warning
WarningMessage.Priority.Critical -> R.color.warning_critical
}
view.card_view.setCardBackgroundColor(view.context.resources.getColor(color))
binding.cardView.setCardBackgroundColor(binding.root.getColor(color))
}
private fun setupControlButtons(warning: WarningMessage) = when (warning.type) {
WarningMessage.Type.Permanent, WarningMessage.Type.TestCard -> {
view.group_controls_temporary.hide()
view.group_controls_rating.hide()
binding.groupControlsTemporary.hide()
binding.groupControlsRating.hide()
}
WarningMessage.Type.Temporary -> {
view.group_controls_rating.hide()
view.group_controls_temporary.show()
binding.groupControlsRating.hide()
binding.groupControlsTemporary.show()
val buttonAction =
when (warning.titleResId) {
R.string.warning_important_security_info -> {
@ -92,29 +90,29 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) {
}
}
}
val buttonTitle = view.getString(
val buttonTitle = binding.root.getString(
warning.buttonTextId ?: R.string.how_to_got_it_button
)
view.btn_got_it.setOnClickListener (buttonAction)
view.btn_got_it.text = buttonTitle
binding.btnGotIt.setOnClickListener (buttonAction)
binding.btnGotIt.text = buttonTitle
}
WarningMessage.Type.AppRating -> {
view.group_controls_temporary.hide()
view.group_controls_rating.show()
binding.groupControlsTemporary.hide()
binding.groupControlsRating.show()
val analyticsHandler = store.state.globalState.analyticsHandlers
view.btn_close.setOnClickListener {
binding.btnClose.setOnClickListener {
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
store.dispatch(GlobalAction.HideWarningMessage(warning))
store.dispatch(WalletAction.Warnings.AppRating.RemindLater)
}
view.btn_can_be_better.setOnClickListener {
binding.btnCanBeBetter.setOnClickListener {
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
store.dispatch(GlobalAction.HideWarningMessage(warning))
store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail()))
}
view.btn_really_cool.setOnClickListener {
val activity = view.context.getActivity() ?: return@setOnClickListener
binding.btnReallyCool.setOnClickListener {
val activity = binding.root.context.getActivity() ?: return@setOnClickListener
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE)
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)

View file

@ -1,8 +1,9 @@
package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.recyclerview.widget.*
@ -12,14 +13,22 @@ import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.dialog_wallet_send.*
import kotlinx.android.synthetic.main.item_wallet_amount_to_send.view.*
import com.tangem.wallet.databinding.DialogWalletSendBinding
import com.tangem.wallet.databinding.ItemWalletAmountToSendBinding
class AmountToSendDialog(context: Context) : BottomSheetDialog(context) {
init {
this.setContentView(R.layout.dialog_wallet_send)
var binding: DialogWalletSendBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DialogWalletSendBinding.inflate(LayoutInflater.from(context))
setContentView(binding!!.root)
}
override fun setOnCancelListener(listener: DialogInterface.OnCancelListener?) {
super.setOnCancelListener(listener)
binding = null
}
fun show(amounts: List<Amount>?) {
@ -27,15 +36,15 @@ class AmountToSendDialog(context: Context) : BottomSheetDialog(context) {
store.dispatch(WalletAction.Send.Cancel)
}
rv_amounts_to_send.layoutManager = LinearLayoutManager(context)
binding!!.rvAmountsToSend.layoutManager = LinearLayoutManager(context)
val dividerItemDecoration = DividerItemDecoration(
ContextThemeWrapper(rv_amounts_to_send.context, R.style.AppTheme),
ContextThemeWrapper(binding!!.root.context, R.style.AppTheme),
DividerItemDecoration.VERTICAL
)
rv_amounts_to_send.addItemDecoration(dividerItemDecoration)
binding!!.rvAmountsToSend.addItemDecoration(dividerItemDecoration)
val viewAdapter = ChooseAmountAdapter()
rv_amounts_to_send.adapter = viewAdapter
binding!!.rvAmountsToSend.adapter = viewAdapter
viewAdapter.submitList(amounts)
@ -48,9 +57,10 @@ class ChooseAmountAdapter
: ListAdapter<Amount, ChooseAmountAdapter.AmountViewHolder>(DiffUtilCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_wallet_amount_to_send, parent, false)
return AmountViewHolder(layout)
val binding = ItemWalletAmountToSendBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return AmountViewHolder(binding)
}
override fun onBindViewHolder(holder: AmountViewHolder, position: Int) {
@ -67,13 +77,13 @@ class ChooseAmountAdapter
) = oldItem == newItem
}
class AmountViewHolder(val view: View) :
RecyclerView.ViewHolder(view) {
class AmountViewHolder(val binding: ItemWalletAmountToSendBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(amount: Amount) {
view.tv_currency_symbol.text = amount.currencySymbol
view.tv_amount.text = amount.value?.toFormattedString(amount.decimals)
view.setOnClickListener {
fun bind(amount: Amount) = with(binding) {
tvCurrencySymbol.text = amount.currencySymbol
tvAmount.text = amount.value?.toFormattedString(amount.decimals)
root.setOnClickListener {
store.dispatch(WalletAction.Send.Cancel)
store.dispatch(WalletAction.Send(amount))
}

View file

@ -1,30 +1,36 @@
package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.dialog_wallet_trade.*
import com.tangem.wallet.databinding.DialogWalletTradeBinding
class ChooseTradeActionDialog(context: Context) : BottomSheetDialog(context) {
init {
this.setContentView(R.layout.dialog_wallet_trade)
var binding: DialogWalletTradeBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DialogWalletTradeBinding.inflate(LayoutInflater.from(context))
setContentView(binding!!.root)
}
override fun show() {
super.show()
this.setOnDismissListener {
binding = null
store.dispatch(WalletAction.HideDialog)
}
dialog_btn_buy.setOnClickListener {
binding!!.dialogBtnBuy.setOnClickListener {
store.dispatch(WalletAction.TradeCryptoAction.Buy)
dismiss()
}
dialog_btn_sell.setOnClickListener {
binding!!.dialogBtnSell.setOnClickListener {
store.dispatch(WalletAction.TradeCryptoAction.Sell)
dismiss()
}

View file

@ -18,64 +18,64 @@ import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter
import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.card_balance.*
import kotlinx.android.synthetic.main.fragment_wallet.*
import kotlinx.android.synthetic.main.layout_balance_error.*
import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
import com.tangem.wallet.databinding.FragmentWalletBinding
class MultiWalletView : WalletView {
private var fragment: WalletFragment? = null
private var binding: FragmentWalletBinding? = null
private var dialog: Dialog? = null
private lateinit var walletsAdapter: WalletAdapter
override fun changeWalletView(fragment: WalletFragment) {
setFragment(fragment)
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
showMultiWalletView(fragment)
setupButtons(fragment)
showMultiWalletView(binding)
setupButtons(binding)
}
private fun showMultiWalletView(fragment: WalletFragment) = with(fragment) {
tv_twin_card_number.hide()
rv_pending_transaction.hide()
l_card_balance.hide()
l_address.hide()
l_buttons_short.hide()
l_buttons_long.hide()
btn_scan_multiwallet?.show()
rv_multiwallet.show()
btn_add_token.show()
setupWalletCardNumber(fragment)
private fun showMultiWalletView(binding: FragmentWalletBinding) = with(binding) {
tvTwinCardNumber.hide()
rvPendingTransaction.hide()
lCardBalance.root.hide()
lAddress.root.hide()
lButtonsShort.root.hide()
lButtonsLong.root.hide()
btnScanMultiwallet.show()
rvMultiwallet.show()
btnAddToken.show()
setupWalletCardNumber(binding)
}
private fun setupWalletCardNumber(fragment: WalletFragment) = with(fragment) {
private fun setupWalletCardNumber(binding: FragmentWalletBinding) = with(binding) {
val card = store.state.globalState.scanResponse?.card
if (card?.backupStatus is Card.BackupStatus.Active) {
val cardCount = (card.backupStatus as Card.BackupStatus.Active).cardCount + 1
tv_twin_card_number.show()
tv_twin_card_number.text =
getString(R.string.wallet_twins_chip_format, 1, cardCount)
tvTwinCardNumber.show()
tvTwinCardNumber.text =
fragment?.getString(R.string.wallet_twins_chip_format, 1, cardCount)
} else {
tv_twin_card_number.hide()
tvTwinCardNumber.hide()
}
}
private fun setupButtons(fragment: WalletFragment) = with(fragment) {
btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) }
private fun setupButtons(binding: FragmentWalletBinding) = with(binding) {
btnScanMultiwallet.setOnClickListener { store.dispatch(WalletAction.Scan) }
}
override fun setFragment(fragment: WalletFragment) {
override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
}
override fun removeFragment() {
this.fragment = null
this.binding = null
}
override fun onViewCreated() {
@ -86,37 +86,42 @@ class MultiWalletView : WalletView {
val fragment = fragment ?: return
walletsAdapter = WalletAdapter()
walletsAdapter.setHasStableIds(true)
fragment.rv_multiwallet.layoutManager = LinearLayoutManager(fragment.requireContext())
fragment.rv_multiwallet.adapter = walletsAdapter
binding?.rvMultiwallet?.layoutManager = LinearLayoutManager(fragment.requireContext())
binding?.rvMultiwallet?.adapter = walletsAdapter
}
override fun onNewState(state: WalletState) {
val fragment = fragment ?: return
val binding = binding ?: return
walletsAdapter.submitList(state.wallets, state.primaryBlockchain, state.primaryToken)
fragment.btn_add_token?.setOnClickListener {
binding.btnAddToken.setOnClickListener {
store.dispatch(TokensAction.LoadCurrencies)
store.dispatch(TokensAction.SetAddedCurrencies(state.wallets))
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
}
handleErrorStates(state, fragment)
handleErrorStates(state = state, binding = binding, fragment = fragment)
handleDialogs(state.walletDialog)
}
private fun handleErrorStates(state: WalletState, fragment: WalletFragment) {
private fun handleErrorStates(
state: WalletState,
binding: FragmentWalletBinding,
fragment: WalletFragment
) {
when (state.primaryWallet?.currencyData?.status) {
BalanceStatus.EmptyCard -> {
showErrorState(
fragment,
binding,
fragment.getText(R.string.wallet_error_empty_card),
fragment.getString(R.string.wallet_error_empty_card_subtitle)
)
configureButtonsForEmptyWalletState(fragment)
configureButtonsForEmptyWalletState(binding)
}
BalanceStatus.UnknownBlockchain -> {
showErrorState(
fragment,
binding,
fragment.getText(R.string.wallet_error_unsupported_blockchain),
fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
)
@ -125,24 +130,28 @@ class MultiWalletView : WalletView {
}
private fun showErrorState(
fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence,
) = with(fragment) {
l_card_balance.show()
l_balance.hide()
l_balance_error.show()
rv_multiwallet.hide()
btn_add_token.hide()
tv_error_title.text = errorTitle
tv_error_descriptions.text = errorDescription
binding: FragmentWalletBinding, errorTitle: CharSequence, errorDescription: CharSequence,
) = with(binding) {
lCardBalance.root.show()
with(lCardBalance) {
lBalance.root.hide()
lBalanceError.root.show()
rvMultiwallet.show()
btnAddToken.hide()
lBalanceError.tvErrorTitle.text = errorTitle
lBalanceError.tvErrorDescriptions.text = errorDescription
}
}
private fun configureButtonsForEmptyWalletState(fragment: WalletFragment) = with(fragment) {
btn_scan_multiwallet.hide()
l_buttons_long.show()
btn_scan_long.setOnClickListener { store.dispatch(WalletAction.Scan) }
btn_confirm_long.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
btn_confirm_long.text = fragment.getText(R.string.wallet_button_create_wallet)
}
private fun configureButtonsForEmptyWalletState(binding: FragmentWalletBinding) =
with(binding) {
btnScanMultiwallet.hide()
lButtonsLong.root.show()
lButtonsLong.btnScanLong.setOnClickListener { store.dispatch(WalletAction.Scan) }
lButtonsLong.btnConfirmLong.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
lButtonsLong.btnConfirmLong.text =
fragment?.getText(R.string.wallet_button_create_wallet)
}
private fun handleDialogs(walletDialog: StateDialog?) {
val fragment = fragment ?: return

View file

@ -5,7 +5,10 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.recyclerview.widget.LinearLayoutManager
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.fitChipsByGroupWidth
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.twins.TwinCardNumber
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
@ -15,42 +18,43 @@ import com.tangem.tap.features.wallet.ui.BalanceWidget
import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
import com.tangem.tap.features.wallet.ui.dialogs.*
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
import com.tangem.tap.features.wallet.ui.dialogs.ChooseTradeActionDialog
import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.card_balance.*
import kotlinx.android.synthetic.main.fragment_wallet.*
import kotlinx.android.synthetic.main.layout_address.*
import kotlinx.android.synthetic.main.layout_wallet_long_buttons.*
import kotlinx.android.synthetic.main.layout_wallet_short_buttons.*
import com.tangem.wallet.databinding.FragmentWalletBinding
class SingleWalletView : WalletView {
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
private var fragment: WalletFragment? = null
private var binding: FragmentWalletBinding? = null
private var dialog: Dialog? = null
override fun setFragment(fragment: WalletFragment) {
override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
}
override fun removeFragment() {
fragment = null
binding = null
}
override fun changeWalletView(fragment: WalletFragment) {
setFragment(fragment)
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
showSingleWalletView(fragment)
showSingleWalletView(binding)
}
private fun showSingleWalletView(fragment: WalletFragment) = with(fragment) {
rv_multiwallet.hide()
btn_add_token.hide()
btn_scan_multiwallet?.hide()
rv_pending_transaction.hide()
l_card_balance.show()
l_address.show()
private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
rvMultiwallet.hide()
btnAddToken.hide()
btnScanMultiwallet.hide()
rvPendingTransaction.hide()
lCardBalance.root.show()
lAddress.root.show()
}
override fun onViewCreated() {
@ -61,17 +65,19 @@ class SingleWalletView : WalletView {
private fun setupTransactionsRecyclerView() {
val fragment = fragment ?: return
pendingTransactionAdapter = PendingTransactionsAdapter()
fragment.rv_pending_transaction.layoutManager = LinearLayoutManager(fragment.requireContext())
fragment.rv_pending_transaction.adapter = pendingTransactionAdapter
binding?.rvPendingTransaction?.layoutManager =
LinearLayoutManager(fragment.requireContext())
binding?.rvPendingTransaction?.adapter = pendingTransactionAdapter
}
override fun onNewState(state: WalletState) {
val fragment = fragment ?: return
val binding = binding ?: return
state.primaryWallet ?: return
setupTwinCards(state.twinCardsState, fragment)
setupButtons(state.primaryWallet, state.isTangemTwins, fragment)
setupAddressCard(state.primaryWallet, fragment)
setupTwinCards(state.twinCardsState, binding)
setupButtons(state.primaryWallet, state.isTangemTwins, binding)
setupAddressCard(state.primaryWallet, binding)
showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
setupBalance(state, state.primaryWallet)
handleDialogs(state.walletDialog, fragment)
@ -79,75 +85,81 @@ class SingleWalletView : WalletView {
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
pendingTransactionAdapter.submitList(pendingTransactions)
fragment?.rv_pending_transaction?.show(pendingTransactions.isNotEmpty())
binding?.rvPendingTransaction?.show(pendingTransactions.isNotEmpty())
}
private fun setupBalance(state: WalletState, primaryWallet: WalletData) {
fragment?.apply {
this.l_balance.show()
BalanceWidget(this, primaryWallet.currencyData, state.isTangemTwins).setup()
val fragment = fragment ?: return
binding?.apply {
lCardBalance.lBalance.root.show()
BalanceWidget(
binding = this.lCardBalance,
fragment = fragment,
data = primaryWallet.currencyData,
isTwinCard = state.isTangemTwins
).setup()
}
}
private fun setupTwinCards(
twinCardsState: TwinCardsState?, fragment: WalletFragment
) = with(fragment) {
twinCardsState: TwinCardsState?, binding: FragmentWalletBinding
) = with(binding) {
twinCardsState?.cardNumber?.let { cardNumber ->
tv_twin_card_number.show()
tvTwinCardNumber.show()
val number = when (cardNumber) {
TwinCardNumber.First -> 1
TwinCardNumber.Second -> 2
}
tv_twin_card_number.text =
this.getString(R.string.wallet_twins_chip_format, number, 2)
tvTwinCardNumber.text =
fragment?.getString(R.string.wallet_twins_chip_format, number, 2)
}
if (twinCardsState?.cardNumber == null) {
tv_twin_card_number.hide()
tvTwinCardNumber.hide()
}
}
private fun setupButtons(
state: WalletData, isTwinsWallet: Boolean, fragment: WalletFragment
) = with(fragment){
state: WalletData, isTwinsWallet: Boolean, binding: FragmentWalletBinding
) = with(binding) {
setupButtonsType(state, fragment)
setupButtonsType(state, binding)
val btnConfirm = if (state.tradeCryptoState.sellingAllowed ||
state.tradeCryptoState.buyingAllowed
) {
btn_confirm_short
lButtonsShort.btnConfirmShort
} else {
btn_confirm_long
lButtonsLong.btnConfirmLong
}
val btnScan = if (state.tradeCryptoState.sellingAllowed ||
state.tradeCryptoState.buyingAllowed
) {
btn_scan_short
lButtonsShort.btnScanShort
} else {
btn_scan_long
lButtonsLong.btnScanLong
}
setupConfirmButton(state, btnConfirm, fragment, isTwinsWallet)
setupConfirmButton(state, btnConfirm, isTwinsWallet)
btnScan.setOnClickListener {
store.dispatch(WalletAction.Scan)
}
btn_copy.setOnClickListener {
lAddress.btnCopy.setOnClickListener {
state.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.CopyAddress(addressString, fragment.requireContext()))
store.dispatch(WalletAction.CopyAddress(addressString, fragment!!.requireContext()))
}
}
btn_show_qr.setOnClickListener {
lAddress.btnShowQr.setOnClickListener {
store.dispatch(WalletAction.ShowDialog.QrCode)
}
setupTradeButton(fragment, state.tradeCryptoState)
setupTradeButton(binding, state.tradeCryptoState)
}
private fun setupTradeButton(fragment: WalletFragment, tradeCryptoState: TradeCryptoState) {
private fun setupTradeButton(binding: FragmentWalletBinding, tradeCryptoState: TradeCryptoState) {
val allowedToBuy = tradeCryptoState.buyingAllowed
val allowedToSell = tradeCryptoState.sellingAllowed
val action = when {
@ -160,7 +172,7 @@ class SingleWalletView : WalletView {
allowedToBuy && !allowedToSell -> R.string.wallet_button_buy
!allowedToBuy && allowedToSell -> R.string.wallet_button_sell
allowedToBuy && allowedToSell -> R.string.wallet_button_trade
else -> R.string.wallet_button_trade
else -> R.string.wallet_button_trade
}
val icon = when {
allowedToBuy && !allowedToSell -> R.drawable.ic_arrow_up_short_btn
@ -168,27 +180,27 @@ class SingleWalletView : WalletView {
allowedToBuy && allowedToSell -> R.drawable.ic_arrows_up_down_short_btn
else -> null
}
with(fragment) {
btn_trade.text = getText(text)
btn_trade.setCompoundDrawablesWithIntrinsicBounds(0, icon ?: 0, 0, 0)
btn_trade.setOnClickListener { if (action != null) store.dispatch(action) }
with(binding) {
lButtonsShort.btnTrade.text = fragment?.getText(text)
lButtonsShort.btnTrade.setCompoundDrawablesWithIntrinsicBounds(0, icon ?: 0, 0, 0)
lButtonsShort.btnTrade.setOnClickListener { if (action != null) store.dispatch(action) }
}
}
private fun setupButtonsType(state: WalletData, fragment: WalletFragment) = with(fragment) {
private fun setupButtonsType(state: WalletData, binding: FragmentWalletBinding) = with(binding) {
if (state.tradeCryptoState.sellingAllowed ||
state.tradeCryptoState.buyingAllowed
) {
l_buttons_long.hide()
l_buttons_short.show()
lButtonsLong.root.hide()
lButtonsShort.root.show()
} else {
l_buttons_long.show()
l_buttons_short.hide()
lButtonsLong.root.show()
lButtonsShort.root.hide()
}
}
private fun setupConfirmButton(
state: WalletData, btnConfirm: Button, fragment: WalletFragment, isTwinsWallet: Boolean,
state: WalletData, btnConfirm: Button, isTwinsWallet: Boolean,
) {
val buttonTitle = when (state.mainButton) {
is WalletMainButton.SendButton -> R.string.wallet_button_send
@ -200,7 +212,7 @@ class SingleWalletView : WalletView {
}
}
}
btnConfirm.text = fragment.getString(buttonTitle)
btnConfirm.text = fragment?.getString(buttonTitle)
btnConfirm.isEnabled = state.mainButton.enabled
btnConfirm.setOnClickListener {
when (state.mainButton) {
@ -211,33 +223,38 @@ class SingleWalletView : WalletView {
}
private fun setupAddressCard(state: WalletData, fragment: WalletFragment) = with(fragment) {
private fun setupAddressCard(state: WalletData, binding: FragmentWalletBinding) = with(binding.lAddress) {
if (state.walletAddresses != null && state.currency is Currency.Blockchain) {
l_address?.show()
binding.lAddress.root.show()
if (state.shouldShowMultipleAddress()) {
(l_address as? ViewGroup)?.beginDelayedTransition()
chip_group_address_type.show()
chip_group_address_type.fitChipsByGroupWidth()
(binding.lAddress.root as? ViewGroup)?.beginDelayedTransition()
chipGroupAddressType.show()
chipGroupAddressType.fitChipsByGroupWidth()
val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
val checkedId =
MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
chipGroupAddressType.setOnCheckedChangeListener { group, checkedId ->
if (checkedId == -1) return@setOnCheckedChangeListener
val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
val type =
MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain)
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
}
} else {
chip_group_address_type.hide()
chipGroupAddressType.hide()
}
tv_address.text = state.walletAddresses.selectedAddress.address
tv_explore?.setOnClickListener {
store.dispatch(WalletAction.ExploreAddress(
tvAddress.text = state.walletAddresses.selectedAddress.address
tvExplore.setOnClickListener {
store.dispatch(
WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,
fragment.requireContext()))
fragment!!.requireContext()
)
)
}
} else {
l_address?.hide()
binding.lAddress.root.hide()
}
}

View file

@ -2,14 +2,15 @@ package com.tangem.tap.features.wallet.ui.wallet
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.databinding.FragmentWalletBinding
interface WalletView {
fun setFragment(fragment: WalletFragment)
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding)
fun removeFragment()
fun changeWalletView(fragment: WalletFragment)
fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
fun onViewCreated()

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB

View file

@ -0,0 +1,14 @@
<vector android:autoMirrored="true" android:height="720dp"
android:viewportHeight="720" android:viewportWidth="360"
android:width="360dp" xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#F8F9F9" android:pathData="M0,0h360v720h-360z"/>
<path android:fillAlpha="0.2" android:pathData="M0,0h360v720h-360z">
<aapt:attr name="android:fillColor">
<gradient android:endX="179.591" android:endY="82.4634"
android:startX="180" android:startY="0" android:type="linear">
<item android:color="#FF090E13" android:offset="0"/>
<item android:color="#00000000" android:offset="1"/>
</gradient>
</aapt:attr>
</path>
</vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/cv_stories"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -33,8 +33,9 @@
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<FrameLayout
<include
android:id="@+id/onboarding_top_container"
layout="@layout/layout_onboarding_container_top"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="@dimen/home_top_weight"
@ -50,6 +51,7 @@
</androidx.appcompat.widget.LinearLayoutCompat>
<include
android:id="@+id/v_confetti"
layout="@layout/view_confetti"
android:visibility="gone" />

View file

@ -218,6 +218,7 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<include
android:id="@+id/v_confetti"
layout="@layout/view_confetti"
android:visibility="gone" />

View file

@ -39,12 +39,14 @@
android:orientation="vertical">
<include
android:id="@+id/l_send_address_payid"
layout="@layout/layout_send_address_payid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" />
<include
android:id="@+id/l_send_amount"
layout="@layout/layout_send_amount"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -12,6 +12,7 @@
android:visibility="gone" />
<include
android:id="@+id/v_onboarding_progress"
layout="@layout/view_onboarding_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"