diff --git a/android/2023-emmsale/app/src/main/AndroidManifest.xml b/android/2023-emmsale/app/src/main/AndroidManifest.xml index ce1c2c0e9..8b4736320 100644 --- a/android/2023-emmsale/app/src/main/AndroidManifest.xml +++ b/android/2023-emmsale/app/src/main/AndroidManifest.xml @@ -33,7 +33,7 @@ android:name=".presentation.ui.onboarding.OnboardingActivity" android:exported="true" /> , - val imageUrl: String?, + val postImageUrl: String?, val remainingDays: Int, val type: String, ) diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailRepository.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailRepository.kt index ecef11dd1..90cece3ec 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailRepository.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailRepository.kt @@ -3,5 +3,5 @@ package com.emmsale.data.eventdetail import com.emmsale.data.common.ApiResult interface EventDetailRepository { - suspend fun fetchEventDetail(eventId: Long): ApiResult + suspend fun getEventDetail(eventId: Long): ApiResult } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailRepositoryImpl.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailRepositoryImpl.kt index bda92aa37..ecf0202a9 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailRepositoryImpl.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailRepositoryImpl.kt @@ -3,14 +3,15 @@ package com.emmsale.data.eventdetail import com.emmsale.data.common.ApiResult import com.emmsale.data.common.handleApi import com.emmsale.data.eventdetail.dto.EventDetailApiModel +import com.emmsale.data.eventdetail.mapper.toData class EventDetailRepositoryImpl( private val eventDetailService: EventDetailService, ) : EventDetailRepository { - override suspend fun fetchEventDetail(eventId: Long): ApiResult { + override suspend fun getEventDetail(eventId: Long): ApiResult { return handleApi( - execute = { eventDetailService.fetchEventDetail(eventId) }, + execute = { eventDetailService.getEventDetail(eventId) }, mapToDomain = EventDetailApiModel::toData, ) } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailService.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailService.kt index 4bbb2cc74..8cc535c06 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailService.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/EventDetailService.kt @@ -7,7 +7,7 @@ import retrofit2.http.Path interface EventDetailService { @GET("events/{eventId}") - suspend fun fetchEventDetail( + suspend fun getEventDetail( @Path("eventId") eventId: Long, ): Response } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/dto/EventDetailApiModel.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/dto/EventDetailApiModel.kt index 7f4445883..9e05a2909 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/dto/EventDetailApiModel.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/dto/EventDetailApiModel.kt @@ -1,6 +1,5 @@ package com.emmsale.data.eventdetail.dto -import com.emmsale.data.eventdetail.EventDetail import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -10,36 +9,30 @@ data class EventDetailApiModel( val id: Long, @SerialName("name") val name: String, - @SerialName("status") - val status: String, - @SerialName("location") - val location: String, + @SerialName("informationUrl") + val informationUrl: String, @SerialName("startDate") val startDate: String, @SerialName("endDate") val endDate: String, - @SerialName("informationUrl") - val informationUrl: String, + @SerialName("applyStartDate") + val applyStartDate: String, + @SerialName("applyEndDate") + val applyEndDate: String, + @SerialName("location") + val location: String, + @SerialName("status") + val status: String, + @SerialName("applyStatus") + val applyStatus: String, @SerialName("tags") val tags: List, @SerialName("imageUrl") val imageUrl: String?, @SerialName("remainingDays") val remainingDays: Int, + @SerialName("applyRemainingDays") + val applyRemainingDays: Int, @SerialName("type") val type: String, -) { - fun toData(): EventDetail = EventDetail( - id = id, - name = name, - status = status, - location = location, - startDate = startDate, - endDate = endDate, - informationUrl = informationUrl, - tags = tags, - imageUrl = imageUrl, - remainingDays = remainingDays, - type = type, - ) -} +) diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/mapper/EventDetailMapper.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/mapper/EventDetailMapper.kt new file mode 100644 index 000000000..515a54a1b --- /dev/null +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/data/eventdetail/mapper/EventDetailMapper.kt @@ -0,0 +1,25 @@ +package com.emmsale.data.eventdetail.mapper + +import com.emmsale.data.eventdetail.EventDetail +import com.emmsale.data.eventdetail.dto.EventDetailApiModel +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +fun EventDetailApiModel.toData(): EventDetail = EventDetail( + id = id, + name = name, + status = status, + location = location, + startDate = startDate.toLocalDateTime(), + endDate = endDate.toLocalDateTime(), + informationUrl = informationUrl, + tags = tags, + postImageUrl = imageUrl, + remainingDays = remainingDays, + type = type, +) + +private fun String.toLocalDateTime(): LocalDateTime { + val format = DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss") + return LocalDateTime.parse(this, format) +} diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/EventDetailActivity.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/EventDetailActivity.kt index e1f1d7652..1555b1299 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/EventDetailActivity.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/EventDetailActivity.kt @@ -1,51 +1,45 @@ -package com.emmsale.presentation.eventdetail +package com.emmsale.presentation.ui.eventdetail import android.content.Context import android.content.Intent import android.os.Bundle import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import com.emmsale.R import com.emmsale.databinding.ActivityEventDetailBinding import com.emmsale.presentation.common.extension.showToast -import com.emmsale.presentation.ui.eventdetail.EventDetailFragmentStateAdpater -import com.emmsale.presentation.ui.eventdetail.EventDetailViewModel -import com.emmsale.presentation.ui.eventdetail.EventTag -import com.emmsale.presentation.ui.eventdetail.uistate.EventDetailUiState import com.google.android.material.tabs.TabLayoutMediator class EventDetailActivity : AppCompatActivity() { private lateinit var binding: ActivityEventDetailBinding - private val viewModel: EventDetailViewModel by viewModels { EventDetailViewModel.factory } private val eventId: Long by lazy { intent.getLongExtra(EVENT_ID_KEY, DEFAULT_EVENT_ID) } - + private val viewModel: EventDetailViewModel by viewModels { EventDetailViewModel.factory(eventId) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setUpBinding() setUpEventDetail() - setBackPress() - viewModel.fetchEventDetail(eventId) + initBackPressButtonClickListener() } private fun setUpBinding() { binding = ActivityEventDetailBinding.inflate(layoutInflater) setContentView(binding.root) + binding.lifecycleOwner = this + binding.vm = viewModel } private fun setUpEventDetail() { viewModel.eventDetail.observe(this) { eventDetailUiState -> - when (eventDetailUiState) { - is EventDetailUiState.Success -> { - binding.eventDetail = eventDetailUiState - addTag(eventDetailUiState.tags) - initFragmentStateAdapter( - eventDetailUiState.informationUrl, - eventDetailUiState.imageUrl, - ) - } - - else -> showToast("행사 받아오기 실패") + if (eventDetailUiState.isError) { + showToast(getString(R.string.eventdetail_fetch_eventdetail_error_message)) + } else { + addEventTag(eventDetailUiState.tags) + initFragmentStateAdapter( + eventDetailUiState.informationUrl, + eventDetailUiState.imageUrl, + ) } } } @@ -53,25 +47,26 @@ class EventDetailActivity : AppCompatActivity() { private fun initFragmentStateAdapter(informationUrl: String, imageUrl: String?) { binding.vpEventdetail.adapter = EventDetailFragmentStateAdpater(this, eventId, informationUrl, imageUrl) - TabLayoutMediator(binding.tablayoutEventdetail, binding.vpEventdetail) { tab, position -> - when (position) { - INFORMATION_TAB_POSITION -> tab.text = "상세 정보" - COMMENT_TAB_POSITION -> tab.text = "댓글" - RECRUITMENT_TAB_POSITION -> tab.text = "같이가요" - } + val tabNames = listOf( + getString(R.string.eventdetail_tab_infromation), + getString(R.string.eventdetail_tab_comment), + getString(R.string.eventdetail_tab_recruitment), + ) + TabLayoutMediator(binding.tablayoutEventdetail, binding.vpEventdetail) { _, _ -> + tabNames }.attach() binding.vpEventdetail.isUserInputEnabled = false } - private fun addTag(tags: List) { - tags.forEach { binding.chipgroupEvendetailTags.addView(createTag(it)) } + private fun addEventTag(tags: List) { + tags.forEach { binding.chipgroupEvendetailTags.addView(createEventTag(it)) } } - private fun createTag(tag: String) = EventTag(this).apply { + private fun createEventTag(tag: String) = EventTag(this).apply { text = tag } - private fun setBackPress() { + private fun initBackPressButtonClickListener() { binding.ivEventdetailBackpress.setOnClickListener { finish() } @@ -80,10 +75,6 @@ class EventDetailActivity : AppCompatActivity() { companion object { private const val EVENT_ID_KEY = "EVENT_ID_KEY" private const val DEFAULT_EVENT_ID = 1L - private const val INFORMATION_TAB_POSITION = 0 - private const val COMMENT_TAB_POSITION = 1 - private const val RECRUITMENT_TAB_POSITION = 2 - fun startActivity(context: Context, eventId: Long) { val intent = Intent(context, EventDetailActivity::class.java) intent.putExtra(EVENT_ID_KEY, eventId) diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/EventDetailViewModel.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/EventDetailViewModel.kt index 88bbb14d7..22cacdd47 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/EventDetailViewModel.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/EventDetailViewModel.kt @@ -1,47 +1,63 @@ package com.emmsale.presentation.ui.eventdetail -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.emmsale.data.common.ApiError import com.emmsale.data.common.ApiException import com.emmsale.data.common.ApiSuccess +import com.emmsale.data.eventdetail.EventDetail import com.emmsale.data.eventdetail.EventDetailRepository -import com.emmsale.data.member.MemberRepository import com.emmsale.presentation.KerdyApplication import com.emmsale.presentation.common.ViewModelFactory +import com.emmsale.presentation.common.livedata.NotNullLiveData +import com.emmsale.presentation.common.livedata.NotNullMutableLiveData import com.emmsale.presentation.ui.eventdetail.uistate.EventDetailUiState import kotlinx.coroutines.launch class EventDetailViewModel( + private val eventId: Long, private val eventDetailRepository: EventDetailRepository, - private val memberRepository: MemberRepository, ) : ViewModel() { - private val _eventDetail: MutableLiveData = - MutableLiveData() - val eventDetail: LiveData - get() = _eventDetail + private val _eventDetail: NotNullMutableLiveData = + NotNullMutableLiveData(EventDetailUiState()) + val eventDetail: NotNullLiveData = _eventDetail - fun fetchEventDetail(id: Long) { - viewModelScope.launch { - when (val result = eventDetailRepository.fetchEventDetail(id)) { - is ApiSuccess -> _eventDetail.postValue( - EventDetailUiState.from(result.data), - ) + init { + fetchEventDetail(eventId) + } - is ApiError -> _eventDetail.postValue(EventDetailUiState.Error) - is ApiException -> _eventDetail.postValue(EventDetailUiState.Error) + private fun fetchEventDetail(id: Long) { + setLoadingState(true) + viewModelScope.launch { + when (val result = eventDetailRepository.getEventDetail(id)) { + is ApiSuccess -> fetchSuccessEventDetail(result.data) + is ApiError -> changeToErrorState() + is ApiException -> changeToErrorState() } } } + private fun setLoadingState(state: Boolean) { + _eventDetail.value = _eventDetail.value.copy(isLoading = state) + } + + private fun fetchSuccessEventDetail(eventDetail: EventDetail) { + _eventDetail.value = EventDetailUiState.from(eventDetail) + } + + private fun changeToErrorState() { + _eventDetail.value = _eventDetail.value.copy( + isError = true, + isLoading = false, + ) + } + companion object { - val factory = ViewModelFactory { + fun factory(eventId: Long) = ViewModelFactory { EventDetailViewModel( + eventId, eventDetailRepository = KerdyApplication.repositoryContainer.eventDetailRepository, - memberRepository = KerdyApplication.repositoryContainer.memberRepository, ) } } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/uistate/EventDetailUiState.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/uistate/EventDetailUiState.kt index eeb3490e0..3ed84ed43 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/uistate/EventDetailUiState.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/eventdetail/uistate/EventDetailUiState.kt @@ -1,30 +1,28 @@ package com.emmsale.presentation.ui.eventdetail.uistate import com.emmsale.data.eventdetail.EventDetail -import java.time.LocalDate import java.time.LocalDateTime -import java.time.LocalTime import java.time.format.DateTimeFormatter -sealed class EventDetailUiState { - data class Success( - val id: Long, - val name: String, - val status: String, - val location: String, - val startDate: String, - val endDate: String, - val informationUrl: String, - val tags: List, - val imageUrl: String?, - ) : EventDetailUiState() - - object Error : EventDetailUiState() +data class EventDetailUiState( + val id: Long = DEFAULT_ID, + val name: String = "", + val status: String = "", + val location: String = "", + val startDate: String = "", + val endDate: String = "", + val informationUrl: String = "", + val tags: List = listOf(), + val imageUrl: String = "", + val isError: Boolean = false, + val isLoading: Boolean = false, +) { companion object { - fun from(eventDetail: EventDetail): Success { + private const val DEFAULT_ID = -1L + fun from(eventDetail: EventDetail): EventDetailUiState { return with(eventDetail) { - Success( + EventDetailUiState( id = id, name = name, status = status, @@ -33,21 +31,16 @@ sealed class EventDetailUiState { endDate = getGeneralDateFormat(endDate), informationUrl = informationUrl, tags = tags, - imageUrl = imageUrl, + imageUrl = postImageUrl ?: "", + isError = false, + isLoading = false, ) } } - private fun getGeneralDateFormat(dateString: String): String { - val formatter = DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss") - val dateTime = LocalDateTime.parse(dateString, formatter) - - val targetDate = LocalDate.of(dateTime.year, dateTime.month, dateTime.dayOfMonth) - val targetTime = LocalTime.of(dateTime.hour, dateTime.minute) - val targetDateTime = LocalDateTime.of(targetDate, targetTime) - + private fun getGeneralDateFormat(dateTime: LocalDateTime): String { val resultFormatter = DateTimeFormatter.ofPattern("yyyy.M.d HH:mm") - return targetDateTime.format(resultFormatter) + return dateTime.format(resultFormatter) } } } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/main/event/conference/ConferenceFragment.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/main/event/conference/ConferenceFragment.kt index 6ba6990fb..677ebad06 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/main/event/conference/ConferenceFragment.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/main/event/conference/ConferenceFragment.kt @@ -12,7 +12,7 @@ import com.emmsale.presentation.common.extension.getSerializableExtraCompat import com.emmsale.presentation.common.extension.showToast import com.emmsale.presentation.common.views.FilterTag import com.emmsale.presentation.common.views.filterChipOf -import com.emmsale.presentation.eventdetail.EventDetailActivity +import com.emmsale.presentation.ui.eventdetail.EventDetailActivity import com.emmsale.presentation.ui.main.event.conference.recyclerview.ConferenceRecyclerViewAdapter import com.emmsale.presentation.ui.main.event.conference.recyclerview.ConferenceRecyclerViewDivider import com.emmsale.presentation.ui.main.event.conference.uistate.ConferenceItemUiState diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/notificationBox/NotificationBoxViewModel.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/notificationBox/NotificationBoxViewModel.kt index 479f9f0e2..fe6f128db 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/notificationBox/NotificationBoxViewModel.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/notificationBox/NotificationBoxViewModel.kt @@ -89,7 +89,7 @@ class NotificationBoxViewModel( private suspend fun getConferenceNameAsync(conferenceId: Long): Deferred = viewModelScope.async { - when (val conference = eventDetailRepository.fetchEventDetail(conferenceId)) { + when (val conference = eventDetailRepository.getEventDetail(conferenceId)) { is ApiSuccess -> conference.data.name is ApiException, is ApiError -> null } diff --git a/android/2023-emmsale/app/src/main/res/layout/activity_event_detail.xml b/android/2023-emmsale/app/src/main/res/layout/activity_event_detail.xml index bb8b79a85..4061ea1cd 100644 --- a/android/2023-emmsale/app/src/main/res/layout/activity_event_detail.xml +++ b/android/2023-emmsale/app/src/main/res/layout/activity_event_detail.xml @@ -4,10 +4,7 @@ xmlns:tools="http://schemas.android.com/tools"> - - + + tools:context=".presentation.ui.eventdetail.EventDetailActivity"> + app:layout_constraintTop_toBottomOf="@id/tv_eventdetail_eventname" /> + + diff --git a/android/2023-emmsale/app/src/main/res/values/strings.xml b/android/2023-emmsale/app/src/main/res/values/strings.xml index 1b0af8991..7737a3feb 100644 --- a/android/2023-emmsale/app/src/main/res/values/strings.xml +++ b/android/2023-emmsale/app/src/main/res/values/strings.xml @@ -131,4 +131,9 @@ 화면을 팽창 시킵니다. 0 + + 이벤트를 불러올 수 없어요 + 상세 정보 + 댓글 + 같이가요