diff --git a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt index 33c16b8a..4618ed95 100644 --- a/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt +++ b/app/src/main/java/com/egobook/app/data/api/DiaryApiService.kt @@ -2,11 +2,14 @@ package com.egobook.app.data.api import com.egobook.app.data.model.ApiResponse import com.egobook.app.data.model.diary.request.DiaryCreateRequest +import com.egobook.app.data.model.diary.request.DiaryExportRequest import com.egobook.app.data.model.diary.request.DiaryUpdateRequest import com.egobook.app.data.model.diary.response.DiariesResponse import com.egobook.app.data.model.diary.response.DiaryCreateResponse import com.egobook.app.data.model.diary.response.DiaryDeleteResponse import com.egobook.app.data.model.diary.response.DiaryEntryResponse +import com.egobook.app.data.model.diary.response.DiaryExportResponse +import com.google.android.gms.common.api.Api import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -51,4 +54,10 @@ interface DiaryApiService { @Body request: DiaryUpdateRequest ): ApiResponse + //일기 내보내기 + @POST("/diaries/export") + suspend fun exportDiary( + @Body request: DiaryExportRequest + ): ApiResponse + } \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryExportRequest.kt b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryExportRequest.kt index 16fd737f..4989d725 100644 --- a/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryExportRequest.kt +++ b/app/src/main/java/com/egobook/app/data/model/diary/request/DiaryExportRequest.kt @@ -7,5 +7,9 @@ import kotlinx.serialization.Serializable data class DiaryExportRequest ( @SerialName("format") val format: String, + @SerialName("startDate") + val startDate: String, + @SerialName("endDate") + val endDate: String, ) \ No newline at end of file diff --git a/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryExportResponse.kt b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryExportResponse.kt new file mode 100644 index 00000000..3f52f18c --- /dev/null +++ b/app/src/main/java/com/egobook/app/data/model/diary/response/DiaryExportResponse.kt @@ -0,0 +1,10 @@ +package com.egobook.app.data.model.diary.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class DiaryExportResponse( + @SerialName("fileUrl") + val fileUrl: String, +) diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/CalenderFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/CalenderFragment.kt index 4bd3ba9c..a13bfe15 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/CalenderFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/CalenderFragment.kt @@ -11,6 +11,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.children +import androidx.core.view.isInvisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle @@ -35,6 +36,7 @@ import java.time.YearMonth import java.time.format.TextStyle import java.util.Locale import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import timber.log.Timber @@ -63,10 +65,22 @@ class CalenderFragment : Fragment() { v.setPadding(v.paddingLeft, v.paddingTop, v.paddingRight, systemBars.bottom) insets } - + + // 1. 먼저 초기 년월 결정 (인자 있으면 그걸로, 없으면 현재 달) + val initialMonth = getInitialMonthFromArgs() + + // 2. 캘린더를 처음에는 숨김 (깜빡임 방지) + binding.calendarView.visibility = View.INVISIBLE + binding.calendarHeaderLayout.visibility = View.INVISIBLE + setupDayOfWeekTitles() - setupCalendar() - observeViewModel() + setupCalendar(initialMonth) // 초기 년월 전달 + + // 3. ViewModel을 초기 년월로 설정 (데이터 로드) + viewModel.setYearMonth(initialMonth) + + // 4. observing 시작 + observeViewModel(initialMonth) // 초기 년월 전달해서 첫 emission 검증 setupMonthDialogResultListener() binding.apply { @@ -102,7 +116,22 @@ class CalenderFragment : Fragment() { } } } - + + /** + * 초기 년월을 인자에서 추출 (없으면 현재 달 반환) + */ + private fun getInitialMonthFromArgs(): YearMonth { + val args = arguments + val year = args?.getInt("year", -1) ?: -1 + val month = args?.getInt("month", -1) ?: -1 + + return if (year != -1 && month != -1) { + YearMonth.of(year, month) + } else { + YearMonth.now() + } + } + /** * MonthDialogFragment에서 월 선택 결과 수신 */ @@ -121,10 +150,17 @@ class CalenderFragment : Fragment() { /** * ViewModel 상태 관찰 - 스와이프 없이 해당 월 즉시 표시 */ - private fun observeViewModel() { + private fun observeViewModel(expectedInitialMonth: YearMonth) { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { + var isCorrectMonthReceived = false viewModel.state.collectLatest { state -> + // 예상한 초기 달이 나올 때까지 대기 (깜빡임 방지) + if (!isCorrectMonthReceived && state.selectedYearMonth != expectedInitialMonth) { + return@collectLatest // 잘못된 달은 무시 + } + isCorrectMonthReceived = true + // 애니메이션 없이 해당 월 즉시 이동 binding.calendarView.scrollToMonth(state.selectedYearMonth) // 상단 텍스트 업데이트 @@ -132,6 +168,12 @@ class CalenderFragment : Fragment() { binding.tvMonth.text = "${state.selectedYearMonth.monthValue}월" // 중요: 감정 데이터 변경 시 해당 월만 캘린더 뷰 갱신 binding.calendarView.notifyMonthChanged(state.selectedYearMonth) + + // 첫 번째 올바른 상태 업데이트 후 캘린더 표시 + if (binding.calendarView.isInvisible) { + binding.calendarView.visibility = View.VISIBLE + binding.calendarHeaderLayout.visibility = View.VISIBLE + } } } } @@ -178,33 +220,30 @@ class CalenderFragment : Fragment() { /** * 달력 설정 및 초기화 */ - private fun setupCalendar() { - val currentMonth = YearMonth.now() - - initializeCalendarRange(currentMonth) - initializeYearMonthText(currentMonth) + private fun setupCalendar(initialMonth: YearMonth) { + initializeCalendarRange(initialMonth) + initializeYearMonthText(initialMonth) setupDayBinder() - // 초기 로드는 ViewModel의 init 블록에서 처리 } /** * 달력 범위 설정 (과거 100개월 ~ 미래 100개월) */ - private fun initializeCalendarRange(currentMonth: YearMonth) { - val startMonth = currentMonth.minusMonths(100) - val endMonth = currentMonth.plusMonths(100) + private fun initializeCalendarRange(initialMonth: YearMonth) { + val startMonth = initialMonth.minusMonths(100) + val endMonth = initialMonth.plusMonths(100) val firstDayOfWeek = DayOfWeek.MONDAY binding.calendarView.setup(startMonth, endMonth, firstDayOfWeek) - binding.calendarView.scrollToMonth(currentMonth) + binding.calendarView.scrollToMonth(initialMonth) } /** * 초기 연/월 텍스트 설정 */ - private fun initializeYearMonthText(currentMonth: YearMonth) { - binding.tvYear.text = currentMonth.year.toString() - binding.tvMonth.text = "${currentMonth.monthValue}월" + private fun initializeYearMonthText(initialMonth: YearMonth) { + binding.tvYear.text = initialMonth.year.toString() + binding.tvMonth.text = "${initialMonth.monthValue}월" } /** diff --git a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt index 6d2d114e..59782a64 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/view/DiaryFragment.kt @@ -113,8 +113,17 @@ import kotlin.getValue } } btnCalender.setOnClickListener { - findNavController().navigate(R.id.action_diaryFragment_to_calenderFragment) + val date = viewModel.state.value.selectedDate + + val action = DiaryFragmentDirections + .actionDiaryFragmentToCalenderFragment( + year = date.year, + month = date.monthValue + ) + + findNavController().navigate(action) } + btnExport.setOnClickListener { applyScreenBlur(BlurLevel.BASE) val dialog = DiaryExportDialogFragment() diff --git a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/CalenderViewModel.kt b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/CalenderViewModel.kt index b8592ef2..6cb594bd 100644 --- a/app/src/main/java/com/egobook/app/ui/diary/viewmodel/CalenderViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/diary/viewmodel/CalenderViewModel.kt @@ -34,10 +34,17 @@ class CalenderViewModel @Inject constructor( get() = _state.value.selectedYearMonth /** - * 초기 로드 + * 초기 로드 - 외부에서 명시적으로 호출 필요 */ init { Log.d("ViewModel1", "=== ViewModel INIT === selectedYearMonth=${_state.value.selectedYearMonth}") + // init에서 자동 로드하지 않음 - Fragment에서 초기 년월 설정 후 명시적 호출 + } + + /** + * 초기 캘린더 데이터 로드 (Fragment에서 명시적 호출) + */ + fun initializeCalendar() { loadCalender(_state.value.selectedYearMonth) } diff --git a/app/src/main/res/layout/fragment_calender.xml b/app/src/main/res/layout/fragment_calender.xml index 89abf74c..bfe21deb 100644 --- a/app/src/main/res/layout/fragment_calender.xml +++ b/app/src/main/res/layout/fragment_calender.xml @@ -49,6 +49,7 @@ android:layout_height="wrap_content" android:orientation="vertical" android:gravity="center" + android:visibility="invisible" app:layout_constraintTop_toBottomOf="@id/top_btn_layout" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"> @@ -58,7 +59,8 @@ android:id="@+id/tv_year" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="2025" + android:text="" + tools:text="2025" android:textColor="@color/cos_black" android:fontFamily="@font/arita_medium" android:textSize="14sp" /> @@ -86,7 +88,8 @@ android:id="@+id/tv_month" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="12월" + android:text="" + tools:text="12월" android:textSize="24sp" android:layout_marginHorizontal="24dp" android:fontFamily="@font/arita_semibold" /> diff --git a/app/src/main/res/navigation/bottom_navigation.xml b/app/src/main/res/navigation/bottom_navigation.xml index be937d7c..731dc5b5 100644 --- a/app/src/main/res/navigation/bottom_navigation.xml +++ b/app/src/main/res/navigation/bottom_navigation.xml @@ -113,6 +113,16 @@ android:id="@+id/calenderFragment" android:name="com.egobook.app.ui.diary.view.CalenderFragment" android:label="달력 화면" > + + + +