Tugas Pertemuan 11 Pemrograman Perangkat Bergerak B

Nama    : Fadaukas Daffa Tajuddin

NRP      : 5025231149

Kelas    : PPB (B)


Membuat Market Siswa


MarketSiswa adalah aplikasi marketplace berbasis Android yang dirancang khusus untuk lingkungan sekolah. Aplikasi ini memungkinkan siswa untuk memasarkan produk (seperti makanan ringan atau kerajinan tangan) kepada sesama siswa dalam platform yang aman dan modern

Fitur Utama

Aplikasi ini telah mengimplementasikan siklus CRUD (Create, Read, Update, Delete) lengkap dengan antarmuka yang responsif:
  • Listing Produk (Read): Menampilkan daftar barang jualan dengan kartu (Card) yang elegan.
  • Pencarian (Filter): Memungkinkan pengguna mencari produk berdasarkan nama atau deskripsi secara real-time.
  • Tambah Produk (Create): Form untuk memasukkan produk baru dengan simulasi loading state.
  • Edit Produk (Update): Memperbarui informasi produk yang sudah ada tanpa kehilangan data.
  • Hapus Produk (Delete): Menghapus listing dengan dialog konfirmasi untuk mencegah ketidaksengajaan.
  • Manajemen Profil: Antarmuka profil pengguna untuk identitas penjual.

Implementasi Code

package com.example.marketsiswa

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

// 1. Data Model
data class Product(
val id: Long = System.currentTimeMillis(),
val name: String,
val price: String,
val description: String
)

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MarketplaceTheme {
MainScreen()
}
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
var currentScreen by remember { mutableStateOf("home") }
val productList = remember { mutableStateListOf<Product>() }
var editingProduct by remember { mutableStateOf<Product?>(null) }

val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()

LaunchedEffect(Unit) {
if (productList.isEmpty()) {
productList.add(Product(name = "Brownies Lumer", price = "15000", description = "Cokelat melimpah, lumer di mulut. Cocok untuk cemilan sore."))
productList.add(Product(name = "Kaos Custom", price = "85000", description = "Bahan cotton combed 30s, adem dan nyaman dipakai harian."))
}
}

Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
text = when (currentScreen) {
"add" -> "Jual Produk Baru"
"edit" -> "Edit Produk"
"profile" -> "Profil Saya"
else -> "MarketSiswa"
},
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleLarge
)
},
navigationIcon = {
if (currentScreen != "home") {
IconButton(onClick = {
currentScreen = "home"
editingProduct = null
}) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Kembali")
}
}
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface
)
)
},
bottomBar = {
NavigationBar {
NavigationBarItem(
selected = currentScreen == "home" || currentScreen == "add" || currentScreen == "edit",
onClick = { currentScreen = "home" },
label = { Text("Beranda") },
icon = { Icon(Icons.Default.Home, contentDescription = null) }
)
NavigationBarItem(
selected = currentScreen == "profile",
onClick = { currentScreen = "profile" },
label = { Text("Profil") },
icon = { Icon(Icons.Default.Person, contentDescription = null) }
)
}
},
floatingActionButton = {
if (currentScreen == "home") {
ExtendedFloatingActionButton(
onClick = { currentScreen = "add" },
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary,
shape = RoundedCornerShape(16.dp)
) {
Icon(Icons.Default.Add, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text("Mulai Jual")
}
}
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
when (currentScreen) {
"home" -> HomeScreen(
products = productList,
onDelete = { product ->
productList.remove(product)
scope.launch {
snackbarHostState.showSnackbar("Produk '${product.name}' dihapus")
}
},
onEdit = { product ->
editingProduct = product
currentScreen = "edit"
}
)
"add" -> ProductActionScreen(
title = "Tambah Produk",
onAction = { newProduct ->
productList.add(0, newProduct)
scope.launch {
currentScreen = "home"
snackbarHostState.showSnackbar("Produk berhasil dipasang!")
}
}
)
"edit" -> ProductActionScreen(
title = "Update Produk",
initialProduct = editingProduct,
onAction = { updatedProduct ->
val index = productList.indexOfFirst { it.id == updatedProduct.id }
if (index != -1) {
productList[index] = updatedProduct
}
scope.launch {
currentScreen = "home"
editingProduct = null
snackbarHostState.showSnackbar("Perubahan disimpan")
}
}
)
"profile" -> ProfileScreen()
}
}
}
}

// --- UI COMPONENTS ---

@Composable
fun HomeScreen(
products: List<Product>,
onDelete: (Product) -> Unit,
onEdit: (Product) -> Unit
) {
var searchQuery by remember { mutableStateOf("") }
val filteredProducts = products.filter {
it.name.contains(searchQuery, ignoreCase = true) ||
it.description.contains(searchQuery, ignoreCase = true)
}

Column(modifier = Modifier.fillMaxSize()) {
OutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
placeholder = { Text("Cari jajanan atau barang...") },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
trailingIcon = {
if (searchQuery.isNotEmpty()) {
IconButton(onClick = { searchQuery = "" }) {
Icon(Icons.Default.Close, contentDescription = null)
}
}
},
shape = RoundedCornerShape(12.dp),
singleLine = true,
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
)
)

if (filteredProducts.isEmpty()) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
Icons.Default.Info,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = Color.LightGray
)
Text("Tidak ada produk ditemukan", color = Color.Gray)
}
}
} else {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(start = 16.dp, end = 16.dp, bottom = 80.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
item {
Text(
"Halo, Siswa!",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.ExtraBold
)
Text(
"Dukung usaha teman sekolahmu hari ini.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(8.dp))
}
items(filteredProducts, key = { it.id }) { product ->
ProductCard(
product = product,
onDelete = { onDelete(product) },
onEdit = { onEdit(product) }
)
}
}
}
}
}

