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/3rd-sc-7-HarmonyCare-App.iml b/.idea/3rd-sc-7-HarmonyCare-App.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/3rd-sc-7-HarmonyCare-App.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..6e86672
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..96ad405
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index dfd57c1..64beaad 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-# 3rd-sc-7-HarmonyCare-App
\ No newline at end of file
+# 3rd-sc-7-HarmonyCare-Android
\ No newline at end of file
diff --git a/front-end/.gitignore b/front-end/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/front-end/.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/front-end/.idea/.gitignore b/front-end/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/front-end/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/front-end/.idea/.name b/front-end/.idea/.name
new file mode 100644
index 0000000..645aeda
--- /dev/null
+++ b/front-end/.idea/.name
@@ -0,0 +1 @@
+HarmonyCare
\ No newline at end of file
diff --git a/front-end/.idea/appInsightsSettings.xml b/front-end/.idea/appInsightsSettings.xml
new file mode 100644
index 0000000..371f2e2
--- /dev/null
+++ b/front-end/.idea/appInsightsSettings.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/.idea/compiler.xml b/front-end/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/front-end/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/.idea/deploymentTargetDropDown.xml b/front-end/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..0c0c338
--- /dev/null
+++ b/front-end/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/.idea/gradle.xml b/front-end/.idea/gradle.xml
new file mode 100644
index 0000000..0897082
--- /dev/null
+++ b/front-end/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/.idea/kotlinc.xml b/front-end/.idea/kotlinc.xml
new file mode 100644
index 0000000..8d81632
--- /dev/null
+++ b/front-end/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/.idea/migrations.xml b/front-end/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/front-end/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/.idea/misc.xml b/front-end/.idea/misc.xml
new file mode 100644
index 0000000..8978d23
--- /dev/null
+++ b/front-end/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/.idea/vcs.xml b/front-end/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/front-end/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/README.md b/front-end/README.md
new file mode 100644
index 0000000..1045a24
--- /dev/null
+++ b/front-end/README.md
@@ -0,0 +1 @@
+HarmonyCare-Android
diff --git a/front-end/app/.gitignore b/front-end/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/front-end/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/front-end/app/build.gradle.kts b/front-end/app/build.gradle.kts
new file mode 100644
index 0000000..992ad78
--- /dev/null
+++ b/front-end/app/build.gradle.kts
@@ -0,0 +1,71 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+ id("androidx.navigation.safeargs.kotlin")
+}
+
+android {
+ namespace = "com.example.harmonycare"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "com.example.harmonycare"
+ 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
+ dataBinding = true
+ }
+
+}
+
+dependencies {
+ //credential
+ implementation("androidx.credentials:credentials:1.2.0")
+ //retrofit 라이브러리
+ implementation("com.squareup.retrofit2:retrofit:2.6.4")
+ //Gson 변환기 라이브러리
+ implementation("com.squareup.retrofit2:converter-gson:2.6.4")
+ //Scalars 변환기 라이브러리
+ implementation("com.squareup.retrofit2:converter-scalars:2.6.4")
+ implementation("com.squareup.okhttp3:okhttp:4.9.1")
+ implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
+ implementation("androidx.browser:browser:1.3.0")
+ implementation("com.google.android.libraries.identity.googleid:googleid:1.1.0")
+ implementation("com.google.android.gms:play-services-auth:20.7.0")
+ implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("com.google.android.material:material:1.11.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
+ implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
+ implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
+ implementation("com.google.android.gms:play-services-pal:20.2.0")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+}
\ No newline at end of file
diff --git a/front-end/app/proguard-rules.pro b/front-end/app/proguard-rules.pro
new file mode 100644
index 0000000..b8e2ed9
--- /dev/null
+++ b/front-end/app/proguard-rules.pro
@@ -0,0 +1,25 @@
+# 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
+-if class androidx.credentials.CredentialManager
+-keep class androidx.credentials.playservices.** {
+ *;
+}
\ No newline at end of file
diff --git a/front-end/app/src/androidTest/java/com/example/harmonycare/ExampleInstrumentedTest.kt b/front-end/app/src/androidTest/java/com/example/harmonycare/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..16e97d4
--- /dev/null
+++ b/front-end/app/src/androidTest/java/com/example/harmonycare/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.harmonycare
+
+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.example.harmonycare", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/AndroidManifest.xml b/front-end/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b21cdcc
--- /dev/null
+++ b/front-end/app/src/main/AndroidManifest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/LoginActivity.kt b/front-end/app/src/main/java/com/example/harmonycare/LoginActivity.kt
new file mode 100644
index 0000000..8e20394
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/LoginActivity.kt
@@ -0,0 +1,164 @@
+package com.example.harmonycare
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import android.widget.ImageButton
+import androidx.appcompat.app.AppCompatActivity
+import android.widget.Toast
+import com.example.harmonycare.data.SharedPreferencesManager
+import com.example.harmonycare.retrofit.ApiManager
+import com.example.harmonycare.retrofit.ApiService
+import com.example.harmonycare.retrofit.RetrofitClient
+import com.example.harmonycare.ui.addbaby.AddBabyActivity
+import java.util.regex.Pattern
+
+
+class LoginActivity : AppCompatActivity() {
+
+ private lateinit var loginButton: ImageButton
+ private lateinit var webView: WebView
+ private lateinit var sharedPreferences: SharedPreferences
+
+ @SuppressLint("SetJavaScriptEnabled")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_login)
+
+ // SharedPreferences 초기화
+ SharedPreferencesManager.init(this)
+
+ webView = findViewById(R.id.webView)
+ loginButton = findViewById(R.id.GoogleLoginButton)
+
+ webView.settings.apply {
+ // 웹뷰 설정 초기화
+ userAgentString = null
+ javaScriptEnabled = false
+
+ // 새로운 설정 적용
+ userAgentString = "Mozilla/5.0 AppleWebKit/535.19 Chrome/120.0.0 Mobile Safari/535.19"
+ javaScriptEnabled = true
+ }
+
+ webView.webViewClient = object : WebViewClient() {
+
+ @Deprecated("Deprecated in Java")
+ override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
+ if (url != null && url.startsWith("http://localhost:8080")) {
+ // 리다이렉션 링크를 가져와서 파라미터 추출
+ val authCode = extractCodeFromUrl(url)
+ Log.d("kkang", "url:$url")
+ Log.d("kkang", "authCode:$authCode")
+ if (authCode != null) {
+
+ val apiService = RetrofitClient.createService(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+ // 여기서 POST요청
+ apiManager.loginUser(authCode,
+ onResponse = { accessToken, hasBaby ->
+ // accessToken을 저장하거나 필요한 작업을 수행합니다.
+ showToast("accessToken 저장됨: $accessToken")
+ Log.d("kkang", "accessToken:$accessToken")
+ SharedPreferencesManager.saveAccessToken(accessToken)
+
+ // hasBaby가 true이면 MainActivity로, false이면 AddBabyActivity로 이동
+ val destinationActivity = if (hasBaby) MainActivity::class.java else AddBabyActivity::class.java
+ startActivity(Intent(this@LoginActivity, destinationActivity))
+
+ // 현재 액티비티 종료
+ //finish()
+ },
+ onFailure = {
+ // 실패한 경우 처리
+ // 실패할 경우에도 반드시 로그를 남기고, 그에 따른 예외 처리를 수행해야 합니다.
+ Log.e("LoginActivity", "Login failed")
+ }
+ )
+ //아기 정보 조회
+ //아기 정보가 있는 경우 바로 MainActivity로 이동
+ // MainActivity로 이동
+ // startActivity(Intent(this@LoginActivity, MainActivity::class.java))
+
+ //아기 정보가 없는 경우, AddBabyActivity로 이동하여 아기 생성
+
+
+ } else {
+ showToast("인증 코드가 없습니다.")
+ }
+ return true // 리다이렉션된 링크 처리를 위해 true 반환
+ }
+ // 다른 URL은 기본적인 방식으로 처리
+ return super.shouldOverrideUrlLoading(view, url)
+ }
+
+ //로그아웃
+
+ }
+
+ val url = "https://accounts.google.com/o/oauth2/v2/auth?" +
+ "scope=https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile&" +
+ "access_type=offline&" +
+ "include_granted_scopes=true&" +
+ "response_type=code&" +
+ "redirect_uri=http://localhost:8080&" +
+ "client_id=185976520158-phphtutm302clototd3rqgecng4a4bg2.apps.googleusercontent.com"
+
+
+
+ /*// Google 로그인 페이지 URL 로드
+ val url = "https://accounts.google.com/o/oauth2/v2/auth?" +
+ "scope=https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile&" +
+ "access_type=offline&" +
+ "include_granted_scopes=true&" +
+ "response_type=code&" +
+ "redirect_uri=http://localhost:8080&" +
+ "client_id=185976520158-phphtutm302clototd3rqgecng4a4bg2.apps.googleusercontent.com"*/
+
+ loginButton.setOnClickListener {
+ // SharedPreferences에서 로그인 정보 삭제하여 로그아웃 처리
+ //logout()
+
+ // 이미지 버튼 클릭 시 웹뷰 로드
+ webView.visibility = View.VISIBLE
+ webView.loadUrl(url)
+ }
+ }
+
+
+ private fun extractCodeFromUrl(url: String): String? {
+ val pattern = Pattern.compile("\\bcode=([^&]+)")
+ val matcher = pattern.matcher(url)
+
+ return if (matcher.find()) {
+ matcher.group(1)
+ } else {
+ null
+ }
+ }
+ private fun logout() {
+ val editor = sharedPreferences.edit()
+ editor.remove("accessToken") // accessToken 제거
+ editor.apply()
+
+ // 여기서 로그아웃 후의 동작을 정의할 수 있음
+ }
+
+ private fun saveAccessTokenCode(accessToken: String) {
+ // SharedPreferences에 accessToken 저장
+ val editor = sharedPreferences.edit()
+ editor.putString("accessToken", accessToken)
+ editor.apply()
+ }
+
+ private fun showToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+ }
+
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/MainActivity.kt b/front-end/app/src/main/java/com/example/harmonycare/MainActivity.kt
new file mode 100644
index 0000000..bc7f6ce
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/MainActivity.kt
@@ -0,0 +1,48 @@
+package com.example.harmonycare
+
+import android.os.Bundle
+import com.google.android.material.bottomnavigation.BottomNavigationView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.navigation.NavController
+import androidx.navigation.findNavController
+import androidx.navigation.ui.AppBarConfiguration
+import androidx.navigation.ui.setupActionBarWithNavController
+import androidx.navigation.ui.setupWithNavController
+import com.example.harmonycare.databinding.ActivityMainBinding
+
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityMainBinding
+ private lateinit var navController: NavController
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ val navView: BottomNavigationView = binding.navView
+
+ navController = findNavController(R.id.nav_host_fragment_activity_main)
+
+ val appBarConfiguration = AppBarConfiguration(
+ setOf(
+ R.id.navigation_checklist, R.id.navigation_record, R.id.navigation_pattern,R.id.navigation_community,R.id.navigation_profile
+ )
+ )
+ setupActionBarWithNavController(navController, appBarConfiguration)
+ navView.setupWithNavController(navController)
+
+ navView.setOnNavigationItemSelectedListener { item ->
+ when (item.itemId) {
+ R.id.navigation_checklist, R.id.navigation_record, R.id.navigation_pattern, R.id.navigation_community, R.id.navigation_profile -> {
+ if (navController.currentDestination?.id != item.itemId) {
+ navController.navigate(item.itemId)
+ }
+ true
+ }
+ else -> false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/data/Baby.kt b/front-end/app/src/main/java/com/example/harmonycare/data/Baby.kt
new file mode 100644
index 0000000..e527be3
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/data/Baby.kt
@@ -0,0 +1,10 @@
+package com.example.harmonycare.data
+
+import com.google.gson.annotations.SerializedName
+
+data class Baby(
+ @SerializedName("name") val name: String,
+ @SerializedName("gender") val gender: String,
+ @SerializedName("birthDate") val birthDate: String,
+ @SerializedName("birthWeight") val birthWeight: Float
+)
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/data/Checklist.kt b/front-end/app/src/main/java/com/example/harmonycare/data/Checklist.kt
new file mode 100644
index 0000000..2a886c0
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/data/Checklist.kt
@@ -0,0 +1,11 @@
+package com.example.harmonycare.data
+
+import java.time.LocalDateTime
+
+data class Checklist (
+ var checklistId: Int,
+ var title: String,
+ var days: List,
+ var checkTime: LocalDateTime,
+ var isCheck: Boolean
+)
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/data/Comment.kt b/front-end/app/src/main/java/com/example/harmonycare/data/Comment.kt
new file mode 100644
index 0000000..2e6fbeb
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/data/Comment.kt
@@ -0,0 +1,7 @@
+package com.example.harmonycare.data
+
+class Comment (
+ var commentId: Int,
+ var content: String,
+ var isMyComment: Boolean
+)
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/data/Post.kt b/front-end/app/src/main/java/com/example/harmonycare/data/Post.kt
new file mode 100644
index 0000000..29be71f
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/data/Post.kt
@@ -0,0 +1,9 @@
+package com.example.harmonycare.data
+
+import java.io.Serializable
+
+data class Post (
+ var communityId: Int,
+ var title: String,
+ var content: String
+) : Serializable
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/data/Record.kt b/front-end/app/src/main/java/com/example/harmonycare/data/Record.kt
new file mode 100644
index 0000000..5767749
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/data/Record.kt
@@ -0,0 +1,11 @@
+package com.example.harmonycare.data
+
+import java.time.LocalDateTime
+
+data class Record(
+ var recordId: Int = 0,
+ var recordTask: String,
+ var startTime: LocalDateTime,
+ var endTime: LocalDateTime,
+ var description: String
+)
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/data/SharedPreferencesManager.kt b/front-end/app/src/main/java/com/example/harmonycare/data/SharedPreferencesManager.kt
new file mode 100644
index 0000000..97ff2f7
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/data/SharedPreferencesManager.kt
@@ -0,0 +1,27 @@
+package com.example.harmonycare.data
+
+import android.content.Context
+import android.content.SharedPreferences
+
+object SharedPreferencesManager {
+
+ private const val PREFS_NAME = "MyPrefs"
+ private const val KEY_AUCCESSTOKEN = "auccesstoken"
+
+ private lateinit var sharedPreferences: SharedPreferences
+
+ fun init(context: Context) {
+ sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ }
+
+ fun saveAccessToken(accessToken: String) {
+ with(sharedPreferences.edit()) {
+ putString(KEY_AUCCESSTOKEN, accessToken)
+ apply()
+ }
+ }
+
+ fun getAccessToken(): String? {
+ return sharedPreferences.getString(KEY_AUCCESSTOKEN, null)
+ }
+}
diff --git a/front-end/app/src/main/java/com/example/harmonycare/login/BaseFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/login/BaseFragment.kt
new file mode 100644
index 0000000..c6ed0a7
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/login/BaseFragment.kt
@@ -0,0 +1,10 @@
+package com.example.harmonycare.login
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.Toolbar
+import androidx.fragment.app.Fragment
+
+abstract class BaseFragment : Fragment() {
+ protected fun setSupportActionBar(toolbar: Toolbar) {
+ (activity as AppCompatActivity).setSupportActionBar(toolbar)
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/login/DataBinding.kt b/front-end/app/src/main/java/com/example/harmonycare/login/DataBinding.kt
new file mode 100644
index 0000000..eb84ce0
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/login/DataBinding.kt
@@ -0,0 +1,25 @@
+package com.example.harmonycare.login
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.fragment.app.Fragment
+interface DataBinding {
+ val binding: VB
+
+ companion object {
+ fun get(activity: AppCompatActivity, resId: Int): VB {
+ return DataBindingUtil.setContentView(activity, resId)
+ }
+
+ fun get(fragment: Fragment, resId: Int): VB {
+ return DataBindingUtil.inflate(fragment.layoutInflater, resId, null, false)
+ }
+
+ fun get(layout: ViewGroup, resId: Int): VB {
+ return DataBindingUtil.inflate(LayoutInflater.from(layout.context), resId, layout, true)
+ }
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/login/LoginTestActivity.kt b/front-end/app/src/main/java/com/example/harmonycare/login/LoginTestActivity.kt
new file mode 100644
index 0000000..5ad0197
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/login/LoginTestActivity.kt
@@ -0,0 +1,49 @@
+/*
+package com.example.harmonycare.login
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.widget.Toolbar
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.setupActionBarWithNavController
+import com.example.harmonycare.R
+import com.example.harmonycare.databinding.ActivityLoginTestBinding
+import com.example.harmonycare.databinding.ActivityMainBinding
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions.*
+
+class LoginTestActivity : AppCompatActivity(), DataBinding {
+
+ override val binding by lazy { DataBinding.get(this,
+ R.layout.activity_login_test
+ ) }
+
+ private val navController by lazy {
+ (supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment).navController
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ onCreateDataBinding()
+ onCreateSupportActionBar()
+ }
+
+ private fun onCreateDataBinding() {
+
+ binding.lifecycleOwner = this
+ }
+
+ private fun onCreateSupportActionBar() {
+ setSupportActionBar(binding.toolbar)
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ return navController.navigateUp() || super.onSupportNavigateUp()
+ }
+
+ override fun setSupportActionBar(toolbar: Toolbar?) {
+ super.setSupportActionBar(toolbar)
+ setupActionBarWithNavController(navController)
+ }
+}
+*/
diff --git a/front-end/app/src/main/java/com/example/harmonycare/login/LoginTestFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/login/LoginTestFragment.kt
new file mode 100644
index 0000000..e10f8ae
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/login/LoginTestFragment.kt
@@ -0,0 +1,89 @@
+/*
+package com.example.harmonycare.login
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import com.example.harmonycare.R
+import com.example.harmonycare.databinding.FragmentLoginBinding
+import com.example.harmonycare.login.BaseFragment
+import com.example.harmonycare.databinding.FragmentLoginTestBinding
+import com.google.android.gms.auth.api.signin.GoogleSignIn
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions.*
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.common.api.Scope
+import com.example.harmonycare.login.DataBinding
+import kotlinx.coroutines.*
+import org.json.JSONObject
+import java.net.URL
+import javax.net.ssl.HttpsURLConnection
+import kotlin.concurrent.thread
+
+class LoginTestFragment : BaseFragment(), DataBinding {
+ override val binding by lazy { DataBinding.get(this, R.layout.fragment_login_test) }
+
+ private val viewModel by activityViewModels()
+
+ companion object {
+ private const val GOOGLE_LOGIN = 1000
+ }
+ companion object {
+ private const val GOOGLE_LOGIN = 1000
+ }
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ when(requestCode) {
+ GOOGLE_LOGIN -> {
+ try {
+ val task = GoogleSignIn.getSignedInAccountFromIntent(data)
+ val account = task.result!!
+
+
+ updateViewModelWithGoogleAccount(account)
+
+ } catch (e: ApiException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ }
+ private fun onCreateOnSigninWithGoogle() {
+ binding.setOnSigninWithGoogle {
+ val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+ .requestEmail()
+ .requestScopes(
+ Scope(PeopleServiceScopes.USER_BIRTHDAY_READ),
+ Scope(PeopleServiceScopes.USER_GENDER_READ)
+ )
+ .requestServerAuthCode(getString(R.string.google_client_id))
+ .build()
+
+ val client = GoogleSignIn.getClient(requireActivity(), gso)
+
+ val signInIntent: Intent = client.signInIntent
+ startActivityForResult(signInIntent, GOOGLE_LOGIN)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ onViewCreated(R.id.google)
+ }
+ private fun onCreateDataBinding() {
+
+ binding.lifecycleOwner = this
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ onCreateDataBinding()
+ onCreateOnSigninWithGoogle()
+ return binding.root
+ }
+
+ private fun onCreateDataBinding() {
+ binding.lifecycleOwner = this
+ }
+
+}*/
diff --git a/front-end/app/src/main/java/com/example/harmonycare/login/MainActivityViewModel.kt b/front-end/app/src/main/java/com/example/harmonycare/login/MainActivityViewModel.kt
new file mode 100644
index 0000000..1579bc1
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/login/MainActivityViewModel.kt
@@ -0,0 +1,24 @@
+package com.example.harmonycare.login
+
+import androidx.lifecycle.ViewModel
+
+class MainActivityViewModel : ViewModel() {
+ var email: String? = null
+ var birth: String? = null
+ var name: String? = null
+ var gender: String? = null
+ var type: String? = null
+
+ companion object {
+ const val GOOGLE = "Google"
+ const val KAKAO = "Kakao"
+ const val NAVER = "Naver"
+ }
+
+ fun reset() {
+ email = null
+ birth = null
+ name = null
+ gender = null
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/retrofit/ApiManager.kt b/front-end/app/src/main/java/com/example/harmonycare/retrofit/ApiManager.kt
new file mode 100644
index 0000000..79712da
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/retrofit/ApiManager.kt
@@ -0,0 +1,706 @@
+package com.example.harmonycare.retrofit
+
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import com.example.harmonycare.data.Checklist
+import com.example.harmonycare.data.Comment
+import com.example.harmonycare.data.Post
+import com.google.gson.annotations.SerializedName
+import okhttp3.RequestBody
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.Header
+import retrofit2.http.Headers
+import retrofit2.http.POST
+import okhttp3.RequestBody.Companion.toRequestBody
+import retrofit2.http.GET
+import retrofit2.http.Query
+import com.example.harmonycare.data.Record
+import retrofit2.http.DELETE
+import retrofit2.http.PUT
+import retrofit2.http.Path
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+interface ApiService {
+
+ @Headers("Content-Type: application/json")
+ @POST("/api/v1/auth/login")
+ fun loginUser(@Body requestBody: RequestBody): Call
+
+ @POST("/api/v1/baby")
+ fun addBaby(
+ @Header("Authorization") accessToken: String,
+ @Body requestBody: RequestBody
+ ): Call
+
+ @POST("/api/v1/record")
+ fun saveRecord(
+ @Header("Authorization") authToken: String,
+ @Body requestBody: RecordSaveRequest
+ ): Call
+
+ @GET("/api/v1/record")
+ fun getRecordsForDay(
+ @Query("day") day: String,
+ @Query("range") range: Int,
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @PUT("/api/v1/record/{recordId}")
+ fun updateRecord(
+ @Path("recordId") recordId: Int,
+ @Header("Authorization") authToken: String,
+ @Body requestBody: RecordSaveRequest
+ ): Call
+
+ @DELETE("/api/v1/record/{recordId}")
+ fun deleteRecord(
+ @Path("recordId") recordId: Int,
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @GET("/api/v1/checklist/me")
+ fun getChecklist(
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @POST("/api/v1/checklist")
+ fun saveChecklist(
+ @Header("Authorization") authToken: String,
+ @Body requestBody: ChecklistSaveRequest
+ ): Call
+
+ @DELETE("/api/v1/checklist/{checklistId}")
+ fun deleteChecklist(
+ @Path("checklistId") checklistId: Int,
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @PUT("/api/v1/checklist/check/{checklistId}")
+ fun toggleChecklist(
+ @Path("checklistId") checklistId: Int,
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @PUT("/api/v1/checklist/{checklistId}")
+ fun updateChecklist(
+ @Path("checklistId") checklistId: Int,
+ @Header("Authorization") authToken: String,
+ @Body requestBody: ChecklistSaveRequest
+ ): Call
+
+ @GET("/api/v1/community")
+ fun getCommunity(
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @POST("/api/v1/community")
+ fun saveCommunity(
+ @Header("Authorization") authToken: String,
+ @Body requestBody: CommunitySaveRequest
+ ): Call
+
+ @POST("/api/v1/comment")
+ fun saveComment(
+ @Header("Authorization") authToken: String,
+ @Body requestBody: CommentSaveRequest
+ ): Call
+
+ @GET("/api/v1/comment/{communityId}")
+ fun getComment(
+ @Path("communityId") communityId: Int,
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @DELETE("/api/v1/comment/{commentId}")
+ fun deleteComment(
+ @Path("commentId") commentId: Int,
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @GET("/api/v1/member/profiles")
+ fun getProfile(
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @GET("/api/v1/community/me")
+ fun getMyCommunity(
+ @Header("Authorization") authToken: String
+ ): Call
+
+ @DELETE("/api/v1/community/{communityId}")
+ fun deleteCommunity(
+ @Path("communityId") communityId: Int,
+ @Header("Authorization") authToken: String
+ ): Call
+}
+
+class ApiManager(private val apiService: ApiService) {
+
+ fun loginUser(authCode: String, onResponse: (accessToken: String, hasBaby: Boolean) -> Unit, onFailure: () -> Unit) {
+ val requestBody = RequestBody.create("application/json; charset=utf-8".toMediaTypeOrNull(), "{\"authcode\": \"$authCode\"}")
+ val call = apiService.loginUser(requestBody)
+ call.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val responseData = response.body()?.response
+ val accessToken = responseData?.token?.accessToken
+ val hasBaby = responseData?.hasBaby ?: false
+ if (accessToken != null) {
+ onResponse(accessToken, hasBaby)
+ return
+ }
+ }
+ onFailure()
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ onFailure()
+ }
+ })
+ }
+
+ fun saveRecord(
+ accessToken: String,
+ recordTask: String,
+ startTime: String,
+ endTime: String,
+ description: String,
+ onResponse: (Int) -> Unit,
+ onFailure: () -> Unit
+ ) {
+ val recordRequest = RecordSaveRequest(recordTask, startTime, endTime, description)
+ val call = apiService.saveRecord("Bearer $accessToken", recordRequest)
+ call.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val saveRecordResponse = response.body()
+ if (saveRecordResponse != null) {
+ onResponse(saveRecordResponse.status)
+ return
+ }
+ }
+ onFailure()
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ onFailure()
+ }
+ })
+ }
+
+ fun getRecordsForDay(day: String, range: Int, authToken: String, onResponse: (List?) -> Unit, onFailure: (Throwable) -> Unit) {
+ val call = apiService.getRecordsForDay(day, range, authToken)
+ call.enqueue(object : Callback {
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ val recordGetResponse: RecordGetResponse? = response.body()
+
+ onResponse(recordGetResponse?.response?.map { recordRequest ->
+ Record(
+ recordId = recordRequest.recordId,
+ recordTask = recordRequest.recordTask,
+ startTime = LocalDateTime.parse(recordRequest.startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
+ endTime = LocalDateTime.parse(recordRequest.endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
+ description = recordRequest.description
+ )
+ })
+ } else {
+ onFailure(Throwable("Unsuccessful response: ${response.code()}"))
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ onFailure(t)
+ }
+ })
+ }
+
+ fun updateRecord(
+ recordId: Int,
+ recordTask: String,
+ startTime: String,
+ endTime: String,
+ description: String,
+ accessToken: String,
+ onResponse: (Boolean) -> Unit,
+ onFailure: (Throwable) -> Unit
+ ) {
+ val recordRequest = RecordSaveRequest(recordTask, startTime, endTime, description)
+ val call = apiService.updateRecord(recordId, "Bearer $accessToken", recordRequest)
+ call.enqueue(object : Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ onResponse(true)
+ } else {
+ onFailure(Throwable("Failed to update record: ${response.code()}"))
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ onFailure(t)
+ }
+ })
+ }
+
+ fun deleteRecord(
+ recordId: Int, accessToken: String, onResponse: (Boolean) -> Unit
+ ) {
+ val call = apiService.deleteRecord(recordId, "Bearer $accessToken")
+ call.enqueue(object : Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ onResponse(true)
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ onResponse(false)
+ }
+
+ })
+ }
+
+ fun getChecklist(accessToken: String, checklistData: (List) -> Unit) {
+ val call = apiService.getChecklist("Bearer $accessToken")
+ call.enqueue(object: Callback {
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ val responseData: ApiSuccessResultListChecklistReadResponse? = response.body()
+
+ responseData?.response?.map { checklist ->
+ Checklist(
+ checklistId = checklist.checklistId,
+ title = checklist.title,
+ days = checklist.days,
+ checkTime = LocalDateTime.parse(checklist.checkTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
+ isCheck = checklist.isCheck
+ )
+ }?.let { checklistData(it) }
+ }
+ }
+
+ override fun onFailure(
+ call: Call,
+ t: Throwable
+ ) {
+ Log.d("kkang", "checklist read failed")
+ }
+
+ })
+ }
+
+ fun saveChecklist(accessToken: String, title: String, days: List, checkTime: String, onResponse: (Boolean) -> Unit) {
+ val requestBody = ChecklistSaveRequest(title, days, checkTime)
+ val call = apiService.saveChecklist("Bearer $accessToken", requestBody)
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ onResponse(true)
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("kkang", "save checklist failed")
+ }
+
+ })
+ }
+
+ fun deleteChecklist(checklistId: Int, accessToken: String, onResponse: (Boolean) -> Unit) {
+ val call = apiService.deleteChecklist(checklistId, "Bearer $accessToken")
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) onResponse(true)
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("kkang", "delete checklist failed")
+ }
+
+ })
+ }
+
+ fun toggleChecklist(checklistId: Int, accessToken: String, onResponse: (Boolean) -> Unit) {
+ val call = apiService.toggleChecklist(checklistId, "Bearer $accessToken")
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) onResponse(true)
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("kkang", "toggle checklist failed")
+ }
+
+ })
+ }
+
+ fun updateChecklist(
+ checklistId: Int,
+ accessToken: String,
+ title: String,
+ days: List,
+ checkTime: String,
+ onResponse: (Boolean) -> Unit) {
+ val requestBody = ChecklistSaveRequest(title, days, checkTime)
+ val call = apiService.updateChecklist(checklistId, "Bearer $accessToken", requestBody)
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ onResponse(true)
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("kkang", "update checklist failed")
+ }
+
+ })
+ }
+
+ fun getCommunity(accessToken: String, communityData: (List) -> Unit) {
+ val call = apiService.getCommunity("Bearer $accessToken")
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ val responseData: ApiSuccessResultListCommunityReadResponse? = response.body()
+ val postList: List? = responseData?.response
+ if (postList != null) {
+ communityData(postList)
+ }
+ }
+ }
+
+ override fun onFailure(
+ call: Call,
+ t: Throwable
+ ) {
+ Log.d("kkang", "get community failed")
+ }
+
+ })
+ }
+
+ fun saveCommunity(accessToken: String, title: String, content: String, onResponse: (Boolean) -> Unit) {
+ val requestBody = CommunitySaveRequest(title, content)
+ val call = apiService.saveCommunity("Bearer $accessToken", requestBody)
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ onResponse(true)
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("kkang", "save community failed")
+ }
+
+ })
+ }
+
+ fun saveComment(accessToken: String, communityId: Int, content: String, onResponse: (Boolean) -> Unit) {
+ val requestBody = CommentSaveRequest(communityId, content)
+ val call = apiService.saveComment("Bearer $accessToken", requestBody)
+ call.enqueue(object : Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ onResponse(true)
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("kkang", "save comment failed")
+ }
+ })
+ }
+
+ fun getComment(accessToken: String, communityId: Int, commentData: (List) -> Unit) {
+ val call = apiService.getComment(communityId, "Bearer $accessToken")
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ val responseData: ApiSuccessResultListCommentReadResponse? = response.body()
+ val commentList: List? = responseData?.response
+ if (commentList != null) {
+ commentData(commentList)
+ }
+ }
+ }
+
+ override fun onFailure(
+ call: Call,
+ t: Throwable
+ ) {
+ Log.d("kkang", "get comment failed")
+ }
+
+ })
+ }
+
+ fun deleteComment(accessToken: String, commentId: Int, onResponse: (Boolean) -> Unit) {
+ val call = apiService.deleteComment(commentId, "Bearer $accessToken")
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ onResponse(true)
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("kkang", "delete comment failed")
+ }
+
+ })
+ }
+
+ fun getProfile(accessToken: String, onResponse: (ProfileReadResponse) -> Unit) {
+ val call = apiService.getProfile("Bearer $accessToken")
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ val responseData: ApiSuccessResultProfileReadResponse? = response.body()
+ val profileResponse: ProfileReadResponse? = responseData?.response
+ if (profileResponse != null) {
+ onResponse(profileResponse)
+ }
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("kkang", "get profile failed")
+ }
+
+ })
+ }
+
+ fun getMyCommunity(accessToken: String, myCommunityData: (List) -> Unit) {
+ val call = apiService.getMyCommunity("Bearer $accessToken")
+ call.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ val responseData: ApiSuccessResultListCommunityReadResponse? = response.body()
+ val postList: List? = responseData?.response
+ if (postList != null) {
+ myCommunityData(postList)
+ }
+ }
+ }
+
+ override fun onFailure(
+ call: Call,
+ t: Throwable
+ ) {
+ Log.d("kkang", "get my post failed")
+ }
+
+ })
+ }
+
+ fun deleteCommunity(accessToken: String, communityId: Int, onResponse: (Boolean) -> Unit) {
+ val callDeleteCommunity = apiService.deleteCommunity(communityId, "Bearer $accessToken")
+ callDeleteCommunity.enqueue(object: Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful) {
+ onResponse(true)
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("kkang", "delete my post failed")
+ }
+
+ })
+ }
+
+ fun deleteAllComment(accessToken: String, communityId: Int, onResponse: (Boolean) -> Unit) {
+ getComment(accessToken, communityId) { comment ->
+ if (comment.isNullOrEmpty()) {
+ onResponse(true)
+ return@getComment
+ }
+ val commentIds = comment.map { it.commentId }
+
+ var isSuccess = true // 모든 댓글이 성공적으로 삭제되었는지 여부를 추적하는 변수
+
+ for (commentId in commentIds) {
+ deleteComment(accessToken, commentId) { isDeleted ->
+ if (!isDeleted) {
+ // 댓글 삭제에 실패하면 isSuccess를 false로 설정하고 반복을 종료합니다.
+ isSuccess = false
+ return@deleteComment
+ }
+ }
+ }
+
+ // 모든 댓글 삭제가 완료되면 isSuccess 값을 onResponse 콜백으로 전달합니다.
+ onResponse(isSuccess)
+ }
+ }
+
+}
+
+// API 응답을 모델링할 클래스 정의
+data class ApiResponse(
+ val status: Int,
+ val response: ResponseData
+)
+
+data class ResponseData(
+ val hasBaby: Boolean,
+ val token: TokenData
+)
+
+data class TokenData(
+ val grantType: String,
+ @SerializedName("accessToken") val accessToken: String
+)
+
+// 기록 저장하기
+data class SaveRecordResponse(
+ val status: Int,
+ val response: Int
+)
+
+data class RecordSaveRequest(
+ val recordTask: String,
+ val startTime: String,
+ val endTime: String,
+ val description: String
+)
+
+// 기록 불러오기
+data class RecordGetRequest(
+ val recordId: Int,
+ val recordTask: String,
+ val startTime: String,
+ val endTime: String,
+ val description: String
+)
+
+data class RecordGetResponse(
+ val status: Int,
+ val response: List
+)
+
+data class RecordDeleteResponse(
+ val status: Int
+)
+
+// 체크리스트 조회하기
+data class ApiSuccessResultListChecklistReadResponse(
+ val status: Int,
+ val response: List
+)
+
+data class ChecklistReadResponse(
+ val checklistId: Int,
+ val title: String,
+ val days: List,
+ val checkTime: String,
+ val isCheck: Boolean
+)
+
+// 체크리스트 저장하기
+data class ChecklistSaveRequest(
+ val title: String,
+ val days: List,
+ val checkTime: String
+)
+
+// 체크리스트 체크 토글
+data class ApiSuccessResultBoolean(
+ val status: Int,
+ val response: Boolean
+)
+
+// 커뮤니티 글 조회
+data class ApiSuccessResultListCommunityReadResponse(
+ val status: Int,
+ val response: List
+)
+
+// 커뮤니티 글 작성
+data class CommunitySaveRequest(
+ val title: String,
+ val content: String
+)
+
+// 커뮤니티 댓글 작성
+data class CommentSaveRequest(
+ val communityId: Int,
+ val content: String
+)
+
+// 커뮤니티 댓글 읽기
+data class ApiSuccessResultListCommentReadResponse(
+ val status: Int,
+ val response: List
+)
+
+// 프로필 불러오기
+data class ApiSuccessResultProfileReadResponse(
+ val status: Int,
+ val response: ProfileReadResponse
+)
+
+data class ProfileReadResponse(
+ val parentName: String,
+ val email: String,
+ val babyName: String,
+ val babyBirthDate: String
+)
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/retrofit/RetrofitClient.kt b/front-end/app/src/main/java/com/example/harmonycare/retrofit/RetrofitClient.kt
new file mode 100644
index 0000000..8f2888e
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/retrofit/RetrofitClient.kt
@@ -0,0 +1,29 @@
+package com.example.harmonycare.retrofit
+
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+object RetrofitClient {
+
+ private const val BASE_URL = "https://harmonycare.app"
+
+ private val okHttpClient = OkHttpClient.Builder()
+ .addInterceptor(HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY // 로그 수준 선택: BASIC, HEADERS, BODY
+ })
+ .build()
+
+ val retrofit: Retrofit by lazy {
+ Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ }
+
+ fun createService(serviceClass: Class): T {
+ return retrofit.create(serviceClass)
+ }
+}
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/addbaby/AddBabyActivity.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/addbaby/AddBabyActivity.kt
new file mode 100644
index 0000000..3195cbf
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/addbaby/AddBabyActivity.kt
@@ -0,0 +1,109 @@
+package com.example.harmonycare.ui.addbaby
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.widget.Button
+import android.widget.EditText
+import android.widget.Toast
+import com.example.harmonycare.MainActivity
+import com.example.harmonycare.R
+import com.example.harmonycare.data.Baby
+import com.example.harmonycare.retrofit.ApiService
+import com.example.harmonycare.retrofit.RetrofitClient
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.RequestBody
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+class AddBabyActivity : AppCompatActivity() {
+ private lateinit var editTextName: EditText
+ private lateinit var editTextDOB: EditText
+ private lateinit var buttonSubmit: Button
+ private lateinit var sharedPreferences: SharedPreferences
+
+
+ private val apiService = RetrofitClient.createService(ApiService::class.java)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+
+ // SharedPreferences 초기화
+ sharedPreferences = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
+
+ // SharedPreferences에서 authcode 가져오기
+ val accessToken = sharedPreferences.getString("accessToken", null)
+
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_add_baby)
+
+ // XML 레이아웃에서 뷰 참조 가져오기
+ editTextName = findViewById(R.id.editTextName)
+ editTextDOB = findViewById(R.id.editTextDOB)
+ buttonSubmit = findViewById(R.id.buttonSubmit)
+
+ // 확인 버튼 클릭 리스너 설정
+ buttonSubmit.setOnClickListener {
+ // 입력값 가져오기
+ val name = editTextName.text.toString()
+ val dob = editTextDOB.text.toString()
+
+ // 입력값이 유효한지 검사
+ if (name.isNotBlank() && dob.isNotBlank()) {
+ val birthDate = dob // 생년월일을 그대로 사용
+
+ // 아기 객체 생성
+ val baby = Baby(name, "MALE", "$birthDate 00:00:00",13.0f)
+
+ // 아기 추가 요청 보내기
+ addBaby(accessToken, baby)
+ } else {
+ Toast.makeText(this, "Please insert your baby's name and birth.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ private fun addBaby(accessToken: String?, baby: Baby) {
+ // accessToken이 null이면 처리하지 않음
+ if (accessToken.isNullOrEmpty()) {
+ Toast.makeText(this, "Access token is missing.", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ // 아기 데이터를 JSON 형식의 RequestBody로 변환
+ val requestBody = createRequestBodyFromBaby(baby)
+
+ // POST 요청 보내기
+ apiService.addBaby("Bearer $accessToken", requestBody).enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ // 성공적으로 추가되었음을 사용자에게 알림
+ Toast.makeText(this@AddBabyActivity, "아기가 성공적으로 추가되었습니다.", Toast.LENGTH_SHORT).show()
+
+ // MainActivity로 이동
+ val intent = Intent(this@AddBabyActivity, MainActivity::class.java)
+ startActivity(intent)
+
+ // 현재 Activity 종료
+ finish()
+ } else {
+ // 실패한 경우에 대한 처리
+ Toast.makeText(this@AddBabyActivity, "아기 추가에 실패했습니다.", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ // 실패한 경우에 대한 처리
+ Toast.makeText(this@AddBabyActivity, "아기 추가에 실패했습니다.", Toast.LENGTH_SHORT).show()
+ }
+ })
+ }
+ // 아기 데이터를 JSON 형식의 RequestBody로 변환하는 함수
+ private fun createRequestBodyFromBaby(baby: Baby): RequestBody {
+ // 여기에 아기 데이터를 JSON 형식의 RequestBody로 변환하는 코드를 작성
+ return RequestBody.create("application/json".toMediaTypeOrNull(), "{\"name\":\"${baby.name}\", \"gender\":\"${baby.gender}\", \"birthDate\":\"${baby.birthDate}\", \"birthWeight\":${baby.birthWeight}}")
+
+ }
+}
+
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/checklist/ChecklistAdapter.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/checklist/ChecklistAdapter.kt
new file mode 100644
index 0000000..e7d1d44
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/checklist/ChecklistAdapter.kt
@@ -0,0 +1,68 @@
+package com.example.harmonycare.ui.checklist
+
+import android.os.Build
+import com.example.harmonycare.databinding.ChecklistItemBinding
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.annotation.RequiresApi
+import androidx.recyclerview.widget.RecyclerView
+import com.example.harmonycare.R
+import com.example.harmonycare.data.Checklist
+
+class ChecklistAdapter(
+ private val dataList: List,
+ private val onItemClick: (Checklist) -> Unit,
+ private val onDeleteClick: (Checklist) -> Unit,
+ private val onCheckClick: (Checklist) -> Unit
+) : RecyclerView.Adapter() {
+ inner class ChecklistViewHolder(private val binding: ChecklistItemBinding) : RecyclerView.ViewHolder(binding.root) {
+ init {
+ itemView.setOnClickListener {
+ val position = adapterPosition
+ if (position != RecyclerView.NO_POSITION) {
+ val checklist = dataList[position]
+ onItemClick(checklist)
+ }
+ }
+ binding.buttonCheckbox.setOnClickListener {
+ val position = adapterPosition
+ if (position != RecyclerView.NO_POSITION) {
+ val checklist = dataList[position]
+ onCheckClick(checklist)
+ }
+ }
+ binding.buttonDelete.setOnClickListener {
+ val position = adapterPosition
+ if (position != RecyclerView.NO_POSITION) {
+ val checklist = dataList[position]
+ onDeleteClick(checklist)
+ }
+ }
+ }
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun bind(checklist: Checklist) {
+ binding.textTitle.text = checklist.title
+ var additionalText: String
+ if (checklist.checkTime.hour < 1) additionalText = "${checklist.checkTime.minute} m"
+ else additionalText = "${checklist.checkTime.hour}h ${checklist.checkTime.minute}m"
+ binding.textCaption.text = "${checklist.days.joinToString(", ") { it.take(3).toLowerCase().capitalize() }}, $additionalText"
+ if (checklist.isCheck) binding.buttonCheckbox.setImageResource(R.drawable.icon_checkbox_orange)
+ else binding.buttonCheckbox.setImageResource(R.drawable.icon_checkbox_gray)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChecklistViewHolder {
+ val binding = ChecklistItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return ChecklistViewHolder(binding)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onBindViewHolder(holder: ChecklistViewHolder, position: Int) {
+ val checklist = dataList[position]
+ holder.bind(checklist)
+ }
+
+ override fun getItemCount(): Int {
+ return dataList.size
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/checklist/ChecklistFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/checklist/ChecklistFragment.kt
new file mode 100644
index 0000000..15f5929
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/checklist/ChecklistFragment.kt
@@ -0,0 +1,260 @@
+package com.example.harmonycare.ui.checklist
+
+import android.app.AlertDialog
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.activity.addCallback
+import androidx.annotation.RequiresApi
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.harmonycare.R
+import com.example.harmonycare.data.Checklist
+import com.example.harmonycare.data.SharedPreferencesManager
+import com.example.harmonycare.databinding.ChecklistDialogBinding
+import com.example.harmonycare.databinding.FragmentChecklistBinding
+import com.example.harmonycare.retrofit.ApiManager
+import com.example.harmonycare.retrofit.ApiService
+import com.example.harmonycare.retrofit.RetrofitClient
+import com.google.android.material.bottomnavigation.BottomNavigationView
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+class ChecklistFragment : Fragment() {
+
+ private var _binding: FragmentChecklistBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var adapter: ChecklistAdapter
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ _binding = FragmentChecklistBinding.inflate(inflater, container, false)
+ val view = binding.root
+ return view
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ getDataListAndSetAdapter()
+
+ binding.fab.setOnClickListener {
+ val bottomSheetDialog = BottomSheetDialog(requireContext())
+ val bottomDialogBinding = ChecklistDialogBinding.inflate(layoutInflater)
+ bottomSheetDialog.setContentView(bottomDialogBinding.root)
+ bottomDialogBinding.timePicker.setIs24HourView(true)
+
+ bottomDialogBinding.buttonSave.setOnClickListener {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ val title = bottomDialogBinding.editText.text.toString()
+ val days = mutableListOf()
+ if (bottomDialogBinding.toggleButtonMon.isChecked) days.add("MONDAY")
+ if (bottomDialogBinding.toggleButtonTue.isChecked) days.add("TUESDAY")
+ if (bottomDialogBinding.toggleButtonWed.isChecked) days.add("WEDNEDSDAY")
+ if (bottomDialogBinding.toggleButtonThu.isChecked) days.add("THURSDAY")
+ if (bottomDialogBinding.toggleButtonFri.isChecked) days.add("FRIDAY")
+ if (bottomDialogBinding.toggleButtonSat.isChecked) days.add("SATURDAY")
+ if (bottomDialogBinding.toggleButtonSun.isChecked) days.add("SUNDAY")
+ val checkTime = hourToString(bottomDialogBinding.timePicker.hour, bottomDialogBinding.timePicker.minute)
+ apiManager.saveChecklist(accessToken, title, days, checkTime, {
+ if (it == true) {
+ getDataListAndSetAdapter()
+ }
+ else {
+ makeToast(requireContext(), "checklist save failed")
+ }
+ })
+ }
+ bottomSheetDialog.dismiss()
+ }
+
+ bottomDialogBinding.buttonClose.setOnClickListener {
+ bottomSheetDialog.dismiss()
+ }
+
+ bottomSheetDialog.show()
+ }
+
+ val bottomNavigationView = requireActivity().findViewById(R.id.nav_view)
+ bottomNavigationView?.visibility = View.VISIBLE
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
+ // 뒤로가기를 누르면 액티비티를 종료
+ requireActivity().finish()
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun getDataList(onDataLoaded: (List) -> Unit) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+
+ apiManager.getChecklist(accessToken,
+ { checklistData ->
+ if (checklistData != null) {
+ onDataLoaded(checklistData)
+ }
+ }
+ )
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun getDataListAndSetAdapter() {
+ getDataList { checklistData ->
+ adapter = ChecklistAdapter(checklistData,
+ onItemClick = { checklist ->
+ showDetailDialog(checklist)
+ },
+ onDeleteClick = { checklist ->
+ showDeleteConfirmationDialog(requireContext()) {
+ // 예 버튼을 클릭했을 때의 동작
+ deleteChecklist(checklist)
+ }
+ },
+ onCheckClick = { checklist ->
+ toggleCheckList(checklist)
+ }
+ )
+ binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
+ binding.recyclerView.adapter = adapter
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun deleteChecklist(checklist: Checklist) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ apiManager.deleteChecklist(checklist.checklistId, accessToken, { response ->
+ if (response == true) {
+ getDataListAndSetAdapter()
+ } else {
+ makeToast(requireContext(), "Failed to delete checklist")
+ }
+ })
+
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun showDetailDialog(checklist: Checklist) {
+ val bottomSheetDialog = BottomSheetDialog(requireContext())
+ val dialogBinding = ChecklistDialogBinding.inflate(layoutInflater)
+ bottomSheetDialog.setContentView(dialogBinding.root)
+
+ dialogBinding.editText.setText(checklist.title)
+ dialogBinding.timePicker.setIs24HourView(true)
+ dialogBinding.timePicker.hour = checklist.checkTime.hour
+ dialogBinding.timePicker.minute = checklist.checkTime.minute
+ if (checklist.days.contains("MONDAY")) dialogBinding.toggleButtonMon.isChecked = true
+ if (checklist.days.contains("TUESDAY")) dialogBinding.toggleButtonTue.isChecked = true
+ if (checklist.days.contains("WEDNEDSDAY")) dialogBinding.toggleButtonWed.isChecked = true
+ if (checklist.days.contains("THURSDAY")) dialogBinding.toggleButtonThu.isChecked = true
+ if (checklist.days.contains("FRIDAY")) dialogBinding.toggleButtonFri.isChecked = true
+ if (checklist.days.contains("SATURDAY")) dialogBinding.toggleButtonSat.isChecked = true
+ if (checklist.days.contains("SUNDAY")) dialogBinding.toggleButtonSun.isChecked = true
+
+ dialogBinding.buttonClose.setOnClickListener {
+ bottomSheetDialog.dismiss()
+ }
+
+ dialogBinding.buttonSave.setOnClickListener {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ val title = dialogBinding.editText.text.toString()
+ val days = mutableListOf()
+ if (dialogBinding.toggleButtonMon.isChecked) days.add("MONDAY")
+ if (dialogBinding.toggleButtonTue.isChecked) days.add("TUESDAY")
+ if (dialogBinding.toggleButtonWed.isChecked) days.add("WEDNEDSDAY")
+ if (dialogBinding.toggleButtonThu.isChecked) days.add("THURSDAY")
+ if (dialogBinding.toggleButtonFri.isChecked) days.add("FRIDAY")
+ if (dialogBinding.toggleButtonSat.isChecked) days.add("SATURDAY")
+ if (dialogBinding.toggleButtonSun.isChecked) days.add("SUNDAY")
+ val checkTime = hourToString(dialogBinding.timePicker.hour, dialogBinding.timePicker.minute)
+ apiManager.updateChecklist(
+ checklist.checklistId, accessToken, title, days, checkTime,
+ { response ->
+ if (response == true) {
+ getDataListAndSetAdapter()
+ } else {
+ makeToast(requireContext(), "Failed to update checklist")
+ }
+ })
+
+ }
+ bottomSheetDialog.dismiss()
+ }
+
+ bottomSheetDialog.show()
+ }
+
+ private fun showDeleteConfirmationDialog(context: Context, onDeleteConfirmed: () -> Unit) {
+ AlertDialog.Builder(context)
+ .setTitle("Delete Confirmation")
+ .setMessage("Are you sure you want to delete this checklist?")
+ .setPositiveButton("Delete") { dialog, which ->
+ onDeleteConfirmed()
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun hourToString(hour: Int, minute: Int): String {
+ val currentTime = LocalDateTime.now()
+ val selectedTime = currentTime.withHour(hour).withMinute(minute).withSecond(0).withNano(0)
+
+ val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd' 'HH:mm:ss")
+ return selectedTime.format(formatter)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun toggleCheckList(checklist: Checklist) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ apiManager.toggleChecklist(checklist.checklistId, accessToken, { response ->
+ if (response == true) {
+ getDataListAndSetAdapter()
+ } else {
+ makeToast(requireContext(), "Failed to toggle checklist")
+ }
+ })
+
+ }
+ }
+
+ fun makeToast(context: Context, message: String, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(context, message, duration).show()
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/community/CommunityAdapter.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/community/CommunityAdapter.kt
new file mode 100644
index 0000000..6a48fbb
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/community/CommunityAdapter.kt
@@ -0,0 +1,90 @@
+package com.example.harmonycare.ui.community
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.example.harmonycare.data.Comment
+import com.example.harmonycare.data.Post
+import com.example.harmonycare.databinding.CommentItemBinding
+import com.example.harmonycare.databinding.CommunityItemBinding
+
+class PostAdapter(private val dataList: List, private val isMyPost: Boolean, private val onItemClick: (Post) -> Unit, private val onDeleteClick: (Post) -> Unit) : RecyclerView.Adapter() {
+ inner class PostViewHolder(private val binding: CommunityItemBinding) : RecyclerView.ViewHolder(binding.root) {
+ init {
+ itemView.setOnClickListener {
+ val position = adapterPosition
+ if (position != RecyclerView.NO_POSITION) {
+ val post = dataList[position]
+ onItemClick(post)
+ }
+ }
+ }
+ fun bind(post: Post) {
+ binding.textTitle.text = post.title
+ binding.textCaption.text = post.content
+ if (isMyPost) {
+ binding.buttonDelete.visibility = View.VISIBLE
+ binding.buttonDelete.setOnClickListener {
+ val position = adapterPosition
+ if (position != RecyclerView.NO_POSITION) {
+ val post = dataList[position]
+ onDeleteClick(post)
+ }
+ }
+ } else {
+ binding.buttonDelete.visibility = View.GONE
+ }
+ }
+ }
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostAdapter.PostViewHolder {
+ val binding = CommunityItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return PostViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: PostAdapter.PostViewHolder, position: Int) {
+ val post = dataList[position]
+ holder.bind(post)
+ holder.itemView.setOnClickListener {
+ onItemClick(post)
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return dataList.size
+ }
+}
+
+class CommentAdapter(private val dataList: List, private val onDeleteClick: (Comment) -> Unit): RecyclerView.Adapter() {
+ inner class CommentViewHolder(private val binding: CommentItemBinding) : RecyclerView.ViewHolder(binding.root) {
+ fun bind(comment: Comment) {
+ binding.textComment.text = comment.content
+ if (comment.isMyComment == true) {
+ binding.buttonDelete.visibility = View.VISIBLE
+ binding.buttonDelete.setOnClickListener {
+ val position = adapterPosition
+ if (position != RecyclerView.NO_POSITION) {
+ val checklist = dataList[position]
+ onDeleteClick(checklist)
+ }
+ }
+ } else {
+ binding.buttonDelete.visibility = View.GONE
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder {
+ val binding = CommentItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return CommentViewHolder(binding)
+ }
+
+ override fun getItemCount(): Int {
+ return dataList.size
+ }
+
+ override fun onBindViewHolder(holder: CommentViewHolder, position: Int) {
+ val post = dataList[position]
+ holder.bind(post)
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/community/CommunityDetailFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/community/CommunityDetailFragment.kt
new file mode 100644
index 0000000..0018fe8
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/community/CommunityDetailFragment.kt
@@ -0,0 +1,144 @@
+package com.example.harmonycare.ui.community
+
+import android.app.AlertDialog
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
+import androidx.activity.addCallback
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.harmonycare.R
+import com.example.harmonycare.data.Comment
+import com.example.harmonycare.data.SharedPreferencesManager
+import com.example.harmonycare.databinding.FragmentCommunityDetailBinding
+import com.example.harmonycare.retrofit.ApiManager
+import com.example.harmonycare.retrofit.ApiService
+import com.example.harmonycare.retrofit.RetrofitClient
+import com.google.android.material.bottomnavigation.BottomNavigationView
+
+class CommunityDetailFragment : Fragment() {
+
+ private lateinit var binding: FragmentCommunityDetailBinding
+ private lateinit var adapter: CommentAdapter
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentCommunityDetailBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val communityId = arguments?.getInt("communityId")
+ val title = arguments?.getString("title")
+ val content = arguments?.getString("content")
+
+ binding.textTitle.text = title
+ binding.textContent.text = content
+
+ if (communityId != null) {
+ getDataListAndSetAdapter(communityId)
+
+ binding.buttonSend.setOnClickListener {
+ val comment = binding.editTextComment.text.toString()
+ if (comment.isNotBlank()) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ apiManager.saveComment(accessToken, communityId, comment, onResponse = {
+ if (it == true) {
+ getDataListAndSetAdapter(communityId)
+ }
+ })
+ }
+
+ binding.editTextComment.setText("")
+ val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(binding.editTextComment.windowToken, 0) // 키보드를 숨깁니다.
+ binding.editTextComment.clearFocus()
+ }
+ }
+ }
+
+ val bottomNavigationView = requireActivity().findViewById(R.id.nav_view)
+ bottomNavigationView?.visibility = View.GONE
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
+ // 프래그먼트를 종료
+ requireActivity().supportFragmentManager.popBackStack()
+ }
+ }
+
+ private fun getDataList(communityId: Int, onDataLoaded: (List) -> Unit) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+
+ apiManager.getComment(accessToken, communityId,
+ { commentData ->
+ onDataLoaded(commentData)
+ }
+ )
+ }
+ }
+
+ private fun getDataListAndSetAdapter(communityId: Int) {
+ getDataList(communityId) { commentData ->
+ adapter = CommentAdapter(commentData, onDeleteClick = { comment ->
+ showDeleteConfirmationDialog(requireContext()) {
+ deleteComment(communityId, comment)
+ }
+ })
+ binding.recyclerViewComment.layoutManager = LinearLayoutManager(requireContext())
+ binding.recyclerViewComment.adapter = adapter
+ }
+ }
+
+ private fun showDeleteConfirmationDialog(context: Context, onDeleteConfirmed: () -> Unit) {
+ AlertDialog.Builder(context)
+ .setTitle("Delete Confirmation")
+ .setMessage("Are you sure you want to delete this comment?")
+ .setPositiveButton("Delete") { dialog, which ->
+ onDeleteConfirmed()
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+ }
+
+ private fun deleteComment(communityId: Int, comment: Comment) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ apiManager.deleteComment(accessToken, comment.commentId, { response ->
+ if (response == true) {
+ getDataListAndSetAdapter(communityId)
+ } else {
+ makeToast(requireContext(), "Failed to delete comment")
+ }
+ })
+
+ }
+ }
+
+ fun makeToast(context: Context, message: String, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(context, message, duration).show()
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/community/CommunityFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/community/CommunityFragment.kt
new file mode 100644
index 0000000..b1afe31
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/community/CommunityFragment.kt
@@ -0,0 +1,132 @@
+package com.example.harmonycare.ui.community
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.activity.addCallback
+import androidx.annotation.RequiresApi
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.harmonycare.R
+import com.example.harmonycare.data.Post
+import com.example.harmonycare.data.SharedPreferencesManager
+import com.example.harmonycare.databinding.CommunityDialogBinding
+import com.example.harmonycare.databinding.FragmentCommunityBinding
+import com.example.harmonycare.retrofit.ApiManager
+import com.example.harmonycare.retrofit.ApiService
+import com.example.harmonycare.retrofit.RetrofitClient
+import com.google.android.material.bottomnavigation.BottomNavigationView
+
+class CommunityFragment : Fragment() {
+
+ private var _binding: FragmentCommunityBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var adapter: PostAdapter
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ _binding = FragmentCommunityBinding.inflate(inflater, container, false)
+ val view = binding.root
+ return view
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ getDataListAndSetAdapter()
+
+ binding.fab.setOnClickListener {
+ val fullDialog = Dialog(requireContext(), R.style.FullScreenDialog)
+ val fullDialogBinding = CommunityDialogBinding.inflate(layoutInflater)
+ fullDialog.setContentView(fullDialogBinding.root)
+
+ fullDialogBinding.buttonClose.setOnClickListener {
+ fullDialog.dismiss()
+ }
+ fullDialogBinding.buttonSave.setOnClickListener {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ val title = fullDialogBinding.editTitle.text.toString()
+ val content = fullDialogBinding.editContent.text.toString()
+
+ apiManager.saveCommunity(accessToken, title, content, onResponse = {
+ if (it == true) {
+ getDataListAndSetAdapter()
+ }
+ else {
+ makeToast(requireContext(), "community save failed")
+ }
+ })
+ }
+ fullDialog.dismiss()
+ }
+
+ fullDialog.show()
+ }
+
+ val bottomNavigationView = requireActivity().findViewById(R.id.nav_view)
+ bottomNavigationView?.visibility = View.VISIBLE
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
+ // 뒤로가기를 누르면 액티비티를 종료
+ requireActivity().finish()
+ }
+ }
+
+ private fun getDataList(onDataLoaded: (List) -> Unit) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+
+ apiManager.getCommunity(accessToken,
+ { communityData ->
+ val sortedData = communityData.sortedByDescending { it.communityId }
+ onDataLoaded(sortedData)
+ }
+ )
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun getDataListAndSetAdapter() {
+ getDataList { communityData ->
+ adapter = PostAdapter(communityData, false,
+ onItemClick = { post ->
+ val action = CommunityFragmentDirections.actionCommunityDetail(
+ communityId = post.communityId,
+ title = post.title,
+ content = post.content
+ )
+ findNavController().navigate(action)
+ },
+ onDeleteClick = {
+
+ }
+ )
+ binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
+ binding.recyclerView.adapter = adapter
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ fun makeToast(context: Context, message: String, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(context, message, duration).show()
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/login/LoginDialog.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/login/LoginDialog.kt
new file mode 100644
index 0000000..558105c
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/login/LoginDialog.kt
@@ -0,0 +1,41 @@
+package com.example.harmonycare.ui.login
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.webkit.WebChromeClient
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import com.example.harmonycare.R
+
+class LoginDialog (
+ context: Context,
+ private val oauthUrl: String,
+ private val callback: (String?) -> Unit // 콜백 함수
+ ) : Dialog(context) {
+
+ private lateinit var webView: WebView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.dialog_login)
+ webView = findViewById(R.id.gooLogin)
+ setupWebView()
+ loadLoginPage()
+ }
+
+ private fun setupWebView() {
+ webView.settings.javaScriptEnabled = true
+ webView.webViewClient = WebViewClient()
+ webView.webChromeClient = WebChromeClient()
+
+ }
+
+ private fun loadLoginPage() {
+ webView.loadUrl(oauthUrl)
+ }
+
+ override fun onBackPressed() {
+ super.onBackPressed()
+ callback(null) // 취소 시 콜백 호출
+ }
+ }
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/login/LoginFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/login/LoginFragment.kt
new file mode 100644
index 0000000..2416783
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/login/LoginFragment.kt
@@ -0,0 +1,36 @@
+package com.example.harmonycare.ui.login
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.example.harmonycare.LoginActivity
+import com.example.harmonycare.R
+import com.google.android.gms.auth.api.signin.GoogleSignIn
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions
+
+class LoginFragment : Fragment() {
+
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+ .requestEmail()
+ .build()
+
+ //val client = GoogleSignIn.getClient(this, gso)
+ super.onCreate(savedInstanceState)
+
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+
+ return inflater.inflate(R.layout.fragment_login, container, false)
+ }
+
+
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/login/LoginRepository.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/login/LoginRepository.kt
new file mode 100644
index 0000000..6f6162e
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/login/LoginRepository.kt
@@ -0,0 +1,87 @@
+package com.example.harmonycare.ui.login
+
+import android.telecom.Call
+import android.util.Log
+import com.google.gson.GsonBuilder
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import retrofit2.converter.scalars.ScalarsConverterFactory
+import retrofit2.http.Body
+import retrofit2.http.Headers
+import retrofit2.http.POST
+
+class LoginRepository {
+
+ /*private val getAccessTokenBaseUrl = "https://www.googleapis.com"
+ private val sendAccessTokenBaseUrl = "server_base_url"
+
+ fun getAccessToken(authCode:String) {
+ LoginService.loginRetrofit(getAccessTokenBaseUrl).getAccessToken(
+ request = LoginGoogleRequestModel(
+ grant_type = "authorization_code",
+ client_id = ClientInformation.CLIENT_ID,
+ client_secret = ClientInformation.CLIENT_SECRET,
+ code = authCode.orEmpty()
+ )
+ ).enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if(response.isSuccessful) {
+ val accessToken = response.body()?.access_token.orEmpty()
+ Log.d(TAG, "accessToken: $accessToken")
+ sendAccessToken(accessToken)
+ }
+ }
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.e(TAG, "getOnFailure: ",t.fillInStackTrace() )
+ }
+ })
+ }
+
+ fun sendAccessToken(accessToken:String){
+ LoginService.loginRetrofit(sendAccessTokenBaseUrl).sendAccessToken(
+ accessToken = SendAccessTokenModel(accessToken)
+ ).enqueue(object :Callback{
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful){
+ Log.d(TAG, "sendOnResponse: ${response.body()}")
+ }
+ }
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.e(TAG, "sendOnFailure: ${t.fillInStackTrace()}", )
+ }
+ })
+ }
+
+ companion object {
+ const val TAG = "LoginRepository"
+ }
+}
+
+interface LoginService {
+ @POST("oauth2/v4/token")
+ fun getAccessToken(
+ @Body request: LoginGoogleRequestModel
+ ):
+ Call
+
+
+ @POST("login")
+ @Headers("content-type: application/json")
+ fun sendAccessToken(
+ @Body accessToken:SendAccessTokenModel
+ ):Call
+
+ companion object {
+
+ private val gson = GsonBuilder().setLenient().create()
+
+ fun loginRetrofit(baseUrl: String): LoginService {
+ return Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .addConverterFactory(ScalarsConverterFactory.create())
+ .addConverterFactory(GsonConverterFactory.create(gson))
+ .build()
+ .create(LoginService::class.java)
+ }
+ }*/
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/BarChartFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/BarChartFragment.kt
new file mode 100644
index 0000000..4b8d9e8
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/BarChartFragment.kt
@@ -0,0 +1,216 @@
+package com.example.harmonycare.ui.pattern
+
+import android.annotation.SuppressLint
+import android.app.DatePickerDialog
+import android.content.Context
+import android.content.SharedPreferences
+import android.graphics.Color
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import com.example.harmonycare.R
+import com.example.harmonycare.databinding.FragmentPieChartBinding
+import com.example.harmonycare.retrofit.ApiService
+import com.example.harmonycare.retrofit.RecordGetRequest
+import com.example.harmonycare.retrofit.RecordGetResponse
+import com.example.harmonycare.retrofit.RetrofitClient
+import com.github.mikephil.charting.animation.Easing
+import com.github.mikephil.charting.charts.PieChart
+import com.github.mikephil.charting.data.PieData
+import com.github.mikephil.charting.data.PieDataSet
+import com.github.mikephil.charting.data.PieEntry
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Locale
+
+class BarChartFragment : Fragment() {
+
+ private var _binding: FragmentPieChartBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var selectedDate: Calendar
+ private lateinit var sharedPreferences: SharedPreferences
+ @SuppressLint("MissingInflatedId")
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentPieChartBinding.inflate(inflater, container, false)
+ val root: View = binding.root
+ //sharedPreference 초기화
+ sharedPreferences = requireActivity().getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
+ // SharedPreferences에서 authcode 가져오기
+ val accessToken = sharedPreferences.getString("accessToken", null)
+
+ selectedDate = Calendar.getInstance()
+ updateSelectedDateButtonText()
+
+ if (accessToken != null) {
+ fetchRecordsForSelectedDate(accessToken)
+ }
+ binding.button.setOnClickListener {
+ showDatePickerDialog()
+ if (accessToken != null) {
+ fetchRecordsForSelectedDate(accessToken)
+ }
+ }
+
+ return root
+ // Customize your PieChart here if needed
+
+ }
+
+ private fun showDatePickerDialog() {
+ val datePickerDialog = DatePickerDialog(
+ requireContext(),
+ { _, year, month, dayOfMonth ->
+ selectedDate.set(year, month, dayOfMonth)
+ updateSelectedDateButtonText()
+ },
+ selectedDate.get(Calendar.YEAR),
+ selectedDate.get(Calendar.MONTH),
+ selectedDate.get(Calendar.DAY_OF_MONTH)
+ )
+ datePickerDialog.datePicker.maxDate = System.currentTimeMillis()
+ datePickerDialog.show()
+ }
+ private fun updateSelectedDateButtonText() {
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ val formattedDate = dateFormat.format(selectedDate.time)
+ binding.button.text = formattedDate
+ }
+ private fun fetchRecordsForSelectedDate(authToken: String) {
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ val formattedDate = dateFormat.format(selectedDate.time)
+
+ val apiService = RetrofitClient.createService(ApiService::class.java)
+ val call = apiService.getRecordsForDay(formattedDate, 1, "Bearer $authToken")
+
+ call.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val recordResponse = response.body()
+ if (recordResponse != null) {
+ displayRecordsOnPieChart(recordResponse.response)
+ } else {
+ // Handle null response
+ }
+ } else {
+ // Handle unsuccessful response
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ // Handle failure
+ }
+ })
+ }
+
+ private fun displayRecordsOnPieChart(recordResponse: List) {
+ val mpPieChart: PieChart = _binding!!.pieChart
+
+ // Clear any existing entries
+ mpPieChart.clear()
+ mpPieChart.setUsePercentValues(false) // 퍼센트 값 사용 안 함
+ mpPieChart.setExtraOffsets(5f, 10f, 5f, 5f)
+ mpPieChart.legend.isEnabled = false
+ mpPieChart.description.isEnabled = false
+ mpPieChart.isDrawHoleEnabled = true
+ mpPieChart.setHoleColor(Color.WHITE)
+ mpPieChart.transparentCircleRadius = 61f
+ mpPieChart.animateY(1000, Easing.EaseInOutCubic)
+ mpPieChart.centerText = "Day + 3"
+ mpPieChart.setCenterTextSize(20f)
+ mpPieChart.invalidate()
+
+ // Define the total duration of a day (in minutes)
+ val totalMinutesInDay = 24 * 60
+
+ // Initialize lists to hold entry data
+ val entries = ArrayList()
+ val colors = ArrayList()
+
+ // recordTask에 따른 색상 맵
+ val taskColorMap = mapOf(
+ "SLEEP" to R.color.sleep_blue,
+ "MEAL" to R.color.meal_green,
+ "PLAY" to R.color.play_purple,
+ "DIAPER" to R.color.diaper_yellow,
+ "BATH" to R.color.bath_orange
+ )
+
+ // Create a list to store occupied time slots
+ val occupiedTimeSlots = mutableListOf()
+
+ // Iterate over the records
+ for (record in recordResponse) {
+ val startTime = getMinutesFromTimeString(record.startTime)
+ val endTime = getMinutesFromTimeString(record.endTime)
+ val timeSlot = startTime until endTime
+ occupiedTimeSlots.add(timeSlot)
+
+ // Get color based on recordTask
+ val colorResId = taskColorMap[record.recordTask] ?: R.color.dark_gray // Default color if not found
+ val color = ContextCompat.getColor(requireContext(), colorResId)
+
+ // Add the entry for this record with corresponding color
+ val duration = endTime - startTime
+ val percentage = duration.toFloat() / totalMinutesInDay * 100
+ entries.add(PieEntry(percentage, record.recordTask))
+ colors.add(color)
+ }
+
+ // Find the empty time slots and add them as gray entries
+ val emptyTimeSlots = findEmptyTimeSlots(occupiedTimeSlots, totalMinutesInDay)
+ for (emptySlot in emptyTimeSlots) {
+ val duration = emptySlot.last - emptySlot.first
+ val percentage = duration.toFloat() / totalMinutesInDay * 100
+ entries.add(PieEntry(percentage, "Empty"))
+ colors.add(Color.GRAY)
+ }
+
+ // Configure the data set
+ val dataSet = PieDataSet(entries, "")
+ dataSet.colors = colors
+ dataSet.valueTextSize = 16f
+ dataSet.valueTextColor = Color.BLACK
+
+ // Create the PieData object and set it to the chart
+ val data = PieData(dataSet)
+ mpPieChart.data = data
+
+ // Refresh the chart
+ mpPieChart.invalidate()
+ }
+
+ private fun findEmptyTimeSlots(occupiedTimeSlots: List, totalMinutesInDay: Int): List {
+ val emptyTimeSlots = mutableListOf()
+ var previousEndTime = 0
+
+ for (occupiedSlot in occupiedTimeSlots) {
+ if (occupiedSlot.first > previousEndTime) {
+ val emptySlot = previousEndTime until occupiedSlot.first
+ emptyTimeSlots.add(emptySlot)
+ }
+ previousEndTime = occupiedSlot.last
+ }
+
+ if (previousEndTime < totalMinutesInDay) {
+ val lastEmptySlot = previousEndTime until totalMinutesInDay
+ emptyTimeSlots.add(lastEmptySlot)
+ }
+
+ return emptyTimeSlots
+ }
+ private fun getMinutesFromTimeString(timeString: String): Int {
+ val parts = timeString.split(" ")[1].split(":")
+ return parts[0].toInt() * 60 + parts[1].toInt()
+ }
+
+
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/PatternFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/PatternFragment.kt
new file mode 100644
index 0000000..96033a6
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/PatternFragment.kt
@@ -0,0 +1,351 @@
+package com.example.harmonycare.ui.pattern
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.addCallback
+import androidx.fragment.app.Fragment
+import com.example.harmonycare.R
+import com.example.harmonycare.databinding.FragmentPatternBinding
+import com.google.android.material.bottomnavigation.BottomNavigationView
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.core.content.ContextCompat
+import java.util.Calendar
+
+
+class PatternFragment : Fragment() {
+
+ private var _binding: FragmentPatternBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var selectedDate: Calendar
+ private lateinit var sharedPreferences: SharedPreferences
+
+ companion object{
+ fun newInstance(): Fragment{
+ return PatternFragment()
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+
+ _binding = FragmentPatternBinding.inflate(inflater, container, false)
+ val root: View = binding.root
+ //sharedPreference 초기화
+ sharedPreferences = requireActivity().getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
+ // SharedPreferences에서 authcode 가져오기
+ val accessToken = sharedPreferences.getString("accessToken", null)
+ setXMLToggle(true)
+ selectedDate = Calendar.getInstance()
+ /*updateSelectedDateButtonText()
+
+ if (accessToken != null) {
+ fetchRecordsForSelectedDate(accessToken)
+ }*/
+ /*binding.button.setOnClickListener {
+ showDatePickerDialog()
+ if (accessToken != null) {
+ fetchRecordsForSelectedDate(accessToken)
+ }
+ }*/
+
+ // Begin a transaction to add the PieChartFragment to the container FrameLayout
+ val transaction = childFragmentManager.beginTransaction()
+
+// Initial transaction to add the PieChartFragment
+ val pieChartFragment = PieChartFragment()
+ transaction.replace(R.id.frag, pieChartFragment)
+ transaction.addToBackStack(null) // Optional: Add the transaction to the back stack
+ transaction.commit()
+
+// Daily 텍스트뷰 클릭 리스너 설정
+ binding.daily.setOnClickListener {
+ setXMLToggle(true)
+ // Begin a transaction to replace the current fragment with the PieChartFragment
+ val pieTransaction = childFragmentManager.beginTransaction()
+ pieTransaction.replace(R.id.frag, pieChartFragment)
+ pieTransaction.addToBackStack(null) // Optional: Add the transaction to the back stack
+ pieTransaction.commit()
+ }
+
+// Weekly 텍스트뷰 클릭 리스너 설정
+ binding.Weekly.setOnClickListener {
+ setXMLToggle(false)
+ // Begin a transaction to replace the current fragment with the BarChartFragment
+ val barTransaction = childFragmentManager.beginTransaction()
+ val barChartFragment = BarChartFragment()
+ barTransaction.replace(R.id.frag, barChartFragment)
+ barTransaction.addToBackStack(null) // Optional: Add the transaction to the back stack
+ barTransaction.commit()
+ }
+ return root
+ }
+
+ /*private fun showDatePickerDialog() {
+ val datePickerDialog = DatePickerDialog(
+ requireContext(),
+ { _, year, month, dayOfMonth ->
+ selectedDate.set(year, month, dayOfMonth)
+ updateSelectedDateButtonText()
+ },
+ selectedDate.get(Calendar.YEAR),
+ selectedDate.get(Calendar.MONTH),
+ selectedDate.get(Calendar.DAY_OF_MONTH)
+ )
+ datePickerDialog.datePicker.maxDate = System.currentTimeMillis()
+ datePickerDialog.show()
+ }*/
+ /*private fun updateSelectedDateButtonText() {
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ val formattedDate = dateFormat.format(selectedDate.time)
+ binding.button.text = formattedDate
+ }
+ private fun fetchRecordsForSelectedDate(authToken: String) {
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ val formattedDate = dateFormat.format(selectedDate.time)
+
+ val apiService = RetrofitClient.createService(ApiService::class.java)
+ val call = apiService.getRecordsForDay(formattedDate, 1, "Bearer $authToken")
+
+ call.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val recordResponse = response.body()
+ if (recordResponse != null) {
+ displayRecordsOnPieChart(recordResponse.response)
+ } else {
+ // Handle null response
+ }
+ } else {
+ // Handle unsuccessful response
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ // Handle failure
+ }
+ })
+ }
+
+ private fun displayRecordsOnPieChart(recordResponse: List) {
+ val mpPieChart: PieChart = _binding!!.MPPieChart
+
+ // Clear any existing entries
+ mpPieChart.clear()
+ mpPieChart.setUsePercentValues(false) // 퍼센트 값 사용 안 함
+ mpPieChart.setExtraOffsets(5f, 10f, 5f, 5f)
+ mpPieChart.legend.isEnabled = false
+ mpPieChart.description.isEnabled = false
+ mpPieChart.isDrawHoleEnabled = true
+ mpPieChart.setHoleColor(Color.WHITE)
+ mpPieChart.transparentCircleRadius = 61f
+ mpPieChart.animateY(1000, Easing.EaseInOutCubic)
+ mpPieChart.centerText = "Day + 3"
+ mpPieChart.setCenterTextSize(20f)
+ mpPieChart.invalidate()
+
+ // Define the total duration of a day (in minutes)
+ val totalMinutesInDay = 24 * 60
+
+ // Initialize lists to hold entry data
+ val entries = ArrayList()
+ val colors = ArrayList()
+
+ // recordTask에 따른 색상 맵
+ val taskColorMap = mapOf(
+ "SLEEP" to R.color.sleep_blue,
+ "MEAL" to R.color.meal_green,
+ "PLAY" to R.color.play_purple,
+ "DIAPER" to R.color.diaper_yellow,
+ "BATH" to R.color.bath_orange
+ )
+
+ // Create a list to store occupied time slots
+ val occupiedTimeSlots = mutableListOf()
+
+ // Iterate over the records
+ for (record in recordResponse) {
+ val startTime = getMinutesFromTimeString(record.startTime)
+ val endTime = getMinutesFromTimeString(record.endTime)
+ val timeSlot = startTime until endTime
+ occupiedTimeSlots.add(timeSlot)
+
+ // Get color based on recordTask
+ val colorResId = taskColorMap[record.recordTask] ?: R.color.dark_gray // Default color if not found
+ val color = ContextCompat.getColor(requireContext(), colorResId)
+
+ // Add the entry for this record with corresponding color
+ val duration = endTime - startTime
+ val percentage = duration.toFloat() / totalMinutesInDay * 100
+ entries.add(PieEntry(percentage, record.recordTask))
+ colors.add(color)
+ }
+
+ // Find the empty time slots and add them as gray entries
+ val emptyTimeSlots = findEmptyTimeSlots(occupiedTimeSlots, totalMinutesInDay)
+ for (emptySlot in emptyTimeSlots) {
+ val duration = emptySlot.last - emptySlot.first
+ val percentage = duration.toFloat() / totalMinutesInDay * 100
+ entries.add(PieEntry(percentage, "Empty"))
+ colors.add(Color.GRAY)
+ }
+
+ // Configure the data set
+ val dataSet = PieDataSet(entries, "")
+ dataSet.colors = colors
+ dataSet.valueTextSize = 16f
+ dataSet.valueTextColor = Color.BLACK
+
+ // Create the PieData object and set it to the chart
+ val data = PieData(dataSet)
+ mpPieChart.data = data
+
+ // Refresh the chart
+ mpPieChart.invalidate()
+ }
+
+ private fun findEmptyTimeSlots(occupiedTimeSlots: List, totalMinutesInDay: Int): List {
+ val emptyTimeSlots = mutableListOf()
+ var previousEndTime = 0
+
+ for (occupiedSlot in occupiedTimeSlots) {
+ if (occupiedSlot.first > previousEndTime) {
+ val emptySlot = previousEndTime until occupiedSlot.first
+ emptyTimeSlots.add(emptySlot)
+ }
+ previousEndTime = occupiedSlot.last
+ }
+
+ if (previousEndTime < totalMinutesInDay) {
+ val lastEmptySlot = previousEndTime until totalMinutesInDay
+ emptyTimeSlots.add(lastEmptySlot)
+ }
+
+ return emptyTimeSlots
+ }
+
+ *//*private fun displayRecordsOnPieChart(recordResponse: List) {
+ val mpPieChart: PieChart = _binding!!.MPPieChart
+
+ // Clear any existing entries
+ mpPieChart.clear()
+ mpPieChart.setUsePercentValues(false) // 퍼센트 값 사용 안 함
+ mpPieChart.setExtraOffsets(5f, 10f, 5f, 5f)
+ mpPieChart.legend.isEnabled = false
+ mpPieChart.description.isEnabled = false
+ mpPieChart.isDrawHoleEnabled = true
+ mpPieChart.setHoleColor(Color.WHITE)
+ mpPieChart.transparentCircleRadius = 61f
+ mpPieChart.animateY(1000, Easing.EaseInOutCubic)
+ mpPieChart.centerText = "Day + 3\n성장지표"
+ mpPieChart.setCenterTextSize(20f)
+ mpPieChart.invalidate()
+
+ // Define the total duration of a day (in minutes)
+ val totalMinutesInDay = 24 * 60
+
+ // Initialize lists to hold entry data
+ val entries = ArrayList()
+ val colors = ArrayList()
+
+ // recordTask에 따른 색상 맵
+ val taskColorMap = mapOf(
+ "SLEEP" to R.color.sleep_blue,
+ "MEAL" to R.color.meal_green,
+ "PLAY" to R.color.play_purple,
+ "DIAPER" to R.color.diaper_yellow,
+ "BATH" to R.color.bath_orange
+ )
+ // Check if the record response is empty
+ if (recordResponse.isEmpty()) {
+ // If the record response is empty, add an entry for the entire day as gray
+ entries.add(PieEntry(100f, "Empty"))
+ colors.add(Color.GRAY)
+ } else {
+ // Iterate over the records
+ var totalRecordedTime = 0
+ for (record in recordResponse) {
+ val startTime = getMinutesFromTimeString(record.startTime)
+ val endTime = getMinutesFromTimeString(record.endTime)
+ val duration = endTime - startTime
+
+ // Calculate the percentage of the day covered by this record
+ val percentage = duration.toFloat() / totalMinutesInDay * 100
+
+ // Get color based on recordTask
+ val colorResId = taskColorMap[record.recordTask] ?: R.color.dark_gray // Default color if not found
+ val color = ContextCompat.getColor(requireContext(), colorResId)
+
+ // Add the entry for this record with corresponding color
+ entries.add(PieEntry(percentage, record.recordTask))
+ colors.add(color)
+
+ // Update the total recorded time
+ totalRecordedTime += duration
+ }
+
+ // Calculate the percentage of empty time slots in the day
+ val emptyPercentage =
+ (totalMinutesInDay - totalRecordedTime).toFloat() / totalMinutesInDay * 100
+
+ // Add an entry for the empty time slots
+ entries.add(PieEntry(emptyPercentage, "Empty"))
+ colors.add(Color.GRAY)
+ }
+
+ // Configure the data set
+ val dataSet = PieDataSet(entries, "")
+ dataSet.colors = colors
+ dataSet.valueTextSize = 16f
+ dataSet.valueTextColor = Color.BLACK
+
+ // Create the PieData object and set it to the chart
+ val data = PieData(dataSet)
+ mpPieChart.data = data
+
+ // Refresh the chart
+ mpPieChart.invalidate()
+ }*//*
+
+ // Helper function to convert time string to minutes
+ private fun getMinutesFromTimeString(timeString: String): Int {
+ val parts = timeString.split(" ")[1].split(":")
+ return parts[0].toInt() * 60 + parts[1].toInt()
+ }*/
+ private fun setXMLToggle(isViewClicked: Boolean) {
+ if (!isViewClicked) {
+ binding.daily.setTextColor(ContextCompat.getColor(requireContext(), R.color.dark_gray))
+ binding.daily.setBackgroundResource(0)
+ binding.Weekly.setTextColor(ContextCompat.getColor(requireContext(), R.color.black))
+ binding.Weekly.setBackgroundResource(R.drawable.item_bg_on)
+ } else {
+ binding.daily.setTextColor(ContextCompat.getColor(requireContext(), R.color.black))
+ binding.daily.setBackgroundResource(R.drawable.item_bg_on)
+ binding.Weekly.setTextColor(ContextCompat.getColor(requireContext(), R.color.dark_gray))
+ binding.Weekly.setBackgroundResource(0)
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+
+ val bottomNavigationView = requireActivity().findViewById(R.id.nav_view)
+ bottomNavigationView?.visibility = View.VISIBLE
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
+ // 뒤로가기를 누르면 액티비티를 종료
+ requireActivity().finish()
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/PatternViewModel.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/PatternViewModel.kt
new file mode 100644
index 0000000..eae7bde
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/PatternViewModel.kt
@@ -0,0 +1,32 @@
+package com.example.harmonycare.ui.pattern
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class PatternViewModel : ViewModel() {
+
+ private val _text = MutableLiveData().apply {
+ value = "This is pattern Fragment"
+ }
+ val text: LiveData = _text
+
+
+ //dataSet 만들기
+ data class CommitData(val date: String, val commitNum: Int)
+ val dataList: List = listOf(
+ CommitData("08-28",3),
+ CommitData("08-29",2),
+ CommitData("08-30",5),
+ CommitData("08-31",2),
+ CommitData("09-01",3),
+ CommitData("09-02",6),
+ CommitData("09-03",7),
+ CommitData("09-04",1),
+ CommitData("09-05",3),
+ CommitData("09-06",2)
+ )
+
+}
+
+
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/PieChartFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/PieChartFragment.kt
new file mode 100644
index 0000000..633f826
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/pattern/PieChartFragment.kt
@@ -0,0 +1,215 @@
+package com.example.harmonycare.ui.pattern
+
+import android.annotation.SuppressLint
+import android.app.DatePickerDialog
+import android.content.Context
+import android.content.SharedPreferences
+import android.graphics.Color
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import com.example.harmonycare.R
+import com.example.harmonycare.retrofit.ApiService
+import com.example.harmonycare.retrofit.RecordGetRequest
+import com.example.harmonycare.retrofit.RecordGetResponse
+import com.example.harmonycare.retrofit.RetrofitClient
+import com.github.mikephil.charting.animation.Easing
+import com.github.mikephil.charting.charts.PieChart
+import com.github.mikephil.charting.data.PieData
+import com.github.mikephil.charting.data.PieDataSet
+import com.github.mikephil.charting.data.PieEntry
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Locale
+import com.example.harmonycare.databinding.FragmentPieChartBinding
+
+class PieChartFragment : Fragment() {
+
+ private var _binding: FragmentPieChartBinding? = null
+ private val binding get() = _binding!!
+ private lateinit var selectedDate: Calendar
+ private lateinit var sharedPreferences: SharedPreferences
+ @SuppressLint("MissingInflatedId")
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentPieChartBinding.inflate(inflater, container, false)
+ val root: View = binding.root
+ //sharedPreference 초기화
+ sharedPreferences = requireActivity().getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
+ // SharedPreferences에서 authcode 가져오기
+ val accessToken = sharedPreferences.getString("accessToken", null)
+
+ selectedDate = Calendar.getInstance()
+ updateSelectedDateButtonText()
+
+ if (accessToken != null) {
+ fetchRecordsForSelectedDate(accessToken)
+ }
+ binding.button.setOnClickListener {
+ showDatePickerDialog()
+ if (accessToken != null) {
+ fetchRecordsForSelectedDate(accessToken)
+ }
+ }
+
+ return root
+ // Customize your PieChart here if needed
+
+ }
+
+ private fun showDatePickerDialog() {
+ val datePickerDialog = DatePickerDialog(
+ requireContext(),
+ { _, year, month, dayOfMonth ->
+ selectedDate.set(year, month, dayOfMonth)
+ updateSelectedDateButtonText()
+ },
+ selectedDate.get(Calendar.YEAR),
+ selectedDate.get(Calendar.MONTH),
+ selectedDate.get(Calendar.DAY_OF_MONTH)
+ )
+ datePickerDialog.datePicker.maxDate = System.currentTimeMillis()
+ datePickerDialog.show()
+ }
+ private fun updateSelectedDateButtonText() {
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ val formattedDate = dateFormat.format(selectedDate.time)
+ binding.button.text = formattedDate
+ }
+ private fun fetchRecordsForSelectedDate(authToken: String) {
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ val formattedDate = dateFormat.format(selectedDate.time)
+
+ val apiService = RetrofitClient.createService(ApiService::class.java)
+ val call = apiService.getRecordsForDay(formattedDate, 1, "Bearer $authToken")
+
+ call.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val recordResponse = response.body()
+ if (recordResponse != null) {
+ displayRecordsOnPieChart(recordResponse.response)
+ } else {
+ // Handle null response
+ }
+ } else {
+ // Handle unsuccessful response
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ // Handle failure
+ }
+ })
+ }
+
+ private fun displayRecordsOnPieChart(recordResponse: List) {
+ val mpPieChart: PieChart = _binding!!.pieChart
+
+ // Clear any existing entries
+ mpPieChart.clear()
+ mpPieChart.setUsePercentValues(false) // 퍼센트 값 사용 안 함
+ mpPieChart.setExtraOffsets(5f, 10f, 5f, 5f)
+ mpPieChart.legend.isEnabled = false
+ mpPieChart.description.isEnabled = false
+ mpPieChart.isDrawHoleEnabled = true
+ mpPieChart.setHoleColor(Color.WHITE)
+ mpPieChart.transparentCircleRadius = 61f
+ mpPieChart.animateY(1000, Easing.EaseInOutCubic)
+ mpPieChart.centerText = "Day + 3"
+ mpPieChart.setCenterTextSize(20f)
+ mpPieChart.invalidate()
+
+ // Define the total duration of a day (in minutes)
+ val totalMinutesInDay = 24 * 60
+
+ // Initialize lists to hold entry data
+ val entries = ArrayList()
+ val colors = ArrayList()
+
+ // recordTask에 따른 색상 맵
+ val taskColorMap = mapOf(
+ "SLEEP" to R.color.sleep_blue,
+ "MEAL" to R.color.meal_green,
+ "PLAY" to R.color.play_purple,
+ "DIAPER" to R.color.diaper_yellow,
+ "BATH" to R.color.bath_orange
+ )
+
+ // Create a list to store occupied time slots
+ val occupiedTimeSlots = mutableListOf()
+
+ // Iterate over the records
+ for (record in recordResponse) {
+ val startTime = getMinutesFromTimeString(record.startTime)
+ val endTime = getMinutesFromTimeString(record.endTime)
+ val timeSlot = startTime until endTime
+ occupiedTimeSlots.add(timeSlot)
+
+ // Get color based on recordTask
+ val colorResId = taskColorMap[record.recordTask] ?: R.color.dark_gray // Default color if not found
+ val color = ContextCompat.getColor(requireContext(), colorResId)
+
+ // Add the entry for this record with corresponding color
+ val duration = endTime - startTime
+ val percentage = duration.toFloat() / totalMinutesInDay * 100
+ entries.add(PieEntry(percentage, record.recordTask))
+ colors.add(color)
+ }
+
+ // Find the empty time slots and add them as gray entries
+ val emptyTimeSlots = findEmptyTimeSlots(occupiedTimeSlots, totalMinutesInDay)
+ for (emptySlot in emptyTimeSlots) {
+ val duration = emptySlot.last - emptySlot.first
+ val percentage = duration.toFloat() / totalMinutesInDay * 100
+ entries.add(PieEntry(percentage, "Empty"))
+ colors.add(Color.GRAY)
+ }
+
+ // Configure the data set
+ val dataSet = PieDataSet(entries, "")
+ dataSet.colors = colors
+ dataSet.valueTextSize = 16f
+ dataSet.valueTextColor = Color.BLACK
+
+ // Create the PieData object and set it to the chart
+ val data = PieData(dataSet)
+ mpPieChart.data = data
+
+ // Refresh the chart
+ mpPieChart.invalidate()
+ }
+
+ private fun findEmptyTimeSlots(occupiedTimeSlots: List, totalMinutesInDay: Int): List {
+ val emptyTimeSlots = mutableListOf()
+ var previousEndTime = 0
+
+ for (occupiedSlot in occupiedTimeSlots) {
+ if (occupiedSlot.first > previousEndTime) {
+ val emptySlot = previousEndTime until occupiedSlot.first
+ emptyTimeSlots.add(emptySlot)
+ }
+ previousEndTime = occupiedSlot.last
+ }
+
+ if (previousEndTime < totalMinutesInDay) {
+ val lastEmptySlot = previousEndTime until totalMinutesInDay
+ emptyTimeSlots.add(lastEmptySlot)
+ }
+
+ return emptyTimeSlots
+ }
+ private fun getMinutesFromTimeString(timeString: String): Int {
+ val parts = timeString.split(" ")[1].split(":")
+ return parts[0].toInt() * 60 + parts[1].toInt()
+ }
+
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/profile/MyPostFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/profile/MyPostFragment.kt
new file mode 100644
index 0000000..64ef359
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/profile/MyPostFragment.kt
@@ -0,0 +1,133 @@
+package com.example.harmonycare.ui.profile
+
+import android.app.AlertDialog
+import android.content.Context
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.activity.addCallback
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.harmonycare.R
+import com.example.harmonycare.data.Comment
+import com.example.harmonycare.data.Post
+import com.example.harmonycare.data.SharedPreferencesManager
+import com.example.harmonycare.databinding.FragmentCommunityBinding
+import com.example.harmonycare.retrofit.ApiManager
+import com.example.harmonycare.retrofit.ApiService
+import com.example.harmonycare.retrofit.RetrofitClient
+import com.example.harmonycare.ui.community.CommunityFragmentDirections
+import com.example.harmonycare.ui.community.PostAdapter
+import com.google.android.material.bottomnavigation.BottomNavigationView
+
+private var _binding: FragmentCommunityBinding? = null
+private val binding get() = _binding!!
+private lateinit var adapter: PostAdapter
+class MyPostFragment : Fragment() {
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ _binding = FragmentCommunityBinding.inflate(inflater, container, false)
+ val view = binding.root
+ return view
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.fab.visibility = View.GONE
+
+ getDataListAndSetAdapter()
+
+ val bottomNavigationView = requireActivity().findViewById(R.id.nav_view)
+ bottomNavigationView?.visibility = View.VISIBLE
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
+ // 프래그먼트를 종료
+ requireActivity().supportFragmentManager.popBackStack()
+ }
+ }
+
+ private fun getDataList(onDataLoaded: (List) -> Unit) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+
+ apiManager.getMyCommunity(accessToken,
+ { myCommunityData ->
+ val sortedData = myCommunityData.sortedByDescending { it.communityId }
+ onDataLoaded(sortedData)
+ }
+ )
+ }
+ }
+
+ private fun getDataListAndSetAdapter() {
+ getDataList { communityData ->
+ adapter = PostAdapter(communityData, true,
+ onItemClick = { post ->
+ val action = MyPostFragmentDirections.actionNavigationMypostToNavigationCommunityDetail(
+ communityId = post.communityId,
+ title = post.title,
+ content = post.content
+ )
+ findNavController().navigate(action)
+ },
+ onDeleteClick = { post ->
+ showDeleteConfirmationDialog(requireContext()) {
+ deleteCommunity(post)
+ }
+ }
+ )
+ binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
+ binding.recyclerView.adapter = adapter
+ }
+ }
+
+ private fun showDeleteConfirmationDialog(context: Context, onDeleteConfirmed: () -> Unit) {
+ AlertDialog.Builder(context)
+ .setTitle("Delete Confirmation")
+ .setMessage("Are you sure you want to delete this comment?")
+ .setPositiveButton("Delete") { dialog, which ->
+ onDeleteConfirmed()
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+ }
+
+ private fun deleteCommunity(post: Post) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ apiManager.deleteAllComment(accessToken, post.communityId, { responseComment ->
+ if (responseComment == true) {
+ apiManager.deleteCommunity(accessToken, post.communityId, { response ->
+ if (response == true) {
+ getDataListAndSetAdapter()
+ } else {
+ makeToast(requireContext(), "Failed to delete community")
+ }
+ })
+ }
+ })
+
+
+ }
+ }
+
+ fun makeToast(context: Context, message: String, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(context, message, duration).show()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/profile/ProfileFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/profile/ProfileFragment.kt
new file mode 100644
index 0000000..46ebcf7
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/profile/ProfileFragment.kt
@@ -0,0 +1,68 @@
+package com.example.harmonycare.ui.profile
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.addCallback
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import com.example.harmonycare.R
+import com.example.harmonycare.data.SharedPreferencesManager
+import com.example.harmonycare.databinding.FragmentProfileBinding
+import com.example.harmonycare.retrofit.ApiManager
+import com.example.harmonycare.retrofit.ApiService
+import com.example.harmonycare.retrofit.RetrofitClient
+import com.google.android.material.bottomnavigation.BottomNavigationView
+
+class ProfileFragment : Fragment() {
+
+ private var _binding: FragmentProfileBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentProfileBinding.inflate(inflater, container, false)
+ val root: View = binding.root
+
+ return root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ apiManager.getProfile(accessToken, onResponse = {
+ binding.textParentName.text = it.parentName
+ binding.textEmail.text = it.email
+ binding.textBabyName.text = it.babyName
+ binding.textBirth.text = it.babyBirthDate
+ })
+ }
+
+ binding.myPost.setOnClickListener {
+ val action = ProfileFragmentDirections.actionNavigationProfileToNavigationMypost()
+ findNavController().navigate(action)
+ }
+
+ val bottomNavigationView = requireActivity().findViewById(R.id.nav_view)
+ bottomNavigationView?.visibility = View.VISIBLE
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
+ // 뒤로가기를 누르면 액티비티를 종료
+ requireActivity().finish()
+ }
+ }
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/record/RecordAdapter.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/record/RecordAdapter.kt
new file mode 100644
index 0000000..35d5263
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/record/RecordAdapter.kt
@@ -0,0 +1,94 @@
+package com.example.harmonycare.ui.record
+
+import android.content.Context
+import android.os.Build
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.RecyclerView
+import com.example.harmonycare.R
+import com.example.harmonycare.databinding.RecordItemBinding
+import com.example.harmonycare.data.Record
+
+class RecordAdapter(
+ val context: Context,
+ private val dataList: List,
+ private val onItemClick: (Record) -> Unit,
+ private val onDeleteClick: (Record) -> Unit
+) : RecyclerView.Adapter() {
+ inner class RecordViewHolder(private val binding: RecordItemBinding) : RecyclerView.ViewHolder(binding.root) {
+ init {
+ itemView.setOnClickListener {
+ val position = adapterPosition
+ if (position != RecyclerView.NO_POSITION) {
+ val record = dataList[position]
+ onItemClick(record)
+ }
+ }
+
+ binding.buttonDelete.setOnClickListener {
+ val position = adapterPosition
+ if (position != RecyclerView.NO_POSITION) {
+ val record = dataList[position]
+ onDeleteClick(record)
+ }
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun bind(record: Record) {
+ when (record.recordTask) {
+ "SLEEP"-> {
+ binding.textTitle.text = "Sleep"
+ binding.textTitle.setTextColor(ContextCompat.getColor(context, R.color.sleep_blue))
+ binding.textCircle.setTextColor(ContextCompat.getColor(context, R.color.sleep_blue))
+ }
+ "MEAL" -> {
+ binding.textTitle.text = "Meal"
+ binding.textTitle.setTextColor(ContextCompat.getColor(context, R.color.meal_green))
+ binding.textCircle.setTextColor(ContextCompat.getColor(context, R.color.meal_green))
+ }
+ "PLAY" -> {
+ binding.textTitle.text = "Play"
+ binding.textTitle.setTextColor(ContextCompat.getColor(context, R.color.play_purple))
+ binding.textCircle.setTextColor(ContextCompat.getColor(context, R.color.play_purple))
+ }
+ "DIAPER" -> {
+ binding.textTitle.text = "Diaper"
+ binding.textTitle.setTextColor(ContextCompat.getColor(context, R.color.diaper_yellow))
+ binding.textCircle.setTextColor(ContextCompat.getColor(context, R.color.diaper_yellow))
+ }
+ "BATH" -> {
+ binding.textTitle.text = "Bath"
+ binding.textTitle.setTextColor(ContextCompat.getColor(context, R.color.bath_orange))
+ binding.textCircle.setTextColor(ContextCompat.getColor(context, R.color.bath_orange))
+ }
+ }
+ binding.textCaption.text = record.description
+ var hour = record.startTime.hour
+ if (hour < 12) binding.textAmpm.text = "AM" else binding.textAmpm.text = "PM"
+ if (record.startTime.hour % 12 == 0) hour = 12 else hour = record.startTime.hour % 12
+ binding.textTime.text = "${hour.toString().padStart(2, '0')}:${record.startTime.minute.toString().padStart(2, '0')}"
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecordViewHolder {
+ val binding = RecordItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return RecordViewHolder(binding)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onBindViewHolder(holder: RecordViewHolder, position: Int) {
+ val record = dataList[position]
+ holder.bind(record)
+ }
+
+ override fun getItemCount(): Int {
+ return dataList.size
+ }
+
+ fun getDataList(): List {
+ return dataList
+ }
+}
diff --git a/front-end/app/src/main/java/com/example/harmonycare/ui/record/RecordFragment.kt b/front-end/app/src/main/java/com/example/harmonycare/ui/record/RecordFragment.kt
new file mode 100644
index 0000000..99d2db3
--- /dev/null
+++ b/front-end/app/src/main/java/com/example/harmonycare/ui/record/RecordFragment.kt
@@ -0,0 +1,367 @@
+package com.example.harmonycare.ui.record
+
+import android.animation.ObjectAnimator
+import android.app.AlertDialog
+import android.content.ContentValues.TAG
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.activity.addCallback
+import androidx.annotation.RequiresApi
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.harmonycare.R
+import com.example.harmonycare.databinding.FragmentRecordBinding
+import com.example.harmonycare.data.Record
+import com.example.harmonycare.data.SharedPreferencesManager
+import com.example.harmonycare.databinding.RecordDialogBinding
+import com.example.harmonycare.retrofit.ApiManager
+import com.example.harmonycare.retrofit.ApiService
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.example.harmonycare.retrofit.RetrofitClient
+import com.google.android.material.bottomnavigation.BottomNavigationView
+import java.time.Duration
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+class RecordFragment : Fragment() {
+
+ private var _binding: FragmentRecordBinding? = null
+ private val binding get() = _binding!!
+ private var isFabOpen = false
+ private lateinit var adapter: RecordAdapter
+ private val handler = Handler(Looper.getMainLooper())
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = FragmentRecordBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ getDataListAndSetAdapter()
+
+ binding.dateTextView.text = LocalDateTime.now().toLocalDate().toString()
+ binding.fabAdd.setOnClickListener {
+ toggleFab()
+ }
+ binding.fabBath.setOnClickListener {
+ saveRecord("BATH", dateTimeToString(LocalDateTime.now()), dateTimeToString(LocalDateTime.now().plusMinutes(1)), "description")
+ }
+ binding.fabDiaper.setOnClickListener {
+ saveRecord("DIAPER", dateTimeToString(LocalDateTime.now()), dateTimeToString(LocalDateTime.now()), "description")
+ }
+ binding.fabMeal.setOnClickListener {
+ saveRecord("MEAL", dateTimeToString(LocalDateTime.now()), dateTimeToString(LocalDateTime.now()), "description")
+ }
+ binding.fabPlay.setOnClickListener {
+ saveRecord("PLAY", dateTimeToString(LocalDateTime.now()), dateTimeToString(LocalDateTime.now()), "description")
+ }
+ binding.fabSleep.setOnClickListener {
+ saveRecord("SLEEP", dateTimeToString(LocalDateTime.now()), dateTimeToString(LocalDateTime.now()), "description")
+ }
+
+ val bottomNavigationView = requireActivity().findViewById(R.id.nav_view)
+ bottomNavigationView?.visibility = View.VISIBLE
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
+ // 뒤로가기를 누르면 액티비티를 종료
+ requireActivity().finish()
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun getDataList(onDataLoaded: (List) -> Unit) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+
+ apiManager.getRecordsForDay(LocalDateTime.now().toLocalDate().toString(), 1, "Bearer $accessToken",
+ { response ->
+ if (response != null) {
+ onDataLoaded(response)
+ }
+ },
+ { error ->
+ makeToast(requireContext(), "data load failed")
+ }
+ )
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun getDataListAndSetAdapter() {
+ getDataList { recordList ->
+ adapter = RecordAdapter(requireContext(), recordList,
+ onItemClick = { record ->
+ showDetailDialog(record)
+ },
+ onDeleteClick = { record ->
+ showDeleteConfirmationDialog(requireContext()) {
+ // 예 버튼을 클릭했을 때의 동작
+ deleteRecord(record)
+ }
+ }
+ )
+ binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
+ binding.recyclerView.adapter = adapter
+
+ startUpdateTimeRunnable()
+ }
+ }
+
+
+ private fun toggleFab() {
+ // 플로팅 액션 버튼 닫기 - 열려있는 플로팅 버튼 집어넣는 애니메이션 세팅
+ if (isFabOpen) {
+ ObjectAnimator.ofFloat(binding.fabSleep, "translationY", 0f).apply { start() }
+ ObjectAnimator.ofFloat(binding.fabMeal, "translationY", 0f).apply { start() }
+ ObjectAnimator.ofFloat(binding.fabPlay, "translationY", 0f).apply { start() }
+ ObjectAnimator.ofFloat(binding.fabDiaper, "translationY", 0f).apply { start() }
+ ObjectAnimator.ofFloat(binding.fabBath, "translationY", 0f).apply { start() }
+ binding.fabAdd.setImageResource(R.drawable.icon_add)
+
+ // 플로팅 액션 버튼 열기 - 닫혀있는 플로팅 버튼 꺼내는 애니메이션 세팅
+ } else {
+ ObjectAnimator.ofFloat(binding.fabSleep, "translationY", -200f).apply { start() }
+ ObjectAnimator.ofFloat(binding.fabMeal, "translationY", -400f).apply { start() }
+ ObjectAnimator.ofFloat(binding.fabPlay, "translationY", -600f).apply { start() }
+ ObjectAnimator.ofFloat(binding.fabDiaper, "translationY", -800f).apply { start() }
+ ObjectAnimator.ofFloat(binding.fabBath, "translationY", -1000f).apply { start() }
+ binding.fabAdd.setImageResource(R.drawable.icon_close)
+ }
+
+ isFabOpen = !isFabOpen
+
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun showDetailDialog(record: Record) {
+ val bottomSheetDialog = BottomSheetDialog(requireContext())
+ val dialogBinding = RecordDialogBinding.inflate(layoutInflater)
+ bottomSheetDialog.setContentView(dialogBinding.root)
+
+ when (record.recordTask) {
+ "SLEEP" -> {
+ dialogBinding.titleTextView.text = "Sleep"
+ dialogBinding.topBar.setBackgroundColor(resources.getColor(R.color.sleep_blue))
+ }
+ "MEAL" -> {
+ dialogBinding.titleTextView.text = "Meal"
+ dialogBinding.topBar.setBackgroundColor(resources.getColor(R.color.meal_green))
+ }
+ "PLAY" -> {
+ dialogBinding.titleTextView.text = "Play"
+ dialogBinding.topBar.setBackgroundColor(resources.getColor(R.color.play_purple))
+ }
+ "DIAPER" -> {
+ dialogBinding.titleTextView.text = "Diaper"
+ dialogBinding.topBar.setBackgroundColor(resources.getColor(R.color.diaper_yellow))
+ }
+ "BATH" -> {
+ dialogBinding.titleTextView.text = "Bath"
+ dialogBinding.topBar.setBackgroundColor(resources.getColor(R.color.bath_orange))
+ }
+ }
+ dialogBinding.editText.setText(record.description)
+ dialogBinding.startTimePicker.hour = record.startTime.hour
+ dialogBinding.startTimePicker.minute = record.startTime.minute
+ dialogBinding.endTimePicker.hour = record.endTime.hour
+ dialogBinding.endTimePicker.minute = record.endTime.minute
+
+ dialogBinding.buttonClose.setOnClickListener {
+ bottomSheetDialog.dismiss()
+ }
+
+ dialogBinding.buttonSave.setOnClickListener {
+ val recordTask = dialogBinding.titleTextView.text.toString().toUpperCase()
+ val startTime = hourToString(dialogBinding.startTimePicker.hour, dialogBinding.startTimePicker.minute)
+ val endTime = hourToString(dialogBinding.endTimePicker.hour, dialogBinding.endTimePicker.minute)
+ val description = dialogBinding.editText.text.toString()
+
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ apiManager.updateRecord(record.recordId, recordTask, startTime, endTime, description, accessToken, onResponse =
+ { response ->
+ getDataListAndSetAdapter()
+ },
+ {
+ error ->
+ makeToast(requireContext(), "data load failed")
+ })
+ }
+ else {
+ makeToast(requireContext(), "accessToken error")
+ }
+ bottomSheetDialog.dismiss()
+ }
+
+ bottomSheetDialog.show()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun hourToString(hour: Int, minute: Int): String {
+ val currentTime = LocalDateTime.now()
+ val selectedTime = currentTime.withHour(hour).withMinute(minute).withSecond(0).withNano(0)
+
+ val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd' 'HH:mm:ss")
+ return selectedTime.format(formatter)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun dateTimeToString(dateTime: LocalDateTime): String {
+ val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd' 'HH:mm:ss")
+ return dateTime.format(formatter)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun saveRecord(recordTask: String, startTime: String, endTime: String, description: String) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ apiManager.saveRecord(accessToken, recordTask, startTime, endTime, description, { response ->
+ // HTTP 응답 코드가 201이면 성공으로 간주합니다.
+ if (response == 201) {
+ // 저장에 성공한 경우
+ Log.d(TAG, "Record saved successfully. Response code: $response")
+ // 데이터가 변경됐으므로 RecyclerView를 업데이트
+ getDataListAndSetAdapter()
+ } else {
+ // HTTP 응답 코드가 201이 아닌 경우 저장에 실패로 간주합니다.
+ Log.e(TAG, "Failed to save record. Response code: $response")
+ // 실패 메시지를 사용자에게 표시하거나 다른 처리를 수행할 수 있습니다.
+ // 예를 들어, 사용자에게 알림을 표시할 수 있습니다.
+ makeToast(requireContext(), "Failed to save record")
+ }
+ }, {
+ // 저장에 실패한 경우
+ Log.e(TAG, "Failed to save record")
+ // 실패 메시지를 사용자에게 표시하거나 다른 처리를 수행할 수 있습니다.
+ // 예를 들어, 사용자에게 알림을 표시할 수 있습니다.
+ makeToast(requireContext(), "Failed to save record")
+ })
+
+ }
+ }
+
+ private fun showDeleteConfirmationDialog(context: Context, onDeleteConfirmed: () -> Unit) {
+ AlertDialog.Builder(context)
+ .setTitle("Delete Confirmation")
+ .setMessage("Are you sure you want to delete this record?")
+ .setPositiveButton("Delete") { dialog, which ->
+ onDeleteConfirmed()
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun deleteRecord(record: Record) {
+ val accessToken = SharedPreferencesManager.getAccessToken()
+
+ if (!accessToken.isNullOrEmpty()) {
+ val apiService = RetrofitClient.retrofit.create(ApiService::class.java)
+ val apiManager = ApiManager(apiService)
+
+ apiManager.deleteRecord(record.recordId, accessToken, { response ->
+ if (response == true) {
+ getDataListAndSetAdapter()
+ } else {
+ makeToast(requireContext(), "Failed to delete record")
+ }
+ })
+
+ }
+ }
+
+ private fun startUpdateTimeRunnable() {
+ handler.removeCallbacksAndMessages(null)
+
+ handler.post(object : Runnable {
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun run() {
+ updateLastUpdateTime()
+ handler.postDelayed(this, 60000) // 1분마다 갱신
+ }
+ })
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun updateLastUpdateTime() {
+ val dataList = adapter.getDataList()
+
+ val diaperRecords = dataList.filter { it.recordTask == "DIAPER" }
+ val mealRecords = dataList.filter { it.recordTask == "MEAL" }
+ val sleepRecords = dataList.filter { it.recordTask == "SLEEP" }
+
+ if (_binding != null) {
+ val binding = _binding!!
+ var recentDiaperTime = findRecentTime(diaperRecords)
+ var recentMealTime = findRecentTime(mealRecords)
+ var recentSleepTime = findRecentTime(sleepRecords)
+
+ binding.recentDiaperTextview.text = recentDiaperTime
+ binding.recentMealTextview.text = recentMealTime
+ binding.recentSleepTextview.text = recentSleepTime
+ } else {
+ Log.e(TAG, "Binding is null when trying to update last update time.")
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun findRecentTime(records: List): String {
+ if (records.isEmpty()) return "No record"
+
+ val currentTime = LocalDateTime.now()
+ val recentRecord = records.maxByOrNull { it.startTime } ?: return "No record"
+ val difference = Duration.between(recentRecord.startTime, currentTime)
+ val differenceMinutes = difference.toMinutes()
+
+ return if (differenceMinutes < 60) {
+ "● $differenceMinutes m ago"
+ } else if (differenceMinutes < 1440) {
+ val differenceHours = difference.toHours()
+ val remainingMinutes = differenceMinutes % 60
+ if (remainingMinutes == 0L) {
+ "● $differenceHours h ago"
+ } else {
+ "● $differenceHours h $remainingMinutes m ago"
+ }
+ } else {
+ "Long time ago"
+ }
+ }
+
+ fun makeToast(context: Context, message: String, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(context, message, duration).show()
+ }
+}
diff --git a/front-end/app/src/main/res/drawable/addbaby.png b/front-end/app/src/main/res/drawable/addbaby.png
new file mode 100644
index 0000000..28dbf76
Binary files /dev/null and b/front-end/app/src/main/res/drawable/addbaby.png differ
diff --git a/front-end/app/src/main/res/drawable/backgroundimage.png b/front-end/app/src/main/res/drawable/backgroundimage.png
new file mode 100644
index 0000000..e7b988c
Binary files /dev/null and b/front-end/app/src/main/res/drawable/backgroundimage.png differ
diff --git a/front-end/app/src/main/res/drawable/bath.png b/front-end/app/src/main/res/drawable/bath.png
new file mode 100644
index 0000000..b60db6b
Binary files /dev/null and b/front-end/app/src/main/res/drawable/bath.png differ
diff --git a/front-end/app/src/main/res/drawable/calendar_icon.png b/front-end/app/src/main/res/drawable/calendar_icon.png
new file mode 100644
index 0000000..6d3e9ad
Binary files /dev/null and b/front-end/app/src/main/res/drawable/calendar_icon.png differ
diff --git a/front-end/app/src/main/res/drawable/checklist.png b/front-end/app/src/main/res/drawable/checklist.png
new file mode 100644
index 0000000..33e50b6
Binary files /dev/null and b/front-end/app/src/main/res/drawable/checklist.png differ
diff --git a/front-end/app/src/main/res/drawable/community.png b/front-end/app/src/main/res/drawable/community.png
new file mode 100644
index 0000000..6d88d90
Binary files /dev/null and b/front-end/app/src/main/res/drawable/community.png differ
diff --git a/front-end/app/src/main/res/drawable/dialog_background.xml b/front-end/app/src/main/res/drawable/dialog_background.xml
new file mode 100644
index 0000000..7b3e21c
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/dialog_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/front-end/app/src/main/res/drawable/diaper.png b/front-end/app/src/main/res/drawable/diaper.png
new file mode 100644
index 0000000..27313c6
Binary files /dev/null and b/front-end/app/src/main/res/drawable/diaper.png differ
diff --git a/front-end/app/src/main/res/drawable/googlelogin.png b/front-end/app/src/main/res/drawable/googlelogin.png
new file mode 100644
index 0000000..400f598
Binary files /dev/null and b/front-end/app/src/main/res/drawable/googlelogin.png differ
diff --git a/front-end/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/front-end/app/src/main/res/drawable/ic_dashboard_black_24dp.xml
new file mode 100644
index 0000000..46fc8de
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/ic_dashboard_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/front-end/app/src/main/res/drawable/ic_home_black_24dp.xml b/front-end/app/src/main/res/drawable/ic_home_black_24dp.xml
new file mode 100644
index 0000000..f8bb0b5
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/ic_home_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/front-end/app/src/main/res/drawable/ic_launcher_background.xml b/front-end/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front-end/app/src/main/res/drawable/ic_launcher_foreground.xml b/front-end/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/front-end/app/src/main/res/drawable/ic_notifications_black_24dp.xml
new file mode 100644
index 0000000..78b75c3
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/ic_notifications_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/front-end/app/src/main/res/drawable/icon_add.png b/front-end/app/src/main/res/drawable/icon_add.png
new file mode 100644
index 0000000..d610e45
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_add.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_back.png b/front-end/app/src/main/res/drawable/icon_back.png
new file mode 100644
index 0000000..b9ed098
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_back.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_bath.png b/front-end/app/src/main/res/drawable/icon_bath.png
new file mode 100644
index 0000000..ede6706
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_bath.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_chat.png b/front-end/app/src/main/res/drawable/icon_chat.png
new file mode 100644
index 0000000..9981c74
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_chat.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_checkbox_gray.png b/front-end/app/src/main/res/drawable/icon_checkbox_gray.png
new file mode 100644
index 0000000..585bc44
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_checkbox_gray.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_checkbox_orange.png b/front-end/app/src/main/res/drawable/icon_checkbox_orange.png
new file mode 100644
index 0000000..2d946b6
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_checkbox_orange.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_close.png b/front-end/app/src/main/res/drawable/icon_close.png
new file mode 100644
index 0000000..83cd8a4
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_close.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_diaper.png b/front-end/app/src/main/res/drawable/icon_diaper.png
new file mode 100644
index 0000000..518ce4d
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_diaper.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_logout.png b/front-end/app/src/main/res/drawable/icon_logout.png
new file mode 100644
index 0000000..9fda4d7
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_logout.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_meal.png b/front-end/app/src/main/res/drawable/icon_meal.png
new file mode 100644
index 0000000..cb132c6
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_meal.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_mypost.png b/front-end/app/src/main/res/drawable/icon_mypost.png
new file mode 100644
index 0000000..213c75b
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_mypost.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_next.png b/front-end/app/src/main/res/drawable/icon_next.png
new file mode 100644
index 0000000..30130fb
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_next.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_play.png b/front-end/app/src/main/res/drawable/icon_play.png
new file mode 100644
index 0000000..84fe6eb
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_play.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_sleep.png b/front-end/app/src/main/res/drawable/icon_sleep.png
new file mode 100644
index 0000000..57d12a1
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_sleep.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_thin_close.png b/front-end/app/src/main/res/drawable/icon_thin_close.png
new file mode 100644
index 0000000..58c56b2
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_thin_close.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_user.png b/front-end/app/src/main/res/drawable/icon_user.png
new file mode 100644
index 0000000..9348498
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_user.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_withdraw.png b/front-end/app/src/main/res/drawable/icon_withdraw.png
new file mode 100644
index 0000000..9de9ce0
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_withdraw.png differ
diff --git a/front-end/app/src/main/res/drawable/icon_x.png b/front-end/app/src/main/res/drawable/icon_x.png
new file mode 100644
index 0000000..5f82ae4
Binary files /dev/null and b/front-end/app/src/main/res/drawable/icon_x.png differ
diff --git a/front-end/app/src/main/res/drawable/item_bg_on.png b/front-end/app/src/main/res/drawable/item_bg_on.png
new file mode 100644
index 0000000..883b628
Binary files /dev/null and b/front-end/app/src/main/res/drawable/item_bg_on.png differ
diff --git a/front-end/app/src/main/res/drawable/item_bg_outline.png b/front-end/app/src/main/res/drawable/item_bg_outline.png
new file mode 100644
index 0000000..c52b9e7
Binary files /dev/null and b/front-end/app/src/main/res/drawable/item_bg_outline.png differ
diff --git a/front-end/app/src/main/res/drawable/loginimage.png b/front-end/app/src/main/res/drawable/loginimage.png
new file mode 100644
index 0000000..e7b988c
Binary files /dev/null and b/front-end/app/src/main/res/drawable/loginimage.png differ
diff --git a/front-end/app/src/main/res/drawable/meal.png b/front-end/app/src/main/res/drawable/meal.png
new file mode 100644
index 0000000..e97f9da
Binary files /dev/null and b/front-end/app/src/main/res/drawable/meal.png differ
diff --git a/front-end/app/src/main/res/drawable/pattern.png b/front-end/app/src/main/res/drawable/pattern.png
new file mode 100644
index 0000000..c2facf0
Binary files /dev/null and b/front-end/app/src/main/res/drawable/pattern.png differ
diff --git a/front-end/app/src/main/res/drawable/play.png b/front-end/app/src/main/res/drawable/play.png
new file mode 100644
index 0000000..1957398
Binary files /dev/null and b/front-end/app/src/main/res/drawable/play.png differ
diff --git a/front-end/app/src/main/res/drawable/profile.png b/front-end/app/src/main/res/drawable/profile.png
new file mode 100644
index 0000000..d7dc9f4
Binary files /dev/null and b/front-end/app/src/main/res/drawable/profile.png differ
diff --git a/front-end/app/src/main/res/drawable/record.png b/front-end/app/src/main/res/drawable/record.png
new file mode 100644
index 0000000..2a3a8f6
Binary files /dev/null and b/front-end/app/src/main/res/drawable/record.png differ
diff --git a/front-end/app/src/main/res/drawable/sleep.png b/front-end/app/src/main/res/drawable/sleep.png
new file mode 100644
index 0000000..4fdc0ca
Binary files /dev/null and b/front-end/app/src/main/res/drawable/sleep.png differ
diff --git a/front-end/app/src/main/res/drawable/toggle_invisible.xml b/front-end/app/src/main/res/drawable/toggle_invisible.xml
new file mode 100644
index 0000000..bda43ff
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/toggle_invisible.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/front-end/app/src/main/res/drawable/toggle_selector.xml b/front-end/app/src/main/res/drawable/toggle_selector.xml
new file mode 100644
index 0000000..7c92d46
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/toggle_selector.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/drawable/toggle_text_selector.xml b/front-end/app/src/main/res/drawable/toggle_text_selector.xml
new file mode 100644
index 0000000..db2a957
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/toggle_text_selector.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/drawable/toggle_visible.xml b/front-end/app/src/main/res/drawable/toggle_visible.xml
new file mode 100644
index 0000000..2731c2e
--- /dev/null
+++ b/front-end/app/src/main/res/drawable/toggle_visible.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/front-end/app/src/main/res/drawable/userimage.png b/front-end/app/src/main/res/drawable/userimage.png
new file mode 100644
index 0000000..f9e5f68
Binary files /dev/null and b/front-end/app/src/main/res/drawable/userimage.png differ
diff --git a/front-end/app/src/main/res/layout/activity_add_baby.xml b/front-end/app/src/main/res/layout/activity_add_baby.xml
new file mode 100644
index 0000000..5ee083b
--- /dev/null
+++ b/front-end/app/src/main/res/layout/activity_add_baby.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/activity_login.xml b/front-end/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..4c3bc32
--- /dev/null
+++ b/front-end/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/activity_login_test.xml b/front-end/app/src/main/res/layout/activity_login_test.xml
new file mode 100644
index 0000000..8b4c45b
--- /dev/null
+++ b/front-end/app/src/main/res/layout/activity_login_test.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/activity_main.xml b/front-end/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..9012e8f
--- /dev/null
+++ b/front-end/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/checklist_dialog.xml b/front-end/app/src/main/res/layout/checklist_dialog.xml
new file mode 100644
index 0000000..7f98fee
--- /dev/null
+++ b/front-end/app/src/main/res/layout/checklist_dialog.xml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/checklist_item.xml b/front-end/app/src/main/res/layout/checklist_item.xml
new file mode 100644
index 0000000..b30a013
--- /dev/null
+++ b/front-end/app/src/main/res/layout/checklist_item.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front-end/app/src/main/res/layout/comment_item.xml b/front-end/app/src/main/res/layout/comment_item.xml
new file mode 100644
index 0000000..2553690
--- /dev/null
+++ b/front-end/app/src/main/res/layout/comment_item.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/community_dialog.xml b/front-end/app/src/main/res/layout/community_dialog.xml
new file mode 100644
index 0000000..3b0eb0f
--- /dev/null
+++ b/front-end/app/src/main/res/layout/community_dialog.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/community_item.xml b/front-end/app/src/main/res/layout/community_item.xml
new file mode 100644
index 0000000..3c7802c
--- /dev/null
+++ b/front-end/app/src/main/res/layout/community_item.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front-end/app/src/main/res/layout/dialog_login.xml b/front-end/app/src/main/res/layout/dialog_login.xml
new file mode 100644
index 0000000..79707ac
--- /dev/null
+++ b/front-end/app/src/main/res/layout/dialog_login.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/fragment_checklist.xml b/front-end/app/src/main/res/layout/fragment_checklist.xml
new file mode 100644
index 0000000..514790f
--- /dev/null
+++ b/front-end/app/src/main/res/layout/fragment_checklist.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/front-end/app/src/main/res/layout/fragment_community.xml b/front-end/app/src/main/res/layout/fragment_community.xml
new file mode 100644
index 0000000..f2283fd
--- /dev/null
+++ b/front-end/app/src/main/res/layout/fragment_community.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/fragment_community_detail.xml b/front-end/app/src/main/res/layout/fragment_community_detail.xml
new file mode 100644
index 0000000..96107e4
--- /dev/null
+++ b/front-end/app/src/main/res/layout/fragment_community_detail.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front-end/app/src/main/res/layout/fragment_login.xml b/front-end/app/src/main/res/layout/fragment_login.xml
new file mode 100644
index 0000000..627b651
--- /dev/null
+++ b/front-end/app/src/main/res/layout/fragment_login.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/fragment_login_test.xml b/front-end/app/src/main/res/layout/fragment_login_test.xml
new file mode 100644
index 0000000..10f9466
--- /dev/null
+++ b/front-end/app/src/main/res/layout/fragment_login_test.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/fragment_pattern.xml b/front-end/app/src/main/res/layout/fragment_pattern.xml
new file mode 100644
index 0000000..2c7ae33
--- /dev/null
+++ b/front-end/app/src/main/res/layout/fragment_pattern.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/fragment_pie_chart.xml b/front-end/app/src/main/res/layout/fragment_pie_chart.xml
new file mode 100644
index 0000000..833e054
--- /dev/null
+++ b/front-end/app/src/main/res/layout/fragment_pie_chart.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/fragment_profile.xml b/front-end/app/src/main/res/layout/fragment_profile.xml
new file mode 100644
index 0000000..f75c857
--- /dev/null
+++ b/front-end/app/src/main/res/layout/fragment_profile.xml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/fragment_record.xml b/front-end/app/src/main/res/layout/fragment_record.xml
new file mode 100644
index 0000000..ed0f889
--- /dev/null
+++ b/front-end/app/src/main/res/layout/fragment_record.xml
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/record_dialog.xml b/front-end/app/src/main/res/layout/record_dialog.xml
new file mode 100644
index 0000000..00ce1be
--- /dev/null
+++ b/front-end/app/src/main/res/layout/record_dialog.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/layout/record_item.xml b/front-end/app/src/main/res/layout/record_item.xml
new file mode 100644
index 0000000..4993d01
--- /dev/null
+++ b/front-end/app/src/main/res/layout/record_item.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front-end/app/src/main/res/menu/bottom_nav_menu.xml b/front-end/app/src/main/res/menu/bottom_nav_menu.xml
new file mode 100644
index 0000000..43c80ba
--- /dev/null
+++ b/front-end/app/src/main/res/menu/bottom_nav_menu.xml
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/front-end/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/front-end/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/front-end/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/front-end/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/front-end/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/front-end/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/front-end/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/front-end/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/front-end/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/front-end/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/front-end/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/front-end/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/front-end/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/front-end/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/front-end/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/front-end/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/front-end/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/front-end/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/front-end/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/front-end/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/front-end/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/front-end/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/front-end/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/front-end/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/front-end/app/src/main/res/navigation/mobile_navigation.xml b/front-end/app/src/main/res/navigation/mobile_navigation.xml
new file mode 100644
index 0000000..2d95483
--- /dev/null
+++ b/front-end/app/src/main/res/navigation/mobile_navigation.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/values-night/themes.xml b/front-end/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..1c743c7
--- /dev/null
+++ b/front-end/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/values/colors.xml b/front-end/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..ba35f37
--- /dev/null
+++ b/front-end/app/src/main/res/values/colors.xml
@@ -0,0 +1,24 @@
+
+
+ #FFBB86FC
+ #F96800
+ #F96800
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+ #F96800
+ #FF9737
+ #F4F4F4
+ #BDBDBD
+ #D9D9D9
+ #999999
+ #1C76FD
+ #5971C0
+ #9EC97F
+ #9265AF
+ #F3C96B
+ #ED895D
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/values/dimens.xml b/front-end/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..e00c2dd
--- /dev/null
+++ b/front-end/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/values/strings.xml b/front-end/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8c9c3b4
--- /dev/null
+++ b/front-end/app/src/main/res/values/strings.xml
@@ -0,0 +1,14 @@
+
+ HarmonyCare
+ Checklist
+ Record
+ Pattern
+ Community
+ Profile
+ Home
+ Dashboard
+ Notifications
+
+ Hello blank fragment
+ 185976520158-phphtutm302clototd3rqgecng4a4bg2.apps.googleusercontent.com
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/values/styles.xml b/front-end/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..e5237eb
--- /dev/null
+++ b/front-end/app/src/main/res/values/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/values/themes.xml b/front-end/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..758e2c5
--- /dev/null
+++ b/front-end/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/xml/backup_rules.xml b/front-end/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/front-end/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/main/res/xml/data_extraction_rules.xml b/front-end/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/front-end/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front-end/app/src/test/java/com/example/harmonycare/ExampleUnitTest.kt b/front-end/app/src/test/java/com/example/harmonycare/ExampleUnitTest.kt
new file mode 100644
index 0000000..d66d290
--- /dev/null
+++ b/front-end/app/src/test/java/com/example/harmonycare/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.example.harmonycare
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/front-end/build.gradle.kts b/front-end/build.gradle.kts
new file mode 100644
index 0000000..7845a03
--- /dev/null
+++ b/front-end/build.gradle.kts
@@ -0,0 +1,7 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+plugins {
+ id("com.android.application") version "8.2.2" apply false
+ id("org.jetbrains.kotlin.android") version "1.9.22" apply false
+ id("androidx.navigation.safeargs.kotlin") version "2.7.6" apply false
+}
\ No newline at end of file
diff --git a/front-end/gradle.properties b/front-end/gradle.properties
new file mode 100644
index 0000000..3c5031e
--- /dev/null
+++ b/front-end/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/front-end/gradle/wrapper/gradle-wrapper.jar b/front-end/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/front-end/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/front-end/gradle/wrapper/gradle-wrapper.properties b/front-end/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e80f8c7
--- /dev/null
+++ b/front-end/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Feb 08 15:35:28 KST 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/front-end/gradlew b/front-end/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/front-end/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/front-end/gradlew.bat b/front-end/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/front-end/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/front-end/settings.gradle.kts b/front-end/settings.gradle.kts
new file mode 100644
index 0000000..a4fa007
--- /dev/null
+++ b/front-end/settings.gradle.kts
@@ -0,0 +1,19 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ maven(url = "https://jitpack.io")
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ maven(url="https://jitpack.io")
+ }
+}
+
+rootProject.name = "HarmonyCare"
+include(":app")
+
\ No newline at end of file