Friday, May 17, 2024

ETS PPB I - Aplikasi Pemesanan Tiket

Nama     : Florentino Benedictus

NRP       : 5025201222

Tahun    : 2024

Kelas      : Pemrograman Perangkat Bergerak I

Link Implementasi      :  Link Github

ETS PPB I - Aplikasi Pemesanan Tiket


Pada ETS kelas PPB I diberi tugas untuk mengimplementasikan dan melakukan redesign pada halaman masuk dan fitur yang sering digunakan pada salah satu aplikasi Android pemesanan tiket pada Android Studio menggunakan Jetpack Compose.

Aplikasi yang saya pilih yaitu Cititrans. Cititrans merupakan layanan pemesanan tiket penumpang untuk transportasi antar kota menggunakan kendaraan darat. Fitur yang sering saya gunakan pada aplikasi ini adalah fitur pemesanan tiket, dimana user dapat menentukan tempat keberangkatan, tempat tujuan, tanggal dan waktu keberangkatan yang diinginkan lalu membeli tiket sesuai dengan kriteria yang diinginkan. Uji coba aplikasi menggunakan Pixel Pro 7 API 30 virtual device.

1. Hasil dan Deskripsi Tampilan
Berikut adalah hasil dan deskripsi tampilan aplikasinya:

* Halaman Login (LoginScreen.kt)
Pada halaman ini user dapat mengisi alamat email dan password akunnya untuk melakukan login pada aplikasi. User yang belum terdaftar juga dapat berpindah ke halaman register. Fitur utama pada halaman ini adalah pengisian email dan password (input tidak akan terlihat) user pada form.

* Halaman Register (RegisterScreen.kt)
Pada halaman ini user dapat membuat akun baru yang dapat digunakan untuk mengakses fitur aplikasi. Fitur utama pada halaman ini adalah pengisian email dan password (input tidak akan terlihat) user pada form.

* Halaman Booking/Pencarian Tiket (BookingScreen.kt)
Halaman ini merupakan halaman utama dimana user dapat menginput kota keberangkatan, kota tujuan, tanggal keberangkatan, dan waktu keberangkatan yang akan digunakan sebagai filter pencarian paket kendaraan yang tersedia untuk waktu tersebut. Fitur utama pada halaman ini adalah dropdown opsi kota, date picker untuk penentuan tanggal keberangkatan, dan time picker untuk penentuan waktu keberangkatan.

* Halaman Daftar Tiket (TicketScreen.kt)
Halaman ini berfungsi untuk menampilkan daftar paket yang tersedia dimana user dapat melihat estimasi waktu kedatangan dan harga dari masing-masing paket, kemudian dapat memilih paket yang diinginkan untuk melanjutkan proses pemesanan tiket. Fitur utama pada halaman ini adalah list card yang digenerate menggunakan datasource dan ditampilkan detailnya, juga penggunaan LazyColumn agar list tiket dapat discroll.

* Halaman Pemilihan Tempat Duduk (SeatScreen.kt)

Pada halaman ini user dapat melihat kursi yang masih tersedia dan memilih kursi yang diinginkan pada kendaraan. User juga dapat mengisi nama penumpang selain dirinya sendiri untuk memesankan orang lain. Jika seluruh kriteria sudah sesuai user dapat melakukan transaksi dengan menekan tombol "pesan" untuk membeli tiket. Fitur utama dari halaman ini adalah pemilihan kursi menggunakan radio button dan formulir pengisian nama penumpang.

* Halaman Transaksi (TransactionScreen.kt)
Pada halaman ini akan terdapat notifikasi bahwa transaksi telah berhasil dilakukan. Detail tiket yang dipesan juga akan ditampilkan dan user dapat mendownload tiket tersebut untuk melakukan perjalanan pada waktu keberangkatan. Fitur utama pada halaman ini adalah menampilkan seluruh atribut tiket yang telah dipilih oleh user yang melalui proses passing variabel yang dimulai dari halaman awal login/register.

2. Deskripsi Implementasi

a. Struktur Aplikasi
Berikut adalah struktur directory aplikasi yang dibuat. Pada MainActivity.kt, onCreate akan memanggil fungsi composable AppScreen pada AppScreen.kt yang akan menjadi fungsi router utama dari aplikasi.
Untuk berpindah halaman dan melakukan passing variabel antar halaman, digunakan modul NavHost bawaan dari Android Studio yang dapat digunakan dengan mengimport packagenya. Dalam kasus ini router AppScreen akan berfungsi untuk menangani navigasi antar 6 halaman yaitu LoginScreen.kt, RegisterScreen.kt, BookingScreen.kt, TicketScreen.kt, SeatScreen.kt, dan TransactionScreen.kt. Navigasi dilakukan dengan cara menentukan tampilan (Screen) yang akan terload ketika suatu tombol/perilaku user memnyebabkan routing dari suatu halaman ke halaman lain.
Untuk mempermudah proses navigasi, dibuat file sealed class Screen.kt yang berfungsi untuk menyimpan nickname dari masing-masing screen dan fungsi withArgs yang akan secara otomatis melakukan append variabel yang ingin di-passing pada format seperti URL. Passing argumen akan dilakukan secara terus menerus pada tiap halaman sampai dengan halaman transaksi dengan argumen yang dipassing secara berurutan yaitu: email user, tempat keberangkatan, tempat tujuan, tanggal keberangkatan, waktu keberangkatan, estimasi waktu kedatangan, nomor kursi penumpang, dan nama penumpang.

