Skip to content

Commit

Permalink
(Wallet) Implement NFT asset details screen (#16671)
Browse files Browse the repository at this point in the history
Implements new NFT asset detail screen supporting GIF, BMP, PNG and JPG images.

(cherry picked from commit 15fd2dc)
  • Loading branch information
simoarpe committed Jan 25, 2023
1 parent 834fd54 commit 9f34c92
Show file tree
Hide file tree
Showing 12 changed files with 488 additions and 19 deletions.
1 change: 1 addition & 0 deletions android/brave_java_resources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,7 @@ brave_java_resources = [
"java/res/layout/activity_brave_wallet_dapps.xml",
"java/res/layout/activity_buy_send_swap.xml",
"java/res/layout/activity_network_selector.xml",
"java/res/layout/activity_nft_detail.xml",
"java/res/layout/activity_onboarding.xml",
"java/res/layout/activity_split_tunnel.xml",
"java/res/layout/activity_welcome_onboarding.xml",
Expand Down
1 change: 1 addition & 0 deletions android/brave_java_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ brave_java_sources = [
"../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BraveWalletDAppsActivity.java",
"../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BuySendSwapActivity.java",
"../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/activities/NetworkSelectorActivity.java",
"../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/activities/NftDetailActivity.java",
"../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/AccountSpinnerAdapter.java",
"../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/ApproveTxFragmentPageAdapter.java",
"../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/CreateAccountAdapter.java",
Expand Down
5 changes: 5 additions & 0 deletions android/java/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"/>

<activity android:name="org.chromium.chrome.browser.crypto_wallet.activities.NftDetailActivity"
android:theme="@style/Theme.Chromium.Activity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"/>

<activity
android:name="org.chromium.chrome.browser.vpn.split_tunnel.SplitTunnelActivity"
android:theme="@style/Theme.Chromium.Activity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ void resetServices(Context context, TxService mTxService, KeyringService mKeyrin
}
}

public class NftDataModel {
public static class NftDataModel {
public BlockchainToken token;
public NetworkInfo networkInfo;
public Erc721MetaData erc721MetaData;
Expand All @@ -131,7 +131,7 @@ public NftDataModel(
}
}

public class Erc721MetaData implements Serializable {
public static class Erc721MetaData implements Serializable {
public String mDescription;
public String mImageUrl;
public String mName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/* Copyright (c) 2023 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */

package org.chromium.chrome.browser.crypto_wallet.activities;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.appcompat.widget.Toolbar;

import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.DrawableCrossFadeTransition;

import org.chromium.brave_wallet.mojom.BlockchainToken;
import org.chromium.brave_wallet.mojom.NetworkInfo;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.app.BraveActivity;
import org.chromium.chrome.browser.app.domain.PortfolioModel;
import org.chromium.chrome.browser.crypto_wallet.util.AndroidUtils;
import org.chromium.chrome.browser.crypto_wallet.util.ImageLoader;
import org.chromium.chrome.browser.crypto_wallet.util.Utils;
import org.chromium.chrome.browser.util.LiveDataUtil;
import org.chromium.chrome.browser.util.TabUtils;
import org.chromium.ui.text.NoUnderlineClickableSpan;

public class NftDetailActivity extends BraveWalletBaseActivity {
private static final String TOKEN_ID_FORMAT = "#%s";
private static final String NFT_URL_FORMAT = "%s/token/%s?a=%s";

private static final String CHAIN_ID = "chainId";
private static final String ASSET_NAME = "assetName";
private static final String ASSET_CONTRACT_ADDRESS = "assetContractAddress";
private static final String NFT_TOKEN_ID_HEX = "nftTokenIdHex";
private static final String NFT_META_DATA = "nftMetaData";

private String mNftName;
private String mChainId;
private String mContractAddress;
private String mNftTokenId;
private String mNftTokenHex;

private ImageView mNftImageView;
private TextView mImageNotAvailableText;
private TextView mNetworkNameView;
private Button mBtnSend;
private TextView mTokenStandardView;
private TextView mTokenIdView;
private TextView mDescriptionContentView;
private TextView mNftDetailTitleView;
private TextView mNftNameView;
private ViewGroup mNftDescriptionLayout;
private Toolbar mToolbar;

private PortfolioModel.Erc721MetaData mErc721MetaData;

@Override
protected void triggerLayoutInflation() {
setContentView(R.layout.activity_nft_detail);

Intent intent = getIntent();
if (intent != null) {
mChainId = intent.getStringExtra(CHAIN_ID);
mNftName = intent.getStringExtra(ASSET_NAME);
mContractAddress = intent.getStringExtra(ASSET_CONTRACT_ADDRESS);
mNftTokenHex = intent.getStringExtra(NFT_TOKEN_ID_HEX);
mNftTokenId = Utils.hexToIntString(mNftTokenHex);
mErc721MetaData =
(PortfolioModel.Erc721MetaData) intent.getSerializableExtra(NFT_META_DATA);
}

// Calculate half screen height and assign it to NFT image view,
// and "Image not found" text view (that will pop up in case of issues).
int halfScreenHeight = AndroidUtils.getScreenHeight() / 2;

mNftImageView = findViewById(R.id.nft_detail_image);
mNftImageView.getLayoutParams().height = halfScreenHeight;

mImageNotAvailableText = findViewById(R.id.nft_detail_image_not_available);
mImageNotAvailableText.getLayoutParams().height = halfScreenHeight;

mDescriptionContentView = findViewById(R.id.description_content);

mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

mBtnSend = findViewById(R.id.btn_send);
// TODO(simone): Enable if it's the NFT owner.
mBtnSend.setVisibility(View.GONE);

mNftDetailTitleView = findViewById(R.id.nft_detail_title);
mNftDetailTitleView.setText(Utils.formatErc721TokenTitle(mNftName, mNftTokenId));

mNftNameView = findViewById(R.id.nft_name);
mNftNameView.setText(mNftName);

mNetworkNameView = findViewById(R.id.blockchain_content);

mTokenStandardView = findViewById(R.id.token_standard_content);
mTokenStandardView.setText(R.string.brave_wallet_nft_erc_721);

mTokenIdView = findViewById(R.id.token_id_content);

mNftDescriptionLayout = findViewById(R.id.nft_description);

setMetadata(mErc721MetaData);

BraveActivity braveActivity = BraveActivity.getBraveActivity();
assert braveActivity != null;

LiveDataUtil.observeOnce(
braveActivity.getWalletModel().getCryptoModel().getNetworkModel().mDefaultNetwork,
defaultNetwork -> {
String networkName = defaultNetwork.chainName;
mNetworkNameView.setText(networkName);
String blockExplorerUrl = defaultNetwork.blockExplorerUrls.length > 0
? defaultNetwork.blockExplorerUrls[0]
: "";

String tokenStr = String.format(TOKEN_ID_FORMAT, mNftTokenId);
SpannableString spannable = new SpannableString(tokenStr);

if (!TextUtils.isEmpty(blockExplorerUrl)) {
String url = String.format(
NFT_URL_FORMAT, blockExplorerUrl, mContractAddress, mNftTokenId);
NoUnderlineClickableSpan linkSpan =
new NoUnderlineClickableSpan(this, R.color.brave_link,
(textView) -> { TabUtils.openLinkWithFocus(this, url); });
spannable.setSpan(
linkSpan, 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
mTokenIdView.setText(spannable);
mTokenIdView.setMovementMethod(LinkMovementMethod.getInstance());

onInitialLayoutInflationComplete();
});
}

private void setMetadata(PortfolioModel.Erc721MetaData erc721MetaData) {
if (erc721MetaData == null) return;
// In case of no errors proceed to assign description and fetch NFT image.
if (erc721MetaData.mErrCode == 0) {
String description = erc721MetaData.mDescription;
String imageUrl = erc721MetaData.mImageUrl;
if (!TextUtils.isEmpty(description)) {
mDescriptionContentView.setText(description);
} else {
// Hide description layout in case of an empty string.
mNftDescriptionLayout.setVisibility(View.GONE);
}
if (ImageLoader.isSupported(imageUrl)) {
loadNftImage(imageUrl);
} else {
setNftImageAsNotAvailable();
}
}
}

private void loadNftImage(String imageUrl) {
ImageLoader.createLoadNftRequest(imageUrl, this, false)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(GlideException glideException, Object model,
Target<Drawable> target, boolean isFirstResource) {
setNftImageAsNotAvailable();
return false;
}

@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource,
boolean isFirstResource) {
target.onResourceReady(
resource, new DrawableCrossFadeTransition(250, true));
return true;
}
})
.into(mNftImageView);
}

private void setNftImageAsNotAvailable() {
mNftImageView.setVisibility(View.GONE);
mImageNotAvailableText.setVisibility(View.VISIBLE);
}

public static Intent getIntent(Context context, String chainId, BlockchainToken asset,
PortfolioModel.NftDataModel nftDataModel) {
Intent intent = new Intent(context, NftDetailActivity.class);
intent.putExtra(CHAIN_ID, chainId);
intent.putExtra(ASSET_NAME, asset.name);
intent.putExtra(ASSET_CONTRACT_ADDRESS, asset.contractAddress);
intent.putExtra(NFT_TOKEN_ID_HEX, asset.tokenId);
intent.putExtra(NFT_META_DATA, nftDataModel.erc721MetaData);
return intent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public void onCheckedChanged(
if (walletListItemModel.hasNftImageLink()
&& ImageLoader.isSupported(nftDataModel.erc721MetaData.mImageUrl)) {
String url = nftDataModel.erc721MetaData.mImageUrl;
ImageLoader.loadNft(url, holder.iconImg, context, false);
ImageLoader.createLoadNftRequest(url, context, false).into(holder.iconImg);
} else {
Utils.setBlockiesBitmapCustomAsset(mExecutor, mHandler, holder.iconImg,
walletListItemModel.getBlockchainToken().contractAddress,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
* You can obtain one at https://mozilla.org/MPL/2.0/. */

package org.chromium.chrome.browser.crypto_wallet.fragments;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
Expand Down Expand Up @@ -47,6 +48,7 @@
import org.chromium.chrome.browser.app.domain.WalletModel;
import org.chromium.chrome.browser.crypto_wallet.BlockchainRegistryFactory;
import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletActivity;
import org.chromium.chrome.browser.crypto_wallet.activities.NftDetailActivity;
import org.chromium.chrome.browser.crypto_wallet.adapters.WalletCoinAdapter;
import org.chromium.chrome.browser.crypto_wallet.listeners.OnWalletListItemClick;
import org.chromium.chrome.browser.crypto_wallet.observers.ApprovedTxObserver;
Expand Down Expand Up @@ -92,6 +94,7 @@ public class PortfolioFragment
private TextView mTvNftTitle;
private SmoothLineChartEquallySpaced mChartES;
private PortfolioModel mPortfolioModel;
private List<PortfolioModel.NftDataModel> mNftDataModels;

public static PortfolioFragment newInstance() {
return new PortfolioFragment();
Expand Down Expand Up @@ -198,6 +201,7 @@ private void setUpObservers() {
});
mPortfolioModel.mNftModels.observe(getViewLifecycleOwner(), nftDataModels -> {
if (nftDataModels.isEmpty() || mPortfolioModel.mPortfolioHelper == null) return;
mNftDataModels = nftDataModels;
setUpNftList(nftDataModels, mPortfolioModel.mPortfolioHelper.getPerTokenCryptoSum(),
mPortfolioModel.mPortfolioHelper.getPerTokenFiatSum());
});
Expand Down Expand Up @@ -337,11 +341,25 @@ public void onAssetClick(BlockchainToken asset) {
if (selectedNetwork == null) {
return;
}

if (asset.isErc721 || asset.isNft) {
// TODO: show nft details of clicked nft
return;
PortfolioModel.NftDataModel selectedNft = null;
for (PortfolioModel.NftDataModel nftDataModel : mNftDataModels) {
if (nftDataModel.token.tokenId.equals(asset.tokenId)) {
selectedNft = nftDataModel;
break;
}
}
if (selectedNft == null) {
return;
}

Intent intent = NftDetailActivity.getIntent(
getContext(), selectedNetwork.chainId, asset, selectedNft);
startActivity(intent);
} else {
Utils.openAssetDetailsActivity(getActivity(), selectedNetwork.chainId, asset);
}
Utils.openAssetDetailsActivity(getActivity(), selectedNetwork.chainId, asset);
}

private void openNetworkSelection() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.chromium.chrome.browser.crypto_wallet.util;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Build;
import android.text.Html;
Expand Down Expand Up @@ -89,4 +90,12 @@ private static void setViewVisibility(boolean isVisible, View... views) {
view.setVisibility(visibility);
}
}

/**
* Gets device screen height in pixels, excluding the navigation bar (if visible) and insets.
* @return device screen height in pixels.
*/
public static int getScreenHeight() {
return Resources.getSystem().getDisplayMetrics().heightPixels;
}
}
Loading

0 comments on commit 9f34c92

Please sign in to comment.