Compare commits

...

11 Commits

Author SHA1 Message Date
RafiFattan23
b186c70643 pertama 2025-11-06 17:25:02 +07:00
099c35f19a Update README.md 2025-11-06 12:04:44 +07:00
0ee43c2e9a First update calculation 2025-11-06 11:34:31 +07:00
7053fa6573 First update labels 2025-11-06 11:10:35 +07:00
51c9a8e5ff First commit 2025-11-06 09:58:07 +07:00
renovate[bot]
b029b3dd10
Update all dependencies 8.7.3 to v8.8.0 (#271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-10 05:25:39 +00:00
renovate[bot]
90545a4a4d
Update dependency gradle 8.11.1 to v8.12 (#263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-21 03:18:17 +00:00
renovate[bot]
e075e6ded8
Update dependency androidx.compose:compose-bom 2024.11.00 to v2024.12.01 (#260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 03:31:35 +00:00
Tomáš Mlynarič
7db6c366a3 Update to kotlin 2.1.0 2024-12-09 17:16:15 +01:00
Jose Alcérreca
076428c67e
Merge pull request #257 from google-developer-training/mlykotom-renovate-fix
Add branches to renovate
2024-12-09 13:44:24 +00:00
Tomáš Mlynarič
59c134aec9
Add branches to renovate 2024-12-09 14:35:04 +01:00
48 changed files with 543 additions and 414 deletions

View File

@ -2,5 +2,10 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": [
"local>android/.github:renovate-config" "local>android/.github:renovate-config"
],
"baseBranches": [
"main",
"starter",
"state"
] ]
} }

View File

