From 0c8df7e4c7dde0b6d93843766a9972dd322cfd47 Mon Sep 17 00:00:00 2001
From: E41212133_Naufal Kadhafi <100177539+Jizzyyy@users.noreply.github.com>
Date: Mon, 5 May 2025 13:17:07 +0700
Subject: [PATCH] Progress 2
---
.idea/discord.xml | 7 +
.idea/misc.xml | 8 +-
.idea/other.xml | 33 ++
app/build.gradle.kts | 33 ++
app/google-services.json | 29 ++
app/src/main/AndroidManifest.xml | 20 ++
.../com/example/caloryapp/MainActivity.kt | 23 +-
.../com/example/caloryapp/model/UserModel.kt | 11 +
.../caloryapp/navigation/Navigation.kt | 28 +-
.../caloryapp/navigation/NavigationScreen.kt | 8 +-
.../caloryapp/{widget => pages}/MainScreen.kt | 43 +--
.../account/ProfileChangePasswordScreen.kt | 175 ++++++++++
.../pages/account/ProfileDetailScreen.kt | 199 +++++++++++-
.../caloryapp/pages/account/ProfileScreen.kt | 17 +-
.../pages/camera/CameraDetectionScreen.kt | 41 ++-
.../caloryapp/pages/camera/CameraPreview.kt | 87 +++++
.../pages/camera/CameraPreviewWithOverlay.kt | 58 ++++
.../caloryapp/pages/camera/FoodClassifier.kt | 22 ++
.../caloryapp/pages/dashboard/HomeScreen.kt | 20 +-
.../pages/onboard/ChangePasswordScreen.kt | 4 +-
.../pages/onboard/ForgotPasswordScreen.kt | 2 +-
.../caloryapp/pages/onboard/LoginScreen.kt | 100 ++++--
.../caloryapp/pages/onboard/RegisterScreen.kt | 302 ++++++++++++++++++
.../pages/onboard/SuccessRegister.kt | 3 +-
.../caloryapp/repository/UserRepository.kt | 105 ++++++
.../com/example/caloryapp/ui/theme/Color.kt | 1 +
.../caloryapp/viewmodel/UserViewModel.kt | 83 +++++
.../widget/CustomPassswordTextField.kt | 89 ++++++
.../caloryapp/widget/CustomTextField.kt | 18 +-
.../caloryapp/widget/GenderDropdown.kt | 72 +++++
build.gradle.kts | 1 +
gradle/libs.versions.toml | 35 ++
32 files changed, 1591 insertions(+), 86 deletions(-)
create mode 100644 .idea/discord.xml
create mode 100644 app/google-services.json
create mode 100644 app/src/main/java/com/example/caloryapp/model/UserModel.kt
rename app/src/main/java/com/example/caloryapp/{widget => pages}/MainScreen.kt (83%)
create mode 100644 app/src/main/java/com/example/caloryapp/pages/account/ProfileChangePasswordScreen.kt
create mode 100644 app/src/main/java/com/example/caloryapp/pages/camera/CameraPreview.kt
create mode 100644 app/src/main/java/com/example/caloryapp/pages/camera/CameraPreviewWithOverlay.kt
create mode 100644 app/src/main/java/com/example/caloryapp/pages/camera/FoodClassifier.kt
create mode 100644 app/src/main/java/com/example/caloryapp/pages/onboard/RegisterScreen.kt
create mode 100644 app/src/main/java/com/example/caloryapp/repository/UserRepository.kt
create mode 100644 app/src/main/java/com/example/caloryapp/viewmodel/UserViewModel.kt
create mode 100644 app/src/main/java/com/example/caloryapp/widget/CustomPassswordTextField.kt
create mode 100644 app/src/main/java/com/example/caloryapp/widget/GenderDropdown.kt
diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 0000000..d8e9561
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 775eba8..e485240 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,9 @@
+
-
-
-
+
+
+
+
diff --git a/.idea/other.xml b/.idea/other.xml
index c5b36a6..04cde7e 100644
--- a/.idea/other.xml
+++ b/.idea/other.xml
@@ -69,6 +69,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -289,6 +300,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -521,6 +543,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5023cd2..bf3f341 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
+ alias(libs.plugins.google.gms.google.services)
}
android {
@@ -63,6 +64,12 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
+ implementation(libs.firebase.firestore)
+ implementation(libs.firebase.auth)
+ implementation(libs.androidx.credentials)
+ implementation(libs.androidx.credentials.play.services.auth)
+ implementation(libs.googleid)
+// implementation(libs.firebase.auth.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@@ -70,4 +77,30 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
+
+ // TensorFlow Lite Core
+ implementation (libs.tensorflow.lite)
+
+ // Opsional: TensorFlow Lite dengan Ops TensorFlow Select
+ implementation (libs.tensorflow.lite.select.tf.ops)
+
+ // Opsional: TensorFlow Lite untuk model yang dikonversi dengan support GPU
+ implementation (libs.tensorflow.lite.gpu)
+
+ // Opsional: TensorFlow Lite Support Library untuk Image Processing
+ implementation (libs.tensorflow.lite.support)
+
+ // Kotlin Coroutines (Jika menggunakan async task)
+ implementation (libs.kotlinx.coroutines.core)
+ implementation (libs.kotlinx.coroutines.android)
+
+ // CameraX Dependencies (Jika menggunakan kamera real-time)
+ implementation(libs.androidx.camera.core)
+ implementation( libs.androidx.camera.camera2)
+ implementation (libs.androidx.camera.lifecycle)
+ implementation (libs.androidx.camera.view)
+ implementation (libs.androidx.camera.extensions)
+
+ // Glide (Untuk memuat gambar dengan cepat)
+ implementation (libs.glide)
}
\ No newline at end of file
diff --git a/app/google-services.json b/app/google-services.json
new file mode 100644
index 0000000..31a3554
--- /dev/null
+++ b/app/google-services.json
@@ -0,0 +1,29 @@
+{
+ "project_info": {
+ "project_number": "1003809764619",
+ "project_id": "caloryapp-a3033",
+ "storage_bucket": "caloryapp-a3033.firebasestorage.app"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:1003809764619:android:c254ab536c723e7700de0e",
+ "android_client_info": {
+ "package_name": "com.example.caloryapp"
+ }
+ },
+ "oauth_client": [],
+ "api_key": [
+ {
+ "current_key": "AIzaSyDxisqLMzEc1vFjjPd8jpXuNse7BMEt7HE"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": []
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 64738b6..05d76d8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,26 @@
+
+
+
+
+
+
+
+
+
+// val result = foodClassifier.classify(byteBuffer)
+// classificationResult = result
+//
+// Toast.makeText(context, "Hasil: $result", Toast.LENGTH_SHORT).show()
+// })
+// }
+ Navigation()
+// LoginScreen(navController = rememberNavController())
// ProfileScreen(navController = rememberNavController())
- MainScreen()
+// MainScreen()
}
}
}
diff --git a/app/src/main/java/com/example/caloryapp/model/UserModel.kt b/app/src/main/java/com/example/caloryapp/model/UserModel.kt
new file mode 100644
index 0000000..4a2b743
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/model/UserModel.kt
@@ -0,0 +1,11 @@
+package com.example.caloryapp.model
+
+data class UserModel(
+ val username: String = "",
+ val fullName: String = "",
+ val email: String = "",
+ val password: String = "",
+ val gender: String = "",
+ val weight: String = "",
+ val height: String = "",
+)
\ 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 ab22ce5..24e4963 100644
--- a/app/src/main/java/com/example/caloryapp/navigation/Navigation.kt
+++ b/app/src/main/java/com/example/caloryapp/navigation/Navigation.kt
@@ -2,38 +2,52 @@ package com.example.caloryapp.navigation
import androidx.compose.runtime.Composable
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.pages.NavBarScreen
+import com.example.caloryapp.pages.account.ProfileDetailScreen
import com.example.caloryapp.pages.onboard.ChangePasswordScreen
import com.example.caloryapp.pages.onboard.ForgotPasswordScreen
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.widget.MainScreen
+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()
NavHost(
navController = navController,
- startDestination = NavigationScreen.OnBoardingScreen.name
+ startDestination = NavigationScreen.LoginScreen.name
) {
composable(NavigationScreen.OnBoardingScreen.name) {
OnBoardingScreen(navController = navController)
}
composable(NavigationScreen.LoginScreen.name) {
- LoginScreen(navController = navController)
+ LoginScreen(navController = navController, viewModel = userViewModel)
}
- composable(NavigationScreen.NavBarScreen.name) {
- NavBarScreen()
+ composable(NavigationScreen.RegisterScreen.name) {
+ RegisterScreen(navController = navController, viewModel = userViewModel)
}
+ composable(NavigationScreen.ProfileDetailScreen.name) {
+ ProfileDetailScreen(navController = navController, viewModel = userViewModel)
+ }
+ composable(NavigationScreen.ProfileChangePasswordScreen.name) {
+ ProfileChangePasswordScreen(navController = navController, viewModel = userViewModel)
+ }
+// composable(NavigationScreen.HomeScreen.name) {
+// HomeScreen(navController = navController, userViewModel)
+// }
composable(NavigationScreen.MainScreen.name) {
- MainScreen()
+ MainScreen(userViewModel)
}
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 68b0798..3c45dc3 100644
--- a/app/src/main/java/com/example/caloryapp/navigation/NavigationScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/navigation/NavigationScreen.kt
@@ -3,6 +3,7 @@ package com.example.caloryapp.navigation
enum class NavigationScreen {
OnBoardingScreen,
LoginScreen,
+ RegisterScreen,
NavBarScreen,
HomeScreen,
MainScreen,
@@ -11,12 +12,15 @@ enum class NavigationScreen {
OTPVerificationScreen,
SuccessChangePassword,
SuccessRegister,
- ProfileScreen;
+ ProfileScreen,
+ ProfileDetailScreen,
+ ProfileChangePasswordScreen;
fun fromRoute(route: String): NavigationScreen =
when (route.substringBefore("/")) {
OnBoardingScreen.name -> OnBoardingScreen
LoginScreen.name -> LoginScreen
+ RegisterScreen.name -> RegisterScreen
NavBarScreen.name -> NavBarScreen
MainScreen.name -> MainScreen
HomeScreen.name -> HomeScreen
@@ -26,6 +30,8 @@ enum class NavigationScreen {
OTPVerificationScreen.name -> OTPVerificationScreen
SuccessChangePassword.name -> SuccessChangePassword
SuccessRegister.name -> SuccessRegister
+ ProfileDetailScreen.name -> ProfileDetailScreen
+ ProfileChangePasswordScreen.name -> ProfileChangePasswordScreen
else -> throw IllegalArgumentException("$route gagal bji")
}
diff --git a/app/src/main/java/com/example/caloryapp/widget/MainScreen.kt b/app/src/main/java/com/example/caloryapp/pages/MainScreen.kt
similarity index 83%
rename from app/src/main/java/com/example/caloryapp/widget/MainScreen.kt
rename to app/src/main/java/com/example/caloryapp/pages/MainScreen.kt
index 2379697..af429be 100644
--- a/app/src/main/java/com/example/caloryapp/widget/MainScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/MainScreen.kt
@@ -1,16 +1,11 @@
-package com.example.caloryapp.widget
+package com.example.caloryapp.pages
import android.annotation.SuppressLint
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.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
@@ -21,17 +16,11 @@ import androidx.compose.material3.DrawerValue
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
-import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -41,19 +30,21 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.caloryapp.R
+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.dashboard.HomeScreen
-import com.example.caloryapp.ui.theme.background
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
+import com.example.caloryapp.viewmodel.UserViewModel
import kotlinx.coroutines.launch
sealed class DrawerScreen(val title: String) {
@@ -63,7 +54,9 @@ sealed class DrawerScreen(val title: String) {
@SuppressLint("UnusedMaterialScaffoldPaddingParameter", "UnusedMaterial3ScaffoldPaddingParameter")
@Composable
-fun MainScreen() {
+fun MainScreen(
+ userViewModel: UserViewModel
+) {
val navController = rememberNavController()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
@@ -71,15 +64,21 @@ fun MainScreen() {
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
- DrawerContent(navController, drawerState, scope)
+ DrawerContent(navController, drawerState, scope, userViewModel)
}
) {
NavHost(navController = navController, startDestination = DrawerScreen.HomeScreen.title) {
composable(DrawerScreen.HomeScreen.title) {
- HomeScreen(navController = navController, drawerState = drawerState, scope = scope)
+ HomeScreen(navController = navController, drawerState = drawerState, scope = scope, userViewModel)
}
composable(DrawerScreen.ProfileScreen.title) {
- ProfileScreen(navController = navController, drawerState = drawerState, scope = scope)
+ ProfileScreen(navController = navController, drawerState = drawerState, scope = scope, viewModel = userViewModel)
+ }
+ composable(NavigationScreen.ProfileDetailScreen.name) {
+ ProfileDetailScreen(navController = navController, viewModel = userViewModel)
+ }
+ composable(NavigationScreen.ProfileChangePasswordScreen.name) {
+ ProfileChangePasswordScreen(navController = navController, viewModel = userViewModel)
}
}
}
@@ -89,8 +88,10 @@ fun MainScreen() {
fun DrawerContent(
navController: NavHostController,
drawerState: DrawerState,
- scope: kotlinx.coroutines.CoroutineScope
+ scope: kotlinx.coroutines.CoroutineScope,
+ viewModel: UserViewModel
) {
+ val user = viewModel.user.value
ModalDrawerSheet(modifier = Modifier.background(primary)) {
Spacer(modifier = Modifier.height(30.dp))
Row(
@@ -106,7 +107,7 @@ fun DrawerContent(
Spacer(modifier = Modifier.width(12.dp))
Column(horizontalAlignment = Alignment.Start) {
Text(
- text = "Naufal Kadhafi",
+ text = user!!.fullName,
style = TextStyle(
fontSize = 20.sp,
color = Color.Black,
@@ -114,7 +115,7 @@ fun DrawerContent(
)
)
Text(
- text = "@kadhafiinl",
+ text = "@${user.username}",
style = TextStyle(
fontSize = 14.sp,
color = Color.Black,
diff --git a/app/src/main/java/com/example/caloryapp/pages/account/ProfileChangePasswordScreen.kt b/app/src/main/java/com/example/caloryapp/pages/account/ProfileChangePasswordScreen.kt
new file mode 100644
index 0000000..3e66e1c
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/pages/account/ProfileChangePasswordScreen.kt
@@ -0,0 +1,175 @@
+package com.example.caloryapp.pages.account
+
+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.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Button
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowLeft
+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.platform.LocalContext
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.navigation.NavController
+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.viewmodel.UserViewModel
+import com.example.caloryapp.widget.CustomPasswordTextField
+
+@Composable
+fun ProfileChangePasswordScreen(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ viewModel: UserViewModel
+) {
+ var oldPassword by remember { mutableStateOf("") }
+ var newPassword by remember { mutableStateOf("") }
+ val userData = viewModel.user.value
+ var isPasswordVisible by remember { mutableStateOf(false) }
+ val context = LocalContext.current
+ val userRepository = UserRepository()
+
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(background)
+ ) {
+ Column(
+ Modifier
+ .padding(horizontal = 25.dp, vertical = 50.dp)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Spacer(modifier = Modifier.height(50.dp))
+ Row(Modifier.fillMaxWidth(), Arrangement.Start) {
+ Icon(
+ imageVector = Icons.Default.KeyboardArrowLeft,
+ contentDescription = null,
+ Modifier
+ .size(28.dp)
+ .clickable { navController.popBackStack() }
+ )
+ Spacer(modifier = Modifier.width(30.dp))
+ Text(
+ text = "Ubah Password",
+ style = TextStyle(
+ fontSize = 25.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ }
+
+ // Form untuk memasukkan password lama dan baru
+ Spacer(modifier.height(30.dp))
+ Text(
+ text = "Password Lama",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomPasswordTextField(
+ value = oldPassword,
+ onValueChange = { oldPassword = it },
+ placeholderText = "Masukkan Password Lama",
+ input = true
+ )
+
+ Spacer(modifier.height(18.dp))
+ Text(
+ text = "Password Baru",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomPasswordTextField(
+ value = newPassword,
+ onValueChange = { newPassword = it },
+ placeholderText = "Masukkan Password Baru",
+ input = true
+ )
+
+ Spacer(modifier.height(18.dp))
+ Button(
+ onClick = {
+ if (oldPassword.isEmpty() || newPassword.isEmpty()) {
+ Toast.makeText(context, "Password tidak boleh kosong", Toast.LENGTH_SHORT)
+ .show()
+ } else {
+ // Panggil fungsi untuk memperbarui password berdasarkan username
+ userData?.username?.let {
+ userRepository.updatePasswordByUsername(
+ it,
+ oldPassword,
+ newPassword
+ ) { isSuccess ->
+ if (isSuccess) {
+ Toast.makeText(
+ context,
+ "Password berhasil diperbarui",
+ Toast.LENGTH_SHORT
+ ).show()
+ navController.popBackStack() // Navigasi kembali setelah berhasil
+ } else {
+ Toast.makeText(
+ context,
+ "Gagal memperbarui password",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ }
+ },
+ modifier = Modifier
+ .width(360.dp)
+ .height(50.dp),
+ colors = androidx.compose.material.ButtonDefaults.buttonColors(backgroundColor = primary),
+ shape = RoundedCornerShape(20.dp)
+ ) {
+ Text(
+ text = "Ubah",
+ style = TextStyle(
+ fontSize = 18.sp,
+ color = Color.White,
+ fontFamily = MaterialTheme.typography.h1.fontFamily,
+ textAlign = TextAlign.Center
+ )
+ )
+ }
+ }
+ }
+}
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 e9c7238..94fd5f5 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,9 +1,206 @@
package com.example.caloryapp.pages.account
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.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.verticalScroll
+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.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.ui.theme.background
+import com.example.caloryapp.ui.theme.bold
+import com.example.caloryapp.ui.theme.primaryblack
+import com.example.caloryapp.viewmodel.UserViewModel
+import com.example.caloryapp.widget.CustomTextField
@Composable
-fun ProfileDetailScreen(modifier: Modifier = Modifier) {
+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("") }
+
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(background)
+ ) {
+ Column(
+ Modifier
+ .padding(horizontal = 25.dp, vertical = 50.dp)
+ .verticalScroll(
+ rememberScrollState()
+ )
+ ) {
+ Spacer(modifier.height(50.dp))
+ Row(
+ Modifier.fillMaxWidth(),
+ Arrangement.Start,
+ ) {
+ Icon(
+ imageVector = Icons.Default.KeyboardArrowLeft,
+ contentDescription = null,
+ Modifier
+ .size(28.dp)
+ .clickable { navController.popBackStack()}
+ )
+ Spacer(modifier = Modifier.width(30.dp))
+ Text(
+ text = "Profil Saya",
+ style = TextStyle(
+ fontSize = 25.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ }
+ Spacer(modifier = Modifier.height(64.dp))
+ Row(Modifier.fillMaxWidth(), Arrangement.Center) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_profile_men),
+ contentDescription = null,
+ Modifier.size(100.dp)
+ )
+ }
+
+ Spacer(modifier.height(45.dp))
+ Text(
+ text = "Nama Lengkap",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = user!!.fullName,
+ onValueChange = { fullName = it },
+ placeholderText = "Masukkan Nama Lengkap",
+ input = false
+ )
+
+ Spacer(modifier.height(20.dp))
+ Text(
+ text = "Username",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = user.username,
+ onValueChange = { },
+ placeholderText = "Masukkan Nama Lengkap",
+ input = false
+ )
+
+ Spacer(modifier.height(20.dp))
+ Text(
+ text = "Email",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = user.email,
+ onValueChange = { },
+ placeholderText = "Masukkan Nama Lengkap",
+ input = false
+ )
+
+ Spacer(modifier.height(20.dp))
+ Text(
+ text = "Gender",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = user.gender,
+ onValueChange = { },
+ placeholderText = "Masukkan Nama Lengkap",
+ input = false
+ )
+
+ Spacer(modifier.height(20.dp))
+ Text(
+ text = "Berat Badan",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = "${user.weight} kg",
+ onValueChange = { },
+ placeholderText = "Masukkan Nama Lengkap",
+ input = false
+ )
+
+ Spacer(modifier.height(20.dp))
+ Text(
+ text = "Tinggi Badan",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = "${user.height} cm",
+ onValueChange = { },
+ placeholderText = "Masukkan Nama Lengkap",
+ input = false
+ )
+ }
+ }
}
\ 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 0c11640..fc67321 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
@@ -33,6 +33,8 @@ 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.NavigationScreen
+import com.example.caloryapp.pages.DrawerScreen
import com.example.caloryapp.ui.theme.background
import com.example.caloryapp.ui.theme.bold
import com.example.caloryapp.ui.theme.medium
@@ -41,6 +43,7 @@ import com.example.caloryapp.ui.theme.primaryblack
import com.example.caloryapp.ui.theme.primarygrey
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 kotlinx.coroutines.launch
import java.text.SimpleDateFormat
@@ -52,8 +55,10 @@ fun ProfileScreen(
modifier: Modifier = Modifier,
navController: NavController,
drawerState: DrawerState,
- scope: kotlinx.coroutines.CoroutineScope
-) {
+ scope: kotlinx.coroutines.CoroutineScope,
+ viewModel: UserViewModel,
+ ) {
+ val user = viewModel.user.value
val openAlertDialog = remember { mutableStateOf(false) }
val currentDate = Calendar.getInstance().time
val dateFormat = SimpleDateFormat("EEEE, dd MMMM yyyy", Locale("id", "ID"))
@@ -77,7 +82,7 @@ fun ProfileScreen(
Row(verticalAlignment = Alignment.CenterVertically) {
Column(horizontalAlignment = Alignment.End) {
Text(
- text = "Naufal Kadhafi",
+ text = user!!.fullName,
style = TextStyle(
fontSize = 20.sp,
color = Color.Black,
@@ -85,7 +90,7 @@ fun ProfileScreen(
)
)
Text(
- text = "@kadhafiinl",
+ text = "@${user.username}",
style = TextStyle(
fontSize = 14.sp,
color = Color.Black,
@@ -121,7 +126,7 @@ fun ProfileScreen(
)
Spacer(modifier.height(10.dp))
Divider(color = primary.copy(alpha = 0.2f), thickness = 3.dp)
- Spacer(modifier.height(20.dp))
+ Spacer(modifier.height(40.dp))
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween) {
Row {
Image(
@@ -139,6 +144,7 @@ fun ProfileScreen(
)
}
Image(
+ modifier = Modifier.clickable { navController.navigate(NavigationScreen.ProfileDetailScreen.name) },
imageVector = ImageVector.vectorResource(id = R.drawable.ic_btn_detail),
contentDescription = null
)
@@ -185,6 +191,7 @@ fun ProfileScreen(
)
}
Image(
+ modifier = Modifier.clickable { navController.navigate(NavigationScreen.ProfileChangePasswordScreen.name) },
imageVector = ImageVector.vectorResource(id = R.drawable.ic_btn_detail),
contentDescription = null
)
diff --git a/app/src/main/java/com/example/caloryapp/pages/camera/CameraDetectionScreen.kt b/app/src/main/java/com/example/caloryapp/pages/camera/CameraDetectionScreen.kt
index 0c57415..3c2478e 100644
--- a/app/src/main/java/com/example/caloryapp/pages/camera/CameraDetectionScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/camera/CameraDetectionScreen.kt
@@ -1,10 +1,49 @@
package com.example.caloryapp.pages.camera
+import android.widget.Toast
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
@Composable
-fun CameraDetectionScreen(modifier: Modifier = Modifier, navController: NavController) {
+fun CameraDetectionScreen(foodClassifier: FoodClassifier, areaCoverage: Map,modifier: Modifier = Modifier, navController: NavController) {
+}
+
+
+//@Composable
+//fun ValidateFoodPlate(foodClassifier: FoodClassifier, areaCoverage: Map) {
+// val context = LocalContext.current
+//
+// val isPortionValid = foodClassifier.validatePortion(areaCoverage)
+// if (!isPortionValid) {
+// Toast.makeText(context, "Porsi makanan TIDAK sesuai! Kebutuhan kalori GAGAL terpenuhi!", Toast.LENGTH_LONG).show()
+// } else {
+// Toast.makeText(context, "Porsi makanan sesuai! Kebutuhan kalori TERPENUHI!", Toast.LENGTH_LONG).show()
+// }
+//}
+
+@Composable
+fun FoodPlateOverlay(isValid: Boolean) {
+ val color = if (isValid) Color.Green else Color.Red
+
+ Canvas(modifier = Modifier.fillMaxSize()) {
+ val width = size.width
+ val height = size.height
+
+ // Visualisasi Food Plate dengan warna berdasarkan validasi porsi
+ drawRect(color, Offset(0f, 0f), Size(width * 0.6f, height * 0.25f))
+ drawRect(Color.Yellow, Offset(0f, height * 0.25f), Size(width * 0.5f, height * 0.5f)) // Karbohidrat
+ drawRect(Color.Red, Offset(width * 0.5f, height * 0.25f), Size(width * 0.5f, height * 0.5f)) // Protein
+ drawRect(Color.Green, Offset(0f, 0f), Size(width * 0.6f, height * 0.25f)) // Sayur
+ drawRect(Color(0xFFFFA500), Offset(width * 0.6f, 0f), Size(width * 0.4f, height * 0.25f)) // Buah
+ drawRect(Color.Blue, Offset(width * 0.75f, height * 0.75f), Size(width * 0.25f, height * 0.25f)) // Lainnya
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/pages/camera/CameraPreview.kt b/app/src/main/java/com/example/caloryapp/pages/camera/CameraPreview.kt
new file mode 100644
index 0000000..ac00d41
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/pages/camera/CameraPreview.kt
@@ -0,0 +1,87 @@
+package com.example.caloryapp.pages.camera
+
+import android.Manifest
+import android.content.Context
+import android.util.Size
+import android.view.Surface
+import android.view.ViewGroup
+import androidx.annotation.OptIn
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ExperimentalGetImage
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.Preview
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.view.PreviewView
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.LifecycleOwner
+import java.nio.ByteBuffer
+import java.util.concurrent.Executors
+
+
+//@Composable
+//fun CameraPreview(
+// onImageCaptured: (ByteBuffer) -> Unit,
+// modifier: Modifier = Modifier
+//) {
+// val context = LocalContext.current
+// val lifecycleOwner = LocalContext.current as LifecycleOwner
+// val cameraExecutor = remember { Executors.newSingleThreadExecutor() }
+// val previewView = remember { PreviewView(context) }
+//
+// DisposableEffect(context) {
+//// val cameraProviderFuture = ProcessCameraProvider.getInstance()
+//// cameraProviderFuture.addListener({
+//// val cameraProvider = cameraProviderFuture.get()
+//// val preview = Preview.Builder().build().also {
+//// it.setSurfaceProvider(previewView.surfaceProvider)
+//// }
+//
+//// val imageAnalysis = ImageAnalysis.Builder()
+//// .setTargetResolution(Size(128, 128))
+//// .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
+//// .build()
+////
+//// imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
+//// val buffer = imageProxy.planes[0].buffer
+//// onImageCaptured(buffer)
+//// imageProxy.close()
+//// }
+////
+//// try {
+//// cameraProvider.unbindAll()
+//// cameraProvider.bindToLifecycle(
+//// lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis
+//// )
+//// } catch (e: Exception) {
+//// e.printStackTrace()
+//// }
+//// }, ContextCompat.getMainExecutor(context))
+////
+//// onDispose { cameraExecutor.shutdown() }
+//// }
+////
+//// AndroidView(
+//// factory = { previewView },
+//// modifier = modifier.fillMaxSize()
+//// )
+//}
+
+
+@OptIn(ExperimentalGetImage::class)
+private fun processImageProxy(imageProxy: ImageProxy, onImageCaptured: (ByteBuffer) -> Unit) {
+ val image = imageProxy.image ?: return
+ val planes = image.planes
+ val buffer: ByteBuffer = planes[0].buffer
+ val data = ByteArray(buffer.remaining())
+ buffer.get(data)
+
+ onImageCaptured(buffer) // Mengirim data gambar untuk klasifikasi
+
+ imageProxy.close()
+}
diff --git a/app/src/main/java/com/example/caloryapp/pages/camera/CameraPreviewWithOverlay.kt b/app/src/main/java/com/example/caloryapp/pages/camera/CameraPreviewWithOverlay.kt
new file mode 100644
index 0000000..e386dff
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/pages/camera/CameraPreviewWithOverlay.kt
@@ -0,0 +1,58 @@
+package com.example.caloryapp.pages.camera
+
+import android.content.Context
+import android.widget.Toast
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+
+@Composable
+fun CameraPreviewWithOverlay(
+ foodClassifier: FoodClassifier,
+ areaCoverage: Map
+) {
+ var classificationResult by remember { mutableStateOf("Memuat...") }
+ Box {
+// CameraPreview(onImageCaptured = { byteBuffer ->
+// val result = foodClassifier.classify(byteBuffer)
+// classificationResult = result
+// })
+//
+// // Menampilkan hasil klasifikasi dan validasi porsi makanan
+// ValidateFoodPlate(
+// classificationResult = classificationResult,
+// areaCoverage = areaCoverage
+// )
+ }
+}
+
+@Composable
+fun ValidateFoodPlate(
+ classificationResult: String,
+ areaCoverage: Map
+) {
+ val context = LocalContext.current
+ val idealPercentage = mapOf(
+ "Karbohidrat" to 25,
+ "Protein" to 25,
+ "Sayur" to 30,
+ "Buah" to 15,
+ "Lainnya" to 5
+ )
+
+ var isValid = true
+ for ((category, ideal) in idealPercentage) {
+ val actual = areaCoverage[category] ?: 0
+ if (actual < ideal * 0.8 || actual > ideal * 1.2) {
+ isValid = false
+ break
+ }
+ }
+
+ if (!isValid) {
+ Toast.makeText(context, "Porsi makanan TIDAK sesuai!", Toast.LENGTH_LONG).show()
+ } else {
+ Toast.makeText(context, "Porsi makanan sesuai kebutuhan kalori harian!", Toast.LENGTH_LONG).show()
+ }
+}
diff --git a/app/src/main/java/com/example/caloryapp/pages/camera/FoodClassifier.kt b/app/src/main/java/com/example/caloryapp/pages/camera/FoodClassifier.kt
new file mode 100644
index 0000000..1249a17
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/pages/camera/FoodClassifier.kt
@@ -0,0 +1,22 @@
+package com.example.caloryapp.pages.camera
+
+import android.content.Context
+import org.tensorflow.lite.Interpreter
+import java.nio.ByteBuffer
+
+class FoodClassifier(context: Context) {
+ private val interpreter: Interpreter
+ private val categories = arrayOf("Karbohidrat", "Protein", "Sayur", "Buah", "Lainnya")
+
+ init {
+ val model = context.assets.open("model_food_plate_densenet.tflite").use { it.readBytes() }
+ interpreter = Interpreter(ByteBuffer.wrap(model))
+ }
+
+ fun classify(imageData: ByteBuffer): String {
+ val output = Array(1) { FloatArray(categories.size) }
+ interpreter.run(imageData, output)
+ val maxIndex = output[0].indices.maxByOrNull { output[0][it] } ?: -1
+ return if (maxIndex >= 0) categories[maxIndex] else "Tidak Terdeteksi"
+ }
+}
\ 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 048cee0..21054e4 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
@@ -38,14 +38,24 @@ 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
+
@Composable
fun HomeScreen(
navController: NavController,
drawerState: DrawerState,
- scope: kotlinx.coroutines.CoroutineScope
+ scope: kotlinx.coroutines.CoroutineScope,
+ viewModel: UserViewModel,
) {
var selectedFilter by remember { mutableStateOf("Semua") }
+ val user = viewModel.user.value
Box(
modifier = Modifier
@@ -61,7 +71,7 @@ fun HomeScreen(
verticalAlignment = Alignment.CenterVertically
) {
Image(
- modifier = Modifier.clickable { scope.launch { drawerState.open() } },
+ modifier = Modifier.clickable { },
painter = painterResource(id = R.drawable.ic_home_acc),
contentDescription = null
)
@@ -69,7 +79,7 @@ fun HomeScreen(
Row(verticalAlignment = Alignment.CenterVertically) {
Column(horizontalAlignment = Alignment.End) {
Text(
- text = "Naufal Kadhafi",
+ text = user!!.fullName,
style = TextStyle(
fontSize = 20.sp,
color = Color.Black,
@@ -77,7 +87,7 @@ fun HomeScreen(
)
)
Text(
- text = "@kadhafiinl",
+ text = "@${user.username}",
style = TextStyle(
fontSize = 14.sp,
color = Color.Black,
@@ -99,7 +109,7 @@ fun HomeScreen(
// Teks sapaan
Row(Modifier.width(215.dp)) {
Text(
- text = "Hai Naufal, Bagaimana kabar kamu hari ini?",
+ text = "Hai ${user!!.fullName}, Bagaimana kabar kamu hari ini?",
style = TextStyle(
fontSize = 22.sp,
color = Color.Black,
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 9361023..1ea3820 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
@@ -66,7 +66,7 @@ fun ChangePasswordScreen(modifier: Modifier = Modifier, navController: NavContro
Spacer(modifier.height(16.dp))
CustomTextField(
value = newPassword,
- onValueChange = { newPassword = it },
+ onValueChange = { newPassword = it },input = true,
placeholderText = "Masukkan Kata Sandi"
)
Spacer(modifier.height(20.dp))
@@ -81,7 +81,7 @@ fun ChangePasswordScreen(modifier: Modifier = Modifier, navController: NavContro
Spacer(modifier.height(16.dp))
CustomTextField(
value = confirmPassword,
- onValueChange = { confirmPassword = it },
+ onValueChange = { confirmPassword = it },input = true,
placeholderText = "Konfirmasi Kata Sandi"
)
Spacer(modifier.height(45.dp))
diff --git a/app/src/main/java/com/example/caloryapp/pages/onboard/ForgotPasswordScreen.kt b/app/src/main/java/com/example/caloryapp/pages/onboard/ForgotPasswordScreen.kt
index b15b735..9323278 100644
--- a/app/src/main/java/com/example/caloryapp/pages/onboard/ForgotPasswordScreen.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/onboard/ForgotPasswordScreen.kt
@@ -79,7 +79,7 @@ fun ForgotPasswordScreen(modifier: Modifier = Modifier, navController: NavContro
Spacer(modifier.height(16.dp))
CustomTextField(
value = gmail,
- onValueChange = { gmail = it },
+ onValueChange = { gmail = it },input = true,
placeholderText = stringResource(R.string.gmail)
)
Spacer(modifier.height(40.dp))
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 be50d61..d9fcede 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
@@ -1,21 +1,26 @@
package com.example.caloryapp.pages.onboard
+import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.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.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -23,6 +28,7 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
@@ -36,15 +42,35 @@ import com.example.caloryapp.navigation.NavigationScreen
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.fontGrey
import com.example.caloryapp.ui.theme.primary
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.CustomTextField
@Composable
-fun LoginScreen(modifier: Modifier = Modifier, navController: NavController) {
+fun LoginScreen(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ viewModel: UserViewModel
+) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
+ val state = viewModel.loginstate.value
+ val userData = viewModel.user.value
+ val context = LocalContext.current
+
+ LaunchedEffect(key1 = state) {
+ if (state is LoginState.Success) {
+ Toast.makeText(context, "Selamat Datang, ${state.user.fullName}", Toast.LENGTH_SHORT).show()
+ navController.navigate(NavigationScreen.MainScreen.name)
+ } else if (state is LoginState.Error) {
+ Toast.makeText(context, "Username atau Kata Sandi Tidak Sesuai!", Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
Box(
modifier
@@ -83,7 +109,7 @@ fun LoginScreen(modifier: Modifier = Modifier, navController: NavController) {
Spacer(modifier.height(16.dp))
CustomTextField(
value = username,
- onValueChange = { username = it },
+ onValueChange = { username = it }, input = true,
placeholderText = "Username"
)
Spacer(modifier.height(16.dp))
@@ -98,27 +124,22 @@ fun LoginScreen(modifier: Modifier = Modifier, navController: NavController) {
Spacer(modifier.height(16.dp))
CustomTextField(
value = password,
- onValueChange = { password = it },
+ onValueChange = { password = it }, input = true,
placeholderText = "Password"
)
Spacer(modifier.height(18.dp))
- Row(
- modifier
- .align(Alignment.End)
- .clickable { navController.navigate(NavigationScreen.ForgotPasswordScreen.name) }) {
- Text(
- text = stringResource(R.string.lupa_password),
- style = TextStyle(
- fontSize = 15.sp,
- color = blueunderlined,
- fontFamily = semibold,
- textDecoration = TextDecoration.Underline
- )
- )
- }
- Spacer(modifier.height(35.dp))
Button(
- onClick = { navController.navigate(NavigationScreen.MainScreen.name) },
+ onClick = {
+ if (username.isEmpty()) {
+ Toast.makeText(context, "Username Tidak Boleh Kosong", Toast.LENGTH_SHORT)
+ .show()
+ } else if (password.isEmpty()){
+ Toast.makeText(context, "Password Tidak Boleh Kosong", Toast.LENGTH_SHORT)
+ .show()
+ } else {
+ viewModel.login(username, password)
+ }
+ },
modifier
.width(360.dp)
.height(50.dp),
@@ -130,17 +151,44 @@ fun LoginScreen(modifier: Modifier = Modifier, navController: NavController) {
style = TextStyle(
fontSize = 18.sp,
color = Color.White,
- fontFamily = bold,
+ fontFamily = MaterialTheme.typography.h1.fontFamily,
textAlign = TextAlign.Center
)
)
}
+
+ Spacer(modifier = Modifier.height(20.dp))
+ Row(
+ Modifier.fillMaxWidth(), Arrangement.Center
+ ) {
+ Text(
+ text = "Belum Punya Akun ?",
+ style = TextStyle(
+ fontSize = 16.sp,
+ color = fontGrey,
+ fontFamily = semibold
+ )
+ )
+ Spacer(modifier = Modifier.width(2.dp))
+ Text(
+ modifier = Modifier.clickable { navController.navigate(NavigationScreen.RegisterScreen.name) },
+ text = "Daftar",
+ style = TextStyle(
+ fontSize = 16.sp,
+ color = blueunderlined,
+ fontFamily = semibold,
+ textDecoration = TextDecoration.Underline
+ )
+ )
+ }
+
+// if (state is LoginState.Error) {
+// Text(text = "", color = MaterialTheme.colors.error)
+// Toast.makeText(context, "Username atau Kata Sandi Tidak Sesuai!", Toast.LENGTH_SHORT)
+// .show()
+// }
+
+ Spacer(modifier.height(42.dp))
}
}
}
-
-//@Preview(showBackground = true)
-//@Composable
-//fun Preview(modifier: Modifier = Modifier) {
-// LoginScreen(navController = rememberNavController())
-//}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/pages/onboard/RegisterScreen.kt b/app/src/main/java/com/example/caloryapp/pages/onboard/RegisterScreen.kt
new file mode 100644
index 0000000..1d6d81a
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/pages/onboard/RegisterScreen.kt
@@ -0,0 +1,302 @@
+package com.example.caloryapp.pages.onboard
+
+import android.widget.Toast
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+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.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+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.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+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.model.UserModel
+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.medium
+import com.example.caloryapp.ui.theme.primary
+import com.example.caloryapp.ui.theme.primaryblack
+import com.example.caloryapp.viewmodel.UserViewModel
+import com.example.caloryapp.widget.CustomTextField
+import com.example.caloryapp.widget.GenderDropdown
+
+@Composable
+fun RegisterScreen(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ viewModel: UserViewModel
+) {
+ 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("") }
+ val context = LocalContext.current
+ val registerState by viewModel.registerState.collectAsState()
+ val firestoreHelper = UserRepository()
+
+ LaunchedEffect(registerState) {
+ try {
+ if (registerState == true) {
+
+ } else if (registerState == false) {
+
+ }
+ } catch (e: Exception) {
+ throw e
+ }
+ if (registerState == true) {
+ Toast.makeText(context, "Registrasi berhasil!", Toast.LENGTH_SHORT).show()
+// navController.navigate("login") // Arahkan ke halaman login
+ } else if (registerState == false) {
+ Toast.makeText(context, "Registrasi gagal!", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(background)
+ ) {
+ Column(
+ Modifier
+ .padding(horizontal = 25.dp, vertical = 50.dp)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Spacer(modifier = Modifier.height(50.dp))
+ Text(
+ text = "Daftar",
+ style = TextStyle(
+ fontSize = 35.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ text = "Yuk isi biodata kamu \n" +
+ "dulu!",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = medium
+ )
+ )
+
+ // username
+ Spacer(modifier = Modifier.height(40.dp))
+ Text(
+ text = stringResource(R.string.username),
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = username,
+ onValueChange = { username = it },
+ placeholderText = "Masukkan Username",
+ input = true
+ )
+
+ // gmail
+ Spacer(modifier.height(16.dp))
+ Text(
+ text = "Email",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = gmail,
+ onValueChange = { gmail = it },
+ input = true,
+ placeholderText = "Masukkan Email"
+ )
+
+ // nama lengkap
+ Spacer(modifier.height(16.dp))
+ Text(
+ text = "Nama Lengkap",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = fullName,
+ onValueChange = { fullName = it }, input = true,
+ placeholderText = "Masukkan Nama Lengkap"
+ )
+
+ // pw
+
+
+ // nama lengkap
+ Spacer(modifier.height(16.dp))
+ Text(
+ text = "Kata Sandi",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = password,
+ onValueChange = { password = it }, input = true,
+ placeholderText = "Masukkan Kata Sandi"
+ )
+
+ // gender
+ Spacer(modifier.height(16.dp))
+ Text(
+ text = "Gender",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ GenderDropdown(selectedGender = selectedGender) {
+ selectedGender = it
+ }
+
+ // berat badan
+ Spacer(modifier.height(16.dp))
+ Text(
+ text = "Berat Badan",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = weight.toString(),
+ onValueChange = { weight = it }, input = true,
+ placeholderText = "Masukkan Berat Badan"
+ )
+
+ // tinggi badan
+ Spacer(modifier.height(16.dp))
+ Text(
+ text = "Tinggi Badan",
+ style = TextStyle(
+ fontSize = 20.sp,
+ color = primaryblack,
+ fontFamily = bold
+ )
+ )
+ Spacer(modifier.height(12.dp))
+ CustomTextField(
+ value = height.toString(),
+ onValueChange = { height = it }, input = true,
+ placeholderText = "Masukkan Tinggi Badan"
+ )
+
+ // btn daftar
+ Spacer(modifier.height(35.dp))
+ Button(
+ onClick = {
+ if (username.isEmpty() && password.isEmpty() && gmail.isEmpty() && fullName.isEmpty() && selectedGender.isEmpty() &&
+ weight.isEmpty() && height.isEmpty()
+ ) {
+ Toast.makeText(context, "Data Tidak Boleh Kosong!", Toast.LENGTH_SHORT)
+ .show()
+ } else if (username.isNotEmpty() && password.isNotEmpty() && gmail.isNotEmpty() && fullName.isNotEmpty() && selectedGender.isNotEmpty() &&
+ weight.isNotEmpty() && height.isNotEmpty()
+ ) {
+ val user = UserModel(
+ username,
+ fullName,
+ gmail,
+ password,
+ selectedGender,
+ weight,
+ height
+ )
+ firestoreHelper.registerUserWithCustomID(user, gmail) { success ->
+ if (success) {
+ Toast.makeText(
+ context,
+ "Kamu Berhasil Membuat Akun !",
+ Toast.LENGTH_SHORT
+ ).show()
+ navController.navigate(NavigationScreen.SuccessRegister.name)
+ } else {
+ Toast.makeText(context, "Error Registering", Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+// viewModel.register(gmail, password, user)
+// viewModel.registerUser(
+// username, fullName, password, gmail, selectedGender,
+// weight, height,
+// onSuccess = {
+//// navController.navigate(NavigationScreen.MainScreen.name)
+// Toast.makeText(context, "Pendaftaran Berhasil!", Toast.LENGTH_SHORT).show()
+// },
+// onFailure = {
+// Log.e("Register", "Pendaftaran gagal")
+// Toast.makeText(context, "GAGAL COKKK", Toast.LENGTH_SHORT).show()
+// }
+// )
+ }
+ },
+ modifier
+ .width(360.dp)
+ .height(50.dp),
+ colors = androidx.compose.material.ButtonDefaults.buttonColors(backgroundColor = primary),
+ shape = RoundedCornerShape(10.dp)
+ ) {
+ Text(
+ text = "Daftar",
+ style = TextStyle(
+ fontSize = 18.sp,
+ color = Color.White,
+ fontFamily = bold,
+ textAlign = TextAlign.Center,
+ letterSpacing = 0.5.sp
+ )
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/pages/onboard/SuccessRegister.kt b/app/src/main/java/com/example/caloryapp/pages/onboard/SuccessRegister.kt
index 4d9e25a..b664bad 100644
--- a/app/src/main/java/com/example/caloryapp/pages/onboard/SuccessRegister.kt
+++ b/app/src/main/java/com/example/caloryapp/pages/onboard/SuccessRegister.kt
@@ -24,6 +24,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
@@ -48,7 +49,7 @@ fun SuccessRegister(modifier: Modifier = Modifier, navController: NavController)
Image(imageVector = ImageVector.vectorResource(id = R.drawable.ic_success), contentDescription = "")
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
new file mode 100644
index 0000000..34179ca
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/repository/UserRepository.kt
@@ -0,0 +1,105 @@
+package com.example.caloryapp.repository
+
+import android.util.Log
+import com.example.caloryapp.model.UserModel
+import com.google.firebase.auth.FirebaseAuth
+import com.google.firebase.firestore.FirebaseFirestore
+import kotlinx.coroutines.tasks.await
+
+class UserRepository {
+ private val db = FirebaseFirestore.getInstance()
+
+ fun registerUser(user: UserModel, onComplete: (Boolean) -> Unit) {
+ db.collection("users")
+ .add(user)
+ .addOnSuccessListener {
+ onComplete(true)
+ }
+ .addOnFailureListener {
+ onComplete(false)
+ }
+ }
+
+ fun registerUserWithCustomID(user: UserModel, userID: String, onComplete: (Boolean) -> Unit) {
+ db.collection("users")
+ .document(userID)
+ .set(user)
+ .addOnSuccessListener {
+ onComplete(true)
+ }
+ .addOnFailureListener {
+ onComplete(false)
+ }
+ }
+
+ fun getUserByUsername(username: String, onComplete: (UserModel?) -> Unit) {
+ db.collection("users")
+ .whereEqualTo("username", username)
+ .get()
+ .addOnSuccessListener { result ->
+ if (!result.isEmpty) {
+ val user = result.documents[0].toObject(UserModel::class.java)
+ onComplete(user)
+ } else {
+ onComplete(null)
+ }
+ }
+ .addOnFailureListener {
+ onComplete(null)
+ }
+ }
+
+ fun updatePasswordByUsername(username: String, oldPassword: String, newPassword: String, onComplete: (Boolean) -> Unit) {
+ db.collection("users")
+ .whereEqualTo("username", username) // Mencari pengguna berdasarkan username
+ .get()
+ .addOnSuccessListener { result ->
+ if (!result.isEmpty) {
+ val userDocument = result.documents[0]
+ val currentPassword = userDocument.getString("password")
+
+ if (currentPassword == oldPassword) {
+ userDocument.reference.update("password", newPassword)
+ .addOnSuccessListener {
+ onComplete(true) // Update berhasil
+ }
+ .addOnFailureListener {
+ onComplete(false) // Update gagal
+ }
+ } else {
+ // Jika password lama tidak cocok
+ onComplete(false) // Password lama salah
+ }
+ } else {
+ // Username tidak ditemukan
+ onComplete(false)
+ }
+ }
+ .addOnFailureListener {
+ onComplete(false) // Terjadi error dalam pencarian data
+ }
+ }
+
+// private val db = FirebaseFirestore.getInstance()
+// private val auth = FirebaseAuth.getInstance()
+//
+// suspend fun registerUser(email: String, password: String, userModel: UserModel): Boolean {
+// val userId = auth.currentUser?.uid ?: return false
+// return try {
+// db.collection("users").document(userId).set(userModel).await()
+// true
+// } catch (e: Exception) {
+// Log.e("Firestore", "Error saving user: ", e)
+// false
+// }
+// }
+//
+// suspend fun getUser(username: String): UserModel? {
+// return try {
+// val document = db.collection("users").document(username).get().await()
+// document.toObject(UserModel::class.java)
+// } catch (e: Exception) {
+// null
+// }
+// }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/ui/theme/Color.kt b/app/src/main/java/com/example/caloryapp/ui/theme/Color.kt
index 6d660ef..a8e182f 100644
--- a/app/src/main/java/com/example/caloryapp/ui/theme/Color.kt
+++ b/app/src/main/java/com/example/caloryapp/ui/theme/Color.kt
@@ -8,6 +8,7 @@ var primarygrey = Color(0xffBABABA)
var background = Color(0xffF4F4F4)
var blueunderlined = Color(0xff0063BF)
var primaryred = Color(0xffE85454)
+val fontGrey = Color(0xFF828282)
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
diff --git a/app/src/main/java/com/example/caloryapp/viewmodel/UserViewModel.kt b/app/src/main/java/com/example/caloryapp/viewmodel/UserViewModel.kt
new file mode 100644
index 0000000..1d32528
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/viewmodel/UserViewModel.kt
@@ -0,0 +1,83 @@
+package com.example.caloryapp.viewmodel
+
+import android.util.Log
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.caloryapp.model.UserModel
+import com.example.caloryapp.repository.UserRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+sealed class LoginState {
+ data object Loading : LoginState()
+ data class Success(val user: UserModel) : LoginState()
+ data class Error(val message: String) : LoginState()
+}
+
+class UserViewModel : ViewModel() {
+ private val repository = UserRepository()
+
+ private val _registerState = MutableStateFlow(null)
+ val registerState: StateFlow = _registerState
+
+ // Menyimpan data login state (loading, success, error)
+ private val _loginstate = mutableStateOf(LoginState.Loading)
+ val loginstate = _loginstate
+
+ // Menyimpan data pengguna yang login
+ private val _user = mutableStateOf(null)
+ val user = _user
+
+ fun login(username: String, password: String) {
+ viewModelScope.launch {
+ _loginstate.value = LoginState.Loading
+ // Fetch user by username from Firestore
+ repository.getUserByUsername(username) { fetchedUser ->
+ if (fetchedUser != null && fetchedUser.password == password) {
+ _user.value = fetchedUser // Store user data in ViewModel
+ _loginstate.value = LoginState.Success(fetchedUser)
+ Log.d("Login", "Fetched user: $fetchedUser")
+ Log.d("Login", "User fullName: ${fetchedUser?.fullName}")
+ Log.d("Login", "User username: ${fetchedUser?.username}")
+
+ } else {
+ _loginstate.value = LoginState.Error("Invalid username or password")
+ }
+ }
+ }
+ }
+
+ // **REGISTER USER**
+// fun register(email: String, password: String, user: UserModel) {
+// viewModelScope.launch {
+// val result = repository.registerUser(email, password, user)
+// _registerState.value = result
+// }
+// }
+
+// fun registerUser(
+// username: String,
+// fullName: String,
+// email: String,
+// password: String,
+// gender: String,
+// weight: String,
+// height: String,
+// onSuccess: () -> Unit,
+// onFailure: () -> Unit
+// ) {
+// val user = UserModel(username, fullName, email, password, gender, weight, height)
+//
+// viewModelScope.launch {
+// val isSuccess = repository.registerUser(user)
+// if (isSuccess) {
+// onSuccess()
+// } else {
+// onFailure()
+// }
+// }
+// }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/widget/CustomPassswordTextField.kt b/app/src/main/java/com/example/caloryapp/widget/CustomPassswordTextField.kt
new file mode 100644
index 0000000..6952a96
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/widget/CustomPassswordTextField.kt
@@ -0,0 +1,89 @@
+package com.example.caloryapp.widget
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+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.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+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 com.example.caloryapp.R
+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(
+ value: String,
+ onValueChange: (String) -> Unit,
+ placeholderText: String,
+ input: Boolean
+) {
+ // State untuk toggle password visibility
+ var isPasswordVisible by remember { mutableStateOf(false) }
+
+ OutlinedTextField(
+ textStyle = TextStyle(
+ fontSize = 16.sp,
+ color = primaryblack,
+ fontFamily = semibold,
+ letterSpacing = 0.5.sp
+ ),
+ value = value,
+ enabled = input,
+ onValueChange = onValueChange,
+ placeholder = {
+ Text(
+ text = placeholderText,
+ style = TextStyle(
+ fontSize = 16.sp,
+ color = primarygrey,
+ fontFamily = bold,
+ letterSpacing = 0.5.sp
+ )
+ )
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .padding(end = 16.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
+ )
+ }
+ },
+ colors = TextFieldDefaults.outlinedTextFieldColors(
+ backgroundColor = Color.White, // Warna putih dengan sedikit transparansi
+ focusedBorderColor = Color.Transparent, // Menghilangkan border saat fokus
+ unfocusedBorderColor = Color.Transparent, // Menghilangkan border saat tidak fokus
+ cursorColor = Color.Black // Warna kursor
+ ),
+ shape = RoundedCornerShape(10.dp) // Membuat border melengkung
+ )
+}
diff --git a/app/src/main/java/com/example/caloryapp/widget/CustomTextField.kt b/app/src/main/java/com/example/caloryapp/widget/CustomTextField.kt
index 8c7e45e..19e4f5c 100644
--- a/app/src/main/java/com/example/caloryapp/widget/CustomTextField.kt
+++ b/app/src/main/java/com/example/caloryapp/widget/CustomTextField.kt
@@ -20,15 +20,24 @@ import com.example.caloryapp.R
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
@Composable
fun CustomTextField(
value: String,
onValueChange: (String) -> Unit,
- placeholderText: String
+ placeholderText: String,
+ input: Boolean
) {
OutlinedTextField(
+ textStyle = TextStyle(
+ fontSize = 16.sp,
+ color = primaryblack,
+ fontFamily = semibold,
+ letterSpacing = 0.5.sp
+ ),
value = value,
+ enabled = input,
onValueChange = onValueChange,
placeholder = {
androidx.compose.material.Text(
@@ -36,7 +45,8 @@ fun CustomTextField(
style = TextStyle(
fontSize = 16.sp,
color = primarygrey,
- fontFamily = bold
+ fontFamily = bold,
+ letterSpacing = 0.5.sp
)
)
},
@@ -44,11 +54,11 @@ fun CustomTextField(
.fillMaxWidth()
.height(50.dp),
colors = TextFieldDefaults.outlinedTextFieldColors(
- backgroundColor = Color(0xFFF8F8F8), // Warna putih dengan sedikit transparansi
+ backgroundColor = Color.White, // Warna putih dengan sedikit transparansi
focusedBorderColor = Color.Transparent, // Menghilangkan border saat fokus
unfocusedBorderColor = Color.Transparent, // Menghilangkan border saat tidak fokus
cursorColor = Color.Black // Warna kursor
),
- shape = RoundedCornerShape(8.dp) // Membuat border melengkung
+ shape = RoundedCornerShape(10.dp) // Membuat border melengkung
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/caloryapp/widget/GenderDropdown.kt b/app/src/main/java/com/example/caloryapp/widget/GenderDropdown.kt
new file mode 100644
index 0000000..30ae064
--- /dev/null
+++ b/app/src/main/java/com/example/caloryapp/widget/GenderDropdown.kt
@@ -0,0 +1,72 @@
+package com.example.caloryapp.widget
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
+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.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.caloryapp.ui.theme.bold
+import com.example.caloryapp.ui.theme.primaryblack
+import com.example.caloryapp.ui.theme.primarygrey
+
+@Composable
+fun GenderDropdown(selectedGender: String, onGenderSelected: (String) -> Unit) {
+ var expanded by remember { mutableStateOf(false) }
+ val genderOptions = listOf("Pria", "Wanita")
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .border(1.dp, Color.White, shape = RoundedCornerShape(8.dp))
+ .background(Color.White, shape = RoundedCornerShape(8.dp))
+ .clickable { expanded = true },
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Row(
+ Modifier.padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = if (selectedGender.isEmpty()) "Pilih Gender" else selectedGender,
+ color = if (selectedGender.isEmpty()) primarygrey else primaryblack,
+ modifier = Modifier.weight(1f),
+ fontSize = 16.sp,
+ fontFamily = bold
+
+ )
+ Icon(Icons.Default.ArrowDropDown, contentDescription = "Dropdown Icon", tint = Color.Gray)
+ }
+
+ DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
+ genderOptions.forEach { gender ->
+ DropdownMenuItem(
+ text = { Text(gender) },
+ onClick = {
+ onGenderSelected(gender)
+ expanded = false
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index f74b04b..932a7ae 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,4 +2,5 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
+ alias(libs.plugins.google.gms.google.services) apply false
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8e39c80..76ebb1d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,21 +1,43 @@
[versions]
agp = "8.5.2"
+cameraCore = "1.4.1"
+cameraCamera2 = "1.4.1"
+glide = "4.12.0"
kotlin = "1.9.0"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
+kotlinxCoroutinesAndroid = "1.7.3"
+kotlinxCoroutinesCore = "1.6.4"
material = "1.7.7"
navigationCompose = "2.8.6"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.0"
composeBom = "2025.01.01"
ohteepee = "1.0.3"
+tensorflowLite = "2.12.0"
+tensorflowLiteSelectTfOps = "2.12.0"
+tensorflowLiteGpu = "2.12.0"
+tensorflowLiteSupport = "0.4.3"
+googleGmsGoogleServices = "4.4.2"
+firebaseFirestore = "25.1.2"
+firebaseAuth = "22.3.1"
+credentials = "1.5.0-rc01"
+credentialsPlayServicesAuth = "1.3.0"
+googleid = "1.1.1"
+#firebaseAuthKtx = "23.2.0"
[libraries]
+androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCamera2" }
+androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCore" }
+androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "cameraCore" }
+androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCore" }
+androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCore" }
androidx-material = { module = "androidx.compose.material:material", version.ref = "material" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@@ -29,9 +51,22 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
+kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
ohteepee = { module = "com.github.composeuisuite:ohteepee", version.ref = "ohteepee" }
+tensorflow-lite-support = { module = "org.tensorflow:tensorflow-lite-support", version.ref = "tensorflowLiteSupport" }
+tensorflow-lite-gpu = { module = "org.tensorflow:tensorflow-lite-gpu", version.ref = "tensorflowLiteGpu" }
+tensorflow-lite-select-tf-ops = { module = "org.tensorflow:tensorflow-lite-select-tf-ops", version.ref = "tensorflowLiteSelectTfOps" }
+tensorflow-lite = { module = "org.tensorflow:tensorflow-lite", version.ref = "tensorflowLite" }
+firebase-firestore = { group = "com.google.firebase", name = "firebase-firestore", version.ref = "firebaseFirestore" }
+firebase-auth = { group = "com.google.firebase", name = "firebase-auth", version.ref = "firebaseAuth" }
+androidx-credentials = { group = "androidx.credentials", name = "credentials", version.ref = "credentials" }
+androidx-credentials-play-services-auth = { group = "androidx.credentials", name = "credentials-play-services-auth", version.ref = "credentialsPlayServicesAuth" }
+googleid = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "googleid" }
+#firebase-auth-ktx = { group = "com.google.firebase", name = "firebase-auth-ktx", version.ref = "firebaseAuthKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+google-gms-google-services = { id = "com.google.gms.google-services", version.ref = "googleGmsGoogleServices" }