b. Objek Tiket
Objek suatu tiket hanya terdiri dari 2 variabel yaitu durasi perjalanan dan harga tiket. Hal ini dikarenakan aplikasi tidak menggunakan database, sehingga agar hasil pencarian tiket yang dilakukan user dapat menampilkan banyak tiket maka kota asal, tujuan, dan waktu keberangkatan akan menyesuaikan input filter user.
data.Datasource.kt akan berfungsi untuk menyimpan sampel tiket yang nantinya akan digunakan untuk list tiket pada TicketScreen.kt

c. Fitur-fitur Tambahan

Pada aplikasi ini juga terdapat beberapa fitur tambahan untuk meningkatkan usabilitas dari aplikasi meliputi:
- Dropdown Menu Menggunakan ExposedDropdownMenuBox
- Date Picker (Kalender) Menggunakan DatePickerDialog
- Time Picker Menggunakan TimePickerDialog

- Denah Posisi Penumpang Menggunakan Icon dan Radio Button


Berikut adalah link prototipe dari aplikasi yang dibuat:


Berikut adalah link Google Docs dari aplikasi yang dibuat:

Berikut adalah link implementasi dari aplikasi yang dibuat:

Berikut adalah link demo dari aplikasi yang dibuat:


Referensi:
https://kuliahppb.blogspot.com/2024/05/evaluasi-tengah-semester.html
https://developer.android.com/develop/ui/views/components/pickers
https://www.youtube.com/watch?v=4gUeyNkGE3g
https://medium.com/@german220291/building-a-custom-exposed-dropdown-menu-with-jetpack-compose-d65232535bf2
https://fonts.google.com/icons
Icon dari Google Search





Tuesday, May 14, 2024

Tugas 10 - Siklus Proses Aktivitas Dengan Aplikasi Dessert Clicker

 Nama     : Florentino Benedictus

NRP       : 5025201222

Tahun    : 2024

Kelas      : Pemrograman Perangkat Bergerak I

Link Implementasi      :  MainActivity.kt


Tugas 10 - Siklus Proses Aktivitas Dengan Aplikasi Dessert Clicker

Pada tugas ini, digunakan aplikasi awal Dessert Clicker untuk mengetahui siklus proses aktivitas yang mencakup pembuatan hingga penghancuran suatu aktivitas. Dessert Clicker merupakan aplikasi yang dapat membelikan makanan penutup untuk pengguna ketika layar diklik oleh pengguna. Pengerjaan tugas menggunakan referensi Google Codelab - Tahapan Siklus Proses Aktivitas.

1. Inisiasi Proyek
Pertama-tama buka link https://github.com/google-developer-training/basic-android-kotlin-compose-training-dessert-clicker/tree/starter yang merupakan kode awal aplikasi, kemudian download ZIP dari branch starter. Ekstrak file .zip yang telah terdownload, pindahkan isi folder hasil ekstrak ke lokasi yang diinginkan, lalu buka Android Studio.
Klik Open pada bagian kanan atas lalu pilih folder project yang sudah di-extract lalu pilih OK. Kemudian tunggu hingga building model Gradle selesai.
Selanjutnya run aplikasi, maka akan terlihat tampilan awal aplikasi Dessert Clicker seperti gambar di atas. Aplikasi Dessert Clicker memiliki beberapa bug yang perlu diperbaiki.

2. Periksa Metode onCreate() dan Tambahkan Logging
Selanjutnya buka MainActivity.kt dari project. Logging dapat ditambahkan untuk metode-metode yang berada pada activity lifecycle meliputi onCreate(), onStart(), onResume(), onRestart(), onPause(), onStop(), onDestroy(). Pertama-tama logging akan ditambahkan untuk onCreate() dengan cara membuat value const TAG yaitu MainActivity sehingga pesan log lebih mudah ditemukan. Kemudian import android.util.Log dan tambahkan logging di bawah super.onCreate(savedInstanceState). Run ulang sehingga log dapat dicek pada logcat menggunakan tag:MainActivity. 


3. Terapkan Metode onStart() dan Metode-Metode lainnya
Metode yang dapat diubah dapat dicek pada Code -> Override Methods. Terlihat bahwa metode-metode activity lifecycle dapat di-override, sehingga tambahkan override dari metode-metode tersebut yang ditambahkan logging di bawah onCreate pada MainActivity.kt.
Ketika aplikasi di-run ulang terlihat akan ada tiga callback siklus yaitu:
- onCreate() saat sistem membuat aplikasi.
- onStart() membuat aplikasi terlihat di layar, tetapi pengguna belum dapat berinteraksi dengan aplikasi.
- onResume() membawa aplikasi ke latar depan, dan pengguna kini dapat berinteraksi dengannya.

4. Kasus Penggunaan 1: Membuka dan Menutup Aktivitas
Ketika tombol back pada device ditekan, maka terlihat bahwa tiga callback siklus dapat terlihat yaitu onPause(), onStop(), dan onDestroy(). Hal ini dikarenakan tombol back menyebabkan aktivitas (dan aplikasi) dihapus dari layar dan dipindahkan ke bagian belakang tumpukan aktivitas.

