diff --git a/.idea/other.xml b/.idea/other.xml
index 04cde7e..05e8821 100644
--- a/.idea/other.xml
+++ b/.idea/other.xml
@@ -388,6 +388,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index bf3f341..1e0b27a 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -56,6 +56,7 @@ dependencies {
// implementation (libs.ohteepee)
implementation (libs.ohteepee)
+ implementation("io.coil-kt.coil3:coil-compose:3.0.0")
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 05d76d8..2bc0b36 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -35,7 +35,6 @@
@@ -43,6 +42,15 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/model_food_calories.h5 b/app/src/main/assets/model_food_calories.h5
new file mode 100644
index 0000000..d4b49f1
Binary files /dev/null and b/app/src/main/assets/model_food_calories.h5 differ
diff --git a/app/src/main/assets/model_food_calories.tflite b/app/src/main/assets/model_food_calories.tflite
new file mode 100644
index 0000000..44031bc
Binary files /dev/null and b/app/src/main/assets/model_food_calories.tflite differ
diff --git a/app/src/main/java/com/example/caloryapp/assets/model_food_plate_densenet.h5 b/app/src/main/assets/model_food_plate_densenet.h5
similarity index 100%
rename from app/src/main/java/com/example/caloryapp/assets/model_food_plate_densenet.h5
rename to app/src/main/assets/model_food_plate_densenet.h5
diff --git a/app/src/main/java/com/example/caloryapp/assets/model_food_plate_densenet.tflite b/app/src/main/assets/model_food_plate_densenet.tflite
similarity index 100%
rename from app/src/main/java/com/example/caloryapp/assets/model_food_plate_densenet.tflite
rename to app/src/main/assets/model_food_plate_densenet.tflite
diff --git a/app/src/main/java/com/example/caloryapp/MainActivity.kt b/app/src/main/java/com/example/caloryapp/MainActivity.kt
index 77d1e8f..8acf70a 100644
--- a/app/src/main/java/com/example/caloryapp/MainActivity.kt
+++ b/app/src/main/java/com/example/caloryapp/MainActivity.kt
@@ -1,20 +1,55 @@
package com.example.caloryapp
+import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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.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.ui.theme.CaloryAppTheme
class MainActivity : ComponentActivity() {
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
// 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 {
CaloryAppTheme {
+// Surface(
+// modifier = Modifier.fillMaxSize(),
+// color = MaterialTheme.colorScheme.background
+// ) {
+// val viewModel = viewModel()
+// ScreenTest(viewModel)
+// }
// val context = LocalContext.current
// var classificationResult by remember { mutableStateOf("Memuat...") }
//
@@ -27,10 +62,14 @@ class MainActivity : ComponentActivity() {
// })
// }
Navigation()
+
// LoginScreen(navController = rememberNavController())
// ProfileScreen(navController = rememberNavController())
// MainScreen()
}
}
}
+ companion object {
+ private const val PERMISSION_REQUEST_CODE = 123
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/foodmodel/FoodCategory.kt b/app/src/main/java/com/example/caloryapp/foodmodel/FoodCategory.kt
new file mode 100644
index 0000000..626b611
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/foodmodel/FoodCategory.kt
@@ -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
+)
+
+// Extension function untuk menghitung total kalori berdasarkan persentase makanan
+fun Map.calculateTotalCalories(plateWeightGrams: Int = 500): Int {
+ return this.entries.sumOf { (category, percentage) ->
+ val weightGrams = plateWeightGrams * percentage
+ (weightGrams * category.caloriesPer100g / 100).toInt()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/foodmodel/FoodDetectionViewModel.kt b/app/src/main/java/com/example/caloryapp/foodmodel/FoodDetectionViewModel.kt
new file mode 100644
index 0000000..71a35a5
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/foodmodel/FoodDetectionViewModel.kt
@@ -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(null)
+ private set
+
+ var isLoading by mutableStateOf(false)
+ private set
+
+ var errorMessage by mutableStateOf(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")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/foodmodel/FoodDetector.kt b/app/src/main/java/com/example/caloryapp/foodmodel/FoodDetector.kt
new file mode 100644
index 0000000..52a72f0
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/foodmodel/FoodDetector.kt
@@ -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()
+ categories.forEachIndexed { index, category ->
+ detectedCategories[category] = confidences[index]
+ }
+
+ return FoodDetectionResult(
+ mainCategory = categories[maxConfidenceIndex],
+ confidence = confidences[maxConfidenceIndex],
+ allCategories = detectedCategories
+ )
+ }
+
+ fun close() {
+ interpreter?.close()
+ interpreter = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/foodmodel/PlateDiagram.kt b/app/src/main/java/com/example/caloryapp/foodmodel/PlateDiagram.kt
new file mode 100644
index 0000000..d4d05c9
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/foodmodel/PlateDiagram.kt
@@ -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,
+ 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
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/navigation/Navigation.kt b/app/src/main/java/com/example/caloryapp/navigation/Navigation.kt
index 24e4963..d270b0d 100644
--- a/app/src/main/java/com/example/caloryapp/navigation/Navigation.kt
+++ b/app/src/main/java/com/example/caloryapp/navigation/Navigation.kt
@@ -1,28 +1,37 @@
package com.example.caloryapp.navigation
+import androidx.compose.material3.DrawerValue
+import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
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.ProfileScreen
+import com.example.caloryapp.pages.camera.ScreenTest
import com.example.caloryapp.pages.onboard.ChangePasswordScreen
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.OnBoardingScreen
import com.example.caloryapp.pages.onboard.RegisterScreen
import com.example.caloryapp.pages.onboard.SuccessChangePassword
import com.example.caloryapp.pages.onboard.SuccessRegister
import com.example.caloryapp.viewmodel.UserViewModel
-import com.example.caloryapp.pages.MainScreen
-import com.example.caloryapp.pages.account.ProfileChangePasswordScreen
@Composable
fun Navigation(modifier: Modifier = Modifier) {
val navController = rememberNavController()
val userViewModel: UserViewModel = viewModel()
+ val foodViewModel: FoodDetectionViewModel = viewModel()
+ val drawerState = rememberDrawerState(DrawerValue.Closed)
+ val scope = rememberCoroutineScope()
NavHost(
navController = navController,
@@ -43,11 +52,25 @@ fun Navigation(modifier: Modifier = Modifier) {
composable(NavigationScreen.ProfileChangePasswordScreen.name) {
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) {
// HomeScreen(navController = navController, userViewModel)
// }
composable(NavigationScreen.MainScreen.name) {
- MainScreen(userViewModel)
+ MainScreen(userViewModel, foodViewModel)
}
composable(NavigationScreen.ForgotPasswordScreen.name) {
ForgotPasswordScreen(navController = navController)
diff --git a/app/src/main/java/com/example/caloryapp/navigation/NavigationScreen.kt b/app/src/main/java/com/example/caloryapp/navigation/NavigationScreen.kt
index 3c45dc3..794ee04 100644
--- a/app/src/main/java/com/example/caloryapp/navigation/NavigationScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/navigation/NavigationScreen.kt
@@ -14,7 +14,8 @@ enum class NavigationScreen {
SuccessRegister,
ProfileScreen,
ProfileDetailScreen,
- ProfileChangePasswordScreen;
+ ProfileChangePasswordScreen,
+ ScreenTest;
fun fromRoute(route: String): NavigationScreen =
when (route.substringBefore("/")) {
@@ -32,6 +33,7 @@ enum class NavigationScreen {
SuccessRegister.name -> SuccessRegister
ProfileDetailScreen.name -> ProfileDetailScreen
ProfileChangePasswordScreen.name -> ProfileChangePasswordScreen
+ ScreenTest.name -> ScreenTest
else -> throw IllegalArgumentException("$route gagal bji")
}
diff --git a/app/src/main/java/com/example/caloryapp/pages/MainScreen.kt b/app/src/main/java/com/example/caloryapp/pages/MainScreen.kt
index af429be..4356821 100644
--- a/app/src/main/java/com/example/caloryapp/pages/MainScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/MainScreen.kt
@@ -35,11 +35,14 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.caloryapp.R
+import com.example.caloryapp.foodmodel.FoodDetectionViewModel
import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.pages.account.ProfileChangePasswordScreen
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.dashboard.HomeScreen
+import com.example.caloryapp.pages.onboard.LoginScreen
import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.medium
import com.example.caloryapp.ui.theme.primary
@@ -55,7 +58,8 @@ sealed class DrawerScreen(val title: String) {
@SuppressLint("UnusedMaterialScaffoldPaddingParameter", "UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreen(
- userViewModel: UserViewModel
+ userViewModel: UserViewModel,
+ foodDetectionViewModel: FoodDetectionViewModel
) {
val navController = rememberNavController()
val drawerState = rememberDrawerState(DrawerValue.Closed)
@@ -80,6 +84,12 @@ fun MainScreen(
composable(NavigationScreen.ProfileChangePasswordScreen.name) {
ProfileChangePasswordScreen(navController = navController, viewModel = userViewModel)
}
+ composable(NavigationScreen.ScreenTest.name) {
+ ScreenTest(navController = navController, viewModel = foodDetectionViewModel)
+ }
+ composable(NavigationScreen.LoginScreen.name) {
+ LoginScreen(navController = navController, viewModel = userViewModel)
+ }
}
}
}
diff --git a/app/src/main/java/com/example/caloryapp/pages/account/ProfileDetailScreen.kt b/app/src/main/java/com/example/caloryapp/pages/account/ProfileDetailScreen.kt
index 94fd5f5..7967382 100644
--- a/app/src/main/java/com/example/caloryapp/pages/account/ProfileDetailScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/account/ProfileDetailScreen.kt
@@ -1,5 +1,6 @@
package com.example.caloryapp.pages.account
+import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -20,42 +21,44 @@ import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowLeft
-import androidx.compose.material3.DrawerState
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.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.caloryapp.R
+import com.example.caloryapp.repository.UserRepository
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.primaryblack
+import com.example.caloryapp.ui.theme.semibold
import com.example.caloryapp.viewmodel.UserViewModel
import com.example.caloryapp.widget.CustomTextField
@Composable
fun ProfileDetailScreen(
modifier: Modifier = Modifier,
-// drawerState: DrawerState,
-// scope: kotlinx.coroutines.CoroutineScope,
navController: NavController,
viewModel: UserViewModel
) {
val user = viewModel.user.value
-
- var username by remember { mutableStateOf("") }
- var gmail by remember { mutableStateOf("") }
- var fullName by remember { mutableStateOf("") }
- var password by remember { mutableStateOf("") }
- var selectedGender by remember { mutableStateOf("") }
- var weight by remember { mutableStateOf("") }
- var height by remember { mutableStateOf("") }
+ var username by remember { mutableStateOf(user!!.username) }
+ var gmail by remember { mutableStateOf(user!!.email) }
+ var fullName by remember { mutableStateOf(user!!.fullName) }
+ var selectedGender by remember { mutableStateOf(user!!.gender) }
+ var weight by remember { mutableStateOf(user!!.weight.toString()) }
+ var height by remember { mutableStateOf(user!!.height.toString()) }
+ var isEditing by remember { mutableStateOf(false) } // Untuk toggle mode edit
+ val userRepository = UserRepository()
+ val context = LocalContext.current
Box(
Modifier
@@ -65,21 +68,16 @@ fun ProfileDetailScreen(
Column(
Modifier
.padding(horizontal = 25.dp, vertical = 50.dp)
- .verticalScroll(
- rememberScrollState()
- )
+ .verticalScroll(rememberScrollState())
) {
Spacer(modifier.height(50.dp))
- Row(
- Modifier.fillMaxWidth(),
- Arrangement.Start,
- ) {
+ Row(Modifier.fillMaxWidth(), Arrangement.Start) {
Icon(
imageVector = Icons.Default.KeyboardArrowLeft,
contentDescription = null,
Modifier
.size(28.dp)
- .clickable { navController.popBackStack()}
+ .clickable { navController.popBackStack() }
)
Spacer(modifier = Modifier.width(30.dp))
Text(
@@ -90,8 +88,56 @@ fun ProfileDetailScreen(
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))
+
+ // Gambar profil
Row(Modifier.fillMaxWidth(), Arrangement.Center) {
Image(
painter = painterResource(id = R.drawable.ic_profile_men),
@@ -101,6 +147,7 @@ fun ProfileDetailScreen(
}
Spacer(modifier.height(45.dp))
+ // Nama Lengkap
Text(
text = "Nama Lengkap",
style = TextStyle(
@@ -111,13 +158,14 @@ fun ProfileDetailScreen(
)
Spacer(modifier.height(12.dp))
CustomTextField(
- value = user!!.fullName,
+ value = fullName,
onValueChange = { fullName = it },
placeholderText = "Masukkan Nama Lengkap",
- input = false
+ input = isEditing
)
Spacer(modifier.height(20.dp))
+ // Username
Text(
text = "Username",
style = TextStyle(
@@ -128,13 +176,14 @@ fun ProfileDetailScreen(
)
Spacer(modifier.height(12.dp))
CustomTextField(
- value = user.username,
- onValueChange = { },
- placeholderText = "Masukkan Nama Lengkap",
- input = false
+ value = username,
+ onValueChange = { username = it },
+ placeholderText = "Masukkan Username",
+ input = false // Tidak dapat mengedit username
)
Spacer(modifier.height(20.dp))
+ // Email
Text(
text = "Email",
style = TextStyle(
@@ -145,13 +194,14 @@ fun ProfileDetailScreen(
)
Spacer(modifier.height(12.dp))
CustomTextField(
- value = user.email,
- onValueChange = { },
- placeholderText = "Masukkan Nama Lengkap",
- input = false
+ value = gmail,
+ onValueChange = { gmail = it },
+ placeholderText = "Masukkan Email",
+ input = isEditing
)
Spacer(modifier.height(20.dp))
+ // Gender
Text(
text = "Gender",
style = TextStyle(
@@ -162,13 +212,14 @@ fun ProfileDetailScreen(
)
Spacer(modifier.height(12.dp))
CustomTextField(
- value = user.gender,
- onValueChange = { },
- placeholderText = "Masukkan Nama Lengkap",
- input = false
+ value = selectedGender,
+ onValueChange = { selectedGender = it },
+ placeholderText = "Masukkan Gender",
+ input = isEditing
)
Spacer(modifier.height(20.dp))
+ // Berat Badan
Text(
text = "Berat Badan",
style = TextStyle(
@@ -179,13 +230,14 @@ fun ProfileDetailScreen(
)
Spacer(modifier.height(12.dp))
CustomTextField(
- value = "${user.weight} kg",
- onValueChange = { },
- placeholderText = "Masukkan Nama Lengkap",
- input = false
+ value = weight,
+ onValueChange = { weight = it },
+ placeholderText = "Masukkan Berat Badan",
+ input = isEditing
)
Spacer(modifier.height(20.dp))
+ // Tinggi Badan
Text(
text = "Tinggi Badan",
style = TextStyle(
@@ -196,11 +248,15 @@ fun ProfileDetailScreen(
)
Spacer(modifier.height(12.dp))
CustomTextField(
- value = "${user.height} cm",
- onValueChange = { },
- placeholderText = "Masukkan Nama Lengkap",
- input = false
+ value = height,
+ onValueChange = { height = it },
+ placeholderText = "Masukkan Tinggi Badan",
+ input = isEditing
)
+
+ // Tombol Edit
+ Spacer(modifier.height(30.dp))
+
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/example/caloryapp/pages/account/ProfileScreen.kt b/app/src/main/java/com/example/caloryapp/pages/account/ProfileScreen.kt
index fc67321..f20fbda 100644
--- a/app/src/main/java/com/example/caloryapp/pages/account/ProfileScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/account/ProfileScreen.kt
@@ -24,6 +24,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
@@ -33,6 +34,7 @@ import androidx.compose.ui.unit.sp
import androidx.core.os.unregisterForAllProfilingResults
import androidx.navigation.NavController
import com.example.caloryapp.R
+import com.example.caloryapp.navigation.Navigation
import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.pages.DrawerScreen
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.viewmodel.UserViewModel
import com.example.caloryapp.widget.SimpleAlertDialog
+import com.google.firebase.auth.FirebaseAuth
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
+fun logoutUser() {
+ FirebaseAuth.getInstance().signOut()
+}
+
@Composable
fun ProfileScreen(
modifier: Modifier = Modifier,
@@ -221,8 +228,9 @@ fun ProfileScreen(
dialogSubTitle = "Apakah Anda yakin ingin keluar?",
onDismissRequest = { openAlertDialog.value = false },
onConfirmation = {
+ logoutUser()
openAlertDialog.value = false
- // Tambahkan aksi logout di sini
+// Navigation()
}
)
}
diff --git a/app/src/main/java/com/example/caloryapp/pages/camera/FoodTest2.kt b/app/src/main/java/com/example/caloryapp/pages/camera/FoodTest2.kt
new file mode 100644
index 0000000..1c8e83d
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/pages/camera/FoodTest2.kt
@@ -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(null)
+ val selectedImageUri: State = _selectedImageUri
+
+ private val _predictionResult = mutableStateOf(null)
+ val predictionResult: State = _predictionResult
+
+ private val _isLoading = mutableStateOf(false)
+ val isLoading: State = _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"
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/pages/camera/ScreenTest.kt b/app/src/main/java/com/example/caloryapp/pages/camera/ScreenTest.kt
new file mode 100644
index 0000000..4e61791
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/pages/camera/ScreenTest.kt
@@ -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(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
+ )
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/pages/dashboard/HomeScreen.kt b/app/src/main/java/com/example/caloryapp/pages/dashboard/HomeScreen.kt
index 21054e4..28ebb48 100644
--- a/app/src/main/java/com/example/caloryapp/pages/dashboard/HomeScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/dashboard/HomeScreen.kt
@@ -14,6 +14,8 @@ 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.material.FloatingActionButton
+import androidx.compose.material.Icon
import androidx.compose.material3.DrawerState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -25,26 +27,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.caloryapp.R
+import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.ui.theme.background
import com.example.caloryapp.ui.theme.bold
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 androidx.lifecycle.viewmodel.compose.viewModel
+import com.example.caloryapp.widget.FilterBar
@Composable
@@ -71,7 +64,7 @@ fun HomeScreen(
verticalAlignment = Alignment.CenterVertically
) {
Image(
- modifier = Modifier.clickable { },
+ modifier = Modifier.clickable { },
painter = painterResource(id = R.drawable.ic_home_acc),
contentDescription = null
)
@@ -132,5 +125,14 @@ fun HomeScreen(
// Filter Bar
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)
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/pages/onboard/ChangePasswordScreen.kt b/app/src/main/java/com/example/caloryapp/pages/onboard/ChangePasswordScreen.kt
index 1ea3820..6b879ce 100644
--- a/app/src/main/java/com/example/caloryapp/pages/onboard/ChangePasswordScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/onboard/ChangePasswordScreen.kt
@@ -1,24 +1,35 @@
package com.example.caloryapp.pages.onboard
+import android.util.Log
+import android.widget.Toast
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.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.shape.RoundedCornerShape
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.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.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
@@ -27,36 +38,57 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.caloryapp.R
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.bold
import com.example.caloryapp.ui.theme.primary
import com.example.caloryapp.ui.theme.primaryblack
+import com.example.caloryapp.widget.CustomPasswordTextField
import com.example.caloryapp.widget.CustomTextField
@Composable
fun ChangePasswordScreen(modifier: Modifier = Modifier, navController: NavController) {
var newPassword 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(
Modifier
.fillMaxSize()
- .background(background)) {
+ .background(background)
+ ) {
Column(Modifier.padding(horizontal = 25.dp, vertical = 50.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(
stringResource(R.string.buat_kata_sandi_baru),
style = TextStyle(
- fontSize = 35.sp,
+ fontSize = 32.sp,
color = primaryblack,
fontFamily = bold
)
)
}
+ Spacer(modifier = Modifier.height(20.dp))
+
Spacer(modifier.height(42.dp))
androidx.compose.material.Text(
- text = stringResource(R.string.masukkan_kata_sandi),
+ text = stringResource(R.string.username),
style = TextStyle(
fontSize = 20.sp,
color = primaryblack,
@@ -65,13 +97,14 @@ fun ChangePasswordScreen(modifier: Modifier = Modifier, navController: NavContro
)
Spacer(modifier.height(16.dp))
CustomTextField(
- value = newPassword,
- onValueChange = { newPassword = it },input = true,
- placeholderText = "Masukkan Kata Sandi"
+ value = username,
+ onValueChange = { username = it },
+ input = true,
+ placeholderText = "Masukkan Username Anda"
)
Spacer(modifier.height(20.dp))
androidx.compose.material.Text(
- text = stringResource(R.string.konfirmasi_kata_sandi),
+ text = "Kata Sandi Baru",
style = TextStyle(
fontSize = 20.sp,
color = primaryblack,
@@ -79,30 +112,92 @@ fun ChangePasswordScreen(modifier: Modifier = Modifier, navController: NavContro
)
)
Spacer(modifier.height(16.dp))
- CustomTextField(
- value = confirmPassword,
- onValueChange = { confirmPassword = it },input = true,
- placeholderText = "Konfirmasi Kata Sandi"
+ CustomPasswordTextField(
+ value = newPassword,
+ onValueChange = { newPassword = it },
+ 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))
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
.width(360.dp)
.height(50.dp),
colors = androidx.compose.material.ButtonDefaults.buttonColors(backgroundColor = primary),
shape = RoundedCornerShape(20.dp)
) {
- androidx.compose.material.Text(
- text = "Simpan",
- style = TextStyle(
- fontSize = 18.sp,
- color = Color.White,
- fontFamily = bold,
- textAlign = TextAlign.Center
+ if (isLoading) {
+ androidx.compose.material3.CircularProgressIndicator(color = Color.White) // Loading indicator
+ } else {
+ androidx.compose.material.Text(
+ text = "Simpan",
+ style = TextStyle(
+ fontSize = 18.sp,
+ color = Color.White,
+ fontFamily = bold,
+ textAlign = TextAlign.Center
+ )
)
- )
+ }
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/example/caloryapp/pages/onboard/LoginScreen.kt b/app/src/main/java/com/example/caloryapp/pages/onboard/LoginScreen.kt
index d9fcede..731178a 100644
--- a/app/src/main/java/com/example/caloryapp/pages/onboard/LoginScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/onboard/LoginScreen.kt
@@ -15,7 +15,9 @@ 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.material.Button
import androidx.compose.material.MaterialTheme
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.viewmodel.LoginState
import com.example.caloryapp.viewmodel.UserViewModel
+import com.example.caloryapp.widget.CustomPasswordTextField
import com.example.caloryapp.widget.CustomTextField
@Composable
@@ -78,7 +81,9 @@ fun LoginScreen(
.background(background)
) {
Column(
- modifier.padding(horizontal = 25.dp, vertical = 50.dp)
+ modifier
+ .padding(horizontal = 25.dp, vertical = 50.dp)
+ .verticalScroll(rememberScrollState())
) {
Spacer(modifier.height(50.dp))
Text(
@@ -110,7 +115,7 @@ fun LoginScreen(
CustomTextField(
value = username,
onValueChange = { username = it }, input = true,
- placeholderText = "Username"
+ placeholderText = "Masukkan Username"
)
Spacer(modifier.height(16.dp))
Text(
@@ -122,12 +127,30 @@ fun LoginScreen(
)
)
Spacer(modifier.height(16.dp))
- CustomTextField(
+ CustomPasswordTextField(
value = password,
- onValueChange = { password = it }, input = true,
- placeholderText = "Password"
+ onValueChange = { password = it },
+ placeholderText = "Masukkan Kata Sandi",
+ input = true
)
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(
onClick = {
if (username.isEmpty()) {
@@ -151,7 +174,7 @@ fun LoginScreen(
style = TextStyle(
fontSize = 18.sp,
color = Color.White,
- fontFamily = MaterialTheme.typography.h1.fontFamily,
+ fontFamily = bold,
textAlign = TextAlign.Center
)
)
diff --git a/app/src/main/java/com/example/caloryapp/pages/onboard/SuccessChangePassword.kt b/app/src/main/java/com/example/caloryapp/pages/onboard/SuccessChangePassword.kt
index e8c76ed..06d9d2a 100644
--- a/app/src/main/java/com/example/caloryapp/pages/onboard/SuccessChangePassword.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/onboard/SuccessChangePassword.kt
@@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.caloryapp.R
+import com.example.caloryapp.navigation.NavigationScreen
import com.example.caloryapp.ui.theme.background
import com.example.caloryapp.ui.theme.bold
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)) {
Spacer(modifier = Modifier.height(50.dp))
Text(
- stringResource(R.string.password_kamu_berhasil_dibuat),
+ "Kata Sandi Kamu Berhasil Diubah",
style = TextStyle(
fontSize = 35.sp,
color = primaryblack,
@@ -58,7 +59,7 @@ fun SuccessChangePassword(modifier: Modifier = Modifier, navController: NavContr
}
Spacer(modifier = Modifier.height(115.dp))
Button(
- onClick = { },
+ onClick = { navController.navigate(NavigationScreen.LoginScreen.name) },
modifier
.width(360.dp)
.height(50.dp),
diff --git a/app/src/main/java/com/example/caloryapp/repository/UserRepository.kt b/app/src/main/java/com/example/caloryapp/repository/UserRepository.kt
index 34179ca..722ab69 100644
--- a/app/src/main/java/com/example/caloryapp/repository/UserRepository.kt
+++ b/app/src/main/java/com/example/caloryapp/repository/UserRepository.kt
@@ -20,6 +20,7 @@ class UserRepository {
}
}
+
fun registerUserWithCustomID(user: UserModel, userID: String, onComplete: (Boolean) -> Unit) {
db.collection("users")
.document(userID)
@@ -49,6 +50,10 @@ class UserRepository {
}
}
+ fun logoutUser() {
+ FirebaseAuth.getInstance().signOut()
+ }
+
fun updatePasswordByUsername(username: String, oldPassword: String, newPassword: String, onComplete: (Boolean) -> Unit) {
db.collection("users")
.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 auth = FirebaseAuth.getInstance()
//
diff --git a/app/src/main/java/com/example/caloryapp/widget/AlertDialogLogOut.kt b/app/src/main/java/com/example/caloryapp/widget/AlertDialogLogOut.kt
index 0caeb8e..0ae3692 100644
--- a/app/src/main/java/com/example/caloryapp/widget/AlertDialogLogOut.kt
+++ b/app/src/main/java/com/example/caloryapp/widget/AlertDialogLogOut.kt
@@ -1,3 +1,5 @@
+@file:Suppress("CAST_NEVER_SUCCEEDS")
+
package com.example.caloryapp.widget
import androidx.compose.foundation.layout.fillMaxWidth
@@ -9,12 +11,16 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.DialogProperties
+import androidx.navigation.NavController
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.medium
import com.example.caloryapp.ui.theme.primaryblack
@@ -41,7 +47,9 @@ fun SimpleAlertDialog(
onDismissRequest()
},
confirmButton = {
- TextButton(onClick = { onConfirmation() }) {
+ TextButton(onClick = {
+ onConfirmation()
+ }) {
androidx.compose.material.Text(
text = stringResource(R.string.ya),
style = TextStyle(
diff --git a/app/src/main/java/com/example/caloryapp/widget/CustomPassswordTextField.kt b/app/src/main/java/com/example/caloryapp/widget/CustomPassswordTextField.kt
index 6952a96..c56724a 100644
--- a/app/src/main/java/com/example/caloryapp/widget/CustomPassswordTextField.kt
+++ b/app/src/main/java/com/example/caloryapp/widget/CustomPassswordTextField.kt
@@ -1,24 +1,27 @@
package com.example.caloryapp.widget
+//noinspection UsingMaterialAndMaterial3Libraries
+//noinspection UsingMaterialAndMaterial3Libraries
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.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
-import androidx.compose.material3.Text
-//noinspection UsingMaterialAndMaterial3Libraries
import androidx.compose.material.OutlinedTextField
-//noinspection UsingMaterialAndMaterial3Libraries
import androidx.compose.material.TextFieldDefaults
+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.Modifier
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.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
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.primarygrey
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
fun CustomPasswordTextField(
@@ -66,16 +63,23 @@ fun CustomPasswordTextField(
},
modifier = Modifier
.fillMaxWidth()
- .height(50.dp)
- .padding(end = 16.dp), // Add padding for the icon
+ .height(50.dp), // Add padding for the icon
visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) {
Icon(
- imageVector = if (isPasswordVisible) Icons.Filled.Check else Icons.Filled.Check,
- contentDescription = "Toggle Password Visibility",
- tint = Color.Gray // Sesuaikan dengan warna ikon pada desain
+ modifier = Modifier.size(25.dp),
+ painter = if (isPasswordVisible) painterResource(id = R.drawable.ic_show) else painterResource(
+ 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(
diff --git a/app/src/main/res/drawable/ic_hide.png b/app/src/main/res/drawable/ic_hide.png
new file mode 100644
index 0000000..62dc80a
Binary files /dev/null and b/app/src/main/res/drawable/ic_hide.png differ
diff --git a/app/src/main/res/drawable/ic_show.png b/app/src/main/res/drawable/ic_show.png
new file mode 100644
index 0000000..a659b82
Binary files /dev/null and b/app/src/main/res/drawable/ic_show.png differ
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..e270417
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file