Tugas 6 Currency Converter
https://github.com/ilomimo/currency-converter
Pendahuluan
Aplikasi “Convert Currency” ini dibuat dengan framework modern Jetpack Compose yang menggantikan pendekatan imperative Android lama dengan pendekatan deklaratif. File utama MainActivity.kt menjadi pusat kendali tampilan sekaligus interaksi logika sederhana seperti input, pemilihan mata uang, dan konversi.
Penjelasan Arsitektur Kode
package com.example.convert_currency
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.convert_currency.ui.theme.ConvertcurrencyTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ConvertcurrencyTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
CurrencyConverterApp()
}
}
}
}
}
Kode di atas menjalankan CurrencyConverterApp() di dalam tema aplikasi. Gunakan Surface untuk menyelimuti seluruh UI.
UI Konversi Mata Uang dan Logika
@Composable
fun CurrencyConverterApp() {
val currencies = listOf("USD", "EUR", "IDR", "JPY", "GBP")
var amount by remember { mutableStateOf("") }
var fromCurrency by remember { mutableStateOf("USD") }
var toCurrency by remember { mutableStateOf("IDR") }
var result by remember { mutableStateOf("") }
val exchangeRates = mapOf(
Pair("USD", "IDR") to 16000.0,
Pair("IDR", "USD") to 0.000062,
Pair("USD", "EUR") to 0.92,
Pair("EUR", "USD") to 1.09,
Pair("USD", "JPY") to 155.0,
Pair("JPY", "USD") to 0.0064,
Pair("EUR", "IDR") to 17300.0,
Pair("IDR", "EUR") to 0.000057,
Pair("GBP", "IDR") to 20000.0,
Pair("IDR", "GBP") to 0.000050,
)
Di atas adalah setup awal: daftar mata uang, state yang menyimpan input user, dan map statis berisi nilai tukar.
Layout dan Interaksi
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF9FAFB))
.padding(horizontal = 24.dp)
.padding(top = 40.dp, bottom = 24.dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("CurrencyX", fontSize = 36.sp, fontWeight = FontWeight.ExtraBold, color = Color(0xFF111827))
Spacer(modifier = Modifier.height(8.dp))
Text("Trusted global exchange tool", fontSize = 16.sp, color = Color.Black)
Spacer(modifier = Modifier.height(32.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.White, shape = RoundedCornerShape(12.dp))
.border(1.dp, Color(0xFFDEE2E6), shape = RoundedCornerShape(12.dp))
.padding(20.dp),
contentAlignment = Alignment.Center
) {
Text("Live exchange rates. No fluff, just facts.", fontSize = 14.sp, color = Color.Black, textAlign = TextAlign.Center)
}
Spacer(modifier = Modifier.height(32.dp))
OutlinedTextField(
value = amount,
onValueChange = { amount = it },
label = { Text("Enter amount") },
textStyle = TextStyle(fontSize = 18.sp),
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.background(Color.White, shape = RoundedCornerShape(12.dp))
)
Spacer(modifier = Modifier.height(16.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
CurrencyDropdown("From", currencies, fromCurrency, { fromCurrency = it }, Modifier.weight(1f))
CurrencyDropdown("To", currencies, toCurrency, { toCurrency = it }, Modifier.weight(1f))
}
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Button(
onClick = {
val key = Pair(fromCurrency, toCurrency)
val rate = exchangeRates[key]
result = if (rate != null && amount.toDoubleOrNull() != null) {
val converted = amount.toDouble() * rate
"%.2f $toCurrency".format(converted)
} else {
"Invalid conversion"
}
},
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF007E6A), contentColor = Color.White)
) {
Text("Convert", fontSize = 18.sp)
}
OutlinedButton(
onClick = {
val temp = fromCurrency
fromCurrency = toCurrency
toCurrency = temp
},
modifier = Modifier.weight(1f)
) {
Text("Swap", fontSize = 18.sp)
}
}
}
if (result.isNotEmpty()) {
Card(
shape = RoundedCornerShape(16.dp),
modifier = Modifier.fillMaxWidth().padding(top = 24.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.padding(24.dp),
contentAlignment = Alignment.Center
) {
Text("Result: $result", fontSize = 22.sp, fontWeight = FontWeight.SemiBold, color = Color(0xFF007E6A))
}
}
}
}
}
Dropdown Reusable Component
@Composable
fun CurrencyDropdown(label: String, options: List<String>, selected: String, onSelected: (String) -> Unit, modifier: Modifier = Modifier) {
var expanded by remember { mutableStateOf(false) }
Box(
modifier = modifier
.background(Color.White, shape = RoundedCornerShape(10.dp))
.border(1.dp, Color(0xFFBBBBBB), shape = RoundedCornerShape(10.dp))
.clickable { expanded = true }
.padding(horizontal = 16.dp, vertical = 12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("$label: $selected", fontSize = 14.sp, color = Color.Black)
Icon(Icons.Default.ArrowDropDown, contentDescription = null, tint = Color.Black)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
options.forEach {
DropdownMenuItem(
text = { Text(it) },
onClick = {
onSelected(it)
expanded = false
}
)
}
}
}
}
Tambahan
Untuk masuk ke ranah produksi, perlu integrasi API nilai tukar dunia nyata (seperti dari exchangeratesapi.io), sistem validasi input lebih kuat, dan fitur caching/offline.
Comments
Post a Comment