5. Kasus Penggunaan 2: Bernavigasi dari dan Kembali ke Aktivitas
Run ulang aplikasi lalu tekan tombol home pada device. Terlihat bahwa kali ini hanya onPause() dan onStop() yang terpanggil karena aplikasi tidak benar-benar tertutup dan berjalan di background. Saat onPause() dipanggil, aplikasi tidak lagi memiliki fokus. Setelah onStop(), aplikasi tidak lagi akan terlihat di layar.
Kemudian klik tombol persegi di kanan bawah device untuk kembali ke aplikasi.
Terlihat bahwa sekarang onRestart(), onStart(), dan onResume() terpanggil. onRestart() berfungsi sebagai pengganti onCreate(). Dalam kasus ini state meliputi jumlah variabel dari program akan tetap sama dengan sebelum ditekan home karena siklus masih terjadi pada aplikasi yang sama.

6. Kasus Penggunaan 3: Menyembunyikan Sebagian Aktivitas
Klik tombol share pada bagian kanan atas aplikasi Dessert Clicker, terlihat bahwa kali ini hanya onPause() yang terpanggil karena aktivitas masih terlihat sebagian tetapi tidak mendapat fokus pengguna (fokus pengguna berada pada tampilan share). Sebaliknya, onResume() akan terpanggil ketika aplikasi kembali mendapatkan fokus.

7. Mempelajari Perubahan Konfigurasi
Beberapa perubahan konfigurasi seperti rotasi layar menyebabkan onDestroy() dipanggil, menyebabkan aktivitas akan dihentikan dan dimulai ulang. Hal ini dapat menyebabkan kehilangan data pada aplikasi. Sebagai contoh pada aplikasi Dessert Clicker, jumlah Desserts sold dan Total Revenue akan ter-reset akibat rotasi layar tersebut.

8. Menggunakan rememberSaveable untuk menyimpan nilai di seluruh perubahan konfigurasi
Bug ini dapat diatasi dengan cara mengubah remember menjadi rememberSaveable untuk menyimpan nilai yang diinginkan.
Terlihat bahwa meskipun onDestroy() terpanggil akibat rotasi layar, jumlah variabel pada aplikasi dapat dipulihkan.

9. Perbandingan Hasil
Berikut adalah perbandingan aplikasi ketika hanya menggunakan remember saja dengan rememberSaveable.


