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

Popular posts from this blog

EAS PPB

Tugas Pertemuan 12 - Dessert Clicker

Tugas Pertemuan 13: Membuat Aplikasi Unscramble