diff --git a/app/src/main/res/menu/nav_items.xml b/app/src/main/res/menu/nav_items.xml
index 4f46f11..da99015 100644
--- a/app/src/main/res/menu/nav_items.xml
+++ b/app/src/main/res/menu/nav_items.xml
@@ -7,15 +7,16 @@
android:id="@id/personal_nav_graph"
android:icon="@drawable/baseline_person_outline_24"
android:title="@string/personal_nav_title"
- app:showAsAction="ifRoom"/>
+ app:showAsAction="ifRoom" />
+ app:showAsAction="ifRoom" />
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index eb7a2fb..4a39921 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -42,6 +42,9 @@ object Versions {
const val RETROFIT = "2.9.0"
const val OKHTTP = "4.10.0"
const val SERIALIZATION_CONVERTER = "0.8.0"
+
+ // DataStore
+ const val DATASTORE = "1.0.0"
}
object Kotlin {
@@ -72,6 +75,9 @@ object AndroidX {
// Pagging
const val PAGING3 = "androidx.paging:paging-runtime:${Versions.PAGING3}"
+
+ // DataStore
+ const val DATASTORE = "androidx.datastore:datastore-preferences:${Versions.DATASTORE}"
}
object Material {
diff --git a/common/build.gradle.kts b/common/build.gradle.kts
index b3ae68a..06c5b56 100644
--- a/common/build.gradle.kts
+++ b/common/build.gradle.kts
@@ -59,4 +59,7 @@ dependencies {
// DI
implementation(DI.HILT)
kapt(DI.HILT_COMPLIER)
+
+ // DataStore
+ implementation(AndroidX.DATASTORE)
}
diff --git a/common/src/main/java/com/konkuk/common/data/UserRepository.kt b/common/src/main/java/com/konkuk/common/data/UserRepository.kt
new file mode 100644
index 0000000..1df5e35
--- /dev/null
+++ b/common/src/main/java/com/konkuk/common/data/UserRepository.kt
@@ -0,0 +1,61 @@
+package com.konkuk.common.data
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class UserRepository(private val context: Context) {
+
+ private val Context.dataStore: DataStore by preferencesDataStore(name = DATASTORE_NAME)
+
+ private val nameKey = stringPreferencesKey(NAME_KEY)
+ private val ageKey = intPreferencesKey(AGE_KEY)
+ private val genderKey = booleanPreferencesKey(GENDER_KEY)
+
+ val nameFlow: Flow = context.dataStore.data
+ .map { preferences ->
+ preferences[nameKey]
+ }
+
+ suspend fun setName(name: String) {
+ context.dataStore.edit { settings ->
+ settings[nameKey] = name
+ }
+ }
+
+ val ageFlow: Flow = context.dataStore.data
+ .map { preferences ->
+ preferences[ageKey]
+ }
+
+ suspend fun setAge(age: Int) {
+ context.dataStore.edit { settings ->
+ settings[ageKey] = age
+ }
+ }
+
+ val genderFlow: Flow = context.dataStore.data
+ .map { preferences ->
+ preferences[genderKey]
+ }
+
+ suspend fun setGender(gender: Boolean) {
+ context.dataStore.edit { settings ->
+ settings[genderKey] = gender
+ }
+ }
+
+ companion object {
+ const val DATASTORE_NAME = "settings"
+ const val NAME_KEY = "nameKey"
+ const val AGE_KEY = "ageKey"
+ const val GENDER_KEY = "genderKey"
+ }
+}
diff --git a/common/src/main/java/com/konkuk/common/di/DatabaseModule.kt b/common/src/main/java/com/konkuk/common/di/DatabaseModule.kt
index 000d497..b48b54a 100644
--- a/common/src/main/java/com/konkuk/common/di/DatabaseModule.kt
+++ b/common/src/main/java/com/konkuk/common/di/DatabaseModule.kt
@@ -4,6 +4,7 @@ import android.content.Context
import androidx.room.Room
import com.konkuk.common.data.AppDatabase
import com.konkuk.common.data.FoodInfoDao
+import com.konkuk.common.data.UserRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -29,4 +30,10 @@ class DatabaseModule {
fun provideFoodInfoDao(db: AppDatabase): FoodInfoDao {
return db.foodInfoDao()
}
+
+ @Provides
+ @Singleton
+ fun provideUserRepository(@ApplicationContext context: Context): UserRepository {
+ return UserRepository(context)
+ }
}
diff --git a/common/src/main/res/color/button_color_selector.xml b/common/src/main/res/color/button_color_selector.xml
new file mode 100644
index 0000000..ad05c67
--- /dev/null
+++ b/common/src/main/res/color/button_color_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/baseline_female_24.xml b/common/src/main/res/drawable/baseline_female_24.xml
new file mode 100644
index 0000000..9da0ba0
--- /dev/null
+++ b/common/src/main/res/drawable/baseline_female_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/common/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml b/common/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml
new file mode 100644
index 0000000..e011dbc
--- /dev/null
+++ b/common/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/common/src/main/res/drawable/baseline_male_24.xml b/common/src/main/res/drawable/baseline_male_24.xml
new file mode 100644
index 0000000..4d49856
--- /dev/null
+++ b/common/src/main/res/drawable/baseline_male_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/common/src/main/res/drawable/btn_customfull.xml b/common/src/main/res/drawable/btn_customfull.xml
new file mode 100644
index 0000000..3d3f07f
--- /dev/null
+++ b/common/src/main/res/drawable/btn_customfull.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/camera_pressed.xml b/common/src/main/res/drawable/camera_pressed.xml
new file mode 100644
index 0000000..953572a
--- /dev/null
+++ b/common/src/main/res/drawable/camera_pressed.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/camera_unpressed.xml b/common/src/main/res/drawable/camera_unpressed.xml
new file mode 100644
index 0000000..9ac8cc5
--- /dev/null
+++ b/common/src/main/res/drawable/camera_unpressed.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/female_pressed.xml b/common/src/main/res/drawable/female_pressed.xml
new file mode 100644
index 0000000..78a6d4f
--- /dev/null
+++ b/common/src/main/res/drawable/female_pressed.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/fragment_bottom_sheet_enroll.xml b/common/src/main/res/drawable/fragment_bottom_sheet_enroll.xml
new file mode 100644
index 0000000..c1b5d9d
--- /dev/null
+++ b/common/src/main/res/drawable/fragment_bottom_sheet_enroll.xml
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/outline_calendar_month_24.xml b/common/src/main/res/drawable/outline_calendar_month_24.xml
new file mode 100644
index 0000000..252b5d2
--- /dev/null
+++ b/common/src/main/res/drawable/outline_calendar_month_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/common/src/main/res/drawable/rectangle_roundfull.xml b/common/src/main/res/drawable/rectangle_roundfull.xml
new file mode 100644
index 0000000..58b9af3
--- /dev/null
+++ b/common/src/main/res/drawable/rectangle_roundfull.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/rectanglesmall4x.png b/common/src/main/res/drawable/rectanglesmall4x.png
new file mode 100644
index 0000000..4c00a72
Binary files /dev/null and b/common/src/main/res/drawable/rectanglesmall4x.png differ
diff --git a/common/src/main/res/drawable/selector_female.xml b/common/src/main/res/drawable/selector_female.xml
new file mode 100644
index 0000000..8403d6e
--- /dev/null
+++ b/common/src/main/res/drawable/selector_female.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/selector_male.xml b/common/src/main/res/drawable/selector_male.xml
new file mode 100644
index 0000000..83da73f
--- /dev/null
+++ b/common/src/main/res/drawable/selector_male.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feat/capture/src/main/java/com/konkuk/capture/ui/capture/CaptureActivity.kt b/feat/capture/src/main/java/com/konkuk/capture/ui/capture/CaptureActivity.kt
index 180ca69..17e122d 100644
--- a/feat/capture/src/main/java/com/konkuk/capture/ui/capture/CaptureActivity.kt
+++ b/feat/capture/src/main/java/com/konkuk/capture/ui/capture/CaptureActivity.kt
@@ -3,6 +3,7 @@ package com.konkuk.capture.ui.capture
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Bitmap
+import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import androidx.activity.result.contract.ActivityResultContracts
@@ -13,7 +14,9 @@ import com.google.mlkit.vision.text.TextRecognition
import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
import com.konkuk.capture.databinding.ActivityCaptureBinding
import com.konkuk.capture.ui.enroll.EnrollTextInput
+import com.konkuk.capture.ui.enroll.EnrollTextInputViewModel.Companion.BITMAP_PICTURE_KEY
import com.konkuk.capture.ui.enroll.EnrollTextInputViewModel.Companion.OCR_RESULT_KEY
+import com.konkuk.capture.ui.enroll.EnrollTextInputViewModel.Companion.URI_PICTURE_KEY
class CaptureActivity : AppCompatActivity() {
@@ -22,11 +25,15 @@ class CaptureActivity : AppCompatActivity() {
private val recognizer =
TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())
+ private var bitmapPicture: Bitmap? = null
+ private var uriPicture: Uri? = null
+
private val takePictureLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult(),
) {
if (it.resultCode == RESULT_OK) {
(it.data?.extras?.get("data") as Bitmap?)?.let { bitmap ->
+ bitmapPicture = bitmap
binding.ivNutritionInfo.setImageBitmap(bitmap)
processImageRecognize(InputImage.fromBitmap(bitmap, 0))
} ?: reCapture()
@@ -40,6 +47,7 @@ class CaptureActivity : AppCompatActivity() {
) {
if (it.resultCode == RESULT_OK) {
it.data?.data?.let { uri ->
+ uriPicture = uri
binding.ivNutritionInfo.setImageURI(uri)
processImageRecognize(InputImage.fromFilePath(this@CaptureActivity, uri))
} ?: reCapture()
@@ -69,6 +77,11 @@ class CaptureActivity : AppCompatActivity() {
reCapture()
})
captureDialog.show(supportFragmentManager, "CaptureDialogFragment")
+
+ binding.llBackBtn.setOnClickListener {
+ finish()
+ startActivity(Intent(this@CaptureActivity, EnrollTextInput::class.java))
+ }
}
@SuppressLint("SetTextI18n")
@@ -88,10 +101,9 @@ class CaptureActivity : AppCompatActivity() {
finish()
startActivity(
Intent(this@CaptureActivity, EnrollTextInput::class.java).apply {
- putExtra(
- OCR_RESULT_KEY,
- visionText.text,
- )
+ putExtra(OCR_RESULT_KEY, visionText.text)
+ putExtra(BITMAP_PICTURE_KEY, bitmapPicture)
+ putExtra(URI_PICTURE_KEY, uriPicture)
},
)
}
diff --git a/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollBindingAdapter.kt b/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollBindingAdapter.kt
new file mode 100644
index 0000000..abacd8f
--- /dev/null
+++ b/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollBindingAdapter.kt
@@ -0,0 +1,14 @@
+package com.konkuk.capture.ui.enroll
+
+import android.widget.ImageView
+import androidx.databinding.BindingAdapter
+
+@BindingAdapter("capturedPicture")
+fun ImageView.capturedPicture(capturedPicture: CapturedPicture) {
+ println("capturedPicture $capturedPicture")
+ when (capturedPicture) {
+ is CapturedPicture.BitmapPicture -> setImageBitmap(capturedPicture.bitmap)
+ is CapturedPicture.UriPicture -> setImageURI(capturedPicture.uri)
+ else -> {}
+ }
+}
diff --git a/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollBottomSheetDialogFragment.kt b/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollBottomSheetDialogFragment.kt
index 342d310..deff66d 100644
--- a/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollBottomSheetDialogFragment.kt
+++ b/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollBottomSheetDialogFragment.kt
@@ -5,16 +5,24 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.konkuk.capture.databinding.FragmentBottomSheetEnrollBinding
import com.konkuk.capture.ui.capture.CaptureActivity
import com.konkuk.capture.ui.search.SearchFoodActivity
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
class EnrollBottomSheetDialogFragment : BottomSheetDialogFragment() {
private var _binding: FragmentBottomSheetEnrollBinding? = null
private val binding: FragmentBottomSheetEnrollBinding get() = requireNotNull(_binding)
+ private val selection = MutableStateFlow(null)
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -27,35 +35,41 @@ class EnrollBottomSheetDialogFragment : BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- binding.lifecycleOwner = viewLifecycleOwner
-
initSelector()
- initNext()
}
- private fun initNext() {
- binding.btnContinue.setOnClickListener {
- dismiss()
- if (binding.selectorCamera.isSelected) {
+ private fun initSelector() = with(binding) {
+ clickLeft = {
+ selection.value = true
+ }
+
+ clickRight = {
+ selection.value = false
+ }
+
+ btnContinue.setOnClickListener {
+ if (selection.value == true) {
val intent = Intent(context, CaptureActivity::class.java)
startActivity(intent)
- } else {
+ } else if (selection.value == false) {
val intent = Intent(context, SearchFoodActivity::class.java)
startActivity(intent)
}
+ dismiss()
}
- }
- private fun initSelector() = with(binding) {
- selectorCamera.isSelected = true
- selectorPen.isSelected = false
- selectorCamera.setOnClickListener {
- selectorCamera.isSelected = true
- selectorPen.isSelected = false
- }
- selectorPen.setOnClickListener {
- selectorCamera.isSelected = false
- selectorPen.isSelected = true
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ selection.collectLatest { selected ->
+ selected?.let {
+ btnContinue.isEnabled = true
+ selectorCamera.isSelected = it
+ selectorPen.isSelected = it.not()
+ } ?: kotlin.run {
+ btnContinue.isEnabled = false
+ }
+ }
+ }
}
}
diff --git a/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollTextInputViewModel.kt b/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollTextInputViewModel.kt
index fd50119..5a9f636 100644
--- a/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollTextInputViewModel.kt
+++ b/feat/capture/src/main/java/com/konkuk/capture/ui/enroll/EnrollTextInputViewModel.kt
@@ -1,5 +1,7 @@
package com.konkuk.capture.ui.enroll
+import android.graphics.Bitmap
+import android.net.Uri
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.konkuk.common.data.FoodInfo
@@ -34,6 +36,8 @@ class EnrollTextInputViewModel @Inject constructor(
val saturatedFat = MutableStateFlow("")
val calories = MutableStateFlow("")
+ val capturedPicture = MutableStateFlow(CapturedPicture.None)
+
init {
savesStateHandle.get(OCR_RESULT_KEY)?.let { text ->
val result = text.replace(" ", "")
@@ -45,6 +49,11 @@ class EnrollTextInputViewModel @Inject constructor(
savesStateHandle.get(API_RESULT_KEY)?.let {
setFoodInfo(it)
}
+ savesStateHandle.get(URI_PICTURE_KEY)?.let {
+ capturedPicture.value = CapturedPicture.UriPicture(it)
+ } ?: savesStateHandle.get(BITMAP_PICTURE_KEY)?.let {
+ capturedPicture.value = CapturedPicture.BitmapPicture(it)
+ }
}
private fun setFoodInfo(foodInfo: FoodInfo) {
@@ -112,5 +121,13 @@ class EnrollTextInputViewModel @Inject constructor(
companion object {
const val OCR_RESULT_KEY = "OCR_RESULT_KEY"
const val API_RESULT_KEY = "API_RESULT_KEY"
+ const val BITMAP_PICTURE_KEY = "BITMAP_PICTURE_KEY"
+ const val URI_PICTURE_KEY = "URI_PICTURE_KEY"
}
}
+
+sealed class CapturedPicture {
+ data class UriPicture(val uri: Uri) : CapturedPicture()
+ data class BitmapPicture(val bitmap: Bitmap) : CapturedPicture()
+ object None : CapturedPicture()
+}
diff --git a/feat/capture/src/main/java/com/konkuk/capture/ui/search/SearchFoodActivity.kt b/feat/capture/src/main/java/com/konkuk/capture/ui/search/SearchFoodActivity.kt
index 231ac1a..b1d630f 100644
--- a/feat/capture/src/main/java/com/konkuk/capture/ui/search/SearchFoodActivity.kt
+++ b/feat/capture/src/main/java/com/konkuk/capture/ui/search/SearchFoodActivity.kt
@@ -2,6 +2,7 @@ package com.konkuk.capture.ui.search
import android.content.Intent
import android.os.Bundle
+import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
@@ -16,6 +17,7 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -59,6 +61,17 @@ class SearchFoodActivity : AppCompatActivity() {
}
}
+ lifecycleScope.launch {
+ viewModel.textField.collectLatest { text ->
+ if (text.isBlank()) {
+ delay(200)
+ binding.llSearchNotiImage.visibility = View.VISIBLE
+ } else {
+ binding.llSearchNotiImage.visibility = View.GONE
+ }
+ }
+ }
+
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.event.collectLatest {
diff --git a/feat/capture/src/main/res/layout/activity_capture.xml b/feat/capture/src/main/res/layout/activity_capture.xml
index d8c2a9d..2110446 100644
--- a/feat/capture/src/main/res/layout/activity_capture.xml
+++ b/feat/capture/src/main/res/layout/activity_capture.xml
@@ -6,20 +6,46 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/stroke_deepblue"
+ android:background="@color/white"
tools:context=".ui.capture.CaptureActivity">
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/ll_back_btn" />
+
-
-
+
+
-
-
+
+
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/feat/capture/src/main/res/layout/activity_enroll_text_input.xml b/feat/capture/src/main/res/layout/activity_enroll_text_input.xml
index 1f2fc2e..7750bfa 100644
--- a/feat/capture/src/main/res/layout/activity_enroll_text_input.xml
+++ b/feat/capture/src/main/res/layout/activity_enroll_text_input.xml
@@ -43,7 +43,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="11dp"
- android:text="직접 입력"
+ android:text="뒤로 가기"
android:textColor="@color/black"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
@@ -60,9 +60,16 @@
+
+
-
+ app:layout_constraintTop_toTopOf="@id/btn_back"
+ app:strokeColor="@color/main_blue"
+ app:strokeWidth="1dp">
+
+
+
+
+
+
+
+
+
+
+
+
-
+ app:layout_constraintTop_toBottomOf="@id/cv_search" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -60,6 +73,7 @@
android:id="@+id/selectorCamera"
android:layout_width="72dp"
android:layout_height="72dp"
+ android:onClick="@{() -> clickLeft.invoke()}"
android:background="@drawable/selector_camera"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -104,6 +118,7 @@
> {
+ operator fun invoke(): Result>> {
return kotlin.runCatching {
flow {
- emit(Calendar.getInstance().get(Calendar.DAY_OF_MONTH))
+ emit(
+ Triple(
+ Calendar.getInstance().get(Calendar.YEAR),
+ Calendar.getInstance().get(Calendar.MONTH) + 1,
+ Calendar.getInstance().get(Calendar.DAY_OF_MONTH),
+ ),
+ )
}
}
}
diff --git a/feat/history/src/main/java/com/konkuk/history/domain/usecase/GetHistoryListUseCase.kt b/feat/history/src/main/java/com/konkuk/history/domain/usecase/GetHistoryListUseCase.kt
index 9ff2b17..b002828 100644
--- a/feat/history/src/main/java/com/konkuk/history/domain/usecase/GetHistoryListUseCase.kt
+++ b/feat/history/src/main/java/com/konkuk/history/domain/usecase/GetHistoryListUseCase.kt
@@ -9,8 +9,16 @@ import javax.inject.Inject
class GetHistoryListUseCase @Inject constructor(
private val historyRepository: HistoryRepository,
) {
- operator fun invoke(selectedDay: Int): Result>> {
+ operator fun invoke(
+ year: Int? = null,
+ month: Int? = null,
+ selectedDay: Int,
+ ): Result>> {
val date = Date(System.currentTimeMillis())
- return historyRepository.getFoodHistory(date.year + 1900, date.month + 1, selectedDay)
+ return historyRepository.getFoodHistory(
+ year ?: (date.year + 1900),
+ month ?: (date.month + 1),
+ selectedDay,
+ )
}
}
diff --git a/feat/history/src/main/java/com/konkuk/history/ui/history/DatePickerFragment.kt b/feat/history/src/main/java/com/konkuk/history/ui/history/DatePickerFragment.kt
new file mode 100644
index 0000000..e384ffd
--- /dev/null
+++ b/feat/history/src/main/java/com/konkuk/history/ui/history/DatePickerFragment.kt
@@ -0,0 +1,28 @@
+package com.konkuk.history.ui.history
+
+import android.app.DatePickerDialog
+import android.app.DatePickerDialog.OnDateSetListener
+import android.app.Dialog
+import android.os.Bundle
+import android.widget.DatePicker
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.viewModels
+import dagger.hilt.android.AndroidEntryPoint
+import java.util.Calendar
+
+@AndroidEntryPoint
+class DatePickerFragment(private val onPicked: (year: Int, month: Int, day: Int) -> Unit) : DialogFragment(), OnDateSetListener {
+
+ private val viewModel by viewModels()
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val c: Calendar = Calendar.getInstance()
+ val year: Int = c.get(Calendar.YEAR)
+ val month: Int = c.get(Calendar.MONTH)
+ val day: Int = c.get(Calendar.DAY_OF_MONTH)
+ return DatePickerDialog(requireContext(), this, year, month, day)
+ }
+
+ override fun onDateSet(datePicker: DatePicker, year: Int, month: Int, day: Int) {
+ onPicked(year, month + 1, day)
+ }
+}
diff --git a/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryDateUiState.kt b/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryDateUiState.kt
index ae214b7..e02d6da 100644
--- a/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryDateUiState.kt
+++ b/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryDateUiState.kt
@@ -5,5 +5,5 @@ import com.konkuk.history.domain.model.HistoryCalendarModel
sealed class HistoryDateUiState {
object Uninitialized : HistoryDateUiState()
data class Error(val message: String) : HistoryDateUiState()
- data class Avail(val today: Int, val selectedDay: Int, val calendarList: List) : HistoryDateUiState()
+ data class Avail(val year: Int, val month: Int, val today: Int, val selectedDay: Int, val calendarList: List) : HistoryDateUiState()
}
diff --git a/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryFragment.kt b/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryFragment.kt
index 48aca0d..3a26dd9 100644
--- a/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryFragment.kt
+++ b/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryFragment.kt
@@ -60,6 +60,12 @@ class HistoryFragment : Fragment() {
val intent = Intent(requireContext(), HistoryStatisticsActivity::class.java).putExtra(
StatisticViewModel.SELECTED_DAY_KEY,
(viewModel.uiState.value.historyDateUiState as HistoryDateUiState.Avail).selectedDay,
+ ).putExtra(
+ StatisticViewModel.SELECTED_MONTH_KEY,
+ (viewModel.uiState.value.historyDateUiState as HistoryDateUiState.Avail).month,
+ ).putExtra(
+ StatisticViewModel.SELECTED_YEAR_KEY,
+ (viewModel.uiState.value.historyDateUiState as HistoryDateUiState.Avail).year,
)
startActivity(intent)
}
@@ -102,6 +108,11 @@ class HistoryFragment : Fragment() {
}
private fun initViews() = with(binding) {
+ ivCalendarButton.setOnClickListener {
+ DatePickerFragment { year, month, day ->
+ viewModel.setDate(year, month, day)
+ }.show(parentFragmentManager, "datePicker")
+ }
}
private fun observeUiState() {
@@ -120,6 +131,7 @@ class HistoryFragment : Fragment() {
is HistoryDateUiState.Uninitialized -> {}
is HistoryDateUiState.Error -> {}
is HistoryDateUiState.Avail -> {
+ tvHistoryTitle.text = "${historyDateUiState.month}월의 기록"
tvProgressTitle.text = "${historyDateUiState.selectedDay}일의 분석결과 보러가기"
calendarAdapter.submitList(historyDateUiState.calendarList.toList())
}
diff --git a/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryViewModel.kt b/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryViewModel.kt
index 4ce59a5..f278217 100644
--- a/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryViewModel.kt
+++ b/feat/history/src/main/java/com/konkuk/history/ui/history/HistoryViewModel.kt
@@ -35,8 +35,13 @@ class HistoryViewModel @Inject constructor(
initFoodHistory()
}
- private fun initCalendar(): List {
+ private fun initCalendar(
+ selectedDate: Triple? = null,
+ ): List {
val calendar = Calendar.getInstance()
+ selectedDate?.let { (year, month, day) ->
+ calendar.set(year, month - 1, day)
+ }
val today = calendar.get(Calendar.DAY_OF_MONTH)
calendar.set(Calendar.DAY_OF_MONTH, 1) // 이번 달의 1일로 설정
@@ -56,14 +61,33 @@ class HistoryViewModel @Inject constructor(
return calendarList
}
+ fun setDate(year: Int, month: Int, day: Int) {
+ _uiState.value = _uiState.value.copy(
+ historyDateUiState = HistoryDateUiState.Avail(
+ year,
+ month,
+ day,
+ day,
+ initCalendar(Triple(year, month, day)),
+ ),
+ )
+ initHistoryList(year, month, day)
+ }
+
private fun initFoodHistory() {
getHistoryDateUseCase().onSuccess { value ->
viewModelScope.launch {
- val today = value.first()
+ val (year, month, today) = value.first()
_uiState.value = _uiState.value.copy(
- historyDateUiState = HistoryDateUiState.Avail(today, today, initCalendar()),
+ historyDateUiState = HistoryDateUiState.Avail(
+ year,
+ month,
+ today,
+ today,
+ initCalendar(),
+ ),
)
- initHistoryList(today)
+ initHistoryList(selectedDay = today)
}
}.onFailure {
_uiState.value = _uiState.value.copy(
@@ -73,8 +97,8 @@ class HistoryViewModel @Inject constructor(
}
}
- private fun initHistoryList(selectedDay: Int) {
- getHistoryListUseCase(selectedDay).onSuccess { value ->
+ private fun initHistoryList(year: Int? = null, month: Int? = null, selectedDay: Int) {
+ getHistoryListUseCase(year, month, selectedDay).onSuccess { value ->
job?.cancel()
job = Job()
CoroutineScope((job ?: Job())).launch {
@@ -106,12 +130,17 @@ class HistoryViewModel @Inject constructor(
},
),
)
- getHistoryList(selectedDay)
+
+ getHistoryList(
+ (_uiState.value.historyDateUiState as HistoryDateUiState.Avail).year,
+ (_uiState.value.historyDateUiState as HistoryDateUiState.Avail).month,
+ selectedDay,
+ )
}
}
- private fun getHistoryList(selectedDay: Int) {
- getHistoryListUseCase(selectedDay).onSuccess { value ->
+ private fun getHistoryList(year: Int, month: Int, selectedDay: Int) {
+ getHistoryListUseCase(year, month, selectedDay).onSuccess { value ->
job?.cancel()
job = Job()
CoroutineScope((job ?: Job())).launch {
diff --git a/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/HistoryStatisticsActivity.kt b/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/HistoryStatisticsActivity.kt
index 86910bf..91f12cb 100644
--- a/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/HistoryStatisticsActivity.kt
+++ b/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/HistoryStatisticsActivity.kt
@@ -2,11 +2,9 @@ package com.konkuk.history.ui.history.statistic
import android.os.Bundle
import androidx.activity.viewModels
-import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.konkuk.history.databinding.ActivityHistoryStatisticsBinding
-import com.konkuk.history.databinding.DialogAgeInputBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@@ -44,29 +42,5 @@ class HistoryStatisticsActivity : AppCompatActivity() {
btnBack.setOnClickListener {
finish()
}
-
- btnAge.setOnClickListener {
- selectAge()
- }
- }
-
- private fun selectAge() {
- val dialogBinding = DialogAgeInputBinding.inflate(layoutInflater)
-
- AlertDialog.Builder(this@HistoryStatisticsActivity)
- .setView(
- dialogBinding.root,
- )
- .setPositiveButton(
- "확인",
- ) { dialog, _ ->
- val age = dialogBinding.etAge.text.toString().toIntOrNull() ?: 0
- if (age > 0) viewModel.changeAge(age)
- dialog.dismiss()
- }
- .setNegativeButton(
- "취소",
- ) { dialog, _ -> dialog.dismiss() }
- .show()
}
}
diff --git a/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/StaticsBindingAdapter.kt b/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/StaticsBindingAdapter.kt
index ea63f2b..ae9d77b 100644
--- a/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/StaticsBindingAdapter.kt
+++ b/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/StaticsBindingAdapter.kt
@@ -6,11 +6,13 @@ import com.konkuk.history.data.datasource.statistic.NutritionStat
@BindingAdapter("gender")
fun TextView.gender(gender: GENDER) {
- this.text = if (gender == GENDER.MALE) "남성" else "여성"
+ this.text = if (gender == GENDER.NONE) "" else if (gender == GENDER.MALE) "남성" else "여성"
}
@BindingAdapter("ageRange")
fun TextView.ageRange(age: Int) {
- val range = NutritionStat.ageList[NutritionStat.getAgeIndex(age)]
- this.text = "${range.first} ~ ${range.last}살"
+ if (age != 0) {
+ val range = NutritionStat.ageList[NutritionStat.getAgeIndex(age)]
+ this.text = "${range.first} ~ ${range.last}살"
+ }
}
diff --git a/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/StatisticViewModel.kt b/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/StatisticViewModel.kt
index 7f93f4a..157f92f 100644
--- a/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/StatisticViewModel.kt
+++ b/feat/history/src/main/java/com/konkuk/history/ui/history/statistic/StatisticViewModel.kt
@@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.konkuk.common.R
+import com.konkuk.common.data.UserRepository
import com.konkuk.history.data.datasource.statistic.NutritionStat
import com.konkuk.history.data.datasource.statistic.StatCSVParser
import com.konkuk.history.domain.model.HistoryItemModel
@@ -22,6 +23,7 @@ class StatisticViewModel @Inject constructor(
private val getHistoryListUseCase: GetHistoryListUseCase,
private val getMonthUseCase: GetMonthUseCase,
private val parser: StatCSVParser,
+ private val userRepository: UserRepository,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val statList = MutableStateFlow(NutritionStat(0, 0, 0, 0, 0, 0))
@@ -33,18 +35,28 @@ class StatisticViewModel @Inject constructor(
val date = MutableStateFlow("-월 -일의 분석")
- private val _gender = MutableStateFlow(GENDER.MALE)
+ private val _gender = MutableStateFlow(GENDER.NONE)
val gender get() = _gender.asStateFlow()
- private val _age = MutableStateFlow(18)
+ private val _age = MutableStateFlow(0)
val age get() = _age.asStateFlow()
val dataList = MutableStateFlow>(emptyList())
init {
- initMyStat(savedStateHandle.get(SELECTED_DAY_KEY)!!)
+ initMyStat(
+ savedStateHandle.get(SELECTED_YEAR_KEY)!!,
+ savedStateHandle.get(SELECTED_MONTH_KEY)!!,
+ savedStateHandle.get(SELECTED_DAY_KEY)!!,
+ )
initAvgStat()
initDateList()
+
+ viewModelScope.launch {
+ _gender.value =
+ if (userRepository.genderFlow.first() == true) GENDER.MALE else GENDER.FEMALE
+ _age.value = userRepository.ageFlow.first()?.let { it } ?: 23
+ }
}
private fun initDateList() {
@@ -131,11 +143,11 @@ class StatisticViewModel @Inject constructor(
}
}
- private fun initMyStat(selectedDay: Int?) {
+ private fun initMyStat(year: Int, month: Int, selectedDay: Int) {
date.value = "${getMonthUseCase()}월 ${selectedDay}일의 분석"
viewModelScope.launch {
val historyList =
- getHistoryListUseCase(requireNotNull(selectedDay)).getOrNull()?.first()
+ getHistoryListUseCase(year, month, selectedDay).getOrNull()?.first()
if (historyList?.size == 0) return@launch
nutList.value = historyList!!.reduce { item, sum ->
HistoryItemModel(
@@ -153,19 +165,13 @@ class StatisticViewModel @Inject constructor(
}
}
- fun changeAge(age: Int) {
- if (age > 0) _age.value = age
- }
-
- fun changeGender() {
- _gender.value = if (gender.value == GENDER.MALE) GENDER.FEMALE else GENDER.MALE
- }
-
companion object {
const val SELECTED_DAY_KEY = "SELECTED_DAY_KEY"
+ const val SELECTED_MONTH_KEY = "SELECTED_MONTH_KEY"
+ const val SELECTED_YEAR_KEY = "SELECTED_YEAR_KEY"
}
}
enum class GENDER {
- MALE, FEMALE
+ MALE, FEMALE, NONE
}
diff --git a/feat/history/src/main/res/layout/activity_history_statistics.xml b/feat/history/src/main/res/layout/activity_history_statistics.xml
index 18d99eb..4b52573 100644
--- a/feat/history/src/main/res/layout/activity_history_statistics.xml
+++ b/feat/history/src/main/res/layout/activity_history_statistics.xml
@@ -73,7 +73,7 @@
android:paddingHorizontal="25dp">
-
+ android:orientation="horizontal">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feat/personal/src/main/java/com/konkuk/personal/ui/personal/PersonalFragment.kt b/feat/personal/src/main/java/com/konkuk/personal/ui/personal/PersonalFragment.kt
index 95f4f90..65f6cc1 100644
--- a/feat/personal/src/main/java/com/konkuk/personal/ui/personal/PersonalFragment.kt
+++ b/feat/personal/src/main/java/com/konkuk/personal/ui/personal/PersonalFragment.kt
@@ -1,6 +1,7 @@
package com.konkuk.personal.ui.personal
import android.annotation.SuppressLint
+import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.util.Log
@@ -24,6 +25,7 @@ import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.konkuk.common.ui.decoration.AnimateProgressBarCommon
import com.konkuk.personal.databinding.FragmentPersonalBinding
+import com.konkuk.personal.ui.settings.SettingsActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@@ -50,6 +52,13 @@ class PersonalFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner
observeUiState()
+ initViews()
+ }
+
+ private fun initViews() {
+ binding.ivSettings.setOnClickListener {
+ startActivity(Intent(requireContext(), SettingsActivity::class.java))
+ }
}
private fun observeUiState() {
diff --git a/feat/personal/src/main/java/com/konkuk/personal/ui/settings/EditAgeActivity.kt b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/EditAgeActivity.kt
new file mode 100644
index 0000000..443eae1
--- /dev/null
+++ b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/EditAgeActivity.kt
@@ -0,0 +1,37 @@
+package com.konkuk.personal.ui.settings
+
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.konkuk.personal.databinding.ActivityEditAgeBinding
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class EditAgeActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityEditAgeBinding
+ private val viewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityEditAgeBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ binding.lifecycleOwner = this
+ binding.vm = viewModel
+
+ initViews()
+ }
+
+ private fun initViews() = with(binding) {
+ ivBackBtn.setOnClickListener {
+ finish()
+ }
+
+ tvSave.setOnClickListener {
+ viewModel.setAge()
+ finish()
+ }
+ }
+}
diff --git a/feat/personal/src/main/java/com/konkuk/personal/ui/settings/EditGenderBottomSheetDialogFragment.kt b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/EditGenderBottomSheetDialogFragment.kt
new file mode 100644
index 0000000..0a6e56a
--- /dev/null
+++ b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/EditGenderBottomSheetDialogFragment.kt
@@ -0,0 +1,67 @@
+package com.konkuk.personal.ui.settings
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.konkuk.personal.databinding.FragmentEditGenderBinding
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+
+@AndroidEntryPoint
+class EditGenderBottomSheetDialogFragment : BottomSheetDialogFragment() {
+
+ private var _binding: FragmentEditGenderBinding? = null
+ private val binding: FragmentEditGenderBinding get() = requireNotNull(_binding)
+
+ private val viewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ _binding = FragmentEditGenderBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.vm = viewModel
+
+ initSelector()
+ initNext()
+ }
+
+ private fun initNext() {
+ binding.btnContinue.setOnClickListener {
+ viewModel.setGender()
+ dismiss()
+ }
+ }
+
+ private fun initSelector() = with(binding) {
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.editableGender.collect { isMale ->
+ isMale?.let {
+ selectorMale.isSelected = it
+ selectorFemale.isSelected = it.not()
+ }
+ }
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ _binding = null
+ super.onDestroy()
+ }
+}
diff --git a/feat/personal/src/main/java/com/konkuk/personal/ui/settings/EditNameActivity.kt b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/EditNameActivity.kt
new file mode 100644
index 0000000..882d933
--- /dev/null
+++ b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/EditNameActivity.kt
@@ -0,0 +1,37 @@
+package com.konkuk.personal.ui.settings
+
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.konkuk.personal.databinding.ActivityEditNameBinding
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class EditNameActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityEditNameBinding
+ private val viewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityEditNameBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ binding.lifecycleOwner = this
+ binding.vm = viewModel
+
+ initViews()
+ }
+
+ private fun initViews() = with(binding) {
+ ivBackBtn.setOnClickListener {
+ finish()
+ }
+
+ tvSave.setOnClickListener {
+ viewModel.setName()
+ finish()
+ }
+ }
+}
diff --git a/feat/personal/src/main/java/com/konkuk/personal/ui/settings/SettingsActivity.kt b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/SettingsActivity.kt
new file mode 100644
index 0000000..621e5e9
--- /dev/null
+++ b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/SettingsActivity.kt
@@ -0,0 +1,48 @@
+package com.konkuk.personal.ui.settings
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import com.konkuk.personal.databinding.ActivitySettingsBinding
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class SettingsActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivitySettingsBinding
+ private val viewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivitySettingsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ binding.lifecycleOwner = this
+ binding.vm = viewModel
+
+ initViews()
+ }
+
+ private fun initViews() = with(binding) {
+ llName.setOnClickListener {
+ startActivity(Intent(this@SettingsActivity, EditNameActivity::class.java))
+ }
+
+ llAge.setOnClickListener {
+ startActivity(Intent(this@SettingsActivity, EditAgeActivity::class.java))
+ }
+
+ llGender.setOnClickListener {
+ val bottomSheetDialogFragment = EditGenderBottomSheetDialogFragment()
+ bottomSheetDialogFragment.show(
+ supportFragmentManager,
+ bottomSheetDialogFragment.tag,
+ )
+ }
+
+ ivBackBtn.setOnClickListener {
+ finish()
+ }
+ }
+}
diff --git a/feat/personal/src/main/java/com/konkuk/personal/ui/settings/SettingsBindingAdapter.kt b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/SettingsBindingAdapter.kt
new file mode 100644
index 0000000..621d1f5
--- /dev/null
+++ b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/SettingsBindingAdapter.kt
@@ -0,0 +1,15 @@
+package com.konkuk.personal.ui.settings
+
+import android.widget.TextView
+import androidx.databinding.BindingAdapter
+
+@BindingAdapter("isEnabled")
+fun TextView.isEnabled(enable: Boolean) {
+ if (enable) {
+ setBackgroundColor(resources.getColor(com.konkuk.common.R.color.main_blue))
+ this.isEnabled = true
+ } else {
+ setBackgroundColor(resources.getColor(com.konkuk.common.R.color.static_gray))
+ this.isEnabled = false
+ }
+}
diff --git a/feat/personal/src/main/java/com/konkuk/personal/ui/settings/SettingsViewModel.kt b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/SettingsViewModel.kt
new file mode 100644
index 0000000..d889c08
--- /dev/null
+++ b/feat/personal/src/main/java/com/konkuk/personal/ui/settings/SettingsViewModel.kt
@@ -0,0 +1,84 @@
+package com.konkuk.personal.ui.settings
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.konkuk.common.data.UserRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class SettingsViewModel @Inject constructor(private val userRepository: UserRepository) :
+ ViewModel() {
+ val name =
+ userRepository.nameFlow
+ .map {
+ if (it == null) "이름을 입력해주세요" else it
+ }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
+ val age =
+ userRepository.ageFlow.map {
+ if (it == null) "나이을 입력해주세요" else it.toString()
+ }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
+ val gender =
+ userRepository.genderFlow.map {
+ if (it == null) "성별을 입력해주세요" else if (it) "남자" else "여자"
+ }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
+
+ val editableName = MutableStateFlow("")
+ val editableAge = MutableStateFlow("")
+ val editableGender = MutableStateFlow(null)
+
+ val canEditName = userRepository.nameFlow.combine(editableName) { savedName, editName ->
+ savedName ?: "" != editName
+ }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
+
+ val canEditAge = userRepository.ageFlow.combine(editableAge) { savedAge, editAge ->
+ savedAge.toString() != editAge && editAge.toIntOrNull() != null
+ }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
+
+ init {
+ viewModelScope.launch {
+ editableName.value = userRepository.nameFlow.first() ?: ""
+ editableAge.value = userRepository.ageFlow.first()?.let {
+ it.toString()
+ } ?: ""
+ editableGender.value = userRepository.genderFlow.first()
+ }
+ }
+
+ fun setName() {
+ viewModelScope.launch {
+ userRepository.setName(editableName.value)
+ }
+ }
+
+ fun setAge() {
+ editableAge.value.toIntOrNull()?.let {
+ viewModelScope.launch {
+ userRepository.setAge(it)
+ }
+ }
+ }
+
+ fun setGender() {
+ editableGender.value?.let {
+ viewModelScope.launch {
+ userRepository.setGender(it)
+ }
+ }
+ }
+
+ fun selectMale() {
+ editableGender.value = true
+ }
+
+ fun selectFemale() {
+ editableGender.value = false
+ }
+}
diff --git a/feat/personal/src/main/res/layout/activity_edit_age.xml b/feat/personal/src/main/res/layout/activity_edit_age.xml
new file mode 100644
index 0000000..d4b9998
--- /dev/null
+++ b/feat/personal/src/main/res/layout/activity_edit_age.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/feat/personal/src/main/res/layout/activity_edit_name.xml b/feat/personal/src/main/res/layout/activity_edit_name.xml
new file mode 100644
index 0000000..fccb82c
--- /dev/null
+++ b/feat/personal/src/main/res/layout/activity_edit_name.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feat/personal/src/main/res/layout/activity_settings.xml b/feat/personal/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..c264cff
--- /dev/null
+++ b/feat/personal/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feat/personal/src/main/res/layout/fragment_edit_gender.xml b/feat/personal/src/main/res/layout/fragment_edit_gender.xml
new file mode 100644
index 0000000..81b254d
--- /dev/null
+++ b/feat/personal/src/main/res/layout/fragment_edit_gender.xml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feat/personal/src/main/res/layout/fragment_personal.xml b/feat/personal/src/main/res/layout/fragment_personal.xml
index 373dbf0..6a7c694 100644
--- a/feat/personal/src/main/res/layout/fragment_personal.xml
+++ b/feat/personal/src/main/res/layout/fragment_personal.xml
@@ -17,8 +17,8 @@
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="24dp">
+
+
+