diff --git a/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationDialogFragment.kt b/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationDialogFragment.kt index 1359591c9..4b8dc1ca0 100644 --- a/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationDialogFragment.kt +++ b/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationDialogFragment.kt @@ -8,17 +8,14 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.navArgs import androidx.transition.TransitionManager import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import org.ergoplatform.TokenAmount -import org.ergoplatform.WalletStateSyncManager import org.ergoplatform.android.Preferences -import org.ergoplatform.android.R import org.ergoplatform.android.databinding.FragmentTokenInformationBinding import org.ergoplatform.android.ui.AndroidStringProvider +import org.ergoplatform.android.ui.copyStringToClipboard import org.ergoplatform.android.ui.openUrlWithBrowser import org.ergoplatform.getExplorerTokenUrl import org.ergoplatform.getExplorerTxUrl -import org.ergoplatform.tokens.isSingularToken -import org.ergoplatform.utils.formatTokenPriceToString +import org.ergoplatform.tokens.getHttpContentLink class TokenInformationDialogFragment : BottomSheetDialogFragment() { private var _binding: FragmentTokenInformationBinding? = null @@ -49,59 +46,15 @@ class TokenInformationDialogFragment : BottomSheetDialogFragment() { binding.progressCircular.visibility = View.GONE token?.apply { binding.mainLayout.visibility = View.VISIBLE - binding.labelTokenName.text = - if (displayName.isBlank()) getString(R.string.label_unnamed_token) else displayName - - binding.labelTokenName.setCompoundDrawablesRelativeWithIntrinsicBounds( - 0, 0, getGenuineDrawableId(), 0 - ) - - binding.labelTokenId.text = tokenId - binding.labelTokenDescription.text = - if (description.isNotBlank()) description else getString(R.string.label_no_description) - binding.labelSupplyAmount.text = - TokenAmount(fullSupply, decimals).toStringUsFormatted(false) - val balanceAmount = TokenAmount(args.amount, decimals) - binding.labelBalanceAmount.text = - balanceAmount.toStringUsFormatted(false) - - val showBalance = args.amount > 0 && !isSingularToken() - val walletSyncManager = WalletStateSyncManager.getInstance() - val tokenPrice = walletSyncManager.tokenPrices[tokenId] - - if (showBalance) tokenPrice?.let { - binding.labelBalanceValue.text = formatTokenPriceToString( - balanceAmount, - it.ergValue, - walletSyncManager, - AndroidStringProvider(requireContext()) - ) + " [${it.priceSource}]" - } - - binding.labelBalanceAmount.visibility = if (showBalance) View.VISIBLE else View.GONE - binding.titleBalanceAmount.visibility = binding.labelBalanceAmount.visibility - binding.labelBalanceValue.visibility = - if (showBalance && tokenPrice != null) View.VISIBLE else View.GONE - binding.labelSupplyAmount.visibility = - if (isSingularToken()) View.GONE else View.VISIBLE - binding.titleSupplyAmount.visibility = binding.labelSupplyAmount.visibility - + updateLayout(viewModel) binding.labelMintingTxId.setOnClickListener { openUrlWithBrowser(requireContext(), getExplorerTxUrl(mintingTxId)) } - - updateNftLayout(viewModel) - - binding.buttonDownloadContent.setOnClickListener { - viewModel.uiLogic.downloadContent( - Preferences(requireContext()) - ) - } } ?: run { binding.tvError.visibility = View.VISIBLE } } viewModel.downloadState.observe(viewLifecycleOwner) { - updateNftLayout(viewModel, true) + updateLayout(viewModel, true) } binding.labelTokenId.setOnClickListener { @@ -116,22 +69,41 @@ class TokenInformationDialogFragment : BottomSheetDialogFragment() { binding.labelTokenDescription.maxLines = if (binding.labelTokenDescription.maxLines == 5) 1000 else 5 } + binding.buttonDownloadContent.setOnClickListener { + viewModel.uiLogic.downloadContent( + Preferences(requireContext()) + ) + } + binding.labelContentLink.setOnClickListener { + viewModel.uiLogic.eip4Token?.nftContentLink?.let { contentLink -> + val context = requireContext() + val success = openUrlWithBrowser(context, contentLink) + + if (!success) { + viewModel.uiLogic.eip4Token!!.getHttpContentLink(Preferences(requireContext())) + ?.let { openUrlWithBrowser(context, it) } + } + } + } + binding.labelContentHash.setOnClickListener { + copyStringToClipboard(binding.labelContentHash.text.toString(), requireContext(), null) + } } - private fun updateNftLayout( + private fun updateLayout( viewModel: TokenInformationViewModel, onlyPreview: Boolean = false ) { val context = requireContext() - val tokenInformationNftLayoutView = TokenInformationNftLayoutView(binding) + val tokenInformationLayoutView = TokenInformationLayoutView(binding) if (onlyPreview) { - tokenInformationNftLayoutView.updatePreview(viewModel.uiLogic) + tokenInformationLayoutView.updateNftPreview(viewModel.uiLogic) } else { - tokenInformationNftLayoutView.update( + tokenInformationLayoutView.updateLayout( viewModel.uiLogic, AndroidStringProvider(context), - Preferences(context) - ) { openUrlWithBrowser(context, it) } + args.amount + ) } } diff --git a/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationLayoutView.kt b/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationLayoutView.kt new file mode 100644 index 000000000..45bf694e3 --- /dev/null +++ b/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationLayoutView.kt @@ -0,0 +1,112 @@ +package org.ergoplatform.android.tokens + +import android.graphics.BitmapFactory +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.view.View +import org.ergoplatform.android.R +import org.ergoplatform.android.databinding.FragmentTokenInformationBinding +import org.ergoplatform.uilogic.tokens.TokenInformationLayoutLogic +import org.ergoplatform.uilogic.tokens.TokenInformationModelLogic + + +class TokenInformationLayoutView(private val binding: FragmentTokenInformationBinding) : + TokenInformationLayoutLogic() { + + override fun setTokenTextFields(displayName: String, tokenId: String, description: String) { + binding.labelTokenName.text = displayName + binding.labelTokenId.text = tokenId + binding.labelTokenDescription.text = description + } + + override fun setLabelSupplyAmountText(supplyAmount: String?) { + binding.labelSupplyAmount.visibility = + if (supplyAmount == null) View.GONE else View.VISIBLE + binding.labelSupplyAmount.text = supplyAmount + binding.titleSupplyAmount.visibility = binding.labelSupplyAmount.visibility + } + + override fun setBalanceAmountAndValue(amount: String?, balanceValue: String?) { + binding.labelBalanceAmount.text = amount + binding.labelBalanceAmount.visibility = if (amount != null) View.VISIBLE else View.GONE + binding.titleBalanceAmount.visibility = binding.labelBalanceAmount.visibility + binding.labelBalanceValue.visibility = + if (balanceValue != null) View.VISIBLE else View.GONE + binding.labelBalanceValue.text = balanceValue + + } + + override fun setTokenGenuine(genuineFlag: Int) { + binding.labelTokenName.setCompoundDrawablesRelativeWithIntrinsicBounds( + 0, 0, getGenuineDrawableId(genuineFlag), 0 + ) + } + + override fun setNftLayoutVisibility(visible: Boolean) { + binding.layoutNft.visibility = if (visible) View.VISIBLE else View.GONE + } + + override fun setContentLinkText(linkText: String) { + binding.labelContentLink.text = linkText + } + + override fun setContentHashText(hashText: String) { + binding.labelContentHash.text = hashText + } + + override fun setThumbnail(thumbnailType: Int) { + val thumbnailDrawable = getThumbnailDrawableId(thumbnailType) + binding.layoutThumbnail.visibility = + if (thumbnailDrawable == 0) View.GONE else View.VISIBLE + binding.imgThumbnail.setImageResource(thumbnailDrawable) + } + + override fun showNftPreview( + downloadState: TokenInformationModelLogic.StateDownload, + downloadPercent: Float, + content: ByteArray? + ) { + binding.layoutPreview.visibility = when (downloadState) { + TokenInformationModelLogic.StateDownload.NOT_AVAILABLE -> View.GONE + TokenInformationModelLogic.StateDownload.NOT_STARTED -> View.GONE + TokenInformationModelLogic.StateDownload.RUNNING -> View.VISIBLE + TokenInformationModelLogic.StateDownload.DONE -> View.VISIBLE + TokenInformationModelLogic.StateDownload.ERROR -> View.VISIBLE + } + + binding.descDownloadContent.visibility = + if (downloadState == TokenInformationModelLogic.StateDownload.NOT_STARTED) View.VISIBLE else View.GONE + binding.buttonDownloadContent.visibility = binding.descDownloadContent.visibility + binding.previewProgress.visibility = + if (downloadState == TokenInformationModelLogic.StateDownload.RUNNING) View.VISIBLE else View.GONE + + val hasError = content?.let { + try { + val image: Drawable = + BitmapDrawable( + binding.root.resources, + BitmapFactory.decodeByteArray(it, 0, it.size) + ) + binding.imgPreview.setImageDrawable(image) + false + } catch (t: Throwable) { + true + } + } ?: false + + if (hasError || downloadState == TokenInformationModelLogic.StateDownload.ERROR) { + binding.imgPreview.setImageResource(R.drawable.ic_warning_amber_24) + } + + } + + override fun showNftHashValidation(hashValid: Boolean?) { + binding.labelContentHash.setCompoundDrawablesRelativeWithIntrinsicBounds( + 0, 0, when (hashValid) { + true -> R.drawable.ic_verified_18 + false -> R.drawable.ic_suspicious_18 + null -> 0 + }, 0 + ) + } +} \ No newline at end of file diff --git a/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationNftLayoutView.kt b/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationNftLayoutView.kt deleted file mode 100644 index fdfb2c4d7..000000000 --- a/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationNftLayoutView.kt +++ /dev/null @@ -1,88 +0,0 @@ -package org.ergoplatform.android.tokens - -import android.view.View -import org.ergoplatform.android.R -import org.ergoplatform.android.databinding.FragmentTokenInformationBinding -import org.ergoplatform.uilogic.tokens.TokenInformationNftLayoutUiLogic -import org.ergoplatform.uilogic.tokens.TokenInformationUiLogic -import android.graphics.BitmapFactory - -import android.graphics.drawable.BitmapDrawable - -import android.graphics.drawable.Drawable - - -class TokenInformationNftLayoutView(private val binding: FragmentTokenInformationBinding) : - TokenInformationNftLayoutUiLogic() { - override fun setNftLayoutVisibility(visible: Boolean) { - binding.layoutNft.visibility = if (visible) View.VISIBLE else View.GONE - } - - override fun setContentLinkText(linkText: String) { - binding.labelContentLink.text = linkText - } - - override fun setContentLinkClickListener(listener: Runnable?) { - binding.labelContentLink.setOnClickListener(listener?.let { View.OnClickListener { listener.run() } }) - } - - override fun setContentHashText(hashText: String) { - binding.labelContentHash.text = hashText - } - - override fun setThumbnail(thumbnailType: Int) { - val thumbnailDrawable = getThumbnailDrawableId(thumbnailType) - binding.layoutThumbnail.visibility = - if (thumbnailDrawable == 0) View.GONE else View.VISIBLE - binding.imgThumbnail.setImageResource(thumbnailDrawable) - } - - override fun showPreview( - downloadState: TokenInformationUiLogic.StateDownload, - downloadPercent: Float, - content: ByteArray? - ) { - binding.layoutPreview.visibility = when (downloadState) { - TokenInformationUiLogic.StateDownload.NOT_AVAILABLE -> View.GONE - TokenInformationUiLogic.StateDownload.NOT_STARTED -> View.GONE - TokenInformationUiLogic.StateDownload.RUNNING -> View.VISIBLE - TokenInformationUiLogic.StateDownload.DONE -> View.VISIBLE - TokenInformationUiLogic.StateDownload.ERROR -> View.VISIBLE - } - - binding.descDownloadContent.visibility = - if (downloadState == TokenInformationUiLogic.StateDownload.NOT_STARTED) View.VISIBLE else View.GONE - binding.buttonDownloadContent.visibility = binding.descDownloadContent.visibility - binding.previewProgress.visibility = - if (downloadState == TokenInformationUiLogic.StateDownload.RUNNING) View.VISIBLE else View.GONE - - val hasError = content?.let { - try { - val image: Drawable = - BitmapDrawable( - binding.root.resources, - BitmapFactory.decodeByteArray(it, 0, it.size) - ) - binding.imgPreview.setImageDrawable(image) - false - } catch (t: Throwable) { - true - } - } ?: false - - if (hasError || downloadState == TokenInformationUiLogic.StateDownload.ERROR) { - binding.imgPreview.setImageResource(R.drawable.ic_warning_amber_24) - } - - } - - override fun showHashValidation(hashValid: Boolean?) { - binding.labelContentHash.setCompoundDrawablesRelativeWithIntrinsicBounds( - 0, 0, when (hashValid) { - true -> R.drawable.ic_verified_18 - false -> R.drawable.ic_suspicious_18 - null -> 0 - }, 0 - ) - } -} \ No newline at end of file diff --git a/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationViewModel.kt b/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationViewModel.kt index b3772005a..0d3acbb4f 100644 --- a/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationViewModel.kt +++ b/android/src/main/java/org/ergoplatform/android/tokens/TokenInformationViewModel.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.CoroutineScope import org.ergoplatform.android.AppDatabase import org.ergoplatform.android.Preferences import org.ergoplatform.persistance.TokenInformation -import org.ergoplatform.uilogic.tokens.TokenInformationUiLogic +import org.ergoplatform.uilogic.tokens.TokenInformationModelLogic class TokenInformationViewModel : ViewModel() { val uiLogic = AndroidUiLogic() @@ -17,8 +17,8 @@ class TokenInformationViewModel : ViewModel() { private var tokenId: String? = null private val _tokenInfo = MutableLiveData() val tokenInfo: LiveData = _tokenInfo - private val _downloadState = MutableLiveData() - val downloadState: LiveData get() = _downloadState + private val _downloadState = MutableLiveData() + val downloadState: LiveData get() = _downloadState fun init(tokenId: String, ctx: Context) { if (this.tokenId != null) @@ -29,7 +29,7 @@ class TokenInformationViewModel : ViewModel() { uiLogic.init(tokenId, AppDatabase.getInstance(ctx), Preferences(ctx)) } - inner class AndroidUiLogic : TokenInformationUiLogic() { + inner class AndroidUiLogic : TokenInformationModelLogic() { override val coroutineScope: CoroutineScope get() = viewModelScope diff --git a/android/src/main/res/layout/fragment_token_information.xml b/android/src/main/res/layout/fragment_token_information.xml index 89df4b57b..7cfede337 100644 --- a/android/src/main/res/layout/fragment_token_information.xml +++ b/android/src/main/res/layout/fragment_token_information.xml @@ -179,9 +179,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" + android:background="?selectableItemBackground" android:drawablePadding="@dimen/small_padding" - android:ellipsize="end" + android:ellipsize="middle" android:gravity="center" + android:maxLines="1" app:drawableTint="@color/primary" tools:drawableEnd="@drawable/ic_verified_18" tools:text="hash" /> diff --git a/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationLayoutLogic.kt b/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationLayoutLogic.kt new file mode 100644 index 000000000..3862eb14e --- /dev/null +++ b/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationLayoutLogic.kt @@ -0,0 +1,112 @@ +package org.ergoplatform.uilogic.tokens + +import org.ergoplatform.TokenAmount +import org.ergoplatform.WalletStateSyncManager +import org.ergoplatform.persistance.THUMBNAIL_TYPE_NONE +import org.ergoplatform.tokens.isSingularToken +import org.ergoplatform.uilogic.STRING_LABEL_NONE +import org.ergoplatform.uilogic.STRING_LABEL_NO_DESCRIPTION +import org.ergoplatform.uilogic.STRING_LABEL_UNNAMED_TOKEN +import org.ergoplatform.uilogic.StringProvider +import org.ergoplatform.utils.formatTokenPriceToString +import org.ergoplatform.utils.toHex + +abstract class TokenInformationLayoutLogic { + fun updateLayout( + uiLogic: TokenInformationModelLogic, + texts: StringProvider, + rawBalanceAmount: Long + ) { + uiLogic.tokenInformation?.apply { + setTokenTextFields( + if (displayName.isBlank()) texts.getString(STRING_LABEL_UNNAMED_TOKEN) else displayName, + tokenId, + if (description.isNotBlank()) description else texts.getString( + STRING_LABEL_NO_DESCRIPTION + ) + ) + setTokenGenuine(genuineFlag) + + val balanceAmount = TokenAmount(rawBalanceAmount, decimals) + + val showBalance = balanceAmount.rawValue > 0 && !isSingularToken() + val walletSyncManager = WalletStateSyncManager.getInstance() + val tokenPrice = walletSyncManager.tokenPrices[tokenId] + + setLabelSupplyAmountText( + if (!isSingularToken()) TokenAmount(fullSupply, decimals).toStringUsFormatted(false) + else null + ) + + val balanceValue = if (showBalance) tokenPrice?.let { + formatTokenPriceToString( + balanceAmount, + it.ergValue, + walletSyncManager, + texts + ) + " [${it.priceSource}]" + } else null + + setBalanceAmountAndValue( + if (showBalance) balanceAmount.toStringUsFormatted(false) else null, + balanceValue + ) + + updateNftLayout(uiLogic, texts) + } + } + + private fun updateNftLayout( + uiLogic: TokenInformationModelLogic, + texts: StringProvider + ) { + // NFT information + val isNft = uiLogic.eip4Token?.isNftAssetType ?: false + setNftLayoutVisibility(isNft) + + if (isNft) { + val eip4Token = uiLogic.eip4Token!! + setContentLinkText(eip4Token.nftContentLink ?: texts.getString(STRING_LABEL_NONE)) + + setContentHashText(eip4Token.nftContentHash?.let { it.toHex() + " [SHA256]" } + ?: texts.getString(STRING_LABEL_NONE)) + + setThumbnail( + if (uiLogic.downloadState != TokenInformationModelLogic.StateDownload.NOT_AVAILABLE) THUMBNAIL_TYPE_NONE + else uiLogic.tokenInformation?.thumbnailType ?: 0 + ) + + // show preview layout when download is running or when picture is ready + updateNftPreview(uiLogic) + } + } + + fun updateNftPreview(uiLogic: TokenInformationModelLogic) { + showNftPreview(uiLogic.downloadState, uiLogic.downloadPercent, uiLogic.downloadedData) + showNftHashValidation(uiLogic.sha256Check) + } + + abstract fun setTokenTextFields(displayName: String, tokenId: String, description: String) + + abstract fun setLabelSupplyAmountText(supplyAmount: String?) + + abstract fun setTokenGenuine(genuineFlag: Int) + + abstract fun setBalanceAmountAndValue(amount: String?, balanceValue: String?) + + abstract fun setContentLinkText(linkText: String) + + abstract fun setNftLayoutVisibility(visible: Boolean) + + abstract fun setContentHashText(hashText: String) + + abstract fun setThumbnail(thumbnailType: Int) + + abstract fun showNftPreview( + downloadState: TokenInformationModelLogic.StateDownload, + downloadPercent: Float, + content: ByteArray? + ) + + abstract fun showNftHashValidation(hashValid: Boolean?) +} diff --git a/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationUiLogic.kt b/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationModelLogic.kt similarity index 99% rename from common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationUiLogic.kt rename to common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationModelLogic.kt index 7976e1b1f..3d40c8577 100644 --- a/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationUiLogic.kt +++ b/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationModelLogic.kt @@ -16,7 +16,7 @@ import org.ergoplatform.utils.ProgressListener import org.ergoplatform.utils.fetchHttpGetWithListener import java.security.MessageDigest -abstract class TokenInformationUiLogic { +abstract class TokenInformationModelLogic { abstract val coroutineScope: CoroutineScope var eip4Token: Eip4Token? = null diff --git a/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationNftLayoutUiLogic.kt b/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationNftLayoutUiLogic.kt deleted file mode 100644 index 5fd2e3bf2..000000000 --- a/common-jvm/src/main/java/org/ergoplatform/uilogic/tokens/TokenInformationNftLayoutUiLogic.kt +++ /dev/null @@ -1,71 +0,0 @@ -package org.ergoplatform.uilogic.tokens - -import org.ergoplatform.persistance.PreferencesProvider -import org.ergoplatform.persistance.THUMBNAIL_TYPE_NONE -import org.ergoplatform.tokens.getHttpContentLink -import org.ergoplatform.uilogic.STRING_LABEL_NONE -import org.ergoplatform.uilogic.StringProvider -import org.ergoplatform.utils.toHex - -abstract class TokenInformationNftLayoutUiLogic { - fun update( - uiLogic: TokenInformationUiLogic, - texts: StringProvider, - preferencesProvider: PreferencesProvider, - openUrlWithBrowser: (String) -> Boolean - ) { - // NFT information - val isNft = uiLogic.eip4Token?.isNftAssetType ?: false - setNftLayoutVisibility(isNft) - - if (isNft) { - val eip4Token = uiLogic.eip4Token!! - setContentLinkText(eip4Token.nftContentLink ?: texts.getString(STRING_LABEL_NONE)) - setContentLinkClickListener(eip4Token.nftContentLink?.let { - Runnable { - val success = openUrlWithBrowser(eip4Token.nftContentLink!!) - - if (!success) { - eip4Token.getHttpContentLink(preferencesProvider)?.let { - openUrlWithBrowser(it) - } - } - } - }) - - setContentHashText(eip4Token.nftContentHash?.let { it.toHex() + " [SHA256]" } - ?: texts.getString(STRING_LABEL_NONE)) - - setThumbnail( - if (uiLogic.downloadState != TokenInformationUiLogic.StateDownload.NOT_AVAILABLE) THUMBNAIL_TYPE_NONE - else uiLogic.tokenInformation?.thumbnailType ?: 0 - ) - - // show preview layout when download is running or when picture is ready - updatePreview(uiLogic) - } - } - - fun updatePreview(uiLogic: TokenInformationUiLogic) { - showPreview(uiLogic.downloadState, uiLogic.downloadPercent, uiLogic.downloadedData) - showHashValidation(uiLogic.sha256Check) - } - - abstract fun setContentLinkText(linkText: String) - - abstract fun setNftLayoutVisibility(visible: Boolean) - - abstract fun setContentLinkClickListener(listener: Runnable?) - - abstract fun setContentHashText(hashText: String) - - abstract fun setThumbnail(thumbnailType: Int) - - abstract fun showPreview( - downloadState: TokenInformationUiLogic.StateDownload, - downloadPercent: Float, - content: ByteArray? - ) - - abstract fun showHashValidation(hashValid: Boolean?) -}