@Composable
fun ProductCard(
product: Product,
onDelete: () -> Unit,
onEdit: () -> Unit
) {
var showDeleteConfirm by remember { mutableStateOf(false) }

if (showDeleteConfirm) {
AlertDialog(
onDismissRequest = { showDeleteConfirm = false },
title = { Text("Hapus Produk?") },
text = { Text("Apakah kamu yakin ingin menghapus '${product.name}' dari listing?") },
confirmButton = {
TextButton(onClick = {
onDelete()
showDeleteConfirm = false
}) { Text("Hapus", color = MaterialTheme.colorScheme.error) }
},
dismissButton = {
TextButton(onClick = { showDeleteConfirm = false }) { Text("Batal") }
}
)
}

ElevatedCard(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.elevatedCardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top
) {
Column(modifier = Modifier.weight(1f)) {
Text(
product.name,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold
)
Text(
"Rp ${product.price}",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.ExtraBold
)
}

Row {
IconButton(onClick = onEdit) {
Icon(Icons.Default.Edit, contentDescription = "Edit", tint = MaterialTheme.colorScheme.primary)
}
IconButton(onClick = { showDeleteConfirm = true }) {
Icon(Icons.Default.Delete, contentDescription = "Hapus", tint = MaterialTheme.colorScheme.error)
}
}
}

Spacer(Modifier.height(8.dp))
Text(
product.description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 3
)

Spacer(Modifier.height(12.dp))
Button(
onClick = { /* Hubungi Penjual logic */ },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
contentPadding = PaddingValues(vertical = 8.dp)
) {
Icon(
Icons.Default.ShoppingCart,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Spacer(Modifier.width(8.dp))
Text("Beli Sekarang")
}
}
}
}

@Composable
fun ProductActionScreen(
title: String,
initialProduct: Product? = null,
onAction: (Product) -> Unit
) {
var name by remember { mutableStateOf(initialProduct?.name ?: "") }
var price by remember { mutableStateOf(initialProduct?.price ?: "") }
var desc by remember { mutableStateOf(initialProduct?.description ?: "") }
var isLoading by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()

Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(title, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)

OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Nama Produk") },
placeholder = { Text("Contoh: Risol Mayo") },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp)
)

OutlinedTextField(
value = price,
onValueChange = { price = it },
label = { Text("Harga (Rp)") },
placeholder = { Text("Contoh: 5000") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp)
)

OutlinedTextField(
value = desc,
onValueChange = { desc = it },
label = { Text("Deskripsi") },
placeholder = { Text("Jelaskan keunggulan produkmu...") },
modifier = Modifier.fillMaxWidth(),
minLines = 4,
shape = RoundedCornerShape(12.dp)
)

Spacer(modifier = Modifier.weight(1f))

Button(
onClick = {
isLoading = true
scope.launch {
delay(800)
onAction(
Product(
id = initialProduct?.id ?: System.currentTimeMillis(),
name = name,
price = price,
description = desc
)
)
isLoading = false
}
},
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
enabled = name.isNotBlank() && price.isNotBlank() && !isLoading,
shape = RoundedCornerShape(12.dp)
) {
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.size(24.dp), color = MaterialTheme.colorScheme.onPrimary)
} else {
Text(if (initialProduct == null) "Pasang Produk" else "Simpan Perubahan", fontWeight = FontWeight.Bold)
}
}
}
}

@Composable
fun ProfileScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(Modifier.height(40.dp))

Box(contentAlignment = Alignment.BottomEnd) {
Icon(
Icons.Default.AccountCircle,
contentDescription = null,
modifier = Modifier.size(120.dp),
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f)
)
IconButton(
onClick = { },
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primary)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = Color.White
)
}
}

Spacer(Modifier.height(16.dp))
Text("John Siswa", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text("XII Rekayasa Perangkat Lunak 1", style = MaterialTheme.typography.bodyLarge, color = Color.Gray)

Spacer(Modifier.height(32.dp))

ProfileMenuItem(icon = Icons.AutoMirrored.Filled.List, title = "Jualanku")
ProfileMenuItem(icon = Icons.Default.Favorite, title = "Favorit")
ProfileMenuItem(icon = Icons.Default.Settings, title = "Pengaturan")

Spacer(Modifier.weight(1f))

TextButton(onClick = { }) {
Text("Keluar", color = MaterialTheme.colorScheme.error)
}
}
}

@Composable
fun ProfileMenuItem(icon: ImageVector, title: String) {
Surface(
onClick = { },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
Spacer(Modifier.width(16.dp))
Text(title, style = MaterialTheme.typography.bodyLarge)
Spacer(Modifier.weight(1f))
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowRight,
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = Color.LightGray
)
}
}
}

@Composable
fun MarketplaceTheme(content: @Composable () -> Unit) {
val colorScheme = lightColorScheme(
primary = Color(0xFF6750A4),
onPrimary = Color.White,
primaryContainer = Color(0xFFEADDFF),
onPrimaryContainer = Color(0xFF21005D),
secondary = Color(0xFF625B71),
surface = Color(0xFFFFFBFE),
onSurface = Color(0xFF1C1B1F),
surfaceVariant = Color(0xFFE7E0EC),
onSurfaceVariant = Color(0xFF49454F)
)

MaterialTheme(
colorScheme = colorScheme,
typography = Typography(),
content = content
)
}

Preview App





Komentar

Postingan populer dari blog ini

Tugas Pertemuan 2 Pemrograman Perangkat Bergerak B

Tugas Pertemuan 3 Pemrograman Perangkat Bergerak B

Tugas Pertemuan 1 Pemrograman Perangkat Bergerak B