@ -1,24 +1,68 @@
Tip Time - Solution Code # 📱 Aplikasi Kalkulator BMI
=================================
Solution code for the [Android Basics with Compose](https://developer.android.com/courses/android-basics-compose/course): Tip Time app. **Dibuat oleh:**
👨‍💻 **Rafi Fattan Fitriardi**
🆔 **NIM: 202310715002**
🏫 **Pemrograman Perangkat Bergerak - F5A5**
---
Introduction ## 📖 Deskripsi Aplikasi
------------ Aplikasi **Kalkulator BMI (Body Mass Index)** ini dibuat sebagai proyek akhir mata kuliah **Pemrograman Perangkat Bergerak**.
The Tip Time app contains various UI elements for calculating a tip, Tujuan utama aplikasi ini adalah membantu pengguna menghitung **Indeks Massa Tubuh (BMI)** berdasarkan **berat badan (kg)** dan **tinggi badan (cm)** untuk mengetahui apakah berat badan tergolong **kurang, ideal, berlebih, atau obesitas**.
teaching about user input, and State in Compose.
Aplikasi ini memiliki **dua halaman utama**:
1. **Halaman Biodata Pengembang** menampilkan informasi pembuat aplikasi (nama, NIM, kelas, dan foto), serta tombol **“MULAI”** untuk berpindah ke laman utama.
2. **Halaman Utama (Kalkulator BMI)** tempat pengguna menginput berat dan tinggi badan, menekan tombol **“Hitung BMI”**, lalu melihat hasil nilai BMI beserta kategori dan saran kesehatannya.
Pre-requisites ---
--------------
* Experience with Kotlin syntax.
* How to create and run a project in Android Studio.
## ⚙️ Fitur Utama
- Input berat dan tinggi badan secara interaktif.
- Perhitungan otomatis nilai BMI.
- Tampilan kategori hasil (Kurus, Normal, Gemuk, Obesitas).
- Antarmuka sederhana dan responsif.
- Navigasi antarhalaman menggunakan tombol **MULAI** dari halaman biodata.
---
## 🧩 Teknologi yang Digunakan
- **Android Studio (Kotlin)**
- **XML Layouts** untuk desain antarmuka
- **Intent** untuk navigasi antar activity
- **Drawable XML** untuk gradasi dan tema warna aplikasi
---
## 💡 Struktur Proyek
```
app/
├── java/com/example/bmiapp/
│ ├── SplashActivity.kt // Halaman biodata pengembang
│ ├── MainActivity.kt // Halaman utama kalkulator BMI
├── res/
│ ├── layout/
│ │ ├── activity_splash.xml
│ │ ├── activity_main.xml
│ ├── drawable/
│ │ ├── splash_gradient.xml
│ ├── mipmap/
│ │ ├── ic_launcher.png // Ikon aplikasi
│ │ ├── ic_launcher_round.png
│ ├── values/
│ ├── colors.xml
│ ├── strings.xml
│ ├── themes.xml
└── AndroidManifest.xml
```
---
## 🧠 Kontribusi & Kredit
Aplikasi ini dikembangkan dengan bantuan **ChatGPT (OpenAI)** dalam pembuatan kode, desain antarmuka, dan penyusunan dokumentasi.
Semua logika, pengujian, dan penyempurnaan dilakukan secara mandiri oleh pengembang.
---
Getting Started
---------------
1. Install Android Studio, if you don't already have it.
2. Download the sample.
3. Import the sample into Android Studio.
4. Build and run the sample.

View File

@ -17,6 +17,7 @@
plugins { plugins {
id("com.android.application") id("com.android.application")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
} }
android { android {
@ -45,18 +46,15 @@ android {
} }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = JavaVersion.VERSION_17.toString()
} }
buildFeatures { buildFeatures {
compose = true compose = true
} }
composeOptions {
kotlinCompilerExtensionVersion = rootProject.extra["compose_compiler_version"].toString()
}
packaging { packaging {
resources { resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
@ -67,7 +65,7 @@ android {
dependencies { dependencies {
implementation(platform("androidx.compose:compose-bom:2024.11.00")) implementation(platform("androidx.compose:compose-bom:2024.12.01"))
implementation("androidx.activity:activity-compose:1.9.3") implementation("androidx.activity:activity-compose:1.9.3")
implementation("androidx.compose.material3:material3") implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui")
@ -75,10 +73,10 @@ dependencies {
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.core:core-ktx:1.15.0") implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.appcompat:appcompat:1.7.1")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.11.00")) androidTestImplementation(platform("androidx.compose:compose-bom:2024.12.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation("androidx.test.ext:junit:1.2.1") androidTestImplementation("androidx.test.ext:junit:1.2.1")

View File

@ -26,15 +26,24 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.TipTime" android:theme="@style/Theme.TipTime"
tools:targetApi="33"> tools:targetApi="33">
<!-- Laman utama (Kalkulator BMI) -->
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="false"
android:theme="@style/Theme.TipTime" />
<!-- Laman biodata pengembang (Splash / Intro Screen) -->
<activity
android:name=".SplashActivity"
android:exported="true" android:exported="true"
android:theme="@style/Theme.TipTime"> android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,18 +1,3 @@
/*
* 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.tiptime package com.example.tiptime
import android.os.Bundle import android.os.Bundle
@ -21,31 +6,12 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.safeDrawingPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.*
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@ -55,7 +21,10 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.tiptime.ui.theme.TipTimeTheme import com.example.tiptime.ui.theme.TipTimeTheme
import java.text.NumberFormat import kotlin.math.pow
import kotlin.math.roundToInt
import androidx.compose.foundation.background
import androidx.compose.ui.graphics.Brush
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -63,10 +32,8 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
TipTimeTheme { TipTimeTheme {
Surface( Surface(modifier = Modifier.fillMaxSize()) {
modifier = Modifier.fillMaxSize(), BmiCalculatorLayout()
) {
TipTimeLayout()
} }
} }
} }
@ -74,65 +41,162 @@ class MainActivity : ComponentActivity() {
} }
@Composable @Composable
fun TipTimeLayout() { fun BmiCalculatorLayout() {
var amountInput by remember { mutableStateOf("") } var heightInput by remember { mutableStateOf("") }
var tipInput by remember { mutableStateOf("") } var weightInput by remember { mutableStateOf("") }
var roundUp by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0 // State untuk hasil BMI
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0 var bmiResult by remember { mutableStateOf<Float?>(null) }
val tip = calculateTip(amount, tipPercent, roundUp) var bmiCategory by remember { mutableStateOf("") }
Column( val height = heightInput.toFloatOrNull() ?: 0f
val weight = weightInput.toFloatOrNull() ?: 0f
val isValid = validateInput(height, weight)
Box(
modifier = Modifier modifier = Modifier
.statusBarsPadding() .fillMaxSize()
.padding(horizontal = 40.dp) .background(
Brush.verticalGradient(
colors = listOf(
MaterialTheme.colorScheme.primary.copy(alpha = 0.4f),
MaterialTheme.colorScheme.secondary.copy(alpha = 0.2f)
)
)
)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.safeDrawingPadding(), .padding(horizontal = 24.dp, vertical = 32.dp)
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) { ) {
Text( Column(horizontalAlignment = Alignment.CenterHorizontally) {
text = stringResource(R.string.calculate_tip),
modifier = Modifier Row(verticalAlignment = Alignment.CenterVertically) {
.padding(bottom = 16.dp, top = 40.dp) Icon(
.align(alignment = Alignment.Start) painter = painterResource(id = R.drawable.ic_launcher_foreground),
) contentDescription = null,
EditNumberField( tint = MaterialTheme.colorScheme.primary,
label = R.string.bill_amount, modifier = Modifier.size(48.dp)
leadingIcon = R.drawable.money, )
keyboardOptions = KeyboardOptions.Default.copy( Spacer(modifier = Modifier.width(8.dp))
keyboardType = KeyboardType.Number, Text(
imeAction = ImeAction.Next text = "Kalkulator BMI",
), style = MaterialTheme.typography.headlineMedium.copy(
value = amountInput, color = MaterialTheme.colorScheme.primary
onValueChanged = { amountInput = it }, )
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(), )
) }
EditNumberField(
label = R.string.how_was_the_service, Spacer(modifier = Modifier.height(24.dp))
leadingIcon = R.drawable.percent,
keyboardOptions = KeyboardOptions.Default.copy( Card(
keyboardType = KeyboardType.Number, elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
imeAction = ImeAction.Done colors = CardDefaults.cardColors(
), containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)
value = tipInput, ),
onValueChanged = { tipInput = it }, modifier = Modifier.fillMaxWidth()
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(), ) {
) Column(
RoundTheTipRow( modifier = Modifier.padding(16.dp),
roundUp = roundUp, horizontalAlignment = Alignment.CenterHorizontally
onRoundUpChanged = { roundUp = it }, ) {
modifier = Modifier.padding(bottom = 32.dp) EditNumberField(
) label = R.string.height,
Text( leadingIcon = R.drawable.number,
text = stringResource(R.string.tip_amount, tip), keyboardOptions = KeyboardOptions(
style = MaterialTheme.typography.displaySmall keyboardType = KeyboardType.Number,
) imeAction = ImeAction.Next
Spacer(modifier = Modifier.height(150.dp)) ),
value = heightInput,
onValueChanged = { heightInput = it },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp)
)
EditNumberField(
label = R.string.weight,
leadingIcon = R.drawable.number,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
value = weightInput,
onValueChanged = { weightInput = it },
modifier = Modifier.fillMaxWidth()
)
}
}
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = {
if (!isValid) {
errorMessage = "Masukkan tinggi (20250 cm) dan berat (10250 kg) yang valid!"
bmiResult = null
} else {
errorMessage = ""
bmiResult = calculateBMI(weight, height)
bmiCategory = getBMICategory(bmiResult!!)
}
},
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
elevation = ButtonDefaults.elevatedButtonElevation(8.dp)
) {
Text(text = "Hitung BMI", style = MaterialTheme.typography.titleMedium)
}
if (errorMessage.isNotEmpty()) {
Text(
text = errorMessage,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 8.dp)
)
}
// Tampilkan hasil hanya jika sudah ditekan
bmiResult?.let { bmi ->
val categoryColor = when (bmiCategory) {
"Kurus" -> MaterialTheme.colorScheme.tertiaryContainer
"Normal" -> MaterialTheme.colorScheme.primaryContainer
"Kelebihan Berat" -> MaterialTheme.colorScheme.secondaryContainer
else -> MaterialTheme.colorScheme.errorContainer
}
Card(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp),
colors = CardDefaults.cardColors(containerColor = categoryColor),
elevation = CardDefaults.cardElevation(defaultElevation = 10.dp)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(20.dp)
) {
Text(
text = "BMI Anda: %.2f".format(bmi),
style = MaterialTheme.typography.titleLarge
)
Text(
text = "Kategori: $bmiCategory",
style = MaterialTheme.typography.bodyLarge
)
}
}
}
}
} }
} }
/**
* Fungsi untuk membuat TextField input angka
*/
@Composable @Composable
fun EditNumberField( fun EditNumberField(
@StringRes label: Int, @StringRes label: Int,
@ -144,53 +208,47 @@ fun EditNumberField(
) { ) {
TextField( TextField(
value = value, value = value,
singleLine = true,
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) },
modifier = modifier,
onValueChange = onValueChanged, onValueChange = onValueChanged,
singleLine = true,
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), contentDescription = null) },
label = { Text(stringResource(label)) }, label = { Text(stringResource(label)) },
keyboardOptions = keyboardOptions keyboardOptions = keyboardOptions,
modifier = modifier
) )
} }
@Composable /**
fun RoundTheTipRow( * Menghitung nilai BMI berdasarkan berat (kg) dan tinggi (cm)
roundUp: Boolean, * Rumus: BMI = berat / (tinggi/100)^2
onRoundUpChanged: (Boolean) -> Unit, */
modifier: Modifier = Modifier fun calculateBMI(weight: Float, height: Float): Float {
) { if (height <= 0) return 0f
Row( return weight / (height / 100).pow(2)
modifier = modifier.fillMaxWidth(), }
verticalAlignment = Alignment.CenterVertically
) { /**
Text(text = stringResource(R.string.round_up_tip)) * Menentukan kategori BMI
Switch( */
modifier = Modifier fun getBMICategory(bmi: Float): String {
.fillMaxWidth() return when {
.wrapContentWidth(Alignment.End), bmi < 18.5 -> "Kurus"
checked = roundUp, bmi < 25 -> "Normal"
onCheckedChange = onRoundUpChanged bmi < 30 -> "Kelebihan Berat"
) else -> "Obesitas"
} }
} }
/** /**
* Calculates the tip based on the user input and format the tip amount * Validasi input agar nilai masuk akal
* according to the local currency.
* Example would be "$10.00".
*/ */
private fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String { fun validateInput(height: Float, weight: Float): Boolean {
var tip = tipPercent / 100 * amount return height in 20f..250f && weight in 10f..250f
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
return NumberFormat.getCurrencyInstance().format(tip)
} }
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun TipTimeLayoutPreview() { fun BmiCalculatorPreview() {
TipTimeTheme { TipTimeTheme {
TipTimeLayout() BmiCalculatorLayout()
} }
} }

View File

@ -0,0 +1,21 @@
package com.example.tiptime
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
val btnMulai = findViewById<Button>(R.id.btnMulai)
btnMulai.setOnClickListener {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
//finish() // supaya tidak bisa kembali ke splash dengan tombol back
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Lapisan gradasi -->
<item>
<shape android:shape="rectangle">
<gradient
android:startColor="#6A11CB"
android:endColor="#2575FC"
android:angle="45" />
</shape>
</item>
<!-- Lapisan overlay transparan -->
<item>
<shape android:shape="rectangle">
<solid android:color="#40FFFFFF" />
</shape>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -1,185 +1,74 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <vector
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp" android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> xmlns:android="http://schemas.android.com/apk/res/android">
<path <path android:fillColor="#3DDC84"
android:fillColor="#3DDC84" android:pathData="M0,0h108v108h-108z"/>
android:pathData="M0,0h108v108h-108z" /> <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:pathData="M9,0L9,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M19,0L19,108" <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:pathData="M29,0L29,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M39,0L39,108" <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:pathData="M49,0L49,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M59,0L59,108" <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:pathData="M69,0L69,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M79,0L79,108" <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:pathData="M89,0L89,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M99,0L99,108" <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:pathData="M0,9L108,9" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M0,19L108,19" <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:pathData="M0,29L108,29" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector> </vector>

View File

@ -1,23 +0,0 @@
<!--
~ 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.
-->
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7.5,11C9.43,11 11,9.43 11,7.5S9.43,4 7.5,4S4,5.57 4,7.5S5.57,11 7.5,11zM7.5,6C8.33,6 9,6.67 9,7.5S8.33,9 7.5,9S6,8.33 6,7.5S6.67,6 7.5,6z"/>
<path android:fillColor="@android:color/white" android:pathData="M4.0025,18.5831l14.5875,-14.5875l1.4142,1.4142l-14.5875,14.5875z"/>
<path android:fillColor="@android:color/white" android:pathData="M16.5,13c-1.93,0 -3.5,1.57 -3.5,3.5s1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S18.43,13 16.5,13zM16.5,18c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5S17.33,18 16.5,18z"/>
</vector>

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/transparent" />
<stroke android:width="2dp" android:color="@android:color/white" />
</shape>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:type="linear"
android:angle="270"
android:startColor="#4A90E2"
android:centerColor="#6A5ACD"
android:endColor="#8A2BE2"
android:useLevel="false" />
</shape>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="24dp"
android:background="@drawable/splash_gradient">
<ImageView
android:layout_width="150dp"
android:layout_height="150dp"
android:src="@mipmap/ic_launcher_round"
android:contentDescription="Foto Profil"
android:layout_marginBottom="16dp" />
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rafi Fattan Fitriardi"
android:textStyle="bold"
android:textSize="20sp"
android:textColor="#FFFFFF"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/tvNim"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="202310715002"
android:textSize="16sp"
android:textColor="#E0E0E0"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/tvKampus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pemrograman Perangkat Bergerak - F5A5"
android:textSize="16sp"
android:textColor="#E0E0E0"
android:shadowColor="#80000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:layout_marginBottom="24dp" />
<Button
android:id="@+id/btnMulai"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MULAI"
android:backgroundTint="#4CAF50"
android:textColor="@android:color/white"
android:paddingHorizontal="32dp"
android:paddingVertical="12dp" />
</LinearLayout>

View File

@ -1,22 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

View File

@ -1,22 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -15,10 +15,11 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<resources> <resources>
<string name="app_name">Tip Time</string> <string name="app_name">BMI Calculator</string>
<string name="calculate_tip">Calculate Tip</string> <string name="calculate_tip">Calculate BMI</string>
<string name="bill_amount">Bill Amount</string> <string name="height">Tinggi Badan</string>
<string name="how_was_the_service">Tip Percentage</string> <string name="weight">Berat Badan</string>
<string name="round_up_tip">Round up tip?</string> <string name="use_usc">Gunakan Unit USC (lbs/in)?</string>
<string name="tip_amount">Tip Amount: %s</string> <string name="bmi_calculation">BMI Anda: %s</string>
<string name="bmi_category">Kategori: %s</string>
</resources> </resources>

View File

@ -0,0 +1,63 @@
package com.example.tiptime
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Unit test untuk memastikan fungsi perhitungan dan kategori BMI
* berjalan dengan benar.
*
* Lokasi file: app/src/test/java/com/example/tiptime/BmiUnitTest.kt
*/
class BmiUnitTest {
@Test
fun calculateBMI_isAccurate() {
// Data: berat 70 kg, tinggi 170 cm
val result = calculateBMI(70f, 170f)
// Hasil ekspektasi ≈ 24.22
assertEquals(24.22f, result, 0.01f)
}
@Test
fun calculateBMI_zeroHeight_returnsZero() {
val result = calculateBMI(70f, 0f)
assertEquals(0f, result, 0.0f)
}
@Test
fun getBMICategory_underweight_returnsKurus() {
val category = getBMICategory(17.0f)
assertEquals("Kurus", category)
}
@Test
fun getBMICategory_normal_returnsNormal() {
val category = getBMICategory(22.5f)
assertEquals("Normal", category)
}
@Test
fun getBMICategory_overweight_returnsKelebihanBerat() {
val category = getBMICategory(27.5f)
assertEquals("Kelebihan Berat", category)
}
@Test
fun getBMICategory_obese_returnsObesitas() {
val category = getBMICategory(31.0f)
assertEquals("Obesitas", category)
}
@Test
fun validateInput_validValues_returnsTrue() {
val isValid = validateInput(170f, 65f)
assertEquals(true, isValid)
}
@Test
fun validateInput_invalidValues_returnsFalse() {
val isValid = validateInput(5f, 500f)
assertEquals(false, isValid)
}
}

View File

@ -14,14 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
buildscript {
extra.apply {
set("compose_compiler_version", "1.5.3")
}
}
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id("com.android.application") version "8.7.3" apply false id("com.android.application") version "8.13.0" apply false
id("com.android.library") version "8.7.3" apply false id("com.android.library") version "8.13.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.10" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false
} }

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

3
gradlew vendored
View File

@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum