diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index b7134804aa..aa9b5835b7 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -12,6 +12,7 @@ public class Server { public static class Method { public static final String VERIFY = URL_TANGEM + "verify"; public static final String VERIFY_AND_GET_ARTWORK = URL_TANGEM + "verify-and-get-artwork"; + public static final String ARTWORK = URL_TANGEM + "artwork"; } } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiHelper.java b/app/src/main/java/com/tangem/data/network/ServerApiHelper.java index d661a152eb..6446705aad 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiHelper.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiHelper.java @@ -194,47 +194,47 @@ public class ServerApiHelper { * HTTP * Card verify */ - private CardVerifyListener cardVerifyListener; - - public interface CardVerifyListener { - void onCardVerify(CardVerifyResponse cardVerifyResponse); - } - - public void setCardVerify(CardVerifyListener listener) { - cardVerifyListener = listener; - } - - public void cardVerify(TangemCard card) { - Retrofit retrofit = new Retrofit.Builder() - .baseUrl(Server.ApiTangem.URL_TANGEM) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - - TangemApi tangemApi = retrofit.create(TangemApi.class); - - CardVerify[] requests = new CardVerify[1]; - requests[0] = new CardVerify(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey())); - - CardVerifyBody cardVerifyBody = new CardVerifyBody(requests); - - Call call = tangemApi.getCardVerify(cardVerifyBody); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.code() == 200) { - CardVerifyResponse cardVerifyResponse = response.body(); - cardVerifyListener.onCardVerify(cardVerifyResponse); - Log.i(TAG, "cardVerify onResponse " + response.code()); - } else - Log.e(TAG, "cardVerify onResponse " + response.code()); - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.e(TAG, "cardVerify onFailure " + t.getMessage()); - } - }); - } +// private CardVerifyListener cardVerifyListener; +// +// public interface CardVerifyListener { +// void onCardVerify(CardVerifyResponse cardVerifyResponse); +// } +// +// public void setCardVerify(CardVerifyListener listener) { +// cardVerifyListener = listener; +// } +// +// public void cardVerify(TangemCard card) { +// Retrofit retrofit = new Retrofit.Builder() +// .baseUrl(Server.ApiTangem.URL_TANGEM) +// .addConverterFactory(GsonConverterFactory.create()) +// .build(); +// +// TangemApi tangemApi = retrofit.create(TangemApi.class); +// +// CardVerify[] requests = new CardVerify[1]; +// requests[0] = new CardVerify(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey())); +// +// CardVerifyBody cardVerifyBody = new CardVerifyBody(requests); +// +// Call call = tangemApi.getCardVerify(cardVerifyBody); +// call.enqueue(new Callback() { +// @Override +// public void onResponse(@NonNull Call call, @NonNull Response response) { +// if (response.code() == 200) { +// CardVerifyResponse cardVerifyResponse = response.body(); +// cardVerifyListener.onCardVerify(cardVerifyResponse); +// Log.i(TAG, "cardVerify onResponse " + response.code()); +// } else +// Log.e(TAG, "cardVerify onResponse " + response.code()); +// } +// +// @Override +// public void onFailure(@NonNull Call call, @NonNull Throwable t) { +// Log.e(TAG, "cardVerify onFailure " + t.getMessage()); +// } +// }); +// } /** @@ -259,8 +259,8 @@ public class ServerApiHelper { TangemApi tangemApi = retrofit.create(TangemApi.class); - List requests = new ArrayList<>(); - requests.add(new CardVerifyAndGetArtwork.Request.RequestItem(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey()),null)); + List requests = new ArrayList<>(); + requests.add(new CardVerifyAndGetArtwork.Request.Item(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey()))); CardVerifyAndGetArtwork.Request requestBody = new CardVerifyAndGetArtwork.Request(requests); @@ -283,6 +283,62 @@ public class ServerApiHelper { }); } + /** + * HTTP + * Last version request from GitHub + */ + private ArtworkListener artworkListener; + + public interface ArtworkListener { + void onArtwork(InputStream inputStream); + } + + public void setArtworkListener(ArtworkListener listener) { + artworkListener = listener; + } + + public void requestArtwork() { + + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); + logging.setLevel(HttpLoggingInterceptor.Level.BODY); + + OkHttpClient httpClient = new OkHttpClient.Builder(). + addInterceptor(logging). +// addInterceptor(new AuthorizationInterceptor()). + build(); + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(Server.ApiUpdateVersion.URL_UPDATE_VERSION) + .addConverterFactory(GsonConverterFactory.create()) + .client(httpClient) + .build(); + + UpdateVersionApi updateVersionApi = retrofit.create(UpdateVersionApi.class); + + Call call = updateVersionApi.getLastVersion(); + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + Log.i(TAG, "lastVersion onResponse " + response.code()); + if (response.code() == 200) { + String stringResponse; + try { + stringResponse = response.body().string(); + lastVersionListener.onLastVersion(stringResponse); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "lastVersion onFailure " + t.getMessage()); + } + }); + + } + + /** * HTTP diff --git a/app/src/main/java/com/tangem/data/network/ServerURL.java b/app/src/main/java/com/tangem/data/network/ServerURL.java index d34a88a20b..0f7627ec4f 100644 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ b/app/src/main/java/com/tangem/data/network/ServerURL.java @@ -1,7 +1,7 @@ package com.tangem.data.network; public class ServerURL { - public static final String API_TANGEM = "https://verify.tangem.com/"; + public static final String API_TANGEM = "https://tangem-services.appspot.com"; //TODO: "https://verify.tangem.com/"; public static final String API_COINMARKETCAP = "https://api.coinmarketcap.com/"; public static final String API_INFURA = "https://mainnet.infura.io/"; public static final String API_ESTIMATEFEE = " https://estimatefee.com/"; diff --git a/app/src/main/java/com/tangem/data/network/TangemApi.java b/app/src/main/java/com/tangem/data/network/TangemApi.java index e6ac8af358..38e658e49b 100644 --- a/app/src/main/java/com/tangem/data/network/TangemApi.java +++ b/app/src/main/java/com/tangem/data/network/TangemApi.java @@ -4,10 +4,13 @@ import com.tangem.data.network.model.CardVerifyAndGetArtwork; import com.tangem.data.network.model.CardVerifyBody; import com.tangem.data.network.model.CardVerifyResponse; +import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.http.Body; +import retrofit2.http.GET; import retrofit2.http.Headers; import retrofit2.http.POST; +import retrofit2.http.Query; public interface TangemApi { @Headers("Content-Type: application/json") @@ -18,4 +21,8 @@ public interface TangemApi { @POST(Server.ApiTangem.Method.VERIFY_AND_GET_ARTWORK) Call getCardVerifyAndGetArtwork(@Body CardVerifyAndGetArtwork.Request requestBody); + @GET(Server.ApiTangem.Method.ARTWORK) + Call getArtwork(@Query("artworkId") String artworkId, @Query("CID") String CID, @Query("publicKey") String publicKey); + + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/CardVerifyAndGetArtwork.kt b/app/src/main/java/com/tangem/data/network/model/CardVerifyAndGetArtwork.kt index 29d46acd37..7c162aae2b 100644 --- a/app/src/main/java/com/tangem/data/network/model/CardVerifyAndGetArtwork.kt +++ b/app/src/main/java/com/tangem/data/network/model/CardVerifyAndGetArtwork.kt @@ -1,52 +1,46 @@ package com.tangem.data.network.model -import com.google.gson.annotations.SerializedName +import android.os.Build +import java.text.SimpleDateFormat +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter +import java.util.* -class CardVerifyAndGetArtwork - { - - data class Request( - @SerializedName("requests") - var requests: List? = null - ) { - - data class RequestItem( - @SerializedName("CID") - var CID: String = "", - - @SerializedName("publicKey") - var publicKey: String = "", - - @SerializedName("artwork") - var artwork: String = "" - ) - - } - - data class Response( - @SerializedName("results") - var results: List? = null +class CardVerifyAndGetArtwork { + data class Request( + var requests: List? = null ) { - data class ResultItem( - @SerializedName("error") - var error: String = "", - - @SerializedName("CID") + data class Item( var CID: String = "", - - @SerializedName("passed") - var passed: Boolean = false, - - @SerializedName("artwork") - var artwork: String = "", - - @SerializedName("batch") - var batch: String = "", - - @SerializedName("update_date") - var update_date: String = "" + var publicKey: String = "" ) + + } + + data class Response( + var results: List? = null + ) { + + data class Item( + var error: String = "", + var CID: String = "", + var passed: Boolean = false, + var batch: String = "", + var artworkId: String = "", + var artworkHash: String = "", + var update_date: String = "" + ) { + fun getUpdateDate(): Instant? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return LocalDateTime.parse(update_date, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toInstant(ZoneOffset.UTC) + }else{ + return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US).parse(update_date).toInstant() + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/ArtworksStorage.kt b/app/src/main/java/com/tangem/domain/wallet/ArtworksStorage.kt new file mode 100644 index 0000000000..8f1931adc5 --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/ArtworksStorage.kt @@ -0,0 +1,193 @@ +package com.tangem.domain.wallet + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import java.io.File +import kotlin.collections.HashMap +import android.graphics.BitmapFactory +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.tangem.util.Util +import com.tangem.wallet.R +import java.io.InputStream +import java.lang.Exception +import java.time.Instant + + +data class ArtworksStorage( + val context: Context +) { + private val cache: FileCache = FileCache(context) + private lateinit var artworks: HashMap + private lateinit var batches: HashMap + private var artworksFile: File = File(context.filesDir, "artworks.json") + private var batchesFile: File = File(context.filesDir, "batches.json") + + init { + if (artworksFile.exists()) { + artworksFile.bufferedReader().use { artworks = Gson().fromJson(it, object : TypeToken>() {}.type) } + } else { + artworks = HashMap() + putResourceArtworkToCatalog("card_default", false) + putResourceArtworkToCatalog("card_btc_001", false) + putResourceArtworkToCatalog("card_btc_005", true) + } + if (batchesFile.exists()) { + batchesFile.bufferedReader().use { batches = Gson().fromJson(it, object : TypeToken>() {}.type) } + } else { + batches = HashMap() + + putBatchToCatalog("0004", "card_btc_001", false) + putBatchToCatalog("0006", "card_btc_001", false) + putBatchToCatalog("0010", "card_btc_001", false) + + putBatchToCatalog("0005", "card_btc_005", false) + putBatchToCatalog("0007", "card_btc_005", false) + putBatchToCatalog("0011", "card_btc_005", true) + +// "0012": card_seed; +// "0013": card_btc_hk_s; +// "0014": card_btc_0014 +// "0015": card_btc_000; +// "0016": card_eth000; +// "0017": card_qlear200; +// "0019": card_cle100; +// "001A": card_btc_001a; +// "001B": card_btc_001b; +// "001C": card_btc_001c; +// "001D": card_eth_001d; + + + } + } + + fun checkNeedUpdateArtwork(batch: String, artworkId: String, artworkHash: String, updateDate: Instant?): Boolean { + val batchInfo = batches[batch] + if (batchInfo == null) { + putBatchToCatalog(batch, artworkId) + return true + } + if (batchInfo.artworkId != artworkId) { + putBatchToCatalog(batch, artworkId) + } + val artwork = artworks[artworkId] ?: return true + + if (artwork.hash == artworkHash) return false + + if (updateDate == null) return false + + return artwork.updateDate == null || artwork.updateDate < updateDate + } + + fun updateArtwork(artworkId: String, inputStream: InputStream, updateDate: Instant) { + val data = inputStream.readBytes() + cache.saveBitmap(artworkId, data) + putArtworkToCatalog(artworkId, data, updateDate, true) + } + + private fun putBatchToCatalog(batch: String, artworkId: String, forceSave: Boolean = true) { + batches[batch] = BatchInfo(artworkId) + if (forceSave) { + val sBatches = Gson().toJson(batches) + batchesFile.bufferedWriter().use { it.write(sBatches) } + } + } + + @SuppressLint("ResourceType") + private fun putResourceArtworkToCatalog(artworkId: String, forceSave: Boolean) { + context.resources.openRawResource(R.drawable.card_default).use { putArtworkToCatalog(artworkId, it.readBytes(), null, forceSave) } + } + + private fun putArtworkToCatalog(artworkId: String, data: ByteArray, instant: Instant?, forceSave: Boolean = true) { + val artworkInfo = ArtworkInfo( + false, Util.bytesToHex(Util.calculateSHA256(data)), instant + ) + artworks[artworkId] = artworkInfo + if (forceSave) { + val sArtworks = Gson().toJson(artworks) + artworksFile.bufferedWriter().use { it.write(sArtworks) } + } + } + + private fun getArtworkBitmap(artworkId: String): Bitmap? { + val info = artworks[artworkId] ?: return null + return if (info.isResource) { + val resID = context.resources.getIdentifier(artworkId, "drawable", context.packageName) + if (resID == 0) return null + BitmapFactory.decodeResource(context.resources, resID) + } else { + cache.getBitmap(artworkId) + } + } + + private fun getDefaultArtworkBitmap(): Bitmap { + val info = artworks[defaultArtworkId] + var bitmap: Bitmap? = null + if (info != null && !info.isResource) { + bitmap = cache.getBitmap(defaultArtworkId) + } + if (bitmap == null) return BitmapFactory.decodeResource(context.resources, R.drawable.card_default) + return bitmap + } + + fun getBatchBitmap(batch: String): Bitmap { + val batchInfo = batches[batch] ?: return getDefaultArtworkBitmap() + return getArtworkBitmap(batchInfo.artworkId) ?: return getDefaultArtworkBitmap() + } + + data class ArtworkInfo( + val isResource: Boolean, + val hash: String, + val updateDate: Instant? + ) + + data class BatchInfo( + val artworkId: String + ) + + inner class FileCache(context: Context) { + + private var cacheDir: File? = null + + init { + cacheDir = File(context.filesDir, "artworks") + //Find the dir to save cached images +// if (android.os.Environment.getExternalStorageState() == android.os.Environment.MEDIA_MOUNTED) +// cacheDir = File(android.os.Environment.getExternalStorageDirectory(), "artworks_cache") +// else +// cacheDir = context.getCacheDir() + if (!cacheDir!!.exists()) + cacheDir!!.mkdirs() + } + + private fun getFile(artworkId: String): File { + return File(cacheDir, "$artworkId.png") + } + + fun getBitmap(artworkId: String): Bitmap? { + return try { + BitmapFactory.decodeFile(getFile(artworkId).absolutePath) + } catch (E: Exception) { + E.printStackTrace() + null + } + } + + fun saveBitmap(artworkId: String, data: ByteArray) { + val file = getFile(artworkId) + file.writeBytes(data) + } + + fun clear() { + val files = cacheDir!!.listFiles() ?: return + for (f in files) + f.delete() + } + + } + + companion object { + const val defaultArtworkId = "" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt index 85950430c6..26f7f31acb 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import android.widget.Toast import com.google.zxing.WriterException import com.tangem.data.network.ServerApiHelper +import com.tangem.data.network.model.CardVerifyAndGetArtwork import com.tangem.data.network.model.InfuraResponse import com.tangem.data.network.request.ElectrumRequest import com.tangem.data.network.request.InfuraRequest @@ -79,6 +80,8 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific private var timerRepeatRefresh: Timer? = null + private val artworksStorage: ArtworksStorage = ArtworksStorage(context!!) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) nfcManager = NfcManager(activity, this) @@ -105,7 +108,8 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific if (card!!.blockchain == Blockchain.Token) tvBalance.setSingleLine(false) - ivTangemCard.setImageResource(card!!.cardImageResource) + //ivTangemCard.setImageResource(card!!.cardImageResource) + ivTangemCard.setImageBitmap(artworksStorage.getBatchBitmap(card!!.batch)) val engine = CoinEngineFactory.create(card!!.blockchain) val visibleFlag = engine?.inOutPutVisible() ?: true @@ -215,13 +219,25 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific } // request card verify listener - serverApiHelper!!.setCardVerify { +// serverApiHelper!!.setCardVerify { +// card!!.isOnlineVerified = it.results!![0].passed +// srlLoadedWallet!!.isRefreshing = false +// +//// Log.i(TAG, "setCardVerify " + it.results!![0].passed) +// } + serverApiHelper!!.setCardVerifyAndGetArtworkListener { card!!.isOnlineVerified = it.results!![0].passed srlLoadedWallet!!.isRefreshing = false + val result=it.results!![0] + if( artworksStorage.checkNeedUpdateArtwork(result.batch, result.artworkId, result.artworkHash, result.getUpdateDate() ) ) + { + + } // Log.i(TAG, "setCardVerify " + it.results!![0].passed) } + // request rate info listener serverApiHelper!!.setRateInfoData { val rate = it.priceUsd.toFloat() @@ -343,7 +359,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific private fun requestCardVerify() { if ((card!!.isOnlineVerified == null || !card!!.isOnlineVerified)) - serverApiHelper!!.cardVerify(card) + serverApiHelper!!.cardVerifyAndGetArtwork(card) //serverApiHelper!!.cardVerify(card) } private fun requestRateInfo(cryptoId: String) {