Berikut adalah isi code MainActivity.kt:
MainActivity.kt
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.dessertclicker
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat
import com.example.dessertclicker.data.Datasource
import com.example.dessertclicker.model.Dessert
import com.example.dessertclicker.ui.theme.DessertClickerTheme
import android.util.Log
import androidx.compose.runtime.saveable.rememberSaveable
private const val TAG = "MainActivity"
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate Called")
setContent {
DessertClickerTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding(),
) {
DessertClickerApp(desserts = Datasource.dessertList)
}
}
}
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart Called")
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume Called")
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, "onRestart Called")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause Called")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop Called")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy Called")
}
}
/**
* Determine which dessert to show.
*/
fun determineDessertToShow(
desserts: List<Dessert>,
dessertsSold: Int
): Dessert {
var dessertToShow = desserts.first()
for (dessert in desserts) {
if (dessertsSold >= dessert.startProductionAmount) {
dessertToShow = dessert
} else {
// The list of desserts is sorted by startProductionAmount. As you sell more desserts,
// you'll start producing more expensive desserts as determined by startProductionAmount
// We know to break as soon as we see a dessert who's "startProductionAmount" is greater
// than the amount sold.
break
}
}
return dessertToShow
}
/**
* Share desserts sold information using ACTION_SEND intent
*/
private fun shareSoldDessertsInformation(intentContext: Context, dessertsSold: Int, revenue: Int) {
val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(
Intent.EXTRA_TEXT,
intentContext.getString(R.string.share_text, dessertsSold, revenue)
)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
try {
ContextCompat.startActivity(intentContext, shareIntent, null)
} catch (e: ActivityNotFoundException) {
Toast.makeText(
intentContext,
intentContext.getString(R.string.sharing_not_available),
Toast.LENGTH_LONG
).show()
}
}
@Composable
private fun DessertClickerApp(
desserts: List<Dessert>
) {
var revenue by rememberSaveable { mutableStateOf(0) }
var dessertsSold by rememberSaveable { mutableStateOf(0) }
val currentDessertIndex by rememberSaveable { mutableStateOf(0) }
var currentDessertPrice by rememberSaveable {
mutableStateOf(desserts[currentDessertIndex].price)
}
var currentDessertImageId by rememberSaveable {
mutableStateOf(desserts[currentDessertIndex].imageId)
}
Scaffold(
topBar = {
val intentContext = LocalContext.current
val layoutDirection = LocalLayoutDirection.current
DessertClickerAppBar(
onShareButtonClicked = {
shareSoldDessertsInformation(
intentContext = intentContext,
dessertsSold = dessertsSold,
revenue = revenue
)
},
modifier = Modifier
.fillMaxWidth()
.padding(
start = WindowInsets.safeDrawing.asPaddingValues()
.calculateStartPadding(layoutDirection),
end = WindowInsets.safeDrawing.asPaddingValues()
.calculateEndPadding(layoutDirection),
)
.background(MaterialTheme.colorScheme.primary)
)
}
) { contentPadding ->
DessertClickerScreen(
revenue = revenue,
dessertsSold = dessertsSold,
dessertImageId = currentDessertImageId,
onDessertClicked = {
// Update the revenue
revenue += currentDessertPrice
dessertsSold++
// Show the next dessert
val dessertToShow = determineDessertToShow(desserts, dessertsSold)
currentDessertImageId = dessertToShow.imageId
currentDessertPrice = dessertToShow.price
},
modifier = Modifier.padding(contentPadding)
)
}
}
@Composable
private fun DessertClickerAppBar(
onShareButtonClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(R.string.app_name),
modifier = Modifier.padding(start = dimensionResource(R.dimen.padding_medium)),
color = MaterialTheme.colorScheme.onPrimary,
style = MaterialTheme.typography.titleLarge,
)
IconButton(
onClick = onShareButtonClicked,
modifier = Modifier.padding(end = dimensionResource(R.dimen.padding_medium)),
) {
Icon(
imageVector = Icons.Filled.Share,
contentDescription = stringResource(R.string.share),
tint = MaterialTheme.colorScheme.onPrimary
)
}
}
}
@Composable
fun DessertClickerScreen(
revenue: Int,
dessertsSold: Int,
@DrawableRes dessertImageId: Int,
onDessertClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Box(modifier = modifier) {
Image(
painter = painterResource(R.drawable.bakery_back),
contentDescription = null,
contentScale = ContentScale.Crop
)
Column {
Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
) {
Image(
painter = painterResource(dessertImageId),
contentDescription = null,
modifier = Modifier
.width(dimensionResource(R.dimen.image_size))
.height(dimensionResource(R.dimen.image_size))
.align(Alignment.Center)
.clickable { onDessertClicked() },
contentScale = ContentScale.Crop,
)
}
TransactionInfo(
revenue = revenue,
dessertsSold = dessertsSold,
modifier = Modifier.background(MaterialTheme.colorScheme.secondaryContainer)
)
}
}
}
@Composable
private fun TransactionInfo(
revenue: Int,
dessertsSold: Int,
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {
DessertsSoldInfo(
dessertsSold = dessertsSold,
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_medium))
)
RevenueInfo(
revenue = revenue,
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_medium))
)
}
}
@Composable
private fun RevenueInfo(revenue: Int, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.total_revenue),
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
Text(
text = "$${revenue}",
textAlign = TextAlign.Right,
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
}
}
@Composable
private fun DessertsSoldInfo(dessertsSold: Int, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.dessert_sold),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
Text(
text = dessertsSold.toString(),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
}
}
@Preview
@Composable
fun MyDessertClickerAppPreview() {
DessertClickerTheme {
DessertClickerApp(listOf(Dessert(R.drawable.cupcake, 5, 0)))
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

Wednesday, May 8, 2024

Tugas 9 - Membuat Aplikasi Woof Menggunakan Desain Material

 Nama     : Florentino Benedictus

NRP       : 5025201222

Tahun    : 2024

Kelas      : Pemrograman Perangkat Bergerak I

Link Implementasi      :  MainActivity.kt, Color.kt, Shape.kt, Theme.kt, Type.kt


Tugas 9 - Aplikasi Woof Menggunakan Desain Material

Pada tugas ini, dibuat aplikasi woof yaitu aplikasi yang dapat menampilkan daftar anjing. Aplikasi akan menggunakan tema material untuk membuat tampilan yang menarik. Pembuatan aplikasi menggunakan referensi Google Codelab - Aplikasi Woof.

1. Inisiasi Proyek
Pertama-tama buka link https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof/tree/starter yang merupakan kode awal aplikasi, kemudian download ZIP dari branch starter. Ekstrak file .zip yang telah terdownload, pindahkan isi folder hasil ekstrak ke lokasi yang diinginkan, lalu buka Android Studio.
Klik Open pada bagian kanan atas lalu pilih folder project yang sudah di-extract lalu pilih OK. Kemudian tunggu hingga building model Gradle selesai.
Jika project template sudah berhasil terbuild, maka ketika dirun akan terlihat layar berisi daftar anjing seperti gambar di atas.

Berikut adalah deskripsi file dan resource dari template kode:
1. com.example.woof > data > Dog.kt. File ini berisi Dog data class yang akan digunakan untuk mewakili foto, nama, usia, dan hobi anjing. File ini juga berisi daftar anjing dan informasi yang akan Anda gunakan sebagai data di aplikasi Anda.
2. res > drawable. File ini berisi semua aset gambar yang Anda butuhkan untuk project ini, termasuk ikon aplikasi, gambar anjing, dan ikon.
3. res > values > strings.xml. File ini berisi string yang Anda gunakan dalam aplikasi ini, termasuk nama aplikasi, nama anjing, deskripsinya, dan lainnya.
4. MainActivity.kt. File ini berisi kode untuk membuat daftar sederhana yang menampilkan foto anjing, nama anjing, dan usia anjing tersebut.
5. WoofApp() berisi LazyColumn yang menampilkan DogItem.
6. DogItem() berisi Row yang menampilkan foto anjing dan informasi tentangnya.
7. DogIcon() menampilkan foto anjing.
8. DogInformation() menampilkan nama dan usia anjing.
9. WoofPreview() memungkinkan Anda melihat pratinjau aplikasi di panel Design.

2. Menambahkan Warna
Selanjutnya untuk membuat aplikasi dasar menjadi lebih menarik maka dapat dilakukan penambahan warna dengan cara mengubah isi com.example.woof/ui.theme/Colors.kt dengan kode di bawah ini:
package com.example.woof.ui.theme

import androidx.compose.ui.graphics.Color

val md_theme_light_primary = Color(0xFF006C4C)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFF89F8C7)
val md_theme_light_onPrimaryContainer = Color(0xFF002114)
val md_theme_light_secondary = Color(0xFF4D6357)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFCFE9D9)
val md_theme_light_onSecondaryContainer = Color(0xFF092016)
val md_theme_light_tertiary = Color(0xFF3D6373)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFC1E8FB)
val md_theme_light_onTertiaryContainer = Color(0xFF001F29)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFBFDF9)
val md_theme_light_onBackground = Color(0xFF191C1A)
val md_theme_light_surface = Color(0xFFFBFDF9)
val md_theme_light_onSurface = Color(0xFF191C1A)
val md_theme_light_surfaceVariant = Color(0xFFDBE5DD)
val md_theme_light_onSurfaceVariant = Color(0xFF404943)
val md_theme_light_outline = Color(0xFF707973)
val md_theme_light_inverseOnSurface = Color(0xFFEFF1ED)
val md_theme_light_inverseSurface = Color(0xFF2E312F)
val md_theme_light_inversePrimary = Color(0xFF6CDBAC)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF006C4C)
val md_theme_light_outlineVariant = Color(0xFFBFC9C2)
val md_theme_light_scrim = Color(0xFF000000)

val md_theme_dark_primary = Color(0xFF6CDBAC)
val md_theme_dark_onPrimary = Color(0xFF003826)
val md_theme_dark_primaryContainer = Color(0xFF005138)
val md_theme_dark_onPrimaryContainer = Color(0xFF89F8C7)
val md_theme_dark_secondary = Color(0xFFB3CCBE)
val md_theme_dark_onSecondary = Color(0xFF1F352A)
val md_theme_dark_secondaryContainer = Color(0xFF354B40)
val md_theme_dark_onSecondaryContainer = Color(0xFFCFE9D9)
val md_theme_dark_tertiary = Color(0xFFA5CCDF)
val md_theme_dark_onTertiary = Color(0xFF073543)
val md_theme_dark_tertiaryContainer = Color(0xFF244C5B)
val md_theme_dark_onTertiaryContainer = Color(0xFFC1E8FB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF191C1A)
val md_theme_dark_onBackground = Color(0xFFE1E3DF)
val md_theme_dark_surface = Color(0xFF191C1A)
val md_theme_dark_onSurface = Color(0xFFE1E3DF)
val md_theme_dark_surfaceVariant = Color(0xFF404943)
val md_theme_dark_onSurfaceVariant = Color(0xFFBFC9C2)
val md_theme_dark_outline = Color(0xFF8A938C)
val md_theme_dark_inverseOnSurface = Color(0xFF191C1A)
val md_theme_dark_inverseSurface = Color(0xFFE1E3DF)
val md_theme_dark_inversePrimary = Color(0xFF006C4C)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF6CDBAC)
val md_theme_dark_outlineVariant = Color(0xFF404943)
val md_theme_dark_scrim = Color(0xFF000000)

Kemudian ubah juga com.example.woof/ui.theme/Theme.kt dengan code di bawah ini:
package com.example.woof.ui.theme

import android.app.Activity
import android.os.Build
import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat

private val LightColors = lightColorScheme(
    primary = md_theme_light_primary,
    onPrimary = md_theme_light_onPrimary,
    primaryContainer = md_theme_light_primaryContainer,
    onPrimaryContainer = md_theme_light_onPrimaryContainer,
    secondary = md_theme_light_secondary,
    onSecondary = md_theme_light_onSecondary,
    secondaryContainer = md_theme_light_secondaryContainer,
    onSecondaryContainer = md_theme_light_onSecondaryContainer,
    tertiary = md_theme_light_tertiary,
    onTertiary = md_theme_light_onTertiary,
    tertiaryContainer = md_theme_light_tertiaryContainer,
    onTertiaryContainer = md_theme_light_onTertiaryContainer,
    error = md_theme_light_error,
    errorContainer = md_theme_light_errorContainer,
    onError = md_theme_light_onError,
    onErrorContainer = md_theme_light_onErrorContainer,
    background = md_theme_light_background,
    onBackground = md_theme_light_onBackground,
    surface = md_theme_light_surface,
    onSurface = md_theme_light_onSurface,
    surfaceVariant = md_theme_light_surfaceVariant,
    onSurfaceVariant = md_theme_light_onSurfaceVariant,
    outline = md_theme_light_outline,
    inverseOnSurface = md_theme_light_inverseOnSurface,
    inverseSurface = md_theme_light_inverseSurface,
    inversePrimary = md_theme_light_inversePrimary,
    surfaceTint = md_theme_light_surfaceTint,
    outlineVariant = md_theme_light_outlineVariant,
    scrim = md_theme_light_scrim,
)

