diff --git a/android/src/main/java/org/ergoplatform/android/wallet/WalletDetailsFragment.kt b/android/src/main/java/org/ergoplatform/android/wallet/WalletDetailsFragment.kt index b97c57769..11a8de825 100644 --- a/android/src/main/java/org/ergoplatform/android/wallet/WalletDetailsFragment.kt +++ b/android/src/main/java/org/ergoplatform/android/wallet/WalletDetailsFragment.kt @@ -231,7 +231,7 @@ class WalletDetailsFragment : Fragment(), AddressChooserCallback { // tokens val tokensList = walletDetailsViewModel.uiLogic.tokensList - binding.cardviewTokens.visibility = if (tokensList.isNotEmpty()) View.VISIBLE else View.GONE + binding.cardviewTokens.visibility = if (walletDetailsViewModel.uiLogic.hasTokens) View.VISIBLE else View.GONE binding.walletTokenNum.text = tokensList.size.toString() binding.walletTokenEntries.apply { diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index f12492f80..4dcf60ab3 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -334,6 +334,10 @@ Please be aware that this content is created by third parties and might be unwanted. Activate Deactivate + Generic + Images + Audio + Video dApps diff --git a/common-jvm/src/main/java/org/ergoplatform/uilogic/StringResources.kt b/common-jvm/src/main/java/org/ergoplatform/uilogic/StringResources.kt index 844a3f2f0..45cd612fa 100644 --- a/common-jvm/src/main/java/org/ergoplatform/uilogic/StringResources.kt +++ b/common-jvm/src/main/java/org/ergoplatform/uilogic/StringResources.kt @@ -316,9 +316,13 @@ const val STRING_LABEL_TIME_SPAN_JUST_NOW = "label_time_span_just_now" const val STRING_LABEL_TIME_SPAN_MINUTES_AGO = "label_time_span_minutes_ago" const val STRING_LABEL_TIME_SPAN_MOMENTS_AGO = "label_time_span_moments_ago" const val STRING_LABEL_TO = "label_to" +const val STRING_LABEL_TOKEN_AUDIO = "label_token_audio" const val STRING_LABEL_TOKEN_DESCRIPTION = "label_token_description" +const val STRING_LABEL_TOKEN_GENERIC = "label_token_generic" +const val STRING_LABEL_TOKEN_IMAGE = "label_token_image" const val STRING_LABEL_TOKEN_SUPPLY = "label_token_supply" const val STRING_LABEL_TOKEN_VERIFICATION_URL = "label_token_verification_url" +const val STRING_LABEL_TOKEN_VIDEO = "label_token_video" const val STRING_LABEL_TOKENS = "label_tokens" const val STRING_LABEL_UNCONFIRMED = "label_unconfirmed" const val STRING_LABEL_UNNAMED_TOKEN = "label_unnamed_token" diff --git a/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/FilterTokenListUiLogic.kt b/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/FilterTokenListUiLogic.kt new file mode 100644 index 000000000..9ac20e6a4 --- /dev/null +++ b/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/FilterTokenListUiLogic.kt @@ -0,0 +1,26 @@ +package org.ergoplatform.uilogic.tokens + +import org.ergoplatform.persistance.TokenInformation + +/** + * Helper methods for filterable token lists + */ +interface FilterTokenListUiLogic { + val tokenFilterMap: MutableMap + + fun toggleTokenFilter(filterType: Int) { + tokenFilterMap[filterType] = !(tokenFilterMap[filterType] ?: false) + onFilterChanged() + } + + fun hasTokenFilter(filterType: Int): Boolean = + tokenFilterMap[filterType] ?: false + + fun isTokenInFilter(ti: TokenInformation?): Boolean { + println(tokenFilterMap.values) + return tokenFilterMap.values.none { it } + || ti?.thumbnailType?.let { tokenFilterMap[it] == true } ?: false + } + + fun onFilterChanged() {} +} \ No newline at end of file diff --git a/common-jvm/src/main/java/org/ergoplatform/uilogic/transactions/SendFundsUiLogic.kt b/common-jvm/src/main/java/org/ergoplatform/uilogic/transactions/SendFundsUiLogic.kt index 8118394ac..180d93bff 100644 --- a/common-jvm/src/main/java/org/ergoplatform/uilogic/transactions/SendFundsUiLogic.kt +++ b/common-jvm/src/main/java/org/ergoplatform/uilogic/transactions/SendFundsUiLogic.kt @@ -16,12 +16,13 @@ import org.ergoplatform.tokens.TokenInfoManager import org.ergoplatform.tokens.isSingularToken import org.ergoplatform.transactions.* import org.ergoplatform.uilogic.* +import org.ergoplatform.uilogic.tokens.FilterTokenListUiLogic import org.ergoplatform.utils.LogUtils import org.ergoplatform.utils.formatFiatToString import org.ergoplatform.wallet.* import kotlin.math.max -abstract class SendFundsUiLogic : SubmitTransactionUiLogic() { +abstract class SendFundsUiLogic : SubmitTransactionUiLogic(), FilterTokenListUiLogic { var receiverAddress: String = "" set(value) { @@ -59,6 +60,7 @@ abstract class SendFundsUiLogic : SubmitTransactionUiLogic() { val tokensAvail: HashMap = HashMap() val tokensChosen: HashMap = HashMap() val tokensInfo: HashMap = HashMap() + override val tokenFilterMap: MutableMap = HashMap() private val paymentRequestWarnings = ArrayList() @@ -399,7 +401,7 @@ abstract class SendFundsUiLogic : SubmitTransactionUiLogic() { */ fun getTokensToChooseFrom(): List { return tokensAvail.values.filter { - !tokensChosen.containsKey(it.tokenId) + !tokensChosen.containsKey(it.tokenId) && isTokenInFilter(tokensInfo[it.tokenId]) }.sortedBy { it.name?.lowercase() } } diff --git a/common-jvm/src/main/java/org/ergoplatform/uilogic/wallet/WalletDetailsUiLogic.kt b/common-jvm/src/main/java/org/ergoplatform/uilogic/wallet/WalletDetailsUiLogic.kt index 70099e34f..ae0ea3461 100644 --- a/common-jvm/src/main/java/org/ergoplatform/uilogic/wallet/WalletDetailsUiLogic.kt +++ b/common-jvm/src/main/java/org/ergoplatform/uilogic/wallet/WalletDetailsUiLogic.kt @@ -4,8 +4,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import org.ergoplatform.ErgoAmount import org.ergoplatform.ApiServiceManager +import org.ergoplatform.ErgoAmount import org.ergoplatform.WalletStateSyncManager import org.ergoplatform.ergoauth.isErgoAuthRequestUri import org.ergoplatform.parsePaymentRequest @@ -19,11 +19,12 @@ import org.ergoplatform.uilogic.STRING_ERROR_QR_CODE_CONTENT_UNKNOWN import org.ergoplatform.uilogic.STRING_HINT_READONLY_SIGNING_REQUEST import org.ergoplatform.uilogic.STRING_LABEL_ALL_ADDRESSES import org.ergoplatform.uilogic.StringProvider +import org.ergoplatform.uilogic.tokens.FilterTokenListUiLogic import org.ergoplatform.uilogic.transactions.AddressTransactionWithTokens import org.ergoplatform.wallet.* import org.ergoplatform.wallet.addresses.getAddressLabel -abstract class WalletDetailsUiLogic { +abstract class WalletDetailsUiLogic: FilterTokenListUiLogic { val maxTransactionsToShow = 5 var wallet: Wallet? = null @@ -32,8 +33,11 @@ abstract class WalletDetailsUiLogic { private set var walletAddress: WalletAddress? = null private set - var tokensList: List = emptyList() - private set + private var fullTokensList: List = emptyList() + val hasTokens get() = fullTokensList.isNotEmpty() + val tokensList + get() = fullTokensList.filter { isTokenInFilter(tokenInformation[it.tokenId]) } + override val tokenFilterMap: MutableMap = HashMap() private var tokenInformationJob: Job? = null val tokenInformation: HashMap = HashMap() @@ -100,7 +104,7 @@ abstract class WalletDetailsUiLogic { private fun refreshAddress() { walletAddress = addressIdx?.let { wallet?.getDerivedAddressEntity(it) } - tokensList = (walletAddress?.let { wallet?.getTokensForAddress(it.publicAddress) } + fullTokensList = (walletAddress?.let { wallet?.getTokensForAddress(it.publicAddress) } ?: wallet?.getTokensForAllAddresses() ?: emptyList()).sortedBy { it.name?.lowercase() } onDataChanged() @@ -148,7 +152,7 @@ abstract class WalletDetailsUiLogic { tokenInformationJob?.cancel() // copy to an own list to prevent race conditions - val tokensList = this.tokensList.toMutableList() + val tokensList = this.fullTokensList.toMutableList() // start gathering token information if (tokensList.isNotEmpty()) { @@ -259,6 +263,10 @@ abstract class WalletDetailsUiLogic { return walletAddress?.let { listOf(it) } ?: wallet?.getSortedDerivedAddressesList() } + override fun onFilterChanged() { + onDataChanged() + } + abstract fun onDataChanged() abstract fun onNewTokenInfoGathered(tokenInformation: TokenInformation) diff --git a/desktop/src/main/java/org/ergoplatform/desktop/tokens/TokenFilterMenu.kt b/desktop/src/main/java/org/ergoplatform/desktop/tokens/TokenFilterMenu.kt new file mode 100644 index 000000000..21851d5b4 --- /dev/null +++ b/desktop/src/main/java/org/ergoplatform/desktop/tokens/TokenFilterMenu.kt @@ -0,0 +1,74 @@ +package org.ergoplatform.desktop.tokens + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.FilterAlt +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.unit.dp +import org.ergoplatform.Application +import org.ergoplatform.compose.settings.defaultPadding +import org.ergoplatform.persistance.THUMBNAIL_TYPE_NFT_AUDIO +import org.ergoplatform.persistance.THUMBNAIL_TYPE_NFT_IMG +import org.ergoplatform.persistance.THUMBNAIL_TYPE_NFT_VID +import org.ergoplatform.persistance.THUMBNAIL_TYPE_NONE +import org.ergoplatform.uilogic.STRING_LABEL_TOKEN_AUDIO +import org.ergoplatform.uilogic.STRING_LABEL_TOKEN_GENERIC +import org.ergoplatform.uilogic.STRING_LABEL_TOKEN_IMAGE +import org.ergoplatform.uilogic.STRING_LABEL_TOKEN_VIDEO +import org.ergoplatform.uilogic.tokens.FilterTokenListUiLogic + +@Composable +fun TokenFilterMenu(uiLogic: FilterTokenListUiLogic) { + var showActionMenu by remember { mutableStateOf(false) } + IconButton(onClick = { showActionMenu = !showActionMenu }) { + Icon(Icons.Default.FilterAlt, null) + DropdownMenu( + showActionMenu, + onDismissRequest = { showActionMenu = false }, + modifier = Modifier.widthIn(min = 150.dp) + ) { + val modifierForFilter: (Int) -> Modifier = { + Modifier.alpha(if (uiLogic.hasTokenFilter(it)) 1f else 0f) + } + + DropdownMenuItem(onClick = { + uiLogic.toggleTokenFilter(THUMBNAIL_TYPE_NONE) + showActionMenu = false + }) { + Icon(Icons.Default.Check, null, modifierForFilter(THUMBNAIL_TYPE_NONE)) + Spacer(Modifier.size(defaultPadding)) + Text(Application.texts.getString(STRING_LABEL_TOKEN_GENERIC)) + } + DropdownMenuItem(onClick = { + uiLogic.toggleTokenFilter(THUMBNAIL_TYPE_NFT_IMG) + showActionMenu = false + }) { + Icon(Icons.Default.Check, null, modifierForFilter(THUMBNAIL_TYPE_NFT_IMG)) + Spacer(Modifier.size(defaultPadding)) + Text(Application.texts.getString(STRING_LABEL_TOKEN_IMAGE)) + } + DropdownMenuItem(onClick = { + uiLogic.toggleTokenFilter(THUMBNAIL_TYPE_NFT_AUDIO) + showActionMenu = false + }) { + Icon(Icons.Default.Check, null, modifierForFilter(THUMBNAIL_TYPE_NFT_AUDIO)) + Spacer(Modifier.size(defaultPadding)) + Text(Application.texts.getString(STRING_LABEL_TOKEN_AUDIO)) + } + DropdownMenuItem(onClick = { + uiLogic.toggleTokenFilter(THUMBNAIL_TYPE_NFT_VID) + showActionMenu = false + }) { + Icon(Icons.Default.Check, null, modifierForFilter(THUMBNAIL_TYPE_NFT_VID)) + Spacer(Modifier.size(defaultPadding)) + Text(Application.texts.getString(STRING_LABEL_TOKEN_VIDEO)) + } + } + } +} \ No newline at end of file diff --git a/desktop/src/main/java/org/ergoplatform/desktop/transactions/ChooseTokenListDialog.kt b/desktop/src/main/java/org/ergoplatform/desktop/transactions/ChooseTokenListDialog.kt index 084a7c48a..5e80e8e81 100644 --- a/desktop/src/main/java/org/ergoplatform/desktop/transactions/ChooseTokenListDialog.kt +++ b/desktop/src/main/java/org/ergoplatform/desktop/transactions/ChooseTokenListDialog.kt @@ -1,10 +1,7 @@ package org.ergoplatform.desktop.transactions import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text @@ -16,6 +13,7 @@ import androidx.compose.ui.text.style.TextAlign import org.ergoplatform.Application import org.ergoplatform.compose.tokens.TokenEntryViewData import org.ergoplatform.compose.tokens.TokenLabel +import org.ergoplatform.desktop.tokens.TokenFilterMenu import org.ergoplatform.desktop.ui.AppDialog import org.ergoplatform.desktop.ui.AppScrollbar import org.ergoplatform.desktop.ui.defaultPadding @@ -23,19 +21,21 @@ import org.ergoplatform.mosaik.MiddleEllipsisText import org.ergoplatform.mosaik.MosaikStyleConfig import org.ergoplatform.mosaik.labelStyle import org.ergoplatform.mosaik.model.ui.text.LabelStyle -import org.ergoplatform.persistance.TokenInformation import org.ergoplatform.persistance.WalletToken import org.ergoplatform.uilogic.STRING_TITLE_ADD_TOKEN +import org.ergoplatform.uilogic.transactions.SendFundsUiLogic @Composable fun ChooseTokenListDialog( - tokenToChooseFrom: List, - tokenInfoMap: HashMap, - onTokenChosen: (WalletToken) -> Unit, + uiLogic: SendFundsUiLogic, + refreshCount: Int, onDismissRequest: () -> Unit, ) { - val preparedList = remember { - tokenToChooseFrom.map { token -> + val tokenInfoMap = uiLogic.tokensInfo + val onTokenChosen: (WalletToken) -> Unit = { uiLogic.newTokenChosen(it.tokenId!!) } + + val preparedList = remember(refreshCount) { + uiLogic.getTokensToChooseFrom().map { token -> TokenEntryViewData( token, false, Application.texts ).apply { bind(tokenInfoMap[token.tokenId]) } @@ -49,12 +49,19 @@ fun ChooseTokenListDialog( Column( Modifier.fillMaxWidth().verticalScroll(scrollState) ) { - Text( - remember { Application.texts.getString(STRING_TITLE_ADD_TOKEN) }, + Row( Modifier.fillMaxWidth().padding(defaultPadding), - textAlign = TextAlign.Center, - style = labelStyle(LabelStyle.BODY1) - ) + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + remember { Application.texts.getString(STRING_TITLE_ADD_TOKEN) }, + Modifier.weight(1f), + textAlign = TextAlign.Center, + style = labelStyle(LabelStyle.BODY1) + ) + + TokenFilterMenu(uiLogic) + } preparedList.forEach { token -> Box( diff --git a/desktop/src/main/java/org/ergoplatform/desktop/transactions/SendFundsComponent.kt b/desktop/src/main/java/org/ergoplatform/desktop/transactions/SendFundsComponent.kt index ea4eed506..8a88b0810 100644 --- a/desktop/src/main/java/org/ergoplatform/desktop/transactions/SendFundsComponent.kt +++ b/desktop/src/main/java/org/ergoplatform/desktop/transactions/SendFundsComponent.kt @@ -6,10 +6,7 @@ import androidx.compose.material.IconButton import androidx.compose.material.ScaffoldState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.QrCodeScanner -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.text.input.TextFieldValue import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.router.pop @@ -25,7 +22,10 @@ import org.ergoplatform.persistance.WalletAddress import org.ergoplatform.persistance.WalletConfig import org.ergoplatform.transactions.TransactionInfo import org.ergoplatform.transactions.TransactionResult -import org.ergoplatform.uilogic.* +import org.ergoplatform.uilogic.STRING_BUTTON_SEND +import org.ergoplatform.uilogic.STRING_INFO_PURPOSE_MESSAGE +import org.ergoplatform.uilogic.STRING_INFO_PURPOSE_MESSAGE_ACCEPT +import org.ergoplatform.uilogic.STRING_INFO_PURPOSE_MESSAGE_DECLINE import org.ergoplatform.uilogic.transactions.SendFundsUiLogic import org.ergoplatform.uilogic.transactions.SuggestedFee import org.ergoplatform.wallet.isReadOnly @@ -65,6 +65,7 @@ class SendFundsComponent( private val editFeeDialogState = mutableStateOf(false) private val addressBookDialogState = AddressBookDialogStateHandler() private val preparedTransactionInfoState = mutableStateOf(null) + private var tokenFilterRefresh by mutableStateOf(0) @Composable override fun renderScreenContents(scaffoldState: ScaffoldState?) { @@ -98,9 +99,8 @@ class SendFundsComponent( if (addTokenDialogState.value) { ChooseTokenListDialog( - remember { uiLogic.getTokensToChooseFrom() }, - uiLogic.tokensInfo, - onTokenChosen = { uiLogic.newTokenChosen(it.tokenId!!) }, + uiLogic, + tokenFilterRefresh, onDismissRequest = { addTokenDialogState.value = false } ) } @@ -242,6 +242,10 @@ class SendFundsComponent( showSigningPrompt(signingPrompt) } + override fun onFilterChanged() { + tokenFilterRefresh++ + } + override fun notifyHasPreparedTx(preparedTx: TransactionInfo) { preparedTransactionInfoState.value = preparedTx } diff --git a/desktop/src/main/java/org/ergoplatform/desktop/wallet/WalletDetailsScreen.kt b/desktop/src/main/java/org/ergoplatform/desktop/wallet/WalletDetailsScreen.kt index dd4881b11..d753aaee2 100644 --- a/desktop/src/main/java/org/ergoplatform/desktop/wallet/WalletDetailsScreen.kt +++ b/desktop/src/main/java/org/ergoplatform/desktop/wallet/WalletDetailsScreen.kt @@ -2,10 +2,7 @@ package org.ergoplatform.desktop.wallet import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* -import androidx.compose.material.Divider -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Text +import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* @@ -22,6 +19,7 @@ import org.ergoplatform.compose.settings.smallIconSize import org.ergoplatform.compose.tokens.TokenEntryViewData import org.ergoplatform.compose.tokens.TokenLabel import org.ergoplatform.desktop.tokens.TokenEntryView +import org.ergoplatform.desktop.tokens.TokenFilterMenu import org.ergoplatform.desktop.ui.* import org.ergoplatform.desktop.wallet.addresses.ChooseAddressButton import org.ergoplatform.mosaik.MiddleEllipsisText @@ -64,7 +62,7 @@ fun WalletDetailsScreen( ErgoBalanceLayout(uiLogic) - if (uiLogic.tokensList.isNotEmpty()) { + if (uiLogic.hasTokens) { WalletTokensLayout(uiLogic, onTokenClicked) } } @@ -225,7 +223,7 @@ private fun WalletTokensLayout( Column(Modifier.padding(defaultPadding)) { // HEADER - Row { + Row(verticalAlignment = Alignment.CenterVertically) { Icon( painterResource("ic_tokenlogo.xml"), null, Modifier.requiredSize(mediumIconSize) @@ -233,15 +231,13 @@ private fun WalletTokensLayout( Text( uiLogic.tokensList.size.toString(), - Modifier.padding(start = defaultPadding) - .align(Alignment.CenterVertically), + Modifier.padding(start = defaultPadding), style = labelStyle(LabelStyle.HEADLINE2), ) Text( Application.texts.getString(STRING_LABEL_TOKENS), - Modifier.padding(start = defaultPadding / 2).weight(1f) - .align(Alignment.CenterVertically), + Modifier.padding(start = defaultPadding / 2).weight(1f), style = labelStyle(LabelStyle.BODY1BOLD), color = uiErgoColor ) @@ -261,9 +257,11 @@ private fun WalletTokensLayout( style = labelStyle(LabelStyle.BODY1), color = MosaikStyleConfig.secondaryLabelColor, modifier = Modifier.align(Alignment.CenterVertically) - .padding(start = defaultPadding / 2), + .padding(horizontal = defaultPadding / 2), ) } + + TokenFilterMenu(uiLogic) } // TOKENS LIST diff --git a/ios/resources/i18n/strings.properties b/ios/resources/i18n/strings.properties index d519e56e5..8a76a9d8a 100644 --- a/ios/resources/i18n/strings.properties +++ b/ios/resources/i18n/strings.properties @@ -359,9 +359,13 @@ label_time_span_just_now=just now label_time_span_minutes_ago={0} minutes ago label_time_span_moments_ago=a few moments ago label_to=to +label_token_audio=Audio label_token_description=Token description +label_token_generic=Generic +label_token_image=Images label_token_supply=Full supply amount label_token_verification_url=Token verification service URL +label_token_video=Video label_tokens=tokens label_unconfirmed=unconfirmed label_unnamed_token=(unnamed token)