diff --git a/Nabi/data/src/main/java/com/nabi/data/datasource/EmotionRemoteDataSource.kt b/Nabi/data/src/main/java/com/nabi/data/datasource/EmotionRemoteDataSource.kt index b2c0c82..118d616 100644 --- a/Nabi/data/src/main/java/com/nabi/data/datasource/EmotionRemoteDataSource.kt +++ b/Nabi/data/src/main/java/com/nabi/data/datasource/EmotionRemoteDataSource.kt @@ -1,10 +1,19 @@ package com.nabi.data.datasource import com.nabi.data.model.BaseResponse +import com.nabi.data.model.PageableResponse import com.nabi.data.model.emotion.DiaryStatisticsResponseDTO +import com.nabi.data.model.emotion.SearchEmotionResponseDTO interface EmotionRemoteDataSource { suspend fun getDiaryStatistics(accessToken: String, startDate: String, endDate: String): Result> + suspend fun searchDiaryByEmotion( + accessToken: String, + emotion: String, + page: Int, + size: Int, + sort: String + ): Result>> } \ No newline at end of file diff --git a/Nabi/data/src/main/java/com/nabi/data/datasourceImpl/EmotionRemoteDataSourceImpl.kt b/Nabi/data/src/main/java/com/nabi/data/datasourceImpl/EmotionRemoteDataSourceImpl.kt index a5c94b0..45abed1 100644 --- a/Nabi/data/src/main/java/com/nabi/data/datasourceImpl/EmotionRemoteDataSourceImpl.kt +++ b/Nabi/data/src/main/java/com/nabi/data/datasourceImpl/EmotionRemoteDataSourceImpl.kt @@ -2,7 +2,9 @@ package com.nabi.data.datasourceImpl import com.nabi.data.datasource.EmotionRemoteDataSource import com.nabi.data.model.BaseResponse +import com.nabi.data.model.PageableResponse import com.nabi.data.model.emotion.DiaryStatisticsResponseDTO +import com.nabi.data.model.emotion.SearchEmotionResponseDTO import com.nabi.data.service.EmotionService import javax.inject.Inject @@ -32,4 +34,27 @@ class EmotionRemoteDataSourceImpl @Inject constructor( } } + override suspend fun searchDiaryByEmotion( + accessToken: String, + emotion: String, + page: Int, + size: Int, + sort: String + ): Result>> { + return try { + val response = emotionService.searchDiaryByEmotion(accessToken, emotion, page, size, sort) + if (response.isSuccessful) { + val emotionResponse = response.body() + if (emotionResponse != null) { + Result.success(emotionResponse) + } else { + Result.failure(Exception("Search Emotion failed: response body is null")) + } + } else { + Result.failure(Exception("Search Emotion failed: ${response.message()}")) + } + } catch (e: Exception) { + Result.failure(e) + } + } } \ No newline at end of file diff --git a/Nabi/data/src/main/java/com/nabi/data/model/emotion/SearchEmotionResponseDTO.kt b/Nabi/data/src/main/java/com/nabi/data/model/emotion/SearchEmotionResponseDTO.kt new file mode 100644 index 0000000..8f839c4 --- /dev/null +++ b/Nabi/data/src/main/java/com/nabi/data/model/emotion/SearchEmotionResponseDTO.kt @@ -0,0 +1,9 @@ +package com.nabi.data.model.emotion + +import com.google.gson.annotations.SerializedName + +data class SearchEmotionResponseDTO( + @SerializedName("diaryEntryDate") val diaryEntryDate: String, + @SerializedName("content") val content: String, + @SerializedName("diaryId") val diaryId: Int +) \ No newline at end of file diff --git a/Nabi/data/src/main/java/com/nabi/data/repository/EmotionRepositoryImpl.kt b/Nabi/data/src/main/java/com/nabi/data/repository/EmotionRepositoryImpl.kt index 019e11f..94d85fa 100644 --- a/Nabi/data/src/main/java/com/nabi/data/repository/EmotionRepositoryImpl.kt +++ b/Nabi/data/src/main/java/com/nabi/data/repository/EmotionRepositoryImpl.kt @@ -1,6 +1,8 @@ package com.nabi.data.repository import com.nabi.data.datasource.EmotionRemoteDataSource +import com.nabi.domain.model.PageableInfo +import com.nabi.domain.model.diary.SearchDiary import com.nabi.domain.model.emotion.EmotionStatistics import com.nabi.domain.repository.EmotionRepository import javax.inject.Inject @@ -33,4 +35,49 @@ class EmotionRepositoryImpl @Inject constructor( Result.failure(result.exceptionOrNull() ?: Exception("Unknown error")) } } + + override suspend fun searchDiaryByEmotion( + accessToken: String, + emotion: String, + page: Int, + size: Int, + sort: String + ): Result>> { + val result = emotionRemoteDataSource.searchDiaryByEmotion(accessToken, emotion, page, size, sort) + + return if (result.isSuccess) { + val res = result.getOrNull() + if (res != null) { + val data = res.data + if (data != null) { + val pageableInfo = PageableInfo( + totalPages = data.totalPages, + totalElements = data.totalElements, + elementSize = data.size, + currentPageNumber = data.number, + isLastPage = data.last + ) + + if (data.size == 0) { + Result.success(Pair(pageableInfo, emptyList())) + } else { + val searchDiaryList = data.content.map { + SearchDiary( + it.content, + it.diaryEntryDate, + it.diaryId + ) + } + Result.success(Pair(pageableInfo, searchDiaryList)) + } + } else { + Result.failure(Exception("Search Emotion Data is null")) + } + } else { + Result.failure(Exception("Search Emotion Data Failed: response body is null")) + } + } else { + Result.failure(result.exceptionOrNull() ?: Exception("Unknown error")) + } + } } \ No newline at end of file diff --git a/Nabi/data/src/main/java/com/nabi/data/service/EmotionService.kt b/Nabi/data/src/main/java/com/nabi/data/service/EmotionService.kt index bd72c4b..ceebcdc 100644 --- a/Nabi/data/src/main/java/com/nabi/data/service/EmotionService.kt +++ b/Nabi/data/src/main/java/com/nabi/data/service/EmotionService.kt @@ -1,11 +1,14 @@ package com.nabi.data.service import com.nabi.data.model.BaseResponse +import com.nabi.data.model.PageableResponse import com.nabi.data.model.emotion.DiaryStatisticsResponseDTO +import com.nabi.data.model.emotion.SearchEmotionResponseDTO import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Header import retrofit2.http.Path +import retrofit2.http.Query interface EmotionService { @@ -16,4 +19,12 @@ interface EmotionService { @Path("endDate") endDate: String ): Response> + @GET("/emotion/search") + suspend fun searchDiaryByEmotion( + @Header("Authorization") accessToken: String, + @Query("emotion") emotion: String, + @Query("page") page: Int, + @Query("size") size: Int, + @Query("sort") sort: String + ): Response>> } \ No newline at end of file diff --git a/Nabi/domain/src/main/java/com/nabi/domain/repository/EmotionRepository.kt b/Nabi/domain/src/main/java/com/nabi/domain/repository/EmotionRepository.kt index 526ffe2..667bcb8 100644 --- a/Nabi/domain/src/main/java/com/nabi/domain/repository/EmotionRepository.kt +++ b/Nabi/domain/src/main/java/com/nabi/domain/repository/EmotionRepository.kt @@ -1,9 +1,19 @@ package com.nabi.domain.repository +import com.nabi.domain.model.PageableInfo +import com.nabi.domain.model.diary.SearchDiary import com.nabi.domain.model.emotion.EmotionStatistics interface EmotionRepository { suspend fun getDiaryStatistics(accessToken: String, startDate: String, endDate: String): Result + suspend fun searchDiaryByEmotion( + accessToken: String, + emotion: String, + page: Int, + size: Int, + sort: String + ): Result>> + } \ No newline at end of file diff --git a/Nabi/domain/src/main/java/com/nabi/domain/usecase/emotion/SearchEmotionUseCase.kt b/Nabi/domain/src/main/java/com/nabi/domain/usecase/emotion/SearchEmotionUseCase.kt new file mode 100644 index 0000000..75e0608 --- /dev/null +++ b/Nabi/domain/src/main/java/com/nabi/domain/usecase/emotion/SearchEmotionUseCase.kt @@ -0,0 +1,13 @@ +package com.nabi.domain.usecase.emotion + +import com.nabi.domain.model.PageableInfo +import com.nabi.domain.model.diary.DiaryInfo +import com.nabi.domain.model.diary.SearchDiary +import com.nabi.domain.model.emotion.EmotionStatistics +import com.nabi.domain.repository.EmotionRepository + +class SearchEmotionUseCase(private val repository: EmotionRepository) { + suspend operator fun invoke(accessToken: String, emotion: String, page: Int, size: Int, sort: String): Result>>{ + return repository.searchDiaryByEmotion("Bearer $accessToken", emotion, page, size, sort) + } +} \ No newline at end of file diff --git a/Nabi/presentation/src/main/java/com/nabi/nabi/di/EmotionUseCaseModule.kt b/Nabi/presentation/src/main/java/com/nabi/nabi/di/EmotionUseCaseModule.kt index a97352a..ad4be4c 100644 --- a/Nabi/presentation/src/main/java/com/nabi/nabi/di/EmotionUseCaseModule.kt +++ b/Nabi/presentation/src/main/java/com/nabi/nabi/di/EmotionUseCaseModule.kt @@ -2,6 +2,7 @@ package com.nabi.nabi.di import com.nabi.domain.repository.EmotionRepository import com.nabi.domain.usecase.emotion.GetEmotionStatisticsUseCase +import com.nabi.domain.usecase.emotion.SearchEmotionUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -14,9 +15,17 @@ object EmotionUseCaseModule { @Provides @Singleton - fun provideHomeUseCase( + fun provideGetEmotionStatisticsUseCase( repository: EmotionRepository ): GetEmotionStatisticsUseCase { return GetEmotionStatisticsUseCase(repository = repository) } + + @Provides + @Singleton + fun provideSearchEmotionUseCase( + repository: EmotionRepository + ): SearchEmotionUseCase { + return SearchEmotionUseCase(repository = repository) + } } \ No newline at end of file diff --git a/Nabi/presentation/src/main/java/com/nabi/nabi/extension/ContextExtension.kt b/Nabi/presentation/src/main/java/com/nabi/nabi/extension/ContextExtension.kt new file mode 100644 index 0000000..77a01cf --- /dev/null +++ b/Nabi/presentation/src/main/java/com/nabi/nabi/extension/ContextExtension.kt @@ -0,0 +1,34 @@ +package com.nabi.nabi.extension + +import android.app.Dialog +import android.content.Context +import android.graphics.Point +import android.os.Build +import android.view.WindowManager + +fun Context.dialogResize(dialog: Dialog, width: Float, height: Float){ + val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager + + if (Build.VERSION.SDK_INT < 30){ + val display = windowManager.defaultDisplay + val size = Point() + + display.getSize(size) + + val window = dialog.window + + val x = (size.x * width).toInt() + val y = (size.y * height).toInt() + + window?.setLayout(x, y) + + } else{ + val rect = windowManager.currentWindowMetrics.bounds + + val window = dialog.window + val x = (rect.width() * width).toInt() + val y = (rect.height() * height).toInt() + + window?.setLayout(x, y) + } +} \ No newline at end of file diff --git a/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/emotion/EmotionSearchAdapter.kt b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/emotion/EmotionSearchAdapter.kt new file mode 100644 index 0000000..0bfc998 --- /dev/null +++ b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/emotion/EmotionSearchAdapter.kt @@ -0,0 +1,56 @@ +package com.nabi.nabi.views.diary.emotion + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.nabi.domain.model.diary.SearchDiary +import com.nabi.nabi.databinding.ItemSearchDiaryBinding +import com.nabi.nabi.views.OnRvItemClickListener + +class EmotionSearchAdapter : ListAdapter(diaryDiffUtil) { + + companion object { + private val diaryDiffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: SearchDiary, newItem: SearchDiary): Boolean = + oldItem.diaryId == newItem.diaryId + + override fun areContentsTheSame(oldItem: SearchDiary, newItem: SearchDiary): Boolean = + oldItem == newItem + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchResultViewHolder { + val binding = ItemSearchDiaryBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SearchResultViewHolder(binding) + } + + override fun onBindViewHolder(holder: SearchResultViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + inner class SearchResultViewHolder(val binding: ItemSearchDiaryBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: SearchDiary) { + binding.tvSearchDiaryDate.text = item.diaryEntryDate + binding.tvContent.text = item.previewContent + + itemView.setOnClickListener { + rvItemClickListener.onClick(item.diaryId) + } + } + } + + private lateinit var rvItemClickListener: OnRvItemClickListener + + fun setRvItemClickListener(rvItemClickListener: OnRvItemClickListener){ + this.rvItemClickListener = rvItemClickListener + } + + override fun submitList(list: List?) { + list?.let { + val filteredList = it.distinctBy { diary -> diary.diaryId } + super.submitList(filteredList) + } ?: super.submitList(null) + } +} diff --git a/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/emotion/EmotionSearchFragment.kt b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/emotion/EmotionSearchFragment.kt new file mode 100644 index 0000000..83e7f04 --- /dev/null +++ b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/emotion/EmotionSearchFragment.kt @@ -0,0 +1,146 @@ +package com.nabi.nabi.views.diary.emotion + +import androidx.core.content.ContextCompat +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.nabi.nabi.R +import com.nabi.nabi.base.BaseFragment +import com.nabi.nabi.custom.CustomDecoration +import com.nabi.nabi.databinding.FragmentEmotionSearchBinding +import com.nabi.nabi.utils.UiState +import com.nabi.nabi.views.MainActivity +import com.nabi.nabi.views.OnRvItemClickListener +import com.nabi.nabi.views.diary.detail.DetailDiaryFragment +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class EmotionSearchFragment( + private val emotion: String +): BaseFragment(R.layout.fragment_emotion_search) { + private val viewModel: EmotionSearchViewModel by viewModels() + private lateinit var emotionSearchAdapter: EmotionSearchAdapter + private var isLoading = false + + override fun initView() { + setSearchDiaryAdapter() + setCurrentEmotion() + viewModel.fetchData(emotion) + } + + override fun onResume() { + super.onResume() + + emotionSearchAdapter.submitList(viewModel.diaryItems.value) + } + + private fun setSearchDiaryAdapter(){ + emotionSearchAdapter = EmotionSearchAdapter().apply { + setRvItemClickListener(object : OnRvItemClickListener { + override fun onClick(item: Int) { + (requireActivity() as MainActivity).replaceFragment(DetailDiaryFragment(item), true) + } + }) + } + binding.rvEmotionDiaryResult.layoutManager = LinearLayoutManager(requireContext()) + binding.rvEmotionDiaryResult.addItemDecoration(CustomDecoration(0.5f, ContextCompat.getColor(requireContext(), R.color.gray2))) + binding.rvEmotionDiaryResult.adapter = emotionSearchAdapter + } + + override fun initListener() { + super.initListener() + + binding.ibBack.setOnClickListener { + requireActivity().supportFragmentManager.popBackStack() + } + + binding.rvEmotionDiaryResult.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + val layoutManager = recyclerView.layoutManager as LinearLayoutManager + val lastVisibleItemPos = layoutManager.findLastVisibleItemPosition() + val itemTotalCount = recyclerView.adapter?.itemCount ?: 0 + + if (lastVisibleItemPos >= itemTotalCount - 6) { + if(!isLoading){ + isLoading = true + viewModel.fetchData(emotion) + } + } + } + }) + + // 행복, 우울, 화남, 불안, 지루 + binding.ivEmotionAnger.setOnClickListener { + viewModel.fetchData("화남", true) + setCurrentEmotion() + } + binding.ivEmotionHappiness.setOnClickListener { + viewModel.fetchData("행복", true) + setCurrentEmotion() + } + binding.ivEmotionBoredom.setOnClickListener { + viewModel.fetchData("지루", true) + setCurrentEmotion() + } + binding.ivEmotionSadness.setOnClickListener { + viewModel.fetchData("우울", true) + setCurrentEmotion() + } + binding.ivEmotionAnxiety.setOnClickListener { + viewModel.fetchData("불안", true) + setCurrentEmotion() + } + } + + private fun setCurrentEmotion(){ + val resourceIds = mutableListOf() + val condition = if(viewModel.searchEmotion.value.isNullOrEmpty()) emotion else viewModel.searchEmotion.value + + when(condition){ + "화남" -> resourceIds.addAll(listOf(R.drawable.img_anger, R.drawable.img_happiness_gray, R.drawable.img_boredom_gray, R.drawable.img_sadness_gray, R.drawable.img_anxiety_gray)) + "행복" -> resourceIds.addAll(listOf(R.drawable.img_anger_gray, R.drawable.img_happiness, R.drawable.img_boredom_gray, R.drawable.img_sadness_gray, R.drawable.img_anxiety_gray)) + "지루" -> resourceIds.addAll(listOf(R.drawable.img_anger_gray, R.drawable.img_happiness_gray, R.drawable.img_boredom, R.drawable.img_sadness_gray, R.drawable.img_anxiety_gray)) + "우울" -> resourceIds.addAll(listOf(R.drawable.img_anger_gray, R.drawable.img_happiness_gray, R.drawable.img_boredom_gray, R.drawable.img_sadness, R.drawable.img_anxiety_gray)) + "불안" -> resourceIds.addAll(listOf(R.drawable.img_anger_gray, R.drawable.img_happiness_gray, R.drawable.img_boredom_gray, R.drawable.img_sadness_gray, R.drawable.img_anxiety)) + } + + resourceIds.run { + binding.ivEmotionAnger.setImageResource(this[0]) + binding.ivEmotionHappiness.setImageResource(this[1]) + binding.ivEmotionBoredom.setImageResource(this[2]) + binding.ivEmotionSadness.setImageResource(this[3]) + binding.ivEmotionAnxiety.setImageResource(this[4]) + } + } + + override fun setObserver() { + super.setObserver() + + viewModel.searchEmotion.observe(viewLifecycleOwner){ + if(it.isNotEmpty()){ + emotionSearchAdapter.submitList(emptyList()) + } + } + + viewModel.uiState.observe(viewLifecycleOwner){ + when(it){ + is UiState.Loading -> {} + is UiState.Failure -> { + showToast("검색 실패: ${it.message}") + } + is UiState.Success -> { + isLoading = false + + if(it.data.isEmpty()) emotionSearchAdapter.submitList(emptyList()) + else { + val temp = emotionSearchAdapter.currentList.toMutableList() + temp.addAll(it.data) + emotionSearchAdapter.submitList(temp) + } + } + } + } + } +} \ No newline at end of file diff --git a/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/emotion/EmotionSearchViewModel.kt b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/emotion/EmotionSearchViewModel.kt new file mode 100644 index 0000000..80f116b --- /dev/null +++ b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/emotion/EmotionSearchViewModel.kt @@ -0,0 +1,67 @@ +package com.nabi.nabi.views.diary.emotion + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.nabi.domain.model.diary.SearchDiary +import com.nabi.domain.model.emotion.EmotionStatistics +import com.nabi.domain.repository.DataStoreRepository +import com.nabi.domain.usecase.emotion.GetEmotionStatisticsUseCase +import com.nabi.domain.usecase.emotion.SearchEmotionUseCase +import com.nabi.nabi.utils.LoggerUtils +import com.nabi.nabi.utils.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class EmotionSearchViewModel @Inject constructor( + private val searchEmotionUseCase: SearchEmotionUseCase, + private val dataStoreRepository: DataStoreRepository +) : ViewModel() { + + private var page = 0 + private val size = 15 + private val sort = "" + private var isFinish = false + + private var _searchEmotion = MutableLiveData("") + val searchEmotion: LiveData get() = _searchEmotion + + private val _uiState = MutableLiveData>>(UiState.Loading) + val uiState: LiveData>> get() = _uiState + + private val _diaryItems = MutableLiveData>(mutableListOf()) + val diaryItems: LiveData> get() = _diaryItems + + fun fetchData(emotion: String, isNewEmotion: Boolean = false) { + if(searchEmotion.value != emotion) resetPageable(emotion) + if(!isNewEmotion && isFinish) return + + _uiState.value = UiState.Loading + _searchEmotion.value = emotion + + viewModelScope.launch { + val accessToken = dataStoreRepository.getAccessToken().getOrNull().orEmpty() + + searchEmotionUseCase(accessToken, emotion, page, size, sort) + .onSuccess { + isFinish = it.first.isLastPage + page++ + + _diaryItems.value?.addAll(it.second) + _uiState.value = UiState.Success(it.second) + }.onFailure { e -> + _uiState.value = UiState.Failure(message = e.message.toString()) + } + } + } + + private fun resetPageable(content: String){ + _diaryItems.value?.clear() + _searchEmotion.value = content + page = 0 + isFinish = false + } +} \ No newline at end of file diff --git a/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/statistics/DiaryStatisticsFragment.kt b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/statistics/DiaryStatisticsFragment.kt index 629ee2d..015b005 100644 --- a/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/statistics/DiaryStatisticsFragment.kt +++ b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/statistics/DiaryStatisticsFragment.kt @@ -1,12 +1,15 @@ package com.nabi.nabi.views.diary.statistics import android.app.DatePickerDialog +import android.os.Bundle import androidx.fragment.app.viewModels import com.nabi.domain.model.emotion.EmotionStatistics import com.nabi.nabi.R import com.nabi.nabi.base.BaseFragment import com.nabi.nabi.databinding.FragmentStatisticsDiaryBinding import com.nabi.nabi.utils.UiState +import com.nabi.nabi.views.MainActivity +import com.nabi.nabi.views.diary.emotion.EmotionSearchFragment import dagger.hilt.android.AndroidEntryPoint import java.text.SimpleDateFormat import java.util.Calendar @@ -21,17 +24,40 @@ class DiaryStatisticsFragment: BaseFragment(R.la private var startDate: Calendar = Calendar.getInstance() private var endDate: Calendar = Calendar.getInstance() - override fun initView() { - endDate.time = calendar.time - calendar.add(Calendar.MONTH, -1) - startDate.time = calendar.time + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (savedInstanceState == null) { + endDate.time = calendar.time + calendar.add(Calendar.MONTH, -1) + startDate.time = calendar.time + } + } + override fun initView() { binding.tvEndDate.text = dateFormat.format(endDate.time) binding.tvStartDate.text = dateFormat.format(startDate.time) fetchData() } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putLong("startDate", startDate.timeInMillis) + outState.putLong("endDate", endDate.timeInMillis) + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + savedInstanceState?.let { + startDate.timeInMillis = it.getLong("startDate") + endDate.timeInMillis = it.getLong("endDate") + binding.tvStartDate.text = dateFormat.format(startDate.time) + binding.tvEndDate.text = dateFormat.format(endDate.time) + fetchData() + } + } + override fun initListener() { super.initListener() @@ -40,7 +66,13 @@ class DiaryStatisticsFragment: BaseFragment(R.la } binding.tvStartDate.setOnClickListener { showDatePickerDialog(true) } - binding.tvEndDate.setOnClickListener { showDatePickerDialog( false) } + binding.tvEndDate.setOnClickListener { showDatePickerDialog(false) } + + binding.ivEmotionAnger.setOnClickListener { (requireActivity() as MainActivity).replaceFragment(EmotionSearchFragment("화남"), true)} + binding.ivEmotionHappiness.setOnClickListener { (requireActivity() as MainActivity).replaceFragment(EmotionSearchFragment("행복"), true)} + binding.ivEmotionBoredom.setOnClickListener { (requireActivity() as MainActivity).replaceFragment(EmotionSearchFragment("지루"), true)} + binding.ivEmotionSadness.setOnClickListener { (requireActivity() as MainActivity).replaceFragment(EmotionSearchFragment("우울"), true)} + binding.ivEmotionAnxiety.setOnClickListener { (requireActivity() as MainActivity).replaceFragment(EmotionSearchFragment("불안"), true)} } private fun showDatePickerDialog(isStartDate: Boolean) { @@ -99,7 +131,6 @@ class DiaryStatisticsFragment: BaseFragment(R.la datePickerDialog.show() } - private fun fetchData() { viewmodel.fetchData( dateFormat.format(startDate.time).replace(".", "-"), diff --git a/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/view/SelectDiaryFragment.kt b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/view/SelectDiaryFragment.kt index d146b14..af57179 100644 --- a/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/view/SelectDiaryFragment.kt +++ b/Nabi/presentation/src/main/java/com/nabi/nabi/views/diary/view/SelectDiaryFragment.kt @@ -1,10 +1,14 @@ package com.nabi.nabi.views.diary.view +import android.app.AlertDialog +import android.view.LayoutInflater import androidx.core.content.res.ResourcesCompat import androidx.viewpager2.widget.ViewPager2 import com.nabi.nabi.R import com.nabi.nabi.base.BaseFragment +import com.nabi.nabi.databinding.DialogNonDayDatePickerBinding import com.nabi.nabi.databinding.FragmentSelectDiaryBinding +import com.nabi.nabi.extension.dialogResize import com.nabi.nabi.views.MainActivity import com.nabi.nabi.views.diary.search.SearchDiaryFragment import com.nabi.nabi.views.diary.statistics.DiaryStatisticsFragment @@ -21,9 +25,13 @@ import java.util.Date import java.util.Locale @AndroidEntryPoint -class SelectDiaryFragment: BaseFragment(R.layout.fragment_select_diary) { +class SelectDiaryFragment : BaseFragment(R.layout.fragment_select_diary) { private lateinit var calendarAdapter: SelectDiaryMonthCalendarStateAdapter + // Define min and max year + private val minYear = 1950 + private val maxYear = Calendar.getInstance().get(Calendar.YEAR) + override fun initView() { calendarAdapter = SelectDiaryMonthCalendarStateAdapter(requireActivity()) binding.vpCalendarMonth.adapter = calendarAdapter @@ -71,6 +79,10 @@ class SelectDiaryFragment: BaseFragment(R.layout.fra binding.ivEmotionBoredom.setOnClickListener { createEmotionTooltip("지루해").showAlignTop(binding.ivEmotionBoredom) } binding.ivEmotionSadness.setOnClickListener { createEmotionTooltip("슬퍼").showAlignTop(binding.ivEmotionSadness) } binding.ivEmotionAnxiety.setOnClickListener { createEmotionTooltip("불안해").showAlignTop(binding.ivEmotionAnxiety) } + + binding.tvCurrentMonth.setOnClickListener { + showNumberPickerDialog() + } } private fun updateCurrentMonthText(position: Int) { @@ -81,8 +93,8 @@ class SelectDiaryFragment: BaseFragment(R.layout.fra binding.tvCurrentMonth.text = currentMonth } - private fun createEmotionTooltip(text: String): Balloon{ - val balloon = createBalloon(context = requireContext()){ + private fun createEmotionTooltip(text: String): Balloon { + return createBalloon(context = requireContext()) { setHeight(42) setWidth(BalloonSizeSpec.WRAP) @@ -106,9 +118,57 @@ class SelectDiaryFragment: BaseFragment(R.layout.fra setLifecycleOwner(viewLifecycleOwner) build() } + } + + private fun showNumberPickerDialog() { + val currentCalendar = Calendar.getInstance() + val displayedMonthYear = binding.tvCurrentMonth.text.toString() + val dateFormat = SimpleDateFormat("MMMM yyyy", Locale.ENGLISH) + val displayedDate = dateFormat.parse(displayedMonthYear) + currentCalendar.time = displayedDate!! + + val year = currentCalendar.get(Calendar.YEAR) + val month = currentCalendar.get(Calendar.MONTH) + + val dialogBinding = DialogNonDayDatePickerBinding.inflate(LayoutInflater.from(requireContext())) + + dialogBinding.npYear.minValue = minYear + dialogBinding.npYear.maxValue = maxYear + dialogBinding.npYear.value = year + + dialogBinding.npMonth.minValue = 1 + dialogBinding.npMonth.maxValue = 12 + dialogBinding.npMonth.value = month + 1 + + val builder = AlertDialog.Builder(requireContext(), R.style.DialogTheme) + .setView(dialogBinding.root) + .setPositiveButton("확인") { _, _ -> + val selectedYear = dialogBinding.npYear.value + val selectedMonth = dialogBinding.npMonth.value - 1 + + val totalDisplayedMonths = (year * 12 + month) + val totalSelectedMonths = (selectedYear * 12 + selectedMonth) + val differenceInMonths = totalSelectedMonths - totalDisplayedMonths + + val currentPosition = binding.vpCalendarMonth.currentItem + val newPosition = currentPosition + differenceInMonths + binding.vpCalendarMonth.setCurrentItem(newPosition, false) + + currentCalendar.set(selectedYear, selectedMonth, 1) + binding.tvCurrentMonth.text = SimpleDateFormat("MMMM yyyy", Locale.ENGLISH).format(currentCalendar.time) + } + .setNegativeButton("취소") { _, _ -> } + .create() + + builder.show() + + val positiveButton = builder.getButton(AlertDialog.BUTTON_POSITIVE) + val negativeButton = builder.getButton(AlertDialog.BUTTON_NEGATIVE) + positiveButton?.setTextAppearance(requireContext(), R.style.DialogButtonStyle) + negativeButton?.setTextAppearance(requireContext(), R.style.DialogButtonStyle) - return balloon + builder.context.dialogResize(builder, 0.8f, 0.3f) } -} \ No newline at end of file +} diff --git a/Nabi/presentation/src/main/res/drawable/img_anger_gray.png b/Nabi/presentation/src/main/res/drawable/img_anger_gray.png new file mode 100644 index 0000000..6efb77f Binary files /dev/null and b/Nabi/presentation/src/main/res/drawable/img_anger_gray.png differ diff --git a/Nabi/presentation/src/main/res/drawable/img_anxiety_gray.png b/Nabi/presentation/src/main/res/drawable/img_anxiety_gray.png new file mode 100644 index 0000000..687f773 Binary files /dev/null and b/Nabi/presentation/src/main/res/drawable/img_anxiety_gray.png differ diff --git a/Nabi/presentation/src/main/res/drawable/img_boredom_gray.png b/Nabi/presentation/src/main/res/drawable/img_boredom_gray.png new file mode 100644 index 0000000..56f310b Binary files /dev/null and b/Nabi/presentation/src/main/res/drawable/img_boredom_gray.png differ diff --git a/Nabi/presentation/src/main/res/drawable/img_happiness_gray.png b/Nabi/presentation/src/main/res/drawable/img_happiness_gray.png new file mode 100644 index 0000000..0859799 Binary files /dev/null and b/Nabi/presentation/src/main/res/drawable/img_happiness_gray.png differ diff --git a/Nabi/presentation/src/main/res/drawable/img_sadness_gray.png b/Nabi/presentation/src/main/res/drawable/img_sadness_gray.png new file mode 100644 index 0000000..290f0c4 Binary files /dev/null and b/Nabi/presentation/src/main/res/drawable/img_sadness_gray.png differ diff --git a/Nabi/presentation/src/main/res/drawable/shape_non_day_date_picker_bg.xml b/Nabi/presentation/src/main/res/drawable/shape_non_day_date_picker_bg.xml new file mode 100644 index 0000000..24989e2 --- /dev/null +++ b/Nabi/presentation/src/main/res/drawable/shape_non_day_date_picker_bg.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/Nabi/presentation/src/main/res/layout/dialog_non_day_date_picker.xml b/Nabi/presentation/src/main/res/layout/dialog_non_day_date_picker.xml new file mode 100644 index 0000000..4c39475 --- /dev/null +++ b/Nabi/presentation/src/main/res/layout/dialog_non_day_date_picker.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/Nabi/presentation/src/main/res/layout/fragment_emotion_search.xml b/Nabi/presentation/src/main/res/layout/fragment_emotion_search.xml new file mode 100644 index 0000000..1545eca --- /dev/null +++ b/Nabi/presentation/src/main/res/layout/fragment_emotion_search.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Nabi/presentation/src/main/res/values/themes.xml b/Nabi/presentation/src/main/res/values/themes.xml index 3b5ad1e..8e4d9bf 100644 --- a/Nabi/presentation/src/main/res/values/themes.xml +++ b/Nabi/presentation/src/main/res/values/themes.xml @@ -17,4 +17,25 @@ + + + + + + + + + \ No newline at end of file