commit f299dfb3a018db7b66ccbe0ca937d19cb427c431 Author: Diassdp <149257568+Diassdp@users.noreply.github.com> Date: Sun Jan 19 11:52:27 2025 +0700 First Commit: Version 1.0 [Non Optimized Version] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..605fe1a --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Health Journal \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..0897082 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..fdf8d99 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..5fe3eb4 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,76 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) + id("com.google.gms.google-services") +} + +android { + namespace = "com.healthjournal" + compileSdk = 34 + + defaultConfig { + applicationId = "com.healthjournal" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + viewBinding = true + } +} + +dependencies { + // Core libraries + implementation(libs.androidx.core.ktx) // Kotlin extensions for core Android functionality + implementation(libs.androidx.appcompat) // AppCompat library for backward compatibility + implementation(libs.material) // Material Design components + implementation(libs.androidx.activity) // Activity library for managing the activity lifecycle + implementation(libs.androidx.constraintlayout) // ConstraintLayout for flexible layouts + implementation ("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1") + implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") + implementation("androidx.navigation:navigation-ui-ktx:2.7.7") + + + // Firebase libraries + implementation(libs.firebase.database) + implementation(libs.firebase.auth) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + implementation(platform("com.google.firebase:firebase-bom:33.1.2")) + implementation("com.google.firebase:firebase-analytics") + + // Splash screen + implementation("androidx.core:core-splashscreen:1.0.0") // For splash screen support + + // DataStore preferences + implementation("androidx.datastore:datastore-preferences:1.0.0") // For storing key-value pairs + + // Coroutines + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2") // Coroutines for asynchronous programming + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2") // Coroutines for Android + + // Circular image views + implementation("de.hdodenhof:circleimageview:3.1.0") // For circular image views + implementation ("com.google.android.material:material:1.7.0") +} \ No newline at end of file diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..5b7bbb2 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "739932964546", + "project_id": "healthjournal-3fcd9", + "storage_bucket": "healthjournal-3fcd9.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:739932964546:android:21b91b8876bf7e7eaf9d5b", + "android_client_info": { + "package_name": "com.healthjournal" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyAhq4eRgReA4iBhAoa79hOLJdbxXFsCMyI" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/healthjournal/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/healthjournal/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..c62a06e --- /dev/null +++ b/app/src/androidTest/java/com/healthjournal/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.healthjournal + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.healthjournal", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7be2c74 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/healthjournal/algorithm/Algorithm.kt b/app/src/main/java/com/healthjournal/algorithm/Algorithm.kt new file mode 100644 index 0000000..2c71ab9 --- /dev/null +++ b/app/src/main/java/com/healthjournal/algorithm/Algorithm.kt @@ -0,0 +1,111 @@ +package com.healthjournal.algorithm + +import com.healthjournal.data.HealthData + +class AlgoritmaKesehatan { + fun recommendationOfTheDay(healthData: HealthData): HashMap { + val bloodSugarAnalysis = getBloodSugarAnalysis(healthData) + val bloodPressureAnalysis = getBloodPressureAnalysis(healthData) + val BMIAnalysis = getBMIAnalysis(healthData) + val tasks = getTaskBasedData(healthData) + + return hashMapOf( + "bloodSugarAnalysis" to bloodSugarAnalysis, + "bloodPressureAnalysis" to bloodPressureAnalysis, + "BMIAnalysis" to BMIAnalysis, + "tasks" to tasks + ) + } + + fun getBloodSugarAnalysis(healthData: HealthData): String { + return when (healthData.gender) { + "Female", "Other" -> when { + healthData.bloodSugar < 70 -> "Gula darah rendah, konsumsi makanan manis seperti buah atau madu." + healthData.bloodSugar in 70.0..140.0 -> "Gula darah normal, pertahankan pola makan sehat." + healthData.bloodSugar > 140 -> "Gula darah tinggi, kurangi makanan manis dan lakukan olahraga teratur." + else -> "Data gula darah tidak valid." + } + "Male" -> when { + healthData.bloodSugar < 70 -> "Gula darah rendah, segera konsumsi makanan berkarbohidrat." + healthData.bloodSugar in 70.0..130.0 -> "Gula darah normal, jaga pola makan seimbang." + healthData.bloodSugar > 130 -> "Gula darah tinggi, batasi konsumsi gula dan periksa kesehatan secara berkala." + else -> "Data gula darah tidak valid." + } + else -> "Data gula darah tidak valid." + } + } + + fun getBloodPressureAnalysis(healthData: HealthData): String { + return when (healthData.gender) { + "Female", "Other" -> when { + healthData.systolicBP < 90 || healthData.diastolicBP < 60 -> "Tekanan darah rendah, tingkatkan asupan cairan dan garam." + healthData.systolicBP in 90..120 && healthData.diastolicBP in 60..80 -> "Tekanan darah normal, pertahankan gaya hidup sehat." + healthData.systolicBP > 120 || healthData.diastolicBP > 80 -> "Tekanan darah tinggi, kurangi garam dan tingkatkan aktivitas fisik." + else -> "Data tekanan darah tidak valid." + } + "Male" -> when { + healthData.systolicBP < 100 || healthData.diastolicBP < 60 -> "Tekanan darah rendah, perbanyak cairan dan istirahat." + healthData.systolicBP in 100..130 && healthData.diastolicBP in 60..85 -> "Tekanan darah normal, pertahankan pola makan sehat." + healthData.systolicBP > 130 || healthData.diastolicBP > 85 -> "Tekanan darah tinggi, konsultasikan dengan dokter untuk pemeriksaan lebih lanjut." + else -> "Data tekanan darah tidak valid." + } + else -> "Data tekanan darah tidak valid." + } + } + + fun getBMIAnalysis(healthData: HealthData): String { + return when (healthData.gender) { + "Female", "Other" -> when { + healthData.BMI < 18.5 -> "BMI rendah, tingkatkan asupan kalori dengan makanan bernutrisi." + healthData.BMI in 18.5..24.9 -> "BMI normal, pertahankan pola makan seimbang." + healthData.BMI > 24.9 -> "BMI tinggi, lakukan olahraga teratur dan perbaiki pola makan." + else -> "Data BMI tidak valid." + } + "Male" -> when { + healthData.BMI < 20 -> "BMI rendah, tambahkan asupan protein dan makanan bergizi." + healthData.BMI in 20.0..25.0 -> "BMI normal, teruskan pola hidup sehat." + healthData.BMI > 25 -> "BMI tinggi, perbanyak aktivitas fisik dan atur pola makan rendah lemak." + else -> "Data BMI tidak valid." + } + else -> "Data BMI tidak valid." + } + } + + fun getTaskBasedData(healthData: HealthData): List> { + val tasks = mutableListOf>() + + // Task for Blood Sugar Control + if (healthData.bloodSugar > 140) { + tasks.add(mapOf("task" to "Cek kadar gula darah 2 kali sehari.", "completed" to false)) + tasks.add(mapOf("task" to "Kurangi konsumsi makanan tinggi gula.", "completed" to false)) + } else if (healthData.bloodSugar < 70) { + tasks.add(mapOf("task" to "Sediakan camilan sehat seperti buah atau kacang.", "completed" to false)) + } + + // Task for Blood Pressure Control + if (healthData.systolicBP > 120 || healthData.diastolicBP > 80) { + tasks.add(mapOf("task" to "Lakukan olahraga ringan seperti jalan kaki selama 30 menit.", "completed" to false)) + tasks.add(mapOf("task" to "Kurangi makanan asin dan berlemak.", "completed" to false)) + } else if (healthData.systolicBP < 90) { + tasks.add(mapOf("task" to "Perbanyak minum air putih dan istirahat.", "completed" to false)) + } + + // Task for BMI Control + if (healthData.BMI > 25) { + tasks.add(mapOf("task" to "Lakukan olahraga rutin minimal 3 kali seminggu.", "completed" to false)) + tasks.add(mapOf("task" to "Konsumsi lebih banyak sayuran dan serat.", "completed" to false)) + } else if (healthData.BMI < 18.5) { + tasks.add(mapOf("task" to "Tambahkan makanan tinggi kalori seperti kacang dan susu.", "completed" to false)) + } + + // Age and Gender Specific Tasks + if (healthData.age > 50) { + tasks.add(mapOf("task" to "Lakukan pemeriksaan kesehatan rutin setiap bulan.", "completed" to false)) + } + if (healthData.gender == "Female" || healthData.gender == "Other") { + tasks.add(mapOf("task" to "Perhatikan kebutuhan kalsium dan zat besi.", "completed" to false)) + } + + return tasks + } +} diff --git a/app/src/main/java/com/healthjournal/data/Data.kt b/app/src/main/java/com/healthjournal/data/Data.kt new file mode 100644 index 0000000..5221a19 --- /dev/null +++ b/app/src/main/java/com/healthjournal/data/Data.kt @@ -0,0 +1,23 @@ +package com.healthjournal.data + +import java.io.Serializable + + +data class HealthData( + val bloodSugar: Float, + val diastolicBP: Int, + val systolicBP: Int, + val BMI: Float, + val age: Int, + val gender: String + ) + +data class ResultData( + val journalID: String, + val bloodSugar: Float, + val diastolicBP: Int, + val systolicBP: Int, + val BMI: Float, + val date: String + ) : Serializable + diff --git a/app/src/main/java/com/healthjournal/model/SessionModel.kt b/app/src/main/java/com/healthjournal/model/SessionModel.kt new file mode 100644 index 0000000..38158ff --- /dev/null +++ b/app/src/main/java/com/healthjournal/model/SessionModel.kt @@ -0,0 +1,8 @@ +package com.healthjournal.model + +data class SessionModel ( + val userId: String, + val name: String, + val statusLogin: Boolean, + val loginTimestamp: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/healthjournal/ui/custom/CustomEdtTxt.kt b/app/src/main/java/com/healthjournal/ui/custom/CustomEdtTxt.kt new file mode 100644 index 0000000..3a47812 --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/custom/CustomEdtTxt.kt @@ -0,0 +1,109 @@ +package com.healthjournal.ui.custom + +import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import android.text.Editable +import android.text.InputFilter +import android.text.InputType +import android.text.TextWatcher +import android.util.AttributeSet +import android.util.Patterns +import androidx.core.content.ContextCompat +import com.healthjournal.R +import com.google.android.material.textfield.TextInputEditText + +class CustomEditText : TextInputEditText { + + private var errorBackground: Drawable? = null + private var defaultBackground: Drawable? = null + private var isError: Boolean = false + + constructor(context: Context) : super(context) { + init() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + background = if (isError) { + errorBackground + } else { + defaultBackground + } + } + + private fun init() { + errorBackground = ContextCompat.getDrawable(context, R.drawable.bg_edt_error) + defaultBackground = ContextCompat.getDrawable(context, R.drawable.bg_edt_default) + + if (inputType == InputType.TYPE_CLASS_NUMBER) { + filters = arrayOf(InputFilter { source, _, _, _, _, _ -> + source.filter { it.isDigit() } + }) + } + + addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + val input = p0.toString() + if (input.isEmpty()) { + error = context.getString(R.string.empty_field) + isError = true + } else { + error = null + isError = false + validateInput(input) + } + } + + override fun afterTextChanged(p0: Editable?) {} + }) + } + + private fun validateInput(input: String) { + when (inputType) { + EMAIL -> { + if (!Patterns.EMAIL_ADDRESS.matcher(input).matches()) { + error = context.getString(R.string.email_validation) + isError = true + } else { + isError = false + } + } + PASSWORD -> { + if (input.length < 6) { + error = context.getString(R.string.password_length) + isError = true + } else { + isError = false + } + } + InputType.TYPE_CLASS_NUMBER -> { + if (!input.matches("\\d+".toRegex())) { + error = context.getString(R.string.empty_field) + isError = true + } else { + isError = false + } + } + } + } + + companion object { + const val EMAIL = 0x00000021 + const val PASSWORD = 0x00000081 + } +} diff --git a/app/src/main/java/com/healthjournal/ui/dashboard/MainActivity.kt b/app/src/main/java/com/healthjournal/ui/dashboard/MainActivity.kt new file mode 100644 index 0000000..92f8c50 --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/dashboard/MainActivity.kt @@ -0,0 +1,227 @@ +package com.healthjournal.ui.dashboard + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.firebase.Firebase +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.database.FirebaseDatabase +import com.google.firebase.database.database +import com.healthjournal.R +import com.healthjournal.data.ResultData +import com.healthjournal.databinding.ActivityMainBinding +import com.healthjournal.ui.journal.input.JournalInputActivity +import com.healthjournal.ui.login.LoginActivity +import com.healthjournal.ui.profile.ProfileActivity +import com.healthjournal.ui.recommendation.RecommendationActivity +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + + +class MainActivity : AppCompatActivity() { + private lateinit var binding: ActivityMainBinding + private lateinit var user: FirebaseAuth + private lateinit var mainAdapter: MainAdapter + private val healthDataList: MutableList = mutableListOf() + private val database = Firebase.database + private var dailyReport = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + user = FirebaseAuth.getInstance() + + userCheck() + setupListener() + populateHistory() + navigationBottomBar() + } + + private fun navigationBottomBar(){ + binding.bottomNavigation.setOnItemSelectedListener { + when(it.itemId){ + R.id.nav_add -> addJournal() + R.id.nav_home -> home() + R.id.nav_profile -> profile() + } + true + } + } + + private fun getWeekCount() { + val today = Calendar.getInstance() + val userID = user.currentUser!!.uid + var count = 0 + + val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()) + + today.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY) + val startDate = dateFormat.format(today.time) + + val endDate = today.clone() as Calendar + endDate.add(Calendar.DAY_OF_WEEK, 6) + val endDateString = dateFormat.format(endDate.time) + + database.getReference("users").child(userID).child("journal") + .get() + .addOnCompleteListener { task -> + if (task.isSuccessful) { + count = task.result.children.count { snapshot -> + val date = snapshot.child("date").value.toString() + date >= startDate && date <= endDateString + } + Log.d("debug", "Entries found: $count for week: $startDate - $endDateString") + binding.tvDailyCounter.text = "$count/7" + } else { + Log.e("error", task.exception?.message.toString()) + Toast.makeText(this, task.exception?.message, Toast.LENGTH_SHORT).show() + } + } + } + + + + private fun addJournal(){ + if (dailyReport != false){ + Toast.makeText(this, "Daily Report Already Created", Toast.LENGTH_SHORT).show() + } else { + startActivity(Intent(this, JournalInputActivity::class.java)) + } + } + + private fun home(){ + } + + private fun profile(){ + intent = Intent(this, ProfileActivity::class.java) + startActivity(intent) + } + + + private fun setupListener(){ + binding.btnRecomendation.setOnClickListener { + startActivity(Intent(this, RecommendationActivity::class.java)) + finish() + } + binding.btnInputData.setOnClickListener { + startActivity(Intent(this, JournalInputActivity::class.java)) + finish() + } + } + + private fun dailycheck() { + val today = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(android.icu.util.Calendar.getInstance().time).toString() + val userID = user.currentUser!!.uid + database.getReference("users").child(userID).child("journal").get().addOnCompleteListener { task -> + if (task.isSuccessful) { + task.result.children.forEach { + if (it.child("date").value.toString() == today) { + dailyReport = true + switchLayout() + populateTodayReport() + } + } + } else { + Log.d("error", task.exception!!.message.toString()) + Toast.makeText(this, task.exception!!.message, Toast.LENGTH_SHORT).show() + } + } + } + + private fun switchLayout(){ + if (binding.clDailyReport1.visibility == View.VISIBLE){ + binding.clDailyReport1.visibility = View.INVISIBLE + binding.clDailyReport2.visibility = View.VISIBLE + binding.btnRecomendation.visibility = View.VISIBLE + binding.ivCuboidIndicator1Green.visibility = View.VISIBLE + binding.ivCuboidIndicator2Green.visibility = View.VISIBLE + binding.ivCuboidIndicator1Orange.visibility = View.INVISIBLE + binding.ivCuboidIndicator2Orange.visibility = View.INVISIBLE + } else { + binding.clDailyReport1.visibility = View.VISIBLE + binding.clDailyReport2.visibility = View.INVISIBLE + binding.btnRecomendation.visibility = View.GONE + binding.ivCuboidIndicator1Green.visibility = View.INVISIBLE + binding.ivCuboidIndicator2Green.visibility = View.INVISIBLE + binding.ivCuboidIndicator1Orange.visibility = View.VISIBLE + binding.ivCuboidIndicator2Orange.visibility = View.VISIBLE + } + } + + private fun userCheck() { + val user = FirebaseAuth.getInstance().currentUser + if (user == null) { + Toast.makeText(this, "Please Login to an account", Toast.LENGTH_SHORT).show() + val intent = Intent(this, LoginActivity::class.java) + startActivity(intent) + finish() + } else { + Toast.makeText(this, "Welcome back!", Toast.LENGTH_SHORT).show() + dailycheck() + getWeekCount() + } + } + + + private fun populateHistory() { + val userID = user.currentUser!!.uid + mainAdapter = MainAdapter(healthDataList) + + binding.rvHealthHistory.apply { + adapter = mainAdapter + layoutManager = LinearLayoutManager(this@MainActivity) + } + + database.getReference("users").child(userID).child("journal").get() + .addOnCompleteListener { task -> + if (task.isSuccessful) { + healthDataList.clear() + task.result.children.forEach { snapshot -> + val journalID = snapshot.child("date").value.toString() + val bloodSugar = snapshot.child("bloodSugar").value.toString().toFloatOrNull() ?: 0f + val diastolicBP = snapshot.child("bloodPressureDIA").value.toString().toIntOrNull() ?: 0 + val systolicBP = snapshot.child("bloodPressureSYS").value.toString().toIntOrNull() ?: 0 + val BMI = snapshot.child("bmi").value.toString().toFloatOrNull() ?: 0f + val date = snapshot.child("date").value.toString() + + val resultData = ResultData(journalID, bloodSugar, diastolicBP, systolicBP, BMI, date) + healthDataList.add(resultData) + } + mainAdapter.notifyDataSetChanged() + } else { + Log.d("error", task.exception?.message.toString()) + Toast.makeText(this, task.exception?.message, Toast.LENGTH_SHORT).show() + } + } + } + + + private fun populateTodayReport(){ + val today = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(android.icu.util.Calendar.getInstance().time).toString() + val userID = user.currentUser!!.uid + database.getReference("users").child(userID).child("journal").get().addOnCompleteListener { task -> + if (task.isSuccessful) { + task.result.children.forEach { + if (it.child("date").value.toString() == today) { + binding.tvBloodsugarLevel.text = it.child("bloodSugar").value.toString()+" mg/dL" + binding.tvBloodsugarDesc.text = it.child("recommendation").child("bloodSugarAnalysis").value.toString() + binding.tvBloodpressureLevel.text = it.child("bloodPressureDIA").value.toString()+"/"+it.child("bloodPressureSYS").value.toString()+" mm Hg" + binding.tvBloodpressureDesc.text = it.child("recommendation").child("bloodPressureAnalysis").value.toString() + binding.tvBmiLevel.text = it.child("BMI").value.toString() +" BMI" + binding.tvBmiDesc.text = it.child("recommendation").child("BMIAnalysis").value.toString() + } + } + } else { + Log.d("error", task.exception!!.message.toString()) + Toast.makeText(this, task.exception!!.message, Toast.LENGTH_SHORT).show() + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/healthjournal/ui/dashboard/MainAdapter.kt b/app/src/main/java/com/healthjournal/ui/dashboard/MainAdapter.kt new file mode 100644 index 0000000..da81314 --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/dashboard/MainAdapter.kt @@ -0,0 +1,73 @@ +package com.healthjournal.ui.dashboard + +import android.content.Intent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.healthjournal.R +import com.healthjournal.data.ResultData +import com.healthjournal.ui.journal.detail.DetailJournalActivity +import java.text.SimpleDateFormat +import java.util.Locale + +class MainAdapter(private val healthDataList: MutableList) : RecyclerView.Adapter() { + + class HealthViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val tvDay: TextView = view.findViewById(R.id.tv_day) + val tvDate: TextView = view.findViewById(R.id.tv_date) + val tvBloodSugar: TextView = view.findViewById(R.id.tv_BS_Level) + val tvBloodPressure: TextView = view.findViewById(R.id.tv_BP_Level) + } + + // Function to update the list and notify the adapter + fun setData(newData: List) { + healthDataList.clear() + healthDataList.addAll(newData) + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HealthViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_health_history, parent, false) + return HealthViewHolder(view) + } + + private fun dayOfWeek(date: String): String { + return try { + val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()) + val parsedDate = dateFormat.parse(date) + if (parsedDate != null) { + SimpleDateFormat("EEEE", Locale.getDefault()).format(parsedDate) + } else { + "Invalid Date" + } + } catch (e: Exception) { + "Invalid Date" + } + } + + private fun countGoals (){ + + } + + override fun onBindViewHolder(holder: HealthViewHolder, position: Int) { + val healthData = healthDataList[position] + + holder.tvDay.text = dayOfWeek(healthData.date) + holder.tvDate.text = healthData.date + holder.tvBloodSugar.text = "${healthData.bloodSugar} mg/dL" + holder.tvBloodPressure.text = "${healthData.diastolicBP}/${healthData.systolicBP} mm Hg" + + holder.itemView.setOnClickListener { + val context = holder.itemView.context + val intent = Intent(context, DetailJournalActivity::class.java).apply { + putExtra("HEALTH_DATA", healthData) + } + context.startActivity(intent) + } + } + + override fun getItemCount(): Int = healthDataList.size +} diff --git a/app/src/main/java/com/healthjournal/ui/journal/detail/DetailJournalActivity.kt b/app/src/main/java/com/healthjournal/ui/journal/detail/DetailJournalActivity.kt new file mode 100644 index 0000000..33938d5 --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/journal/detail/DetailJournalActivity.kt @@ -0,0 +1,92 @@ +package com.healthjournal.ui.journal.detail + +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.google.firebase.Firebase +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.database.database +import com.healthjournal.R +import com.healthjournal.data.ResultData +import com.healthjournal.databinding.ActivityDetailJournalBinding + +class DetailJournalActivity : AppCompatActivity() { + private lateinit var binding: ActivityDetailJournalBinding + private lateinit var user: FirebaseAuth + private val database = Firebase.database + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityDetailJournalBinding.inflate(layoutInflater) + setContentView(binding.root) + val healthData = intent.getSerializableExtra("HEALTH_DATA") as? ResultData + + } + + private fun populateDetailJournal() { + val userID = user.currentUser?.uid + val journalDate = intent.getStringExtra("JOURNAL_DATE") + if (userID != null && journalDate != null) { + database.getReference("users").child(userID).child("journal").child(journalDate).get() + .addOnCompleteListener(DetailJournalActivity()) { + if (it.isSuccessful) { + val data = it.result + if (data != null) { +/* + binding.tvBloodsugarLevel2.text = data.bloodSugarLevel + binding.tvBloodsugarDesc.text = data.bloodSugarDesc + binding.tvBloodpressureLevel2.text = data.bloodPressureLevel + binding.tvBloodpressureDesc.text = data.bloodPressureDesc + binding.tvBMILevel2.text = data.bmiLevel + binding.tvBMIDesc.text = data.bmiDesc + binding.tvGoals2.text = countGoalsCompleted(data.goalsCompleted) + binding.tvJournalNote.text = data.journalNote +*/ + } + } else { + Toast.makeText(this, it.exception!!.message, Toast.LENGTH_SHORT).show() + Log.d("error", it.exception!!.message.toString()) + } + } + } + } + + private fun countGoalsCompleted(goalsCompleted: List): String { + var count = 0 + for (completed in goalsCompleted) { + if (completed) { + count++ + } + } + return count.toString() + } + + private fun deleteHistory() { + val userID = user.currentUser?.uid + val journalDate = intent.getStringExtra("JOURNAL_DATE") + if (userID != null && journalDate != null) { + database.getReference("users").child(userID).child("journal").child(journalDate) + .removeValue() + .addOnCompleteListener(DetailJournalActivity()) { + if (it.isSuccessful) { + Toast.makeText(this, "History deleted successfully", Toast.LENGTH_SHORT) + .show() + finish() + } else { + Toast.makeText(this, it.exception!!.message, Toast.LENGTH_SHORT).show() + } + } + } + } + + private fun setupListener() { + binding.btnDeleteHistory.setOnClickListener() { + deleteHistory() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/healthjournal/ui/journal/input/JournalInputActivity.kt b/app/src/main/java/com/healthjournal/ui/journal/input/JournalInputActivity.kt new file mode 100644 index 0000000..d6cf591 --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/journal/input/JournalInputActivity.kt @@ -0,0 +1,127 @@ +package com.healthjournal.ui.journal.input + +import android.content.Intent +import android.icu.util.Calendar +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.database.FirebaseDatabase +import com.healthjournal.algorithm.AlgoritmaKesehatan +import com.healthjournal.databinding.ActivityJournalInputBinding +import com.healthjournal.ui.dashboard.MainActivity +import com.healthjournal.data.HealthData +import java.text.SimpleDateFormat +import java.util.Locale + +class JournalInputActivity : AppCompatActivity() { + private lateinit var binding: ActivityJournalInputBinding + private lateinit var user: FirebaseAuth + private lateinit var database: FirebaseDatabase + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityJournalInputBinding.inflate(layoutInflater) + setContentView(binding.root) + + user = FirebaseAuth.getInstance() + database = FirebaseDatabase.getInstance() + + val userId = user.currentUser?.uid + if (userId != null) { + database.getReference("users").child(userId).get() + .addOnCompleteListener { task -> + if (task.isSuccessful && task.result.exists()) { + binding.edtInputWeight.setText(task.result.child("weight").value.toString()) + binding.edtHeight.setText(task.result.child("height").value.toString()) + binding.edtAge.setText(task.result.child("date").value.toString()) + binding.edtGender.setText(task.result.child("gender").value.toString()) + } else { + Toast.makeText(this, "Error: ${task.exception?.message}", Toast.LENGTH_SHORT).show() + Log.d("error", task.exception?.message.toString()) + } + } + } else { + Toast.makeText(this, "User not logged in", Toast.LENGTH_SHORT).show() + finish() + } + + setupListener() + } + + private fun setupListener() { + binding.btnInputData.setOnClickListener { + submitData() + } + } + + private fun calculateBMI(weight: Int, height: Int): Float { + val heightInMeters = height / 100f + val BMI = weight / (heightInMeters * heightInMeters) + return BMI + } + + fun calculateAge(birthDate: String): Int { + val sdf = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()) + val date = sdf.parse(birthDate) + val birthCalendar = Calendar.getInstance() + birthCalendar.time = date + val today = Calendar.getInstance() + + var age = today.get(Calendar.YEAR) - birthCalendar.get(Calendar.YEAR) + + if (today.get(Calendar.DAY_OF_YEAR) < birthCalendar.get(Calendar.DAY_OF_YEAR)) { + age-- + } + + return age + } + + private fun submitData() { + val userId = user.currentUser?.uid + if (userId == null) { + Toast.makeText(this, "User not logged in", Toast.LENGTH_SHORT).show() + return + } + val journalDate = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(Calendar.getInstance().time).toString() + val bloodPressureSYS = binding.edtInputBloodPressureSYS.text.toString() + val bloodPressureDIA = binding.edtInputBloodPressureDIA.text.toString() + val bloodSugar = binding.edtInputBloodSugar.text.toString() + val weight = binding.edtInputWeight.text.toString().toInt() + val height = binding.edtHeight.text.toString().toInt() + val note = binding.edtInputNote.text.toString() + val age = calculateAge(binding.edtAge.text.toString()) + val gender = binding.edtGender.text.toString() + Log.d("journalDate", journalDate) + if (weight != null && bloodPressureSYS.isNotEmpty() && bloodPressureDIA.isNotEmpty() && bloodSugar.isNotEmpty()) { + database.getReference("users").child(userId).child("height").get().addOnSuccessListener { snapshot -> + val BMI = calculateBMI(weight, height) + val healthData = HealthData(bloodSugar.toFloat(), bloodPressureSYS.toInt(), bloodPressureDIA.toInt(), BMI,age,gender) + val recommendation = AlgoritmaKesehatan().recommendationOfTheDay(healthData) + + val data = hashMapOf( + "date" to journalDate, + "bloodPressureSYS" to bloodPressureSYS, + "bloodPressureDIA" to bloodPressureDIA, + "bloodSugar" to bloodSugar, + "BMI" to BMI, + "note" to note, + "recommendation" to recommendation + ) + database.getReference("users").child(userId).child("journal").push().setValue(data).addOnCompleteListener { + if (it.isSuccessful) { + Toast.makeText(this, "Data Input Success", Toast.LENGTH_SHORT).show() + startActivity(Intent(this, MainActivity::class.java)) + finish() + } else { + Toast.makeText(this, it.exception?.message, Toast.LENGTH_SHORT).show() + Log.d("error", it.exception?.message.toString()) + } + } + } + } else { + Toast.makeText(this, "Please fill all fields correctly", Toast.LENGTH_SHORT).show() + } + } +} diff --git a/app/src/main/java/com/healthjournal/ui/login/LoginActivity.kt b/app/src/main/java/com/healthjournal/ui/login/LoginActivity.kt new file mode 100644 index 0000000..c44427a --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/login/LoginActivity.kt @@ -0,0 +1,57 @@ +package com.healthjournal.ui.login + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.google.firebase.auth.FirebaseAuth +import com.healthjournal.databinding.ActivityLoginBinding +import com.healthjournal.ui.dashboard.MainActivity +import com.healthjournal.ui.register.RegisterActivity + +class LoginActivity : AppCompatActivity() { + private lateinit var binding: ActivityLoginBinding + private lateinit var user: FirebaseAuth + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityLoginBinding.inflate(layoutInflater) + setContentView(binding.root) + user = FirebaseAuth.getInstance() + setuplistener() + } + + private fun setuplistener() { + binding.btnLogin.setOnClickListener{ + loginUser() + } + + binding.btnRegister.setOnClickListener{ + startActivity(Intent(this@LoginActivity, RegisterActivity::class.java)) + finish() + } + } + + private fun loginUser() { + val email = binding.edtEmail.text.toString() + val password = binding.edtPassword.text.toString() + if (email.isEmpty() || password.isEmpty()) { + if (email.isEmpty()) binding.edtEmail.error = "Email cannot be empty" + if (password.isEmpty()) binding.edtPassword.error = "Password cannot be empty" + Toast.makeText(this, "Email & Password cannot be empty", Toast.LENGTH_SHORT).show() + } else { + user.signInWithEmailAndPassword(email, password).addOnCompleteListener(LoginActivity()){ task -> + if (task.isSuccessful) { + Toast.makeText(this, "Login Success", Toast.LENGTH_SHORT).show() + startActivity(Intent(this@LoginActivity, MainActivity::class.java)) + finish() + } + else{ + Toast.makeText(this, task.exception!!.message, Toast.LENGTH_SHORT).show() + Log.d("error", task.exception!!.message.toString()) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/healthjournal/ui/profile/ProfileActivity.kt b/app/src/main/java/com/healthjournal/ui/profile/ProfileActivity.kt new file mode 100644 index 0000000..b9c924b --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/profile/ProfileActivity.kt @@ -0,0 +1,81 @@ +package com.healthjournal.ui.profile + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.google.firebase.Firebase +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.database.database +import com.healthjournal.R +import com.healthjournal.databinding.ActivityProfileBinding +import com.healthjournal.ui.login.LoginActivity + +class ProfileActivity : AppCompatActivity() { + private lateinit var binding: ActivityProfileBinding + private lateinit var user: FirebaseAuth + private val database = Firebase.database + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityProfileBinding.inflate(layoutInflater) + setContentView(binding.root) + setupListener() + populateData() + } + + private fun setupListener() { + binding.btnLogout.setOnClickListener { + logout() + } + + binding.btnUpdateData.setOnClickListener { + updateData() + } + } + + private fun logout(){ + user.signOut() + startActivity(Intent(this, LoginActivity::class.java)) + finish() + } + + private fun updateData(){ + val userID = user.currentUser?.uid + val name = binding.edtName.text.toString() + val email = binding.edtEmail.text.toString() + val date = binding.edtDates.text.toString() + val weight = binding.edtInputWeight.text.toString() + val height = binding.edtInputHeight.text.toString() + val userRef = database.getReference("users").child(userID!!) + userRef.child("name").setValue(name) + userRef.child("email").setValue(email) + userRef.child("date").setValue(date) + userRef.child("weight").setValue(weight) + userRef.child("height").setValue(height) + Toast.makeText(this, "Data Updated", Toast.LENGTH_SHORT).show() + populateData() + } + + private fun populateData(){ + val userID = user.currentUser?.uid + val userRef = database.getReference("users").child(userID!!) + userRef.get().addOnCompleteListener(ProfileActivity()){ + if (it.isSuccessful){ + binding.edtName.setText(it.result.child("name").value.toString()) + binding.edtDates.setText(it.result.child("date").value.toString()) + binding.edtInputHeight.setText(it.result.child("height").value.toString()) + binding.edtInputWeight.setText(it.result.child("weight").value.toString()) + } else { + Toast.makeText(this, it.exception.toString(), Toast.LENGTH_SHORT).show() + Log.d("ProfileActivity", it.exception.toString()) + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/healthjournal/ui/recommendation/RecomendationAdapter.kt b/app/src/main/java/com/healthjournal/ui/recommendation/RecomendationAdapter.kt new file mode 100644 index 0000000..25b5bcc --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/recommendation/RecomendationAdapter.kt @@ -0,0 +1,40 @@ +package com.healthjournal.ui.recommendation + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CheckBox +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.healthjournal.R + +class RecommendationAdapter( + private val recommendationList: List>, + private val onCheckedChange: (position: Int, isChecked: Boolean) -> Unit +) : RecyclerView.Adapter() { + + inner class RecommendationViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val tvGoalName: TextView = view.findViewById(R.id.tv_goal_name) + val checkBox: CheckBox = view.findViewById(R.id.toggle) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecommendationViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_goals_recommendation, parent, false) + return RecommendationViewHolder(view) + } + + override fun onBindViewHolder(holder: RecommendationViewHolder, position: Int) { + val (task, isCompleted) = recommendationList[position] + holder.tvGoalName.text = task + holder.checkBox.isChecked = isCompleted + holder.checkBox.setOnCheckedChangeListener { _, isChecked -> + onCheckedChange(position, isChecked) + } + holder.itemView.setOnClickListener { + holder.checkBox.isChecked = !holder.checkBox.isChecked + } + } + + override fun getItemCount(): Int = recommendationList.size +} diff --git a/app/src/main/java/com/healthjournal/ui/recommendation/RecommendationActivity.kt b/app/src/main/java/com/healthjournal/ui/recommendation/RecommendationActivity.kt new file mode 100644 index 0000000..57cf637 --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/recommendation/RecommendationActivity.kt @@ -0,0 +1,99 @@ +package com.healthjournal.ui.recommendation + +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.database.FirebaseDatabase +import com.healthjournal.databinding.ActivityRecommendationBinding +import java.text.SimpleDateFormat +import java.util.Locale + +class RecommendationActivity : AppCompatActivity() { + private lateinit var binding: ActivityRecommendationBinding + private lateinit var user: FirebaseAuth + private lateinit var database: FirebaseDatabase + private lateinit var adapter: RecommendationAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityRecommendationBinding.inflate(layoutInflater) + setContentView(binding.root) + + user = FirebaseAuth.getInstance() + database = FirebaseDatabase.getInstance() + + populateData() + } + + private fun populateData() { + val userID = user.currentUser?.uid ?: return + val today = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()) + .format(android.icu.util.Calendar.getInstance().time) + + database.getReference("users").child(userID).child("journal") + .get() + .addOnCompleteListener { task -> + if (task.isSuccessful) { + task.result.children.forEach { + if (it.child("date").value.toString() == today) { + binding.tvBloodsugarLevel2.text = "${it.child("bloodSugar").value} mg/dL" + binding.tvBloodsugarDesc.text = it.child("recommendation").child("bloodSugarAnalysis").value.toString() + binding.tvBloodpressureLevel2.text = "${it.child("bloodPressureDIA").value}/${it.child("bloodPressureSYS").value} mm Hg" + binding.tvBloodpressureDesc.text = it.child("recommendation").child("bloodPressureAnalysis").value.toString() + binding.tvBMILevel2.text = "${it.child("BMI").value} BMI" + binding.tvBMIDesc.text = it.child("recommendation").child("BMIAnalysis").value.toString() + + val tasks = it.child("recommendation").child("tasks").value + val referencePath = it.key ?: return@forEach + populateRecomendation(tasks, userID, referencePath) + } + } + } else { + Log.e("error", task.exception?.message.toString()) + Toast.makeText(this, task.exception?.message, Toast.LENGTH_SHORT).show() + } + } + } + + private fun populateRecomendation(tasks: Any?, userID: String, journalKey: String) { + val taskList = mutableListOf>() + + // Handle List format for tasks + if (tasks is List<*>) { + tasks.forEach { taskData -> + if (taskData is Map<*, *> && taskData["task"] != null && taskData["completed"] != null) { + val taskDescription = taskData["task"].toString() + val isCompleted = taskData["completed"] as? Boolean ?: false + taskList.add(Pair(taskDescription, isCompleted)) + } + } + + // Set up RecyclerView and Adapter + adapter = RecommendationAdapter(taskList) { position, isChecked -> + taskList[position] = taskList[position].copy(second = isChecked) + + // Update Firebase + database.getReference("users") + .child(userID) + .child("journal") + .child(journalKey) + .child("recommendation") + .child("tasks") + .child(position.toString()) + .child("completed") + .setValue(isChecked) + } + + binding.rvTasks.apply { + layoutManager = LinearLayoutManager(this@RecommendationActivity) + adapter = this@RecommendationActivity.adapter + } + } else { + Log.e("error", "Invalid tasks data format") + Log.e("error", tasks.toString()) + } + } +} diff --git a/app/src/main/java/com/healthjournal/ui/register/RegisterActivity.kt b/app/src/main/java/com/healthjournal/ui/register/RegisterActivity.kt new file mode 100644 index 0000000..50424fa --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/register/RegisterActivity.kt @@ -0,0 +1,60 @@ +package com.healthjournal.ui.register + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.google.firebase.auth.FirebaseAuth +import com.healthjournal.databinding.ActivityRegisterBinding +import com.healthjournal.ui.users.UsersInputActivity + +class RegisterActivity : AppCompatActivity() { + private lateinit var binding: ActivityRegisterBinding + private lateinit var user: FirebaseAuth + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityRegisterBinding.inflate(layoutInflater) + setContentView(binding.root) + user = FirebaseAuth.getInstance() + setuplistener() + } + + private fun setuplistener(){ + binding.btnRegister.setOnClickListener{ + registerUser() + } + binding.btnLogin.setOnClickListener{ + + } + } + + private fun registerUser(){ + val email = binding.edtEmail.text.toString() + val password = binding.edtPassword.text.toString() + val confirmPassword = binding.edtPasswordConfirm.text.toString() + + if (email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()){ + if (email.isEmpty()) binding.edtEmail.error = "Email cannot be empty" + if (password.isEmpty()) binding.edtPassword.error = "Password cannot be empty" + if (confirmPassword.isEmpty()) binding.edtPasswordConfirm.error = "Confirm Password cannot be empty" + } else { + if (password == confirmPassword){ + user.createUserWithEmailAndPassword(email, password).addOnCompleteListener(RegisterActivity()){ task -> + if(task.isSuccessful){ + Toast.makeText(this, "Register Success", Toast.LENGTH_SHORT).show() + startActivity(Intent(this@RegisterActivity,UsersInputActivity::class.java)) + finish() + } else{ + Toast.makeText(this, task.exception!!.message, Toast.LENGTH_SHORT).show() + Log.d("error", task.exception!!.message.toString()) + } + } + } else { + binding.edtPasswordConfirm.error = "Password does not match" + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/healthjournal/ui/splashscreen/SplashscreenActivity.kt b/app/src/main/java/com/healthjournal/ui/splashscreen/SplashscreenActivity.kt new file mode 100644 index 0000000..7e406ad --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/splashscreen/SplashscreenActivity.kt @@ -0,0 +1,21 @@ +package com.healthjournal.ui.splashscreen + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.healthjournal.R + +class SplashscreenActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContentView(R.layout.activity_splashscreen) + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/healthjournal/ui/users/UsersInputActivity.kt b/app/src/main/java/com/healthjournal/ui/users/UsersInputActivity.kt new file mode 100644 index 0000000..588c429 --- /dev/null +++ b/app/src/main/java/com/healthjournal/ui/users/UsersInputActivity.kt @@ -0,0 +1,101 @@ +package com.healthjournal.ui.users + +import android.app.DatePickerDialog +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.google.firebase.Firebase +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.database.database +import com.healthjournal.databinding.ActivityUsersInputBinding +import com.healthjournal.ui.dashboard.MainActivity +import java.util.Calendar + +class UsersInputActivity : AppCompatActivity() { + private lateinit var binding: ActivityUsersInputBinding + private lateinit var user: FirebaseAuth + private val database = Firebase.database + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityUsersInputBinding.inflate(layoutInflater) + setContentView(binding.root) + user = FirebaseAuth.getInstance() + val gender = arrayOf("Male", "Female", "Other") + + // Adapter for conditions + val conditionAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, gender) + binding.dropdownGender.setAdapter(conditionAdapter) + + userCheck() + setuplistener() + } + + private fun userCheck(){ + if(user.currentUser != null){ + user.currentUser?.let { + binding.tvEmail.text = it.email + } + } + } + + private fun setuplistener(){ + binding.btnInput.setOnClickListener{ + inputUserData() + } + + binding.edtDateOfBirth.setOnClickListener{ + datepicker() + } + } + + private fun datepicker(){ + val calendar = Calendar.getInstance() + val year = calendar.get(Calendar.YEAR) + val month = calendar.get(Calendar.MONTH) + val day = calendar.get(Calendar.DAY_OF_MONTH) + + val datePickerDialog = DatePickerDialog( + this, + { _, selectedYear, selectedMonth, selectedDay -> + val selectedDate = "$selectedDay/${selectedMonth + 1}/$selectedYear" + binding.edtDateOfBirth.setText(selectedDate) + }, + year, month, day + ) + datePickerDialog.show() + } + + private fun inputUserData() { + val userID = user.currentUser?.uid + val name = binding.edtName.text.toString() + val gender = binding.dropdownGender.text.toString() + val date = binding.edtDateOfBirth.text.toString() + val weight = binding.edtInputWeight.text.toString() + val height = binding.edtInputHeigh.text.toString() + if(userID != null){ + if (name.isNotEmpty() && gender.isNotEmpty() && date.isNotEmpty() && weight.isNotEmpty() && height.isNotEmpty()){ + val data = hashMapOf( + "name" to name, + "gender" to gender, + "date" to date, + "height" to height, + "weight" to weight) + database.getReference("users").child(userID).setValue(data).addOnCompleteListener(UsersInputActivity()){ + if(it.isSuccessful){ + Toast.makeText(this, "Data Input Success", Toast.LENGTH_SHORT).show() + startActivity(Intent(this@UsersInputActivity, MainActivity::class.java)) + finish() + } else{ + Toast.makeText(this, it.exception!!.message, Toast.LENGTH_SHORT).show() + Log.d("error", it.exception!!.message.toString()) + } + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_button.xml b/app/src/main/res/drawable/bg_button.xml new file mode 100644 index 0000000..0be0aa0 --- /dev/null +++ b/app/src/main/res/drawable/bg_button.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_edt_default.xml b/app/src/main/res/drawable/bg_edt_default.xml new file mode 100644 index 0000000..ed92bcc --- /dev/null +++ b/app/src/main/res/drawable/bg_edt_default.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_edt_error.xml b/app/src/main/res/drawable/bg_edt_error.xml new file mode 100644 index 0000000..f372450 --- /dev/null +++ b/app/src/main/res/drawable/bg_edt_error.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_form.xml b/app/src/main/res/drawable/bg_form.xml new file mode 100644 index 0000000..665162f --- /dev/null +++ b/app/src/main/res/drawable/bg_form.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_input.xml b/app/src/main/res/drawable/bg_input.xml new file mode 100644 index 0000000..acc1082 --- /dev/null +++ b/app/src/main/res/drawable/bg_input.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_login.png b/app/src/main/res/drawable/bg_login.png new file mode 100644 index 0000000..e37f734 Binary files /dev/null and b/app/src/main/res/drawable/bg_login.png differ diff --git a/app/src/main/res/drawable/bg_profile.xml b/app/src/main/res/drawable/bg_profile.xml new file mode 100644 index 0000000..863500b --- /dev/null +++ b/app/src/main/res/drawable/bg_profile.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/cuboid.png b/app/src/main/res/drawable/cuboid.png new file mode 100644 index 0000000..89341ef Binary files /dev/null and b/app/src/main/res/drawable/cuboid.png differ diff --git a/app/src/main/res/drawable/cuboid_green.png b/app/src/main/res/drawable/cuboid_green.png new file mode 100644 index 0000000..3f04a0b Binary files /dev/null and b/app/src/main/res/drawable/cuboid_green.png differ diff --git a/app/src/main/res/drawable/daily_counter.xml b/app/src/main/res/drawable/daily_counter.xml new file mode 100644 index 0000000..30a6003 --- /dev/null +++ b/app/src/main/res/drawable/daily_counter.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/frame_1.png b/app/src/main/res/drawable/frame_1.png new file mode 100644 index 0000000..36ee077 Binary files /dev/null and b/app/src/main/res/drawable/frame_1.png differ diff --git a/app/src/main/res/drawable/frame_2.png b/app/src/main/res/drawable/frame_2.png new file mode 100644 index 0000000..c95f5a3 Binary files /dev/null and b/app/src/main/res/drawable/frame_2.png differ diff --git a/app/src/main/res/drawable/frame_3.png b/app/src/main/res/drawable/frame_3.png new file mode 100644 index 0000000..4973bdc Binary files /dev/null and b/app/src/main/res/drawable/frame_3.png differ diff --git a/app/src/main/res/drawable/frame_4.png b/app/src/main/res/drawable/frame_4.png new file mode 100644 index 0000000..d9ff8cd Binary files /dev/null and b/app/src/main/res/drawable/frame_4.png differ diff --git a/app/src/main/res/drawable/ic_add.png b/app/src/main/res/drawable/ic_add.png new file mode 100644 index 0000000..dbc2569 Binary files /dev/null and b/app/src/main/res/drawable/ic_add.png differ diff --git a/app/src/main/res/drawable/ic_home.png b/app/src/main/res/drawable/ic_home.png new file mode 100644 index 0000000..df3df3a Binary files /dev/null and b/app/src/main/res/drawable/ic_home.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_profile.png b/app/src/main/res/drawable/ic_profile.png new file mode 100644 index 0000000..d5b5249 Binary files /dev/null and b/app/src/main/res/drawable/ic_profile.png differ diff --git a/app/src/main/res/drawable/profile_dummy_img.png b/app/src/main/res/drawable/profile_dummy_img.png new file mode 100644 index 0000000..6793ec7 Binary files /dev/null and b/app/src/main/res/drawable/profile_dummy_img.png differ diff --git a/app/src/main/res/font/poppins_regular.ttf b/app/src/main/res/font/poppins_regular.ttf new file mode 100644 index 0000000..9f0c71b Binary files /dev/null and b/app/src/main/res/font/poppins_regular.ttf differ diff --git a/app/src/main/res/layout/activity_detail_journal.xml b/app/src/main/res/layout/activity_detail_journal.xml new file mode 100644 index 0000000..6fa8ebf --- /dev/null +++ b/app/src/main/res/layout/activity_detail_journal.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_journal_input.xml b/app/src/main/res/layout/activity_journal_input.xml new file mode 100644 index 0000000..d47095d --- /dev/null +++ b/app/src/main/res/layout/activity_journal_input.xml @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..ff553cd --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..fa4f3ca --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml new file mode 100644 index 0000000..fd5165f --- /dev/null +++ b/app/src/main/res/layout/activity_profile.xml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_recommendation.xml b/app/src/main/res/layout/activity_recommendation.xml new file mode 100644 index 0000000..15f8fe5 --- /dev/null +++ b/app/src/main/res/layout/activity_recommendation.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml new file mode 100644 index 0000000..3a7cdd5 --- /dev/null +++ b/app/src/main/res/layout/activity_register.xml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splashscreen.xml b/app/src/main/res/layout/activity_splashscreen.xml new file mode 100644 index 0000000..05da7f0 --- /dev/null +++ b/app/src/main/res/layout/activity_splashscreen.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_users_input.xml b/app/src/main/res/layout/activity_users_input.xml new file mode 100644 index 0000000..1288e1f --- /dev/null +++ b/app/src/main/res/layout/activity_users_input.xml @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_goals_recommendation.xml b/app/src/main/res/layout/item_goals_recommendation.xml new file mode 100644 index 0000000..fccd473 --- /dev/null +++ b/app/src/main/res/layout/item_goals_recommendation.xml @@ -0,0 +1,41 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_health_history.xml b/app/src/main/res/layout/item_health_history.xml new file mode 100644 index 0000000..d8c1ec8 --- /dev/null +++ b/app/src/main/res/layout/item_health_history.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml new file mode 100644 index 0000000..fcbeda2 --- /dev/null +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..841e765 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..2e86a12 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,16 @@ + + + #FF000000 + #FFFFFFFF + + #004343 + #006C6C + + #7ABDBD + #E8F4F8 + + #D9D9D9 + + #FF5722 + #FF0000 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..b898e51 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ + + Health Journal + + + // Validation + Masukkan alamat email yang valid! + Kolom ini tidak boleh kurang dari 8 karakter! + Kolom ini tidak boleh kosong! + This field is required! + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..18d9972 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + +