Progress 3

This commit is contained in:
E41212133_Naufal Kadhafi 2025-05-09 01:23:54 +07:00
parent 0c8df7e4c7
commit e13a914e28
29 changed files with 1251 additions and 109 deletions

View File

@ -388,6 +388,17 @@
<option name="screenX" value="1008" /> <option name="screenX" value="1008" />
<option name="screenY" value="2244" /> <option name="screenY" value="2244" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
<option name="codename" value="komodo" />
<option name="id" value="komodo" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro XL" />
<option name="screenDensity" value="360" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="33" /> <option name="api" value="33" />
<option name="brand" value="google" /> <option name="brand" value="google" />

View File

@ -56,6 +56,7 @@ dependencies {
// implementation (libs.ohteepee) // implementation (libs.ohteepee)
implementation (libs.ohteepee) implementation (libs.ohteepee)
implementation("io.coil-kt.coil3:coil-compose:3.0.0")
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)

View File

@ -35,7 +35,6 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.CaloryApp"> android:theme="@style/Theme.CaloryApp">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -43,6 +42,15 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
</manifest> </manifest>

Binary file not shown.

Binary file not shown.

View File

@ -1,20 +1,55 @@
package com.example.caloryapp package com.example.caloryapp
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.caloryapp.foodmodel.FoodDetectionViewModel
import com.example.caloryapp.navigation.Navigation import com.example.caloryapp.navigation.Navigation
import com.example.caloryapp.pages.camera.FoodCalorieScreen
import com.example.caloryapp.pages.camera.FoodCalorieViewModel2
import com.example.caloryapp.pages.camera.ScreenTest
//import com.example.caloryapp.pages.NavBarScreen //import com.example.caloryapp.pages.NavBarScreen
import com.example.caloryapp.ui.theme.CaloryAppTheme import com.example.caloryapp.ui.theme.CaloryAppTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
// val foodClassifier = FoodClassifier(this) // val foodClassifier = FoodClassifier(this)
if (ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_MEDIA_IMAGES
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.READ_MEDIA_IMAGES),
PERMISSION_REQUEST_CODE
)
}
setContent { setContent {
CaloryAppTheme { CaloryAppTheme {
// Surface(
// modifier = Modifier.fillMaxSize(),
// color = MaterialTheme.colorScheme.background
// ) {
// val viewModel = viewModel<FoodDetectionViewModel>()
// ScreenTest(viewModel)
// }
// val context = LocalContext.current // val context = LocalContext.current
// var classificationResult by remember { mutableStateOf("Memuat...") } // var classificationResult by remember { mutableStateOf("Memuat...") }
// //
@ -27,10 +62,14 @@ class MainActivity : ComponentActivity() {
// }) // })
// } // }
Navigation() Navigation()
// LoginScreen(navController = rememberNavController()) // LoginScreen(navController = rememberNavController())
// ProfileScreen(navController = rememberNavController()) // ProfileScreen(navController = rememberNavController())
// MainScreen() // MainScreen()
} }
} }
} }
companion object {
private const val PERMISSION_REQUEST_CODE = 123
}
} }

View File

@ -0,0 +1,28 @@
package com.example.caloryapp.foodmodel
enum class FoodCategory(
val displayName: String,
val caloriesPer100g: Int,
val colorHex: String,
val icon: String
) {
CARBS("Karbohidrat", 150, "#FECD45", "🍚"), // Tetap 150 kal/100g
PROTEIN("Protein", 250, "#FC8369", "🍗"), // Ubah ke 250 kal/100g
VEGETABLES("Sayuran", 30, "#4AB54A", "🥦"), // Tetap 30 kal/100g
FRUITS("Buah", 60, "#FF6B6B", "🍎"), // Tetap 60 kal/100g
OTHER("Lainnya", 200, "#A0A0A0", "🍬") // Tetap 200 kal/100g
}
data class FoodDetectionResult(
val mainCategory: FoodCategory,
val confidence: Float,
val allCategories: Map<FoodCategory, Float>
)
// Extension function untuk menghitung total kalori berdasarkan persentase makanan
fun Map<FoodCategory, Float>.calculateTotalCalories(plateWeightGrams: Int = 500): Int {
return this.entries.sumOf { (category, percentage) ->
val weightGrams = plateWeightGrams * percentage
(weightGrams * category.caloriesPer100g / 100).toInt()
}
}

View File

@ -0,0 +1,60 @@
package com.example.caloryapp.foodmodel
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
class FoodDetectionViewModel : ViewModel() {
var detectionResult by mutableStateOf<FoodDetectionResult?>(null)
private set
var isLoading by mutableStateOf(false)
private set
var errorMessage by mutableStateOf<String?>(null)
private set
fun detectFoodFromImage(context: Context, uri: Uri) {
isLoading = true
errorMessage = null
viewModelScope.launch {
try {
val bitmap = loadBitmapFromUri(context, uri)
// Buat detector di sini, tidak dalam withContext
val detector = FoodDetector(context)
val result = withContext(Dispatchers.IO) {
detector.detectFood(bitmap)
}
detectionResult = result
} catch (e: IOException) {
Log.e("FoodViewModel", "Error IO: ${e.message}", e)
errorMessage = "Gagal memuat gambar atau model: ${e.message}"
} catch (e: Exception) {
Log.e("FoodViewModel", "Error umum: ${e.message}", e)
errorMessage = "Terjadi kesalahan: ${e.message}"
} finally {
isLoading = false
}
}
}
private fun loadBitmapFromUri(context: Context, uri: Uri): Bitmap {
val contentResolver = context.contentResolver
return contentResolver.openInputStream(uri).use { inputStream ->
android.graphics.BitmapFactory.decodeStream(inputStream)
} ?: throw IOException("Gagal membuka gambar")
}
}

View File

@ -0,0 +1,93 @@
package com.example.caloryapp.foodmodel
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Matrix
import org.tensorflow.lite.Interpreter
import java.io.FileInputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.channels.FileChannel
class FoodDetector(private val context: Context) {
private var interpreter: Interpreter? = null
private val categories = listOf(
FoodCategory.CARBS,
FoodCategory.PROTEIN,
FoodCategory.VEGETABLES,
FoodCategory.FRUITS,
FoodCategory.OTHER
)
init {
loadModel()
}
private fun loadModel() {
val assetFileDescriptor = context.assets.openFd("model_food_plate_densenet.tflite")
val fileInputStream = FileInputStream(assetFileDescriptor.fileDescriptor)
val fileChannel = fileInputStream.channel
val startOffset = assetFileDescriptor.startOffset
val declaredLength = assetFileDescriptor.declaredLength
val mappedByteBuffer = fileChannel.map(
FileChannel.MapMode.READ_ONLY,
startOffset, declaredLength
)
interpreter = Interpreter(mappedByteBuffer)
}
fun detectFood(bitmap: Bitmap): FoodDetectionResult {
// Resize bitmap to 128x128 (model input size)
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, 128, 128, true)
// Convert bitmap to ByteBuffer - Perbaikan disini
val modelInput = ByteBuffer.allocateDirect(128 * 128 * 3 * 4) // 4 bytes per float
modelInput.order(ByteOrder.nativeOrder())
// Citra harus dinormalisasi ke [0, 1] seperti di Python
val pixels = IntArray(128 * 128)
resizedBitmap.getPixels(pixels, 0, 128, 0, 0, 128, 128)
for (pixel in pixels) {
// Extract RGB values and normalize to [0, 1]
val r = (pixel shr 16 and 0xFF) / 255.0f
val g = (pixel shr 8 and 0xFF) / 255.0f
val b = (pixel and 0xFF) / 255.0f
// Memastikan urutan RGB sesuai dengan model
modelInput.putFloat(r)
modelInput.putFloat(g)
modelInput.putFloat(b)
}
modelInput.rewind() // Penting: reset posisi buffer ke awal
// Run model - pastikan outputBuffer memiliki dimensi yang benar
val outputBuffer = Array(1) { FloatArray(5) } // 5 categories
interpreter?.run(modelInput, outputBuffer)
// Get predicted category
val confidences = outputBuffer[0]
val maxConfidenceIndex = confidences.indices.maxByOrNull { confidences[it] } ?: 0
// Membuat hasil yang lebih baik dengan threshold confidence
val minConfidenceThreshold = 0.4f // Atur sesuai kebutuhan
// Create detection result with confidence map
val detectedCategories = mutableMapOf<FoodCategory, Float>()
categories.forEachIndexed { index, category ->
detectedCategories[category] = confidences[index]
}
return FoodDetectionResult(
mainCategory = categories[maxConfidenceIndex],
confidence = confidences[maxConfidenceIndex],
allCategories = detectedCategories
)
}
fun close() {
interpreter?.close()
interpreter = null
}
}

View File

@ -0,0 +1,52 @@
package com.example.caloryapp.foodmodel
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
@Composable
fun PlateDiagram(
categories: Map<FoodCategory, Float>,
modifier: Modifier = Modifier
) {
Box(modifier = modifier) {
Canvas(modifier = Modifier.matchParentSize()) {
val canvasWidth = size.width
val canvasHeight = size.height
val radius = minOf(canvasWidth, canvasHeight) / 2
val center = androidx.compose.ui.geometry.Offset(canvasWidth / 2, canvasHeight / 2)
// Draw plate outline
drawCircle(
color = Color.LightGray,
radius = radius,
center = center,
style = Stroke(width = 2f)
)
// Draw food sections based on confidence
var startAngle = 0f
categories.forEach { (category, confidence) ->
val sweepAngle = 360f * confidence
val color = Color(android.graphics.Color.parseColor(category.colorHex))
drawArc(
color = color,
startAngle = startAngle,
sweepAngle = sweepAngle,
useCenter = true,
topLeft = center - androidx.compose.ui.geometry.Offset(radius, radius),
size = androidx.compose.ui.geometry.Size(radius * 2, radius * 2)
)
startAngle += sweepAngle
}
}
}
}

View File

@ -1,28 +1,37 @@
package com.example.caloryapp.navigation package com.example.caloryapp.navigation
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.example.caloryapp.pages.onboard.LoginScreen import com.example.caloryapp.foodmodel.FoodDetectionViewModel
import com.example.caloryapp.pages.MainScreen
import com.example.caloryapp.pages.account.ProfileChangePasswordScreen
import com.example.caloryapp.pages.account.ProfileDetailScreen import com.example.caloryapp.pages.account.ProfileDetailScreen
import com.example.caloryapp.pages.account.ProfileScreen
import com.example.caloryapp.pages.camera.ScreenTest
import com.example.caloryapp.pages.onboard.ChangePasswordScreen import com.example.caloryapp.pages.onboard.ChangePasswordScreen
import com.example.caloryapp.pages.onboard.ForgotPasswordScreen import com.example.caloryapp.pages.onboard.ForgotPasswordScreen
import com.example.caloryapp.pages.onboard.LoginScreen
import com.example.caloryapp.pages.onboard.OTPVerificationScreen import com.example.caloryapp.pages.onboard.OTPVerificationScreen
import com.example.caloryapp.pages.onboard.OnBoardingScreen import com.example.caloryapp.pages.onboard.OnBoardingScreen
import com.example.caloryapp.pages.onboard.RegisterScreen import com.example.caloryapp.pages.onboard.RegisterScreen
import com.example.caloryapp.pages.onboard.SuccessChangePassword import com.example.caloryapp.pages.onboard.SuccessChangePassword
import com.example.caloryapp.pages.onboard.SuccessRegister import com.example.caloryapp.pages.onboard.SuccessRegister
import com.example.caloryapp.viewmodel.UserViewModel import com.example.caloryapp.viewmodel.UserViewModel
import com.example.caloryapp.pages.MainScreen
import com.example.caloryapp.pages.account.ProfileChangePasswordScreen
@Composable @Composable
fun Navigation(modifier: Modifier = Modifier) { fun Navigation(modifier: Modifier = Modifier) {
val navController = rememberNavController() val navController = rememberNavController()
val userViewModel: UserViewModel = viewModel() val userViewModel: UserViewModel = viewModel()
val foodViewModel: FoodDetectionViewModel = viewModel()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
NavHost( NavHost(
navController = navController, navController = navController,
@ -43,11 +52,25 @@ fun Navigation(modifier: Modifier = Modifier) {
composable(NavigationScreen.ProfileChangePasswordScreen.name) { composable(NavigationScreen.ProfileChangePasswordScreen.name) {
ProfileChangePasswordScreen(navController = navController, viewModel = userViewModel) ProfileChangePasswordScreen(navController = navController, viewModel = userViewModel)
} }
composable(NavigationScreen.ScreenTest.name) {
ScreenTest(navController = navController, viewModel = foodViewModel)
}
composable(NavigationScreen.ProfileScreen.name) {
ProfileScreen(
navController = navController,
viewModel = userViewModel,
scope = scope,
drawerState = drawerState
)
}
// composable(NavigationScreen.ScreenTest.name) {
// ScreenTest(navController = navController, viewModel = userViewModel)
// }
// composable(NavigationScreen.HomeScreen.name) { // composable(NavigationScreen.HomeScreen.name) {
// HomeScreen(navController = navController, userViewModel) // HomeScreen(navController = navController, userViewModel)
// } // }
composable(NavigationScreen.MainScreen.name) { composable(NavigationScreen.MainScreen.name) {
MainScreen(userViewModel) MainScreen(userViewModel, foodViewModel)
} }
composable(NavigationScreen.ForgotPasswordScreen.name) { composable(NavigationScreen.ForgotPasswordScreen.name) {
ForgotPasswordScreen(navController = navController) ForgotPasswordScreen(navController = navController)

View File

@ -14,7 +14,8 @@ enum class NavigationScreen {
SuccessRegister, SuccessRegister,
ProfileScreen, ProfileScreen,
ProfileDetailScreen, ProfileDetailScreen,
ProfileChangePasswordScreen; ProfileChangePasswordScreen,
ScreenTest;
fun fromRoute(route: String): NavigationScreen = fun fromRoute(route: String): NavigationScreen =
when (route.substringBefore("/")) { when (route.substringBefore("/")) {
@ -32,6 +33,7 @@ enum class NavigationScreen {
SuccessRegister.name -> SuccessRegister SuccessRegister.name -> SuccessRegister
ProfileDetailScreen.name -> ProfileDetailScreen ProfileDetailScreen.name -> ProfileDetailScreen
ProfileChangePasswordScreen.name -> ProfileChangePasswordScreen ProfileChangePasswordScreen.name -> ProfileChangePasswordScreen
ScreenTest.name -> ScreenTest
else -> throw IllegalArgumentException("$route gagal bji") else -> throw IllegalArgumentException("$route gagal bji")
} }

View File

@ -35,11 +35,14 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.example.caloryapp.R import com.example.caloryapp.R
import com.example.caloryapp.foodmodel.FoodDetectionViewModel
import com.example.caloryapp.navigation.NavigationScreen import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.pages.account.ProfileChangePasswordScreen import com.example.caloryapp.pages.account.ProfileChangePasswordScreen
import com.example.caloryapp.pages.account.ProfileDetailScreen import com.example.caloryapp.pages.account.ProfileDetailScreen
import com.example.caloryapp.pages.account.ProfileScreen import com.example.caloryapp.pages.account.ProfileScreen
import com.example.caloryapp.pages.camera.ScreenTest
import com.example.caloryapp.pages.dashboard.HomeScreen import com.example.caloryapp.pages.dashboard.HomeScreen
import com.example.caloryapp.pages.onboard.LoginScreen
import com.example.caloryapp.ui.theme.bold import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.medium import com.example.caloryapp.ui.theme.medium
import com.example.caloryapp.ui.theme.primary import com.example.caloryapp.ui.theme.primary
@ -55,7 +58,8 @@ sealed class DrawerScreen(val title: String) {
@SuppressLint("UnusedMaterialScaffoldPaddingParameter", "UnusedMaterial3ScaffoldPaddingParameter") @SuppressLint("UnusedMaterialScaffoldPaddingParameter", "UnusedMaterial3ScaffoldPaddingParameter")
@Composable @Composable
fun MainScreen( fun MainScreen(
userViewModel: UserViewModel userViewModel: UserViewModel,
foodDetectionViewModel: FoodDetectionViewModel
) { ) {
val navController = rememberNavController() val navController = rememberNavController()
val drawerState = rememberDrawerState(DrawerValue.Closed) val drawerState = rememberDrawerState(DrawerValue.Closed)
@ -80,6 +84,12 @@ fun MainScreen(
composable(NavigationScreen.ProfileChangePasswordScreen.name) { composable(NavigationScreen.ProfileChangePasswordScreen.name) {
ProfileChangePasswordScreen(navController = navController, viewModel = userViewModel) ProfileChangePasswordScreen(navController = navController, viewModel = userViewModel)
} }
composable(NavigationScreen.ScreenTest.name) {
ScreenTest(navController = navController, viewModel = foodDetectionViewModel)
}
composable(NavigationScreen.LoginScreen.name) {
LoginScreen(navController = navController, viewModel = userViewModel)
}
} }
} }
} }

View File

@ -1,5 +1,6 @@
package com.example.caloryapp.pages.account package com.example.caloryapp.pages.account
import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -20,42 +21,44 @@ import androidx.compose.material.Icon
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowLeft import androidx.compose.material.icons.filled.KeyboardArrowLeft
import androidx.compose.material3.DrawerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.caloryapp.R import com.example.caloryapp.R
import com.example.caloryapp.repository.UserRepository
import com.example.caloryapp.ui.theme.background import com.example.caloryapp.ui.theme.background
import com.example.caloryapp.ui.theme.blueunderlined
import com.example.caloryapp.ui.theme.bold import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.primaryblack import com.example.caloryapp.ui.theme.primaryblack
import com.example.caloryapp.ui.theme.semibold
import com.example.caloryapp.viewmodel.UserViewModel import com.example.caloryapp.viewmodel.UserViewModel
import com.example.caloryapp.widget.CustomTextField import com.example.caloryapp.widget.CustomTextField
@Composable @Composable
fun ProfileDetailScreen( fun ProfileDetailScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
// drawerState: DrawerState,
// scope: kotlinx.coroutines.CoroutineScope,
navController: NavController, navController: NavController,
viewModel: UserViewModel viewModel: UserViewModel
) { ) {
val user = viewModel.user.value val user = viewModel.user.value
var username by remember { mutableStateOf(user!!.username) }
var username by remember { mutableStateOf("") } var gmail by remember { mutableStateOf(user!!.email) }
var gmail by remember { mutableStateOf("") } var fullName by remember { mutableStateOf(user!!.fullName) }
var fullName by remember { mutableStateOf("") } var selectedGender by remember { mutableStateOf(user!!.gender) }
var password by remember { mutableStateOf("") } var weight by remember { mutableStateOf(user!!.weight.toString()) }
var selectedGender by remember { mutableStateOf("") } var height by remember { mutableStateOf(user!!.height.toString()) }
var weight by remember { mutableStateOf("") } var isEditing by remember { mutableStateOf(false) } // Untuk toggle mode edit
var height by remember { mutableStateOf("") } val userRepository = UserRepository()
val context = LocalContext.current
Box( Box(
Modifier Modifier
@ -65,21 +68,16 @@ fun ProfileDetailScreen(
Column( Column(
Modifier Modifier
.padding(horizontal = 25.dp, vertical = 50.dp) .padding(horizontal = 25.dp, vertical = 50.dp)
.verticalScroll( .verticalScroll(rememberScrollState())
rememberScrollState()
)
) { ) {
Spacer(modifier.height(50.dp)) Spacer(modifier.height(50.dp))
Row( Row(Modifier.fillMaxWidth(), Arrangement.Start) {
Modifier.fillMaxWidth(),
Arrangement.Start,
) {
Icon( Icon(
imageVector = Icons.Default.KeyboardArrowLeft, imageVector = Icons.Default.KeyboardArrowLeft,
contentDescription = null, contentDescription = null,
Modifier Modifier
.size(28.dp) .size(28.dp)
.clickable { navController.popBackStack()} .clickable { navController.popBackStack() }
) )
Spacer(modifier = Modifier.width(30.dp)) Spacer(modifier = Modifier.width(30.dp))
Text( Text(
@ -90,8 +88,56 @@ fun ProfileDetailScreen(
fontFamily = bold fontFamily = bold
) )
) )
Row(
Modifier.fillMaxWidth(), Arrangement.End
) {
Text(
modifier = Modifier.clickable {
if (username.isNotEmpty() && fullName.isNotEmpty() && gmail.isNotEmpty() && selectedGender.isNotEmpty() && weight.isNotEmpty() && height.isNotEmpty()) {
if (isEditing) {
// Jika tombol Edit ditekan dan dalam mode edit, simpan perubahan
userRepository.updateUserData(
username,
fullName,
gmail,
selectedGender,
weight,
height
) { success ->
if (success) {
Toast.makeText(
context,
"Data Berhasil Diubah",
Toast.LENGTH_SHORT
)
.show()
isEditing = false
}
}
} else {
isEditing = true
}
} else {
Toast.makeText(
context,
"Gagal",
Toast.LENGTH_SHORT
).show()
}
},
text = if (isEditing) "Simpan" else "Edit",
style = TextStyle(
fontSize = 20.sp,
color = blueunderlined,
fontFamily = semibold
)
)
}
} }
Spacer(modifier = Modifier.height(64.dp)) Spacer(modifier = Modifier.height(64.dp))
// Gambar profil
Row(Modifier.fillMaxWidth(), Arrangement.Center) { Row(Modifier.fillMaxWidth(), Arrangement.Center) {
Image( Image(
painter = painterResource(id = R.drawable.ic_profile_men), painter = painterResource(id = R.drawable.ic_profile_men),
@ -101,6 +147,7 @@ fun ProfileDetailScreen(
} }
Spacer(modifier.height(45.dp)) Spacer(modifier.height(45.dp))
// Nama Lengkap
Text( Text(
text = "Nama Lengkap", text = "Nama Lengkap",
style = TextStyle( style = TextStyle(
@ -111,13 +158,14 @@ fun ProfileDetailScreen(
) )
Spacer(modifier.height(12.dp)) Spacer(modifier.height(12.dp))
CustomTextField( CustomTextField(
value = user!!.fullName, value = fullName,
onValueChange = { fullName = it }, onValueChange = { fullName = it },
placeholderText = "Masukkan Nama Lengkap", placeholderText = "Masukkan Nama Lengkap",
input = false input = isEditing
) )
Spacer(modifier.height(20.dp)) Spacer(modifier.height(20.dp))
// Username
Text( Text(
text = "Username", text = "Username",
style = TextStyle( style = TextStyle(
@ -128,13 +176,14 @@ fun ProfileDetailScreen(
) )
Spacer(modifier.height(12.dp)) Spacer(modifier.height(12.dp))
CustomTextField( CustomTextField(
value = user.username, value = username,
onValueChange = { }, onValueChange = { username = it },
placeholderText = "Masukkan Nama Lengkap", placeholderText = "Masukkan Username",
input = false input = false // Tidak dapat mengedit username
) )
Spacer(modifier.height(20.dp)) Spacer(modifier.height(20.dp))
// Email
Text( Text(
text = "Email", text = "Email",
style = TextStyle( style = TextStyle(
@ -145,13 +194,14 @@ fun ProfileDetailScreen(
) )
Spacer(modifier.height(12.dp)) Spacer(modifier.height(12.dp))
CustomTextField( CustomTextField(
value = user.email, value = gmail,
onValueChange = { }, onValueChange = { gmail = it },
placeholderText = "Masukkan Nama Lengkap", placeholderText = "Masukkan Email",
input = false input = isEditing
) )
Spacer(modifier.height(20.dp)) Spacer(modifier.height(20.dp))
// Gender
Text( Text(
text = "Gender", text = "Gender",
style = TextStyle( style = TextStyle(
@ -162,13 +212,14 @@ fun ProfileDetailScreen(
) )
Spacer(modifier.height(12.dp)) Spacer(modifier.height(12.dp))
CustomTextField( CustomTextField(
value = user.gender, value = selectedGender,
onValueChange = { }, onValueChange = { selectedGender = it },
placeholderText = "Masukkan Nama Lengkap", placeholderText = "Masukkan Gender",
input = false input = isEditing
) )
Spacer(modifier.height(20.dp)) Spacer(modifier.height(20.dp))
// Berat Badan
Text( Text(
text = "Berat Badan", text = "Berat Badan",
style = TextStyle( style = TextStyle(
@ -179,13 +230,14 @@ fun ProfileDetailScreen(
) )
Spacer(modifier.height(12.dp)) Spacer(modifier.height(12.dp))
CustomTextField( CustomTextField(
value = "${user.weight} kg", value = weight,
onValueChange = { }, onValueChange = { weight = it },
placeholderText = "Masukkan Nama Lengkap", placeholderText = "Masukkan Berat Badan",
input = false input = isEditing
) )
Spacer(modifier.height(20.dp)) Spacer(modifier.height(20.dp))
// Tinggi Badan
Text( Text(
text = "Tinggi Badan", text = "Tinggi Badan",
style = TextStyle( style = TextStyle(
@ -196,11 +248,15 @@ fun ProfileDetailScreen(
) )
Spacer(modifier.height(12.dp)) Spacer(modifier.height(12.dp))
CustomTextField( CustomTextField(
value = "${user.height} cm", value = height,
onValueChange = { }, onValueChange = { height = it },
placeholderText = "Masukkan Nama Lengkap", placeholderText = "Masukkan Tinggi Badan",
input = false input = isEditing
) )
// Tombol Edit
Spacer(modifier.height(30.dp))
} }
} }
} }

View File

@ -24,6 +24,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
@ -33,6 +34,7 @@ import androidx.compose.ui.unit.sp
import androidx.core.os.unregisterForAllProfilingResults import androidx.core.os.unregisterForAllProfilingResults
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.caloryapp.R import com.example.caloryapp.R
import com.example.caloryapp.navigation.Navigation
import com.example.caloryapp.navigation.NavigationScreen import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.pages.DrawerScreen import com.example.caloryapp.pages.DrawerScreen
import com.example.caloryapp.ui.theme.background import com.example.caloryapp.ui.theme.background
@ -45,11 +47,16 @@ import com.example.caloryapp.ui.theme.primaryred
import com.example.caloryapp.ui.theme.regular import com.example.caloryapp.ui.theme.regular
import com.example.caloryapp.viewmodel.UserViewModel import com.example.caloryapp.viewmodel.UserViewModel
import com.example.caloryapp.widget.SimpleAlertDialog import com.example.caloryapp.widget.SimpleAlertDialog
import com.google.firebase.auth.FirebaseAuth
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
fun logoutUser() {
FirebaseAuth.getInstance().signOut()
}
@Composable @Composable
fun ProfileScreen( fun ProfileScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -221,8 +228,9 @@ fun ProfileScreen(
dialogSubTitle = "Apakah Anda yakin ingin keluar?", dialogSubTitle = "Apakah Anda yakin ingin keluar?",
onDismissRequest = { openAlertDialog.value = false }, onDismissRequest = { openAlertDialog.value = false },
onConfirmation = { onConfirmation = {
logoutUser()
openAlertDialog.value = false openAlertDialog.value = false
// Tambahkan aksi logout di sini // Navigation()
} }
) )
} }

View File

@ -0,0 +1,286 @@
package com.example.caloryapp.pages.camera
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.tensorflow.lite.Interpreter
import org.tensorflow.lite.support.common.FileUtil
import java.io.File
import java.io.FileInputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel
// Model ViewModel
class FoodCalorieViewModel2 : ViewModel() {
// Kategori makanan dan informasi kalori
private val categoryNames = listOf("Karbohidrat", "Protein", "Sayur", "Buah", "Lainnya")
private val caloriesPerCategory = mapOf(
0 to 140, // Karbohidrat (nilai rata-rata 130-150)
1 to 225, // Protein (nilai rata-rata 200-250)
2 to 35, // Sayur (nilai rata-rata 25-50)
3 to 65, // Buah (nilai rata-rata 50-80)
4 to 350 // Lainnya (nilai rata-rata 300-400)
)
// State untuk UI
private val _selectedImageUri = mutableStateOf<Uri?>(null)
val selectedImageUri: State<Uri?> = _selectedImageUri
private val _predictionResult = mutableStateOf<PredictionResult?>(null)
val predictionResult: State<PredictionResult?> = _predictionResult
private val _isLoading = mutableStateOf(false)
val isLoading: State<Boolean> = _isLoading
// Fungsi untuk memuat model TFLite
private fun loadModelFile(context: Context): MappedByteBuffer {
return try {
val assetManager = context.assets
val modelPath = MODEL_PATH
val fileDescriptor = assetManager.openFd(modelPath)
FileInputStream(fileDescriptor.fileDescriptor).use { inputStream ->
val fileChannel = inputStream.channel
val startOffset = fileDescriptor.startOffset
val declaredLength = fileDescriptor.declaredLength
fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
}
} catch (e: Exception) {
e.printStackTrace()
throw RuntimeException("Error loading model: " + e.message)
}
}
// Fungsi untuk memproses gambar
private fun preprocessImage(bitmap: Bitmap): ByteBuffer {
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, 128, 128, true)
val byteBuffer = ByteBuffer.allocateDirect(4 * 128 * 128 * 3)
byteBuffer.order(ByteOrder.nativeOrder())
val pixels = IntArray(128 * 128)
resizedBitmap.getPixels(pixels, 0, 128, 0, 0, 128, 128)
for (pixel in pixels) {
// Normalisasi nilai RGB (0-255) ke (0-1)
byteBuffer.putFloat(((pixel shr 16) and 0xFF) / 255.0f) // R
byteBuffer.putFloat(((pixel shr 8) and 0xFF) / 255.0f) // G
byteBuffer.putFloat((pixel and 0xFF) / 255.0f) // B
}
return byteBuffer
}
// Fungsi untuk melakukan prediksi
fun predictImage(context: Context, uri: Uri) {
viewModelScope.launch {
_isLoading.value = true
_selectedImageUri.value = uri
try {
withContext(Dispatchers.IO) {
// Load gambar
val inputStream = context.contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
// Preprocess gambar
val modelInput = preprocessImage(bitmap)
// Load model TFLite
val modelBuffer = loadModelFile(context)
val interpreter = Interpreter(modelBuffer)
// Persiapkan output
val outputBuffer = Array(1) { FloatArray(5) }
// Jalankan inferensi
interpreter.run(modelInput, outputBuffer)
// Interpretasi hasil
val outputArray = outputBuffer[0]
val maxIndex = outputArray.indices.maxByOrNull { outputArray[it] } ?: 0
val confidence = outputArray[maxIndex]
// Dapatkan kategori dan kalori
val category = categoryNames[maxIndex]
val calories = caloriesPerCategory[maxIndex] ?: 0
// Perbarui UI
withContext(Dispatchers.Main) {
_predictionResult.value = PredictionResult(
category = category,
calories = calories,
confidence = confidence
)
}
// Tutup interpreter
interpreter.close()
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
_isLoading.value = false
}
}
}
companion object {
private const val MODEL_PATH = "model_food_plate_densenet.tflite"
// "model_food_calories.tflite"
}
// Kelas untuk hasil prediksi
data class PredictionResult(
val category: String,
val calories: Int,
val confidence: Float
)
}
// Layar utama
@Composable
fun FoodCalorieScreen(viewModel: FoodCalorieViewModel2) {
val context = LocalContext.current
val selectedImageUri by viewModel.selectedImageUri
val predictionResult by viewModel.predictionResult
val isLoading by viewModel.isLoading
// Create an ActivityResultLauncher for picking images from gallery
val galleryLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent()
) { uri: Uri? ->
// Handle the returned Uri
uri?.let {
viewModel.predictImage(context, it)
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Food Calorie Detector",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 24.dp)
)
// Tampilkan gambar yang dipilih
selectedImageUri?.let { uri ->
val bitmap = remember(uri) {
val inputStream = context.contentResolver.openInputStream(uri)
BitmapFactory.decodeStream(inputStream)
}
bitmap?.let {
Image(
bitmap = it.asImageBitmap(),
contentDescription = "Selected Food Image",
modifier = Modifier
.size(200.dp)
.padding(bottom = 16.dp)
)
}
}
// Tombol pilih gambar (updated)
Button(
onClick = {
// Launch gallery to pick an image
galleryLauncher.launch("image/*")
},
modifier = Modifier.padding(vertical = 16.dp)
) {
Text("Select Food Image")
}
// Tampilkan hasil prediksi
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.padding(16.dp))
} else {
predictionResult?.let { result ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "Detected Food: ${result.category}",
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Estimated Calories: ${result.calories} kcal (per 100g)",
style = MaterialTheme.typography.bodyLarge
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Confidence: ${(result.confidence * 100).toInt()}%",
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Daily value: ${calculateDailyValue(result.category, result.calories)}",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
}
}
// Fungsi untuk menghitung persentase dari kebutuhan harian
fun calculateDailyValue(category: String, calories: Int): String {
// Asumsi kebutuhan harian rata-rata adalah 2000 kalori
val dailyCalorieNeed = 2000
// Asumsi porsi standar (dalam gram)
val standardPortion = when (category) {
"Karbohidrat" -> 150 // Misalnya 150g nasi
"Protein" -> 100 // Misalnya 100g daging/ikan
"Sayur" -> 200 // Misalnya 200g sayuran
"Buah" -> 150 // Misalnya 150g buah
"Lainnya" -> 50 // Misalnya 50g camilan
else -> 100
}
// Kalori per porsi standar
val caloriesPerPortion = calories * standardPortion / 100
// Persentase dari kebutuhan harian
val percentage = (caloriesPerPortion * 100 / dailyCalorieNeed.toFloat()).toInt()
return "A standard portion ($standardPortion g) provides $caloriesPerPortion kcal, which is $percentage% of daily needs"
}

View File

@ -0,0 +1,259 @@
package com.example.caloryapp.pages.camera
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
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.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.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import coil3.compose.rememberAsyncImagePainter
import com.example.caloryapp.foodmodel.FoodDetectionViewModel
import com.example.caloryapp.foodmodel.PlateDiagram
import com.example.caloryapp.foodmodel.calculateTotalCalories
import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.medium
import com.example.caloryapp.ui.theme.primary
import com.example.caloryapp.ui.theme.semibold
@Composable
fun ScreenTest(navController: NavController, viewModel: FoodDetectionViewModel) {
val context = LocalContext.current
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
val getContent = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent()
) { uri: Uri? ->
uri?.let {
selectedImageUri = it
viewModel.detectFoodFromImage(context, it)
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 25.dp, vertical = 50.dp)
.verticalScroll(rememberScrollState()),
) {
Spacer(modifier = Modifier.height(60.dp))
Text(
text = "Pindai Makanan Kamu",
style = TextStyle(
fontSize = 38.sp,
color = primary,
fontFamily = bold
)
)
Spacer(modifier = Modifier.height(18.dp))
Text(
text = "Penuhi Kebutuhan Kalori Kamu Hari ini Yuk!",
style = TextStyle(
fontSize = 21.sp,
color = primary,
fontFamily = bold
)
)
// Selected image preview
if (selectedImageUri != null) {
Spacer(modifier = Modifier.height(40.dp))
Row(Modifier.fillMaxWidth(), Arrangement.Center) {
Image(
painter = rememberAsyncImagePainter(selectedImageUri),
contentDescription = "Selected image",
modifier = Modifier
.size(250.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(16.dp))
}
Spacer(modifier = Modifier.height(40.dp))
} else {
// Placeholder
Spacer(modifier = Modifier.height(40.dp))
Row(Modifier.fillMaxWidth(), Arrangement.Center) {
Box(
modifier = Modifier
.size(250.dp)
.clip(RoundedCornerShape(8.dp))
.padding(16.dp),
contentAlignment = Alignment.Center
) {
androidx.compose.material.Text(
text = "Pilih Gambar Makanan",
style = TextStyle(
fontSize = 18.sp,
color = primary,
letterSpacing = 1.sp,
fontFamily = medium,
textAlign = TextAlign.Center
)
)
}
Spacer(modifier = Modifier.height(16.dp))
}
Spacer(modifier = Modifier.height(40.dp))
}
// Button to select image
androidx.compose.material.Button(
onClick = {
getContent.launch("image/*")
},
modifier = Modifier
.width(360.dp)
.height(50.dp),
colors = androidx.compose.material.ButtonDefaults.buttonColors(backgroundColor = primary),
shape = RoundedCornerShape(20.dp)
) {
androidx.compose.material.Text(
text = "Pilih",
style = TextStyle(
fontSize = 18.sp,
color = Color.White,
fontFamily = semibold,
textAlign = TextAlign.Center
)
)
}
// Button(
// onClick = { },
// modifier = Modifier.fillMaxWidth()
// ) {
// Text("Pilih Gambar dari Galeri")
// }
Spacer(modifier = Modifier.height(32.dp))
// Loading indicator
if (viewModel.isLoading) {
CircularProgressIndicator()
}
// Error message
viewModel.errorMessage?.let { error ->
Text(
text = error,
color = Color.Red,
modifier = Modifier.padding(vertical = 8.dp)
)
}
// Detection results
viewModel.detectionResult?.let { result ->
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Text(
// text = "Hasil Deteksi",
// fontSize = 20.sp,
// fontWeight = FontWeight.Bold,
// modifier = Modifier.padding(bottom = 16.dp)
// )
// Food plate diagram
PlateDiagram(
categories = result.allCategories,
modifier = Modifier
.size(200.dp)
.padding(8.dp)
)
Spacer(modifier = Modifier.height(16.dp))
// Main category and caloriesd
val totalCalories = result.allCategories.calculateTotalCalories()
// Text(
// text = "Kategori Utama: ${result.mainCategory.displayName}",
// fontWeight = FontWeight.SemiBold
// )
Text(
text = "$totalCalories",
style = TextStyle(
fontSize = 24.sp,
color = primary,
fontFamily = bold,
textDecoration = TextDecoration.Underline
)
)
Text(
text = "Kalori di Makanan Kamu Hari ini",
style = TextStyle(
fontSize = 18.sp,
color = primary,
fontFamily = semibold
)
)
Spacer(modifier = Modifier.height(20.dp))
// Spacer(modifier = Modifier.height(16.dp))
// Categories breakdown
// Text(
// text = "Komposisi Makanan:",
// fontWeight = FontWeight.Medium,
// modifier = Modifier.padding(bottom = 8.dp)
// )
result.allCategories.forEach { (category, confidence) ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "${category.icon} ${category.displayName}",
color = Color(android.graphics.Color.parseColor(category.colorHex))
)
Text(
text = "%.0f%%".format(confidence * 100),
fontWeight = FontWeight.Medium
)
}
}
}
}
}
}

View File

@ -14,6 +14,8 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -25,26 +27,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.caloryapp.R import com.example.caloryapp.R
import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.ui.theme.background import com.example.caloryapp.ui.theme.background
import com.example.caloryapp.ui.theme.bold import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.primary import com.example.caloryapp.ui.theme.primary
import com.example.caloryapp.ui.theme.primaryblack
import com.example.caloryapp.ui.theme.primarygrey
import com.example.caloryapp.widget.FilterBar
import kotlinx.coroutines.launch
import android.widget.Toast
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModel
import com.example.caloryapp.model.UserModel
import com.example.caloryapp.pages.camera.FoodClassifier
import com.example.caloryapp.viewmodel.UserViewModel import com.example.caloryapp.viewmodel.UserViewModel
import androidx.lifecycle.viewmodel.compose.viewModel import com.example.caloryapp.widget.FilterBar
@Composable @Composable
@ -71,7 +64,7 @@ fun HomeScreen(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Image( Image(
modifier = Modifier.clickable { }, modifier = Modifier.clickable { },
painter = painterResource(id = R.drawable.ic_home_acc), painter = painterResource(id = R.drawable.ic_home_acc),
contentDescription = null contentDescription = null
) )
@ -132,5 +125,14 @@ fun HomeScreen(
// Filter Bar // Filter Bar
FilterBar(selectedFilter = selectedFilter, onFilterSelected = { selectedFilter = it }) FilterBar(selectedFilter = selectedFilter, onFilterSelected = { selectedFilter = it })
} }
FloatingActionButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 50.dp, bottom = 70.dp),
contentColor = Color.White,
backgroundColor = primary,
onClick = { navController.navigate(NavigationScreen.ScreenTest.name) }) {
Icon(painter = painterResource(id = R.drawable.scan), contentDescription = null)
}
} }
} }

View File

@ -1,24 +1,35 @@
package com.example.caloryapp.pages.onboard package com.example.caloryapp.pages.onboard
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.background 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.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowLeft
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -27,36 +38,57 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.caloryapp.R import com.example.caloryapp.R
import com.example.caloryapp.navigation.NavigationScreen import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.repository.UserRepository
import com.example.caloryapp.ui.theme.background import com.example.caloryapp.ui.theme.background
import com.example.caloryapp.ui.theme.bold import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.primary import com.example.caloryapp.ui.theme.primary
import com.example.caloryapp.ui.theme.primaryblack import com.example.caloryapp.ui.theme.primaryblack
import com.example.caloryapp.widget.CustomPasswordTextField
import com.example.caloryapp.widget.CustomTextField import com.example.caloryapp.widget.CustomTextField
@Composable @Composable
fun ChangePasswordScreen(modifier: Modifier = Modifier, navController: NavController) { fun ChangePasswordScreen(modifier: Modifier = Modifier, navController: NavController) {
var newPassword by remember { mutableStateOf("") } var newPassword by remember { mutableStateOf("") }
var confirmPassword by remember { mutableStateOf("") } var confirmPassword by remember { mutableStateOf("") }
var username by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) } // Untuk menampilkan status loading
val context = LocalContext.current
val userRepository = UserRepository()
Box( Box(
Modifier Modifier
.fillMaxSize() .fillMaxSize()
.background(background)) { .background(background)
) {
Column(Modifier.padding(horizontal = 25.dp, vertical = 50.dp)) { Column(Modifier.padding(horizontal = 25.dp, vertical = 50.dp)) {
Spacer(Modifier.height(45.dp)) Spacer(Modifier.height(45.dp))
Row(Modifier.width(260.dp)) { Row(
Modifier.fillMaxWidth(),
Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.KeyboardArrowLeft,
contentDescription = null,
Modifier
.size(28.dp)
.clickable { navController.popBackStack() }
)
Spacer(modifier = Modifier.width(15.dp))
Text( Text(
stringResource(R.string.buat_kata_sandi_baru), stringResource(R.string.buat_kata_sandi_baru),
style = TextStyle( style = TextStyle(
fontSize = 35.sp, fontSize = 32.sp,
color = primaryblack, color = primaryblack,
fontFamily = bold fontFamily = bold
) )
) )
} }
Spacer(modifier = Modifier.height(20.dp))
Spacer(modifier.height(42.dp)) Spacer(modifier.height(42.dp))
androidx.compose.material.Text( androidx.compose.material.Text(
text = stringResource(R.string.masukkan_kata_sandi), text = stringResource(R.string.username),
style = TextStyle( style = TextStyle(
fontSize = 20.sp, fontSize = 20.sp,
color = primaryblack, color = primaryblack,
@ -65,13 +97,14 @@ fun ChangePasswordScreen(modifier: Modifier = Modifier, navController: NavContro
) )
Spacer(modifier.height(16.dp)) Spacer(modifier.height(16.dp))
CustomTextField( CustomTextField(
value = newPassword, value = username,
onValueChange = { newPassword = it },input = true, onValueChange = { username = it },
placeholderText = "Masukkan Kata Sandi" input = true,
placeholderText = "Masukkan Username Anda"
) )
Spacer(modifier.height(20.dp)) Spacer(modifier.height(20.dp))
androidx.compose.material.Text( androidx.compose.material.Text(
text = stringResource(R.string.konfirmasi_kata_sandi), text = "Kata Sandi Baru",
style = TextStyle( style = TextStyle(
fontSize = 20.sp, fontSize = 20.sp,
color = primaryblack, color = primaryblack,
@ -79,30 +112,92 @@ fun ChangePasswordScreen(modifier: Modifier = Modifier, navController: NavContro
) )
) )
Spacer(modifier.height(16.dp)) Spacer(modifier.height(16.dp))
CustomTextField( CustomPasswordTextField(
value = confirmPassword, value = newPassword,
onValueChange = { confirmPassword = it },input = true, onValueChange = { newPassword = it },
placeholderText = "Konfirmasi Kata Sandi" placeholderText = "Masukkan Kata Sandi Baru",
input = true
) )
Spacer(modifier.height(20.dp))
androidx.compose.material.Text(
text = "Konfirmasi Kata Sandi Baru",
style = TextStyle(
fontSize = 20.sp,
color = primaryblack,
fontFamily = bold
)
)
Spacer(modifier.height(16.dp))
CustomPasswordTextField(
value = confirmPassword,
onValueChange = { confirmPassword = it },
placeholderText = "Konfirmasi Kata Sandi Baru",
input = true
)
Spacer(modifier.height(45.dp)) Spacer(modifier.height(45.dp))
Button( Button(
onClick = { navController.navigate(NavigationScreen.SuccessChangePassword.name) }, onClick = {
if (username.isEmpty()) {
Toast.makeText(context, "Username Tidak Boleh Kosong!", Toast.LENGTH_SHORT)
.show()
} else if (newPassword.isEmpty()) {
Toast.makeText(context, "Masukkan Kata Sandi Terlebih Dahulu!", Toast.LENGTH_SHORT)
.show()
} else if (confirmPassword.isEmpty()) {
Toast.makeText(context, "Konfirmasi Kata Sandi Belum Diisi!", Toast.LENGTH_SHORT)
.show()
} else if (username.isNotEmpty() && newPassword.isNotEmpty() && confirmPassword.isNotEmpty()) {
if (newPassword == confirmPassword) {
isLoading = true // Menampilkan loading
userRepository.updatePasswordByUsername2(
username,
newPassword,
confirmPassword
) { success ->
isLoading = false // Menyembunyikan loading setelah selesai
if (success) {
// Navigasi ke layar sukses jika berhasil
navController.navigate(NavigationScreen.SuccessChangePassword.name)
} else {
// Tampilkan pesan kesalahan jika gagal
Log.e("ChangePassword", "Username Tidak Terdapat atau Tidak Sesuai")
Toast.makeText(context, "Username Tidak Terdapat atau Tidak Sesuai", Toast.LENGTH_SHORT)
.show()
}
}
} else {
// Tampilkan pesan kesalahan jika kata sandi tidak cocok
Log.e("ChangePassword", "Kata sandi tidak cocok")
Toast.makeText(
context,
"Konfirmasi Kata sandi tidak cocok",
Toast.LENGTH_SHORT
)
.show()
}
}
},
modifier modifier
.width(360.dp) .width(360.dp)
.height(50.dp), .height(50.dp),
colors = androidx.compose.material.ButtonDefaults.buttonColors(backgroundColor = primary), colors = androidx.compose.material.ButtonDefaults.buttonColors(backgroundColor = primary),
shape = RoundedCornerShape(20.dp) shape = RoundedCornerShape(20.dp)
) { ) {
androidx.compose.material.Text( if (isLoading) {
text = "Simpan", androidx.compose.material3.CircularProgressIndicator(color = Color.White) // Loading indicator
style = TextStyle( } else {
fontSize = 18.sp, androidx.compose.material.Text(
color = Color.White, text = "Simpan",
fontFamily = bold, style = TextStyle(
textAlign = TextAlign.Center fontSize = 18.sp,
color = Color.White,
fontFamily = bold,
textAlign = TextAlign.Center
)
) )
) }
} }
} }
} }
} }

View File

@ -15,7 +15,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
@ -48,6 +50,7 @@ import com.example.caloryapp.ui.theme.primaryblack
import com.example.caloryapp.ui.theme.semibold import com.example.caloryapp.ui.theme.semibold
import com.example.caloryapp.viewmodel.LoginState import com.example.caloryapp.viewmodel.LoginState
import com.example.caloryapp.viewmodel.UserViewModel import com.example.caloryapp.viewmodel.UserViewModel
import com.example.caloryapp.widget.CustomPasswordTextField
import com.example.caloryapp.widget.CustomTextField import com.example.caloryapp.widget.CustomTextField
@Composable @Composable
@ -78,7 +81,9 @@ fun LoginScreen(
.background(background) .background(background)
) { ) {
Column( Column(
modifier.padding(horizontal = 25.dp, vertical = 50.dp) modifier
.padding(horizontal = 25.dp, vertical = 50.dp)
.verticalScroll(rememberScrollState())
) { ) {
Spacer(modifier.height(50.dp)) Spacer(modifier.height(50.dp))
Text( Text(
@ -110,7 +115,7 @@ fun LoginScreen(
CustomTextField( CustomTextField(
value = username, value = username,
onValueChange = { username = it }, input = true, onValueChange = { username = it }, input = true,
placeholderText = "Username" placeholderText = "Masukkan Username"
) )
Spacer(modifier.height(16.dp)) Spacer(modifier.height(16.dp))
Text( Text(
@ -122,12 +127,30 @@ fun LoginScreen(
) )
) )
Spacer(modifier.height(16.dp)) Spacer(modifier.height(16.dp))
CustomTextField( CustomPasswordTextField(
value = password, value = password,
onValueChange = { password = it }, input = true, onValueChange = { password = it },
placeholderText = "Password" placeholderText = "Masukkan Kata Sandi",
input = true
) )
Spacer(modifier.height(18.dp)) Spacer(modifier.height(18.dp))
Row(
Modifier.fillMaxWidth(),
Arrangement.End
) {
Text(
modifier = Modifier.clickable { navController.navigate(NavigationScreen.ChangePasswordScreen.name) },
text = "Lupa Password",
style = TextStyle(
fontSize = 16.sp,
color = blueunderlined,
fontFamily = semibold,
textDecoration = TextDecoration.Underline
)
)
}
Spacer(modifier.height(35.dp))
Button( Button(
onClick = { onClick = {
if (username.isEmpty()) { if (username.isEmpty()) {
@ -151,7 +174,7 @@ fun LoginScreen(
style = TextStyle( style = TextStyle(
fontSize = 18.sp, fontSize = 18.sp,
color = Color.White, color = Color.White,
fontFamily = MaterialTheme.typography.h1.fontFamily, fontFamily = bold,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
) )

View File

@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.caloryapp.R import com.example.caloryapp.R
import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.ui.theme.background import com.example.caloryapp.ui.theme.background
import com.example.caloryapp.ui.theme.bold import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.primary import com.example.caloryapp.ui.theme.primary
@ -42,7 +43,7 @@ fun SuccessChangePassword(modifier: Modifier = Modifier, navController: NavContr
Column(Modifier.padding(horizontal = 25.dp, vertical = 50.dp)) { Column(Modifier.padding(horizontal = 25.dp, vertical = 50.dp)) {
Spacer(modifier = Modifier.height(50.dp)) Spacer(modifier = Modifier.height(50.dp))
Text( Text(
stringResource(R.string.password_kamu_berhasil_dibuat), "Kata Sandi Kamu Berhasil Diubah",
style = TextStyle( style = TextStyle(
fontSize = 35.sp, fontSize = 35.sp,
color = primaryblack, color = primaryblack,
@ -58,7 +59,7 @@ fun SuccessChangePassword(modifier: Modifier = Modifier, navController: NavContr
} }
Spacer(modifier = Modifier.height(115.dp)) Spacer(modifier = Modifier.height(115.dp))
Button( Button(
onClick = { }, onClick = { navController.navigate(NavigationScreen.LoginScreen.name) },
modifier modifier
.width(360.dp) .width(360.dp)
.height(50.dp), .height(50.dp),

View File

@ -20,6 +20,7 @@ class UserRepository {
} }
} }
fun registerUserWithCustomID(user: UserModel, userID: String, onComplete: (Boolean) -> Unit) { fun registerUserWithCustomID(user: UserModel, userID: String, onComplete: (Boolean) -> Unit) {
db.collection("users") db.collection("users")
.document(userID) .document(userID)
@ -49,6 +50,10 @@ class UserRepository {
} }
} }
fun logoutUser() {
FirebaseAuth.getInstance().signOut()
}
fun updatePasswordByUsername(username: String, oldPassword: String, newPassword: String, onComplete: (Boolean) -> Unit) { fun updatePasswordByUsername(username: String, oldPassword: String, newPassword: String, onComplete: (Boolean) -> Unit) {
db.collection("users") db.collection("users")
.whereEqualTo("username", username) // Mencari pengguna berdasarkan username .whereEqualTo("username", username) // Mencari pengguna berdasarkan username
@ -80,6 +85,68 @@ class UserRepository {
} }
} }
fun updateUserData(username: String, fullName: String, email: String, gender: String, weight: String, height: String, onComplete: (Boolean) -> Unit) {
db.collection("users")
.whereEqualTo("username", username)
.get()
.addOnSuccessListener { result ->
if (!result.isEmpty) {
val userDocument = result.documents[0]
// Memperbarui data pengguna
userDocument.reference.update(
"fullName", fullName,
"email", email,
"gender", gender,
"weight", weight,
"height", height
)
.addOnSuccessListener {
onComplete(true) // Update berhasil
}
.addOnFailureListener {
onComplete(false) // Update gagal
}
} else {
onComplete(false) // Username tidak ditemukan
}
}
.addOnFailureListener {
onComplete(false) // Terjadi error dalam pencarian data
}
}
fun updatePasswordByUsername2(username: String, newPassword: String, confirmPassword: String, onComplete: (Boolean) -> Unit) {
// Memeriksa apakah kata sandi baru dan konfirmasi cocok
if (newPassword != confirmPassword) {
onComplete(false) // Kata sandi tidak cocok
return
}
// Mencari pengguna berdasarkan username
db.collection("users")
.whereEqualTo("username", username)
.get()
.addOnSuccessListener { result ->
if (!result.isEmpty) {
val userDocument = result.documents[0]
// Memperbarui kata sandi pengguna
userDocument.reference.update("password", newPassword)
.addOnSuccessListener {
onComplete(true) // Update berhasil
}
.addOnFailureListener {
onComplete(false) // Update gagal
}
} else {
onComplete(false) // Username tidak ditemukan
}
}
.addOnFailureListener {
onComplete(false) // Terjadi error dalam pencarian data
}
}
// private val db = FirebaseFirestore.getInstance() // private val db = FirebaseFirestore.getInstance()
// private val auth = FirebaseAuth.getInstance() // private val auth = FirebaseAuth.getInstance()
// //

View File

@ -1,3 +1,5 @@
@file:Suppress("CAST_NEVER_SUCCEEDS")
package com.example.caloryapp.widget package com.example.caloryapp.widget
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -9,12 +11,16 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavController
import com.example.caloryapp.R import com.example.caloryapp.R
import com.example.caloryapp.navigation.Navigation
import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.ui.theme.bold import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.medium import com.example.caloryapp.ui.theme.medium
import com.example.caloryapp.ui.theme.primaryblack import com.example.caloryapp.ui.theme.primaryblack
@ -41,7 +47,9 @@ fun SimpleAlertDialog(
onDismissRequest() onDismissRequest()
}, },
confirmButton = { confirmButton = {
TextButton(onClick = { onConfirmation() }) { TextButton(onClick = {
onConfirmation()
}) {
androidx.compose.material.Text( androidx.compose.material.Text(
text = stringResource(R.string.ya), text = stringResource(R.string.ya),
style = TextStyle( style = TextStyle(

View File

@ -1,24 +1,27 @@
package com.example.caloryapp.widget package com.example.caloryapp.widget
//noinspection UsingMaterialAndMaterial3Libraries
//noinspection UsingMaterialAndMaterial3Libraries
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material3.Text
//noinspection UsingMaterialAndMaterial3Libraries
import androidx.compose.material.OutlinedTextField import androidx.compose.material.OutlinedTextField
//noinspection UsingMaterialAndMaterial3Libraries
import androidx.compose.material.TextFieldDefaults import androidx.compose.material.TextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.caloryapp.R import com.example.caloryapp.R
@ -26,12 +29,6 @@ import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.primaryblack import com.example.caloryapp.ui.theme.primaryblack
import com.example.caloryapp.ui.theme.primarygrey import com.example.caloryapp.ui.theme.primarygrey
import com.example.caloryapp.ui.theme.semibold import com.example.caloryapp.ui.theme.semibold
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Star
import androidx.compose.runtime.setValue
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
@Composable @Composable
fun CustomPasswordTextField( fun CustomPasswordTextField(
@ -66,16 +63,23 @@ fun CustomPasswordTextField(
}, },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(50.dp) .height(50.dp), // Add padding for the icon
.padding(end = 16.dp), // Add padding for the icon
visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(), visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = { trailingIcon = {
IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) { IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) {
Icon( Icon(
imageVector = if (isPasswordVisible) Icons.Filled.Check else Icons.Filled.Check, modifier = Modifier.size(25.dp),
contentDescription = "Toggle Password Visibility", painter = if (isPasswordVisible) painterResource(id = R.drawable.ic_show) else painterResource(
tint = Color.Gray // Sesuaikan dengan warna ikon pada desain id = R.drawable.ic_hide
),
contentDescription = null,
tint = Color.Gray
) )
// Icon(
// imageVector = if (isPasswordVisible) Icons.Filled.Check else Icons.Filled.Check,
// contentDescription = "Toggle Password Visibility",
// tint = Color.Gray // Sesuaikan dengan warna ikon pada desain
// )
} }
}, },
colors = TextFieldDefaults.outlinedTextFieldColors( colors = TextFieldDefaults.outlinedTextFieldColors(

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="my_images"
path="/" />
</paths>