diff --git a/app/build.gradle b/app/build.gradle index 723664896..12fb6ae07 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -309,8 +309,8 @@ dependencies { //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' // Image Loader - implementation 'com.github.bumptech.glide:glide:4.12.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' + implementation 'com.github.bumptech.glide:glide:4.13.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0' implementation group: 'com.google.guava', name: 'guava', version: '30.1.1-android' diff --git a/app/src/main/java/com/alphawallet/app/ui/NFTAssetsFragment.java b/app/src/main/java/com/alphawallet/app/ui/NFTAssetsFragment.java index 0b8e9b8fb..7bbb980b3 100644 --- a/app/src/main/java/com/alphawallet/app/ui/NFTAssetsFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/NFTAssetsFragment.java @@ -183,6 +183,13 @@ public class NFTAssetsFragment extends BaseFragment implements OnAssetClickListe Bundle result = new Bundle(); result.putBoolean(SYNC_STATUS, token.getTokenCount() != token.getTokenAssets().size()); getParentFragmentManager().setFragmentResult(SIGNAL_NFT_SYNC, result); + forceRedraw(); + } + + // This is a trick to fix a bug + private void forceRedraw() + { + search.setText(""); } private boolean hasTokenScriptOverride(Token t) diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/NFTAssetsAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/NFTAssetsAdapter.java index 88695efe1..b0e4806db 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/NFTAssetsAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/NFTAssetsAdapter.java @@ -7,8 +7,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.NonNull; @@ -34,8 +32,10 @@ import java.util.Map; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; -public class NFTAssetsAdapter extends RecyclerView.Adapter { +public class NFTAssetsAdapter extends RecyclerView.Adapter +{ private final Activity activity; private final OnAssetClickListener listener; private final Token token; @@ -103,56 +103,48 @@ public class NFTAssetsAdapter extends RecyclerView.Adapter listener.onAssetClicked(new Pair<>(tokenId, asset))); + } + + private void displayImage(@NonNull ViewHolder holder, NFTAsset asset) + { + if (asset.hasImageAsset()) + { + holder.icon.setupTokenImageThumbnail(asset, isGrid); + } + else + { + holder.icon.showFallbackLayout(token); + } + } + + private void displayTitle(@NotNull ViewHolder holder, NFTAsset asset, Token token, BigInteger tokenId) { int assetCount = asset.isCollection() ? asset.getCollectionCount() : asset.getBalance().intValue(); int textId = assetCount == 1 ? R.string.asset_description_text : R.string.asset_description_text_plural; - if (asset.isBlank()) + String title = asset.getName() == null ? String.format("ID #%s", tokenId) : asset.getName(); + holder.title.setText(title); + + // set subtitle + if (token.isERC721()) { - holder.title.setText(String.format("ID #%s", tokenId)); + // Hide subtitle containing redundant information + holder.subtitle.setVisibility(View.GONE); } else { - if (asset.getName() != null) - { - holder.title.setText(asset.getName()); - if (token.isERC721()) - { - // Hide subtitle containing redundant information - holder.subtitle.setVisibility(View.GONE); - } - else - { - holder.subtitle.setVisibility(View.VISIBLE); - holder.subtitle.setText(activity.getString(textId, assetCount, asset.getAssetCategory(tokenId).getValue())); - } - } - else - { - holder.title.setText(String.format("ID #%s", tokenId)); - holder.subtitle.setVisibility(View.GONE); - } - - if (asset.hasImageAsset()) - { - holder.icon.setupTokenImageThumbnail(asset); - } - else - { - holder.icon.showFallbackLayout(token); - } + holder.subtitle.setText(activity.getString(textId, assetCount, asset.getAssetCategory(tokenId).getValue())); + holder.subtitle.setVisibility(View.VISIBLE); } - - holder.layout.setOnClickListener(v -> listener.onAssetClicked(new Pair<>(tokenId, asset))); - holder.loadingSpinner.setVisibility(View.GONE); } private void fetchAsset(ViewHolder holder, Pair pair) @@ -273,12 +265,12 @@ public class NFTAssetsAdapter extends RecyclerView.Adapter 0, "roundingRadius must be greater than 0."); + this.roundingRadius = roundingRadius; + } + + @Override + protected Bitmap transform( + @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) + { + return TransformationUtils.roundedCorners(pool, toTransform, roundingRadius, roundingRadius, 0, 0); + } + + @Override + public boolean equals(Object o) + { + if (o instanceof RoundedTopCorners) + { + RoundedTopCorners other = (RoundedTopCorners) o; + return roundingRadius == other.roundingRadius; + } + return false; + } + + @Override + public int hashCode() + { + return Util.hashCode(ID.hashCode(), Util.hashCode(roundingRadius)); + } + + @Override + public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) + { + messageDigest.update(ID_BYTES); + + byte[] radiusData = ByteBuffer.allocate(4).putInt(roundingRadius).array(); + messageDigest.update(radiusData); + } +} + diff --git a/app/src/main/java/com/alphawallet/app/walletconnect/WalletConnectV2SessionRequestHandler.java b/app/src/main/java/com/alphawallet/app/walletconnect/WalletConnectV2SessionRequestHandler.java index 8b163d25f..c6cca97c9 100644 --- a/app/src/main/java/com/alphawallet/app/walletconnect/WalletConnectV2SessionRequestHandler.java +++ b/app/src/main/java/com/alphawallet/app/walletconnect/WalletConnectV2SessionRequestHandler.java @@ -86,13 +86,10 @@ public class WalletConnectV2SessionRequestHandler } } - @SuppressWarnings("checkstyle:MissingSwitchDefault") private boolean validateChainId(Signable signable) { switch (signable.getMessageType()) { - case SIGN_ERROR: - return false; case SIGN_MESSAGE: case SIGN_PERSONAL_MESSAGE: case SIGN_TYPED_DATA: @@ -104,9 +101,10 @@ public class WalletConnectV2SessionRequestHandler case ATTESTATION: //TODO: Check attestation signing chain return true; + case SIGN_ERROR: + default: + return false; } - - return false; } private WalletConnectV2SessionItem getSessionItem() diff --git a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java index ea30276e6..bc827f6e0 100644 --- a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java +++ b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java @@ -657,7 +657,6 @@ public class ActionSheetDialog extends ActionSheet implements StandardFunctionIn /** * Either Send or Sign (WalletConnect only) the transaction */ - @SuppressWarnings("checkstyle:MissingSwitchDefault") private void handleTransactionOperation() { if (walletType != WalletType.HARDWARE) @@ -699,6 +698,7 @@ public class ActionSheetDialog extends ActionSheet implements StandardFunctionIn case SIGN_MESSAGE: case WALLET_CONNECT_REQUEST: case NODE_STATUS_INFO: + default: break; } diff --git a/app/src/main/java/com/alphawallet/app/widget/NFTImageView.java b/app/src/main/java/com/alphawallet/app/widget/NFTImageView.java index 85f1b02a9..fc44e81d5 100644 --- a/app/src/main/java/com/alphawallet/app/widget/NFTImageView.java +++ b/app/src/main/java/com/alphawallet/app/widget/NFTImageView.java @@ -8,6 +8,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.media.MediaPlayer; @@ -21,6 +22,7 @@ import android.view.ViewGroup; import android.webkit.MimeTypeMap; import android.webkit.WebChromeClient; import android.webkit.WebView; +import android.webkit.WebViewClient; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.RelativeLayout; @@ -32,9 +34,11 @@ import com.alphawallet.app.C; import com.alphawallet.app.R; import com.alphawallet.app.entity.nftassets.NFTAsset; import com.alphawallet.app.entity.tokens.Token; +import com.alphawallet.app.util.RoundedTopCorners; import com.alphawallet.app.util.Utils; import com.bumptech.glide.Glide; import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.load.resource.bitmap.CenterCrop; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; @@ -47,6 +51,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import timber.log.Timber; @@ -69,7 +74,7 @@ public class NFTImageView extends RelativeLayout /** * Prevent glide dumping log errors - it is expected that load will fail */ - private final RequestListener requestListener = new RequestListener() + private final RequestListener requestListener = new RequestListener<>() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) @@ -79,13 +84,14 @@ public class NFTImageView extends RelativeLayout if (msg.contains(C.GLIDE_URL_INVALID)) //URL not valid: use the attribute name { handler.post(() -> { - progressBar.setVisibility(View.GONE); - fallbackLayout.setVisibility(View.VISIBLE); + progressBar.setVisibility(GONE); + fallbackLayout.setVisibility(VISIBLE); }); } else if (model != null) //or fallback to webview if there was some other problem { setWebView(model.toString(), ImageType.IMAGE); + fallbackLayout.setVisibility(GONE); } return false; } @@ -93,14 +99,14 @@ public class NFTImageView extends RelativeLayout @Override public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - progressBar.setVisibility(View.GONE); + progressBar.setVisibility(GONE); + fallbackLayout.setVisibility(GONE); return false; } }; private Request loadRequest; private String imageUrl; - private boolean hasContent; - private boolean showProgress; + private boolean isThumbnail; public NFTImageView(Context context, @Nullable AttributeSet attrs) { @@ -116,9 +122,8 @@ public class NFTImageView extends RelativeLayout overlay = findViewById(R.id.overlay_rect); mediaPlayer = null; - webLayout.setVisibility(View.GONE); - webView.setVisibility(View.GONE); - showProgress = false; + webLayout.setVisibility(GONE); + webView.setVisibility(GONE); if (loadRequest != null && loadRequest.isRunning()) { @@ -130,13 +135,20 @@ public class NFTImageView extends RelativeLayout } public void setupTokenImageThumbnail(NFTAsset asset) + { + setupTokenImageThumbnail(asset, false); + } + + public void setupTokenImageThumbnail(NFTAsset asset, boolean onlyRoundTopCorners) { fallbackIcon.setupFallbackTextIcon(asset.getName()); - loadImage(asset.getThumbnail(), asset.getBackgroundColor(), 1); + isThumbnail = true; + loadImage(asset.getThumbnail(), asset.getBackgroundColor(), 30, onlyRoundTopCorners); } public void setupTokenImage(NFTAsset asset) throws IllegalArgumentException { + isThumbnail = false; String anim = asset.getAnimation(); fallbackIcon.setupFallbackTextIcon(asset.getName()); @@ -148,23 +160,20 @@ public class NFTImageView extends RelativeLayout } else if (shouldLoad(asset.getImage())) { - showLoadingProgress(); - progressBar.setVisibility(showProgress ? View.VISIBLE : View.GONE); - loadImage(asset.getImage(), asset.getBackgroundColor(), 16); + loadImage(asset.getImage(), asset.getBackgroundColor(), 16, false); playAudioIfAvailable(anim); } } - private void loadImage(String url, String backgroundColor, int corners) throws IllegalArgumentException + private void loadImage(String url, String backgroundColor, int corners, boolean onlyRoundTopCorners) throws IllegalArgumentException { if (!Utils.stillAvailable(getContext())) return; - setWebViewHeight((int)getLayoutParams().width); + setWebViewHeight(getLayoutParams().width); this.imageUrl = url; - fallbackLayout.setVisibility(View.GONE); image.setVisibility(View.VISIBLE); - webLayout.setVisibility(View.GONE); + webLayout.setVisibility(GONE); try { @@ -177,9 +186,24 @@ public class NFTImageView extends RelativeLayout holdingView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.transparent)); } + + if (url.toLowerCase(Locale.ROOT).contains(".webp")) + { + corners += 30; + } + Transformation roundedCorners; + if (onlyRoundTopCorners) + { + roundedCorners = new RoundedTopCorners(corners); + } + else + { + roundedCorners = new RoundedCorners(corners); + } + loadRequest = Glide.with(getContext()) .load(url) - .transform(new CenterCrop(), new RoundedCorners(corners)) + .transform(new CenterCrop(), roundedCorners) .transition(withCrossFade()) .override(Target.SIZE_ORIGINAL) .timeout(30 * 1000) @@ -193,17 +217,26 @@ public class NFTImageView extends RelativeLayout webView.setVerticalScrollBarEnabled(false); webView.setHorizontalScrollBarEnabled(false); webView.setWebChromeClient(new WebChromeClient()); + webView.setWebViewClient(new WebViewClient() + { + @Override + public void onPageFinished(WebView view, String url) + { + super.onPageFinished(view, url); + progressBar.setVisibility(GONE); + } + }); //determine how to display this URL final DisplayType useType = new DisplayType(imageUrl, hint); handler.post(() -> { this.imageUrl = imageUrl; - image.setVisibility(View.GONE); + image.setVisibility(GONE); webLayout.setVisibility(View.VISIBLE); webView.setVisibility(View.VISIBLE); overlay.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.GONE); + progressBar.setVisibility(GONE); if (useType.getImageType() == ImageType.WEB) { @@ -234,6 +267,10 @@ public class NFTImageView extends RelativeLayout String loader = loadFile(getContext(), R.raw.token_graphic).replace("[URL]", imageUrl); String base64 = android.util.Base64.encodeToString(loader.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT); webView.loadData(base64, "text/html; charset=utf-8", "base64"); + if (isThumbnail) + { + setWebViewHeight(500); + } } }); } @@ -271,17 +308,6 @@ public class NFTImageView extends RelativeLayout fallbackLayout.setVisibility(View.VISIBLE); fallbackIcon.bindData(token); - hasContent = true; - } - - public boolean hasContent() - { - return hasContent; - } - - public void showLoadingProgress() - { - this.showProgress = true; } public boolean shouldLoad(String url) @@ -318,7 +344,7 @@ public class NFTImageView extends RelativeLayout return (url != null && MimeTypeMap.getFileExtensionFromUrl(url).equals("glb")); } - private static final List audioTypes = new ArrayList<>(Arrays.asList( "mp3", "ogg", "wav", "flac", "aac", "opus", "weba" )); + private static final List audioTypes = new ArrayList<>(Arrays.asList("mp3", "ogg", "wav", "flac", "aac", "opus", "weba")); private boolean isAudio(String url) { diff --git a/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java b/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java index 2ef21664f..0e5655f20 100644 --- a/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java +++ b/app/src/main/java/com/alphawallet/app/widget/TokenIcon.java @@ -33,10 +33,13 @@ import com.alphawallet.app.service.TokensService; import com.alphawallet.app.ui.widget.TokensAdapterCallback; import com.alphawallet.app.ui.widget.entity.IconItem; import com.alphawallet.app.ui.widget.entity.StatusType; +import com.alphawallet.app.util.RoundedTopCorners; import com.alphawallet.app.util.Utils; import com.bumptech.glide.Glide; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.FitCenter; import com.bumptech.glide.request.Request; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; diff --git a/app/src/main/res/drawable/background_round_bottom_corners.xml b/app/src/main/res/drawable/background_round_bottom_corners.xml new file mode 100644 index 000000000..2408d2a2f --- /dev/null +++ b/app/src/main/res/drawable/background_round_bottom_corners.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/drawable/background_round_top_corners.xml b/app/src/main/res/drawable/background_round_top_corners.xml new file mode 100644 index 000000000..6fe1636d1 --- /dev/null +++ b/app/src/main/res/drawable/background_round_top_corners.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/layout/item_asset_detail.xml b/app/src/main/res/layout/item_asset_detail.xml index 78348bd35..4c1ae0b47 100644 --- a/app/src/main/res/layout/item_asset_detail.xml +++ b/app/src/main/res/layout/item_asset_detail.xml @@ -72,8 +72,7 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_asset_image.xml b/app/src/main/res/layout/item_asset_image.xml index 47dce539e..9eeb5f3c3 100644 --- a/app/src/main/res/layout/item_asset_image.xml +++ b/app/src/main/res/layout/item_asset_image.xml @@ -4,31 +4,31 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/layout_holder" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> + android:layout_height="match_parent"> + android:visibility="invisible" + tools:visibility="invisible" /> + android:padding="4dp" + android:visibility="gone" > + android:visibility="invisible"> - - - + android:layout_alignParentTop="true" + android:paddingBottom="35dp"> - - - + android:layout_height="match_parent" /> - + - + - + - + android:layout_alignParentStart="true" + android:ellipsize="end" + android:gravity="center" + android:singleLine="true" + tools:text="Title" /> - - - - - - + - - \ No newline at end of file + + diff --git a/app/src/main/res/layout/item_token_grid.xml b/app/src/main/res/layout/item_token_grid.xml index 799430462..f638cb9d2 100644 --- a/app/src/main/res/layout/item_token_grid.xml +++ b/app/src/main/res/layout/item_token_grid.xml @@ -7,6 +7,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="@dimen/small_12" + android:padding="1dp" android:background="@drawable/background_grid_icon" android:gravity="center_horizontal" android:orientation="vertical"> @@ -60,4 +61,4 @@ android:focusable="true" android:orientation="vertical" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_token_icon_square.xml b/app/src/main/res/layout/item_token_icon_square.xml index a8671a8aa..3a057326e 100644 --- a/app/src/main/res/layout/item_token_icon_square.xml +++ b/app/src/main/res/layout/item_token_icon_square.xml @@ -4,8 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/view_container" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" + android:layout_height="match_parent" app:layout_constraintDimensionRatio="1:1"> - \ No newline at end of file +