diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/CoinViewModel.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/CoinViewModel.kt new file mode 100644 index 00000000..634f2f4e --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/CoinViewModel.kt @@ -0,0 +1,44 @@ +package com.example.walletapp + + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.State +import com.example.walletapp.data.repository.CoinRepository + +class CoinViewModel : ViewModel() { + private val repository = CoinRepository() + + private val _prices = mutableStateOf>(emptyMap()) // Token -> USD price + val prices: State> = _prices + + private val _errorMessage = mutableStateOf("") + val errorMessage: State = _errorMessage + + fun getTokenPrices(ids: String, + vsCurrencies: String) { + viewModelScope.launch { + try { + // Fetch prices for starknet and bitcoin in USD + val response = repository.getTokenPrices(ids,vsCurrencies) + if (response.isSuccessful) { + response.body()?.let { priceMap -> + val parsedPrices = mutableMapOf() + priceMap.forEach { (token, currencyMap) -> + parsedPrices[token] = currencyMap["usd"] ?: 0.0 + } + _prices.value = parsedPrices + } ?: run { + _errorMessage.value = "No data available." + } + } else { + _errorMessage.value = "Error: ${response.code()} ${response.message()}" + } + } catch (e: Exception) { + _errorMessage.value = "Exception: ${e.localizedMessage}" + } + } + } +} diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/WalletActivity.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/WalletActivity.kt index 74d6621c..4ae7194b 100644 --- a/wallet_app/android/app/src/main/java/com/example/walletapp/WalletActivity.kt +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/WalletActivity.kt @@ -4,6 +4,7 @@ import StarknetClient import android.app.Activity import android.content.Intent import android.os.Bundle +import android.service.quickaccesswallet.WalletCard import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -51,11 +52,13 @@ import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.* import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.draw.clip import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties +import androidx.lifecycle.viewmodel.compose.viewModel import com.swmansion.starknet.data.types.Felt import com.swmansion.starknet.provider.exceptions.RpcRequestFailedException import kotlinx.coroutines.Dispatchers @@ -93,6 +96,19 @@ class WalletActivity : ComponentActivity() { val starknetClient = StarknetClient(BuildConfig.RPC_URL) var balance by remember { mutableStateOf("") } + val coinViewModel: CoinViewModel = viewModel() + + val coinsPrices : HashMap = rememberSaveable { + hashMapOf() + } + + val prices by coinViewModel.prices + val errorMessage by coinViewModel.errorMessage + + LaunchedEffect(Unit) { + coinViewModel.getTokenPrices(ids = "starknet,ethereum", vsCurrencies = "usd") // Fetch starknet and bitcoin prices in USD + } + LaunchedEffect (Unit){ try { // Get the balance of the account @@ -106,6 +122,20 @@ class WalletActivity : ComponentActivity() { withContext(Dispatchers.Main) { Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() } } } + if (errorMessage.isNotEmpty()) { + Text( + text = errorMessage, + color = MaterialTheme.colors.error, + modifier = Modifier.padding(16.dp) + ) + } else { + Column(modifier = Modifier.padding(16.dp)) { + prices.forEach { (token, price) -> + coinsPrices[token] = price.toDoubleWithTwoDecimal() + Spacer(modifier = Modifier.height(8.dp)) + } + } + } Column( modifier = Modifier @@ -151,7 +181,7 @@ class WalletActivity : ComponentActivity() { WalletCard( icon = painterResource(id = R.drawable.ic_ethereum), - amount = "$11,625.7", + amount = coinsPrices["ethereum"]?.let { "$ $it" } ?: "", exchange = balance, type = "ETH" ) @@ -159,7 +189,7 @@ class WalletActivity : ComponentActivity() { // TOOD(#82): load actual balance WalletCard( icon = painterResource(id = R.drawable.token2), - amount = "$1.78", + amount = coinsPrices["starknet"]?.let { "$ $it" } ?: "", exchange ="4.44", type = "STRK" ) @@ -225,6 +255,11 @@ fun BigDecimal.toDoubleWithTwoDecimal(): String { val decimalFormat = DecimalFormat("#.00") return decimalFormat.format(this.toDouble()) } +fun Double.toDoubleWithTwoDecimal(): String { + val decimalFormat = DecimalFormat("#.00") + val formattedValue = decimalFormat.format(this) + return if (this < 1) "0$formattedValue" else formattedValue +} @Composable fun WalletCard(icon: Painter, amount: String, exchange: String, type: String) { @@ -252,6 +287,8 @@ fun BigDecimal.toDoubleWithTwoDecimal(): String { color = Color.White, fontSize = 18.sp ) + + Row { Text( text = exchange, diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/data/datasource/CoinGeckoApi.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/data/datasource/CoinGeckoApi.kt new file mode 100644 index 00000000..857b647d --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/data/datasource/CoinGeckoApi.kt @@ -0,0 +1,18 @@ +package com.example.walletapp.data.datasource + +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.Query + +interface CoinGeckoApi { + @Headers( + "accept: application/json", + "x-cg-demo-api-key: CG-mRdWfNFoZnKVan4GNdTrhZjL" + ) + @GET("simple/price") + suspend fun getTokenPrices( + @Query("ids") ids: String, // Comma-separated token IDs + @Query("vs_currencies") vsCurrencies: String // Comma-separated currency codes + ): Response>> +} diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/data/repository/CoinRepository.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/data/repository/CoinRepository.kt new file mode 100644 index 00000000..94378f74 --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/data/repository/CoinRepository.kt @@ -0,0 +1,17 @@ +package com.example.walletapp.data.repository + +import com.example.walletapp.di.RetrofitClient +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import retrofit2.Response + +class CoinRepository { + suspend fun getTokenPrices( + ids: String, + vsCurrencies: String + ): Response>> { + return withContext(Dispatchers.IO) { + RetrofitClient.apiService.getTokenPrices(ids, vsCurrencies) + } + } +} \ No newline at end of file diff --git a/wallet_app/android/app/src/main/java/com/example/walletapp/di/RetrofitClient.kt b/wallet_app/android/app/src/main/java/com/example/walletapp/di/RetrofitClient.kt new file mode 100644 index 00000000..1a5078e3 --- /dev/null +++ b/wallet_app/android/app/src/main/java/com/example/walletapp/di/RetrofitClient.kt @@ -0,0 +1,18 @@ +package com.example.walletapp.di + +import com.example.walletapp.data.datasource.CoinGeckoApi + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object RetrofitClient { + private const val BASE_URL = "https://api.coingecko.com/api/v3/" + + val apiService: CoinGeckoApi by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(CoinGeckoApi::class.java) + } +}