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