Progress 2

This commit is contained in:
E41212133_Naufal Kadhafi 2025-05-05 13:17:07 +07:00
parent 7092f1c308
commit 0c8df7e4c7
32 changed files with 1591 additions and 86 deletions

7
.idea/discord.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="androidx.compose.runtime.Composable" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">

View File

@ -69,6 +69,17 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="a16x" />
<option name="id" value="a16x" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A16 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
@ -289,6 +300,17 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="gta9pwifi" />
<option name="id" value="gta9pwifi" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-X210" />
<option name="screenDensity" value="240" />
<option name="screenX" value="1200" />
<option name="screenY" value="1920" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
@ -521,6 +543,17 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="id" value="tokay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>

View File

@ -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)
}

29
app/google-services.json Normal file
View File

@ -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"
}

View File

@ -1,6 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.READ_MEDIA_IMAGES"
tools:ignore="SelectedPhotoAccess" />
<uses-permission
android:name="android.permission.READ_MEDIA_VIDEO"
tools:ignore="SelectedPhotoAccess" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-feature android:name="android.hardware.camera.any" />
<application
android:allowBackup="true"

View File

@ -4,25 +4,32 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.navigation.compose.rememberNavController
import com.example.caloryapp.navigation.Navigation
import com.example.caloryapp.pages.account.ProfileScreen
import com.example.caloryapp.pages.dashboard.HomeScreen
//import com.example.caloryapp.pages.NavBarScreen
import com.example.caloryapp.pages.onboard.LoginScreen
import com.example.caloryapp.pages.onboard.OnBoardingScreen
import com.example.caloryapp.ui.theme.CaloryAppTheme
import com.example.caloryapp.widget.MainScreen
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
// val foodClassifier = FoodClassifier(this)
setContent {
CaloryAppTheme {
// Navigation()
// val context = LocalContext.current
// var classificationResult by remember { mutableStateOf("Memuat...") }
//
// Box {
// CameraPreview(onImageCaptured = { byteBuffer ->
// 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()
}
}
}

View File

@ -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 = "",
)

View File

@ -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)

View File

@ -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")
}

View File

@ -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,

View File

@ -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
)
)
}
}
}
}

View File

@ -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
)
}
}
}

View File

@ -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
)

View File

@ -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<String, Int>,modifier: Modifier = Modifier, navController: NavController) {
}
//@Composable
//fun ValidateFoodPlate(foodClassifier: FoodClassifier, areaCoverage: Map<String, Int>) {
// 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
}
}

View File

@ -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()
}

View File

@ -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<String, Int>
) {
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<String, Int>
) {
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()
}
}

View File

@ -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"
}
}

View File

@ -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,

View File

@ -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))

View File

@ -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))

View File

@ -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())
//}

View File

@ -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
)
)
}
}
}
}

View File

@ -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),

View File

@ -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
// }
// }
}

View File

@ -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)

View File

@ -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<Boolean?>(null)
val registerState: StateFlow<Boolean?> = _registerState
// Menyimpan data login state (loading, success, error)
private val _loginstate = mutableStateOf<LoginState>(LoginState.Loading)
val loginstate = _loginstate
// Menyimpan data pengguna yang login
private val _user = mutableStateOf<UserModel?>(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()
// }
// }
// }
}

View File

@ -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
)
}

View File

@ -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
)
}

View File

@ -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
}
)
}
}
}
}

View File

@ -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
}

View File

@ -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" }