private val DarkColors = darkColorScheme(
    primary = md_theme_dark_primary,
    onPrimary = md_theme_dark_onPrimary,
    primaryContainer = md_theme_dark_primaryContainer,
    onPrimaryContainer = md_theme_dark_onPrimaryContainer,
    secondary = md_theme_dark_secondary,
    onSecondary = md_theme_dark_onSecondary,
    secondaryContainer = md_theme_dark_secondaryContainer,
    onSecondaryContainer = md_theme_dark_onSecondaryContainer,
    tertiary = md_theme_dark_tertiary,
    onTertiary = md_theme_dark_onTertiary,
    tertiaryContainer = md_theme_dark_tertiaryContainer,
    onTertiaryContainer = md_theme_dark_onTertiaryContainer,
    error = md_theme_dark_error,
    errorContainer = md_theme_dark_errorContainer,
    onError = md_theme_dark_onError,
    onErrorContainer = md_theme_dark_onErrorContainer,
    background = md_theme_dark_background,
    onBackground = md_theme_dark_onBackground,
    surface = md_theme_dark_surface,
    onSurface = md_theme_dark_onSurface,
    surfaceVariant = md_theme_dark_surfaceVariant,
    onSurfaceVariant = md_theme_dark_onSurfaceVariant,
    outline = md_theme_dark_outline,
    inverseOnSurface = md_theme_dark_inverseOnSurface,
    inverseSurface = md_theme_dark_inverseSurface,
    inversePrimary = md_theme_dark_inversePrimary,
    surfaceTint = md_theme_dark_surfaceTint,
    outlineVariant = md_theme_dark_outlineVariant,
    scrim = md_theme_dark_scrim,
)

@Composable
fun WoofTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = false,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColors
        else -> LightColors
    }
    val view = LocalView.current
    if (!view.isInEditMode) {
        SideEffect {
            setUpEdgeToEdge(view, darkTheme)
        }
    }

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

/**
 * Sets up edge-to-edge for the window of this [view]. The system icon colors are set to either
 * light or dark depending on whether the [darkTheme] is enabled or not.
 */
private fun setUpEdgeToEdge(view: View, darkTheme: Boolean) {
    val window = (view.context as Activity).window
    WindowCompat.setDecorFitsSystemWindows(window, false)
    window.statusBarColor = Color.Transparent.toArgb()
    val navigationBarColor = when {
        Build.VERSION.SDK_INT >= 29 -> Color.Transparent.toArgb()
        Build.VERSION.SDK_INT >= 26 -> Color(0xFF, 0xFF, 0xFF, 0x63).toArgb()
        // Min sdk version for this app is 24, this block is for SDK versions 24 and 25
        else -> Color(0x00, 0x00, 0x00, 0x50).toArgb()
    }
    window.navigationBarColor = navigationBarColor
    val controller = WindowCompat.getInsetsController(window, view)
    controller.isAppearanceLightStatusBars = !darkTheme
    controller.isAppearanceLightNavigationBars = !darkTheme
}

Sehingga ketika code di run ulang, maka terlihat warna tampilan akan berubah.

3. Tambahkan Composable Card Pada MainActivity.kt
Selanjutnya tambahkan composable Card() untuk menggabungkan Row() pada DogItem(). Kemudian tambahkan spacing antar Card dengan cara mengecek app/res/values/dimens.xml dimana template sudah menyediakan nilai dimensi yang dapat digunakan.
Sehingga untuk membuat spacing pada Card dapat digunakan modifier padding_small melalui dimensionResource(id = R.dimen.padding_small) pada fungsi WoofApp() di MainActivity.kt seperti gambar di atas.
Ketika di-run ulang, maka terlihat terdapat warna pada tiap composable Card dengan spacing antar Cardnya.

4. Preview Tema Gelap
Selanjutnya tambahkan WoofDarkThemePreview() untuk melihat preview dari tema gelap aplikasi Woof. Terlihat perbandingan antara tema terang dan gelap dari aplikasi.

5. Menambahkan Bentuk
Bentuk dari komponen pada aplikasi juga diubah, salah satunya dengan atribut yang telah didefinisikan pada Shape.kt. Atribut ini berfungsi untuk membuat bentuk rounded.
Selanjutnya, tambahkan fungsi DogIcon() pada MainActivity.kt dengan .clip(MaterialTheme.shapes.small) untuk membuat gambar menjadi rounded dan contentScale = ContentScale.Crop sehingga gambar yang ditampilkan akan tercrop secara otomatis.
Bentuk dari card juga dapat diubah dari bentuk awal yang seluruh titik sudutnya rounded menjadi tidak lingkaran sepenuhnya dengan cara menambahkan atribut medium pada Shape.kt. Nantinya secara otomatis konfigurasi akan terupdate karena objek Shape telah terload pada MaterialTheme di Theme.kt.

6. Menambahkan Tipografi
Kita juga dapat memodifikasi tipe huruf pada aplikasi yang dibuat. Caranya pilih File -> New -> Android Resource Directory.
Kemudian pilih Directory name dan Resource type yaitu font.
Selanjutnya karena font yang akan digunakan tidak tersedia secara default maka perlu didownload font custom Montserrat dan Abril Fatface dari Google Font. Klik get font pada masing-masing halaman lalu download. Ganti nama file ttf pada folder Abril_Fatface menjadi abril_fatface_regular.ttf dan pada Montserrat/static, ganti nama Montserrat-Bold.ttf menjadi montserrat_bold.ttf dan ganti nama Montserrat-Regular.ttf menjadi montserrat_regular.ttf kemudian pindahkan ketiga file tersebut ke directory project pada app/res/font yang sebelumnya telah dibuat.
Kemudian buka com.example.woof/ui.theme/Type.kt yaitu file yang berfungsi untuk menyimpan konfigurasi tipologi. Tambahkan value AbrilFatface dan Montserrat yang masing-masing mengimport dari resource di directory font. Kemudian update juga val Typography yang sebelumnya kosong pada template untuk membuat atribut tipografi yang nantinya akan digunakan.
Selanjutnya tambahkan style pada fungsi DogInformation untuk variabel dogName dan dogAge. dogName dapat menggunakan atribut displayMedium (Montserrat Bold 20sp) dan dogAge dapat menggunakan atribut bodyLarge (Montserrat Normal 14sp). Ketika aplikasi di-run ulang maka terlihat tipe dan ukuran font sudah terupdate.

7. Menambahkan Panel Atas & Hasil Akhir
Selanjutnya, dapat dibuat TopAppBar yaitu panel atas yang berada pada Scaffold dan berfungsi untuk branding/memberi karakteristik aplikasi, seperti judul maupun logo. Pertama-tama tambahkan atribut topBar dari Scaffold yang tersedia pada WoofApp() di MainActivity.kt. topBar akan memanggil fungsi composable WoofTopAppBar(). WoofTopAppBar() akan menjadi fungsi utama dari panel atas yang akan dibuat.
Kemudian buat WoofTopAppBar(). Gunakan CenterAlignedTopAppBar sehingga panel atas berada di tengah. Lalu isi parameter title dengan composable Row yang akan berisi Image, yaitu gambar dari logo yang dibuat dengan modifier dimens.xml dan composable Text yang akan berisi nama aplikasi yaitu Woof yang tersimpan pada resource strings.xml dengan variabel displayLarge (AbrilFatface Normal 36sp). Pada bagian Row juga perlu ditambahkan verticalAlignment = Alignment.CenterVertically sehingga posisi logo dan teks akan sejajar. 
Berikut adalah hasil akhir dari aplikasi dengan modifikasi desain dan tema material, dimana aplikasi memiliki judul dan logo pada panel atas, memiliki warna Card, dan modifikasi bentuk Card dan gambar.


Berikut adalah isi code MainActivity.kt, ui.theme/Color.kt, ui.theme/Shape.kt, ui.theme/Theme.kt, dan ui.theme/Type.kt:
MainActivity.kt
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.woof
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.example.woof.data.Dog
import com.example.woof.data.dogs
import com.example.woof.ui.theme.WoofTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WoofTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize()
) {
WoofApp()
}
}
}
}
}
/**
* Composable that displays an app bar and a list of dogs.
*/
@Composable
fun WoofApp() {
Scaffold(
topBar = {
WoofTopAppBar()
}
) { it ->
LazyColumn(contentPadding = it) {
items(dogs) {
DogItem(
dog = it,
modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
)
}
}
}
}
/**
* Composable that displays a list item containing a dog icon and their information.
*
* @param dog contains the data that populates the list item
* @param modifier modifiers to set to this composable
*/
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
Card(modifier = modifier) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(id = R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
}
}
}
/**
* Composable that displays a photo of a dog.
*
* @param dogIcon is the resource ID for the image of the dog
* @param modifier modifiers to set to this composable
*/
@Composable
fun DogIcon(
@DrawableRes dogIcon: Int,
modifier: Modifier = Modifier
) {
Image(
modifier = modifier
.size(dimensionResource(R.dimen.image_size))
.padding(dimensionResource(R.dimen.padding_small))
.clip(MaterialTheme.shapes.small),
contentScale = ContentScale.Crop,
painter = painterResource(dogIcon),
// Content Description is not needed here - image is decorative, and setting a null content
// description allows accessibility services to skip this element during navigation.
contentDescription = null
)
}
/**
* Composable that displays a dog's name and age.
*
* @param dogName is the resource ID for the string of the dog's name
* @param dogAge is the Int that represents the dog's age
* @param modifier modifiers to set to this composable
*/
@Composable
fun DogInformation(
@StringRes dogName: Int,
dogAge: Int,
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {
Text(
text = stringResource(dogName),
style = MaterialTheme.typography.displayMedium,
modifier = Modifier.padding(top = dimensionResource(R.dimen.padding_small))
)
Text(
text = stringResource(R.string.years_old, dogAge),
style = MaterialTheme.typography.bodyLarge
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WoofTopAppBar(modifier: Modifier = Modifier) {
CenterAlignedTopAppBar(
title = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier
.size(dimensionResource(id = R.dimen.image_size))
.padding(dimensionResource(id = R.dimen.padding_small)),
painter = painterResource(R.drawable.ic_woof_logo),
contentDescription = null
)
Text(
text = stringResource(R.string.app_name),
style = MaterialTheme.typography.displayLarge
)
}
},
modifier = modifier
)
}
/**
* Composable that displays what the UI of the app looks like in light theme in the design tab.
*/
@Preview
@Composable
fun WoofPreview() {
WoofTheme(darkTheme = false) {
WoofApp()
}
}
@Preview
@Composable
fun WoofDarkThemePreview() {
WoofTheme(darkTheme = true) {
WoofApp()
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

Color.kt
package com.example.woof.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF006C4C)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFF89F8C7)
val md_theme_light_onPrimaryContainer = Color(0xFF002114)
val md_theme_light_secondary = Color(0xFF4D6357)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFCFE9D9)
val md_theme_light_onSecondaryContainer = Color(0xFF092016)
val md_theme_light_tertiary = Color(0xFF3D6373)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFC1E8FB)
val md_theme_light_onTertiaryContainer = Color(0xFF001F29)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFBFDF9)
val md_theme_light_onBackground = Color(0xFF191C1A)
val md_theme_light_surface = Color(0xFFFBFDF9)
val md_theme_light_onSurface = Color(0xFF191C1A)
val md_theme_light_surfaceVariant = Color(0xFFDBE5DD)
val md_theme_light_onSurfaceVariant = Color(0xFF404943)
val md_theme_light_outline = Color(0xFF707973)
val md_theme_light_inverseOnSurface = Color(0xFFEFF1ED)
val md_theme_light_inverseSurface = Color(0xFF2E312F)
val md_theme_light_inversePrimary = Color(0xFF6CDBAC)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF006C4C)
val md_theme_light_outlineVariant = Color(0xFFBFC9C2)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFF6CDBAC)
val md_theme_dark_onPrimary = Color(0xFF003826)
val md_theme_dark_primaryContainer = Color(0xFF005138)
val md_theme_dark_onPrimaryContainer = Color(0xFF89F8C7)
val md_theme_dark_secondary = Color(0xFFB3CCBE)
val md_theme_dark_onSecondary = Color(0xFF1F352A)
val md_theme_dark_secondaryContainer = Color(0xFF354B40)
val md_theme_dark_onSecondaryContainer = Color(0xFFCFE9D9)
val md_theme_dark_tertiary = Color(0xFFA5CCDF)
val md_theme_dark_onTertiary = Color(0xFF073543)
val md_theme_dark_tertiaryContainer = Color(0xFF244C5B)
val md_theme_dark_onTertiaryContainer = Color(0xFFC1E8FB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF191C1A)
val md_theme_dark_onBackground = Color(0xFFE1E3DF)
val md_theme_dark_surface = Color(0xFF191C1A)
val md_theme_dark_onSurface = Color(0xFFE1E3DF)
val md_theme_dark_surfaceVariant = Color(0xFF404943)
val md_theme_dark_onSurfaceVariant = Color(0xFFBFC9C2)
val md_theme_dark_outline = Color(0xFF8A938C)
val md_theme_dark_inverseOnSurface = Color(0xFF191C1A)
val md_theme_dark_inverseSurface = Color(0xFFE1E3DF)
val md_theme_dark_inversePrimary = Color(0xFF006C4C)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF6CDBAC)
val md_theme_dark_outlineVariant = Color(0xFF404943)
val md_theme_dark_scrim = Color(0xFF000000)
view raw Color.kt hosted with ❤ by GitHub

Shape.kt
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.woof.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(50.dp),
medium = RoundedCornerShape(bottomStart = 16.dp, topEnd = 16.dp)
)
view raw Shape.kt hosted with ❤ by GitHub

Theme.kt
package com.example.woof.ui.theme
import android.app.Activity
import android.os.Build
import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val LightColors = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim,
)
private val DarkColors = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
@Composable
fun WoofTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = false,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColors
else -> LightColors
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
setUpEdgeToEdge(view, darkTheme)
}
}
MaterialTheme(
colorScheme = colorScheme,
shapes = Shapes,
typography = Typography,
content = content
)
}
/**
* Sets up edge-to-edge for the window of this [view]. The system icon colors are set to either
* light or dark depending on whether the [darkTheme] is enabled or not.
*/
private fun setUpEdgeToEdge(view: View, darkTheme: Boolean) {
val window = (view.context as Activity).window
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.Transparent.toArgb()
val navigationBarColor = when {
Build.VERSION.SDK_INT >= 29 -> Color.Transparent.toArgb()
Build.VERSION.SDK_INT >= 26 -> Color(0xFF, 0xFF, 0xFF, 0x63).toArgb()
// Min sdk version for this app is 24, this block is for SDK versions 24 and 25
else -> Color(0x00, 0x00, 0x00, 0x50).toArgb()
}
window.navigationBarColor = navigationBarColor
val controller = WindowCompat.getInsetsController(window, view)
controller.isAppearanceLightStatusBars = !darkTheme
controller.isAppearanceLightNavigationBars = !darkTheme
}
view raw Theme.kt hosted with ❤ by GitHub

Type.kt
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.woof.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import com.example.woof.R
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp
val AbrilFatface = FontFamily(
Font(R.font.abril_fatface_regular)
)
val Montserrat = FontFamily(
Font(R.font.montserrat_regular),
Font(R.font.montserrat_bold, FontWeight.Bold)
)
// Set of Material typography styles to start with
val Typography = Typography(
displayLarge = TextStyle(
fontFamily = AbrilFatface,
fontWeight = FontWeight.Normal,
fontSize = 36.sp
),
displayMedium = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
),
labelSmall = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.Bold,
fontSize = 14.sp
),
bodyLarge = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.Normal,
fontSize = 14.sp
)
)
view raw Type.kt hosted with ❤ by GitHub


Referensi

https://developer.android.com/codelabs/basic-android-kotlin-compose-material-theming?hl=id&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-3-pathway-3%3Fhl%3Did%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-material-theming#3

EAS PPB I - Aplikasi Alfamind

Nama       : Florentino Benedictus NRP          : 5025201222 Tahun     : 2024 Kelas        : Pemrograman Perangkat Bergerak I Link Desain An...