feat(wallet): NFT thumbnails and metadata display in Assets tab
- Add NFT metadata fetching via Koios asset_info endpoint - Parse CIP-25 onchain_metadata for image, name, description - Convert IPFS URLs to ipfs.io gateway URLs - Display 64dp thumbnails with 8dp rounded corners using Coil AsyncImage - Add bottom sheet detail view for NFT expansion (larger image + metadata) - Graceful fallback with placeholder icons on image load failure - Load metadata in presenter, cache results for 30 minutes - Parallel metadata fetching for better performance
This commit is contained in:
parent
a57fd79098
commit
2d8df4f23f
8 changed files with 572 additions and 11 deletions
|
|
@ -81,4 +81,15 @@ interface CardanoClient {
|
|||
* @return Bech32 Cardano address if handle exists, null if not found
|
||||
*/
|
||||
suspend fun resolveHandle(handle: String): Result<String?>
|
||||
|
||||
/**
|
||||
* Get CIP-25 NFT metadata for a specific asset.
|
||||
*
|
||||
* Uses the Koios asset_info endpoint to fetch onchain_metadata.
|
||||
*
|
||||
* @param policyId The minting policy ID (hex, 56 chars)
|
||||
* @param assetName The asset name (hex encoded)
|
||||
* @return [NftMetadata] if CIP-25 metadata exists, null otherwise
|
||||
*/
|
||||
suspend fun getNftMetadata(policyId: String, assetName: String): Result<NftMetadata?>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Sulkta Coop.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package io.element.android.features.wallet.api
|
||||
|
||||
/**
|
||||
* CIP-25 NFT metadata parsed from Koios asset_info response.
|
||||
*
|
||||
* @property name The NFT name
|
||||
* @property image Resolved HTTP URL for the image (IPFS gateway or direct HTTPS)
|
||||
* @property description NFT description if available
|
||||
* @property rawMetadata Original metadata map for additional fields
|
||||
*/
|
||||
data class NftMetadata(
|
||||
val name: String,
|
||||
val image: String?,
|
||||
val description: String?,
|
||||
val rawMetadata: Map<String, Any>,
|
||||
) {
|
||||
companion object {
|
||||
private const val IPFS_GATEWAY = "https://ipfs.io/ipfs/"
|
||||
|
||||
/**
|
||||
* Resolve IPFS URLs to HTTP gateway URLs.
|
||||
*/
|
||||
fun resolveImageUrl(url: String?): String? {
|
||||
if (url == null) return null
|
||||
return when {
|
||||
url.startsWith("ipfs://") -> IPFS_GATEWAY + url.removePrefix("ipfs://")
|
||||
url.startsWith("Qm") -> IPFS_GATEWAY + url // Direct IPFS hash
|
||||
url.startsWith("https://") || url.startsWith("http://") -> url
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Join array-based image URL (some NFTs split the URL across multiple strings).
|
||||
*/
|
||||
fun joinImageParts(parts: List<String>?): String? {
|
||||
if (parts.isNullOrEmpty()) return null
|
||||
return resolveImageUrl(parts.joinToString(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue