diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b346f57e..39197578 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,10 +20,10 @@ tools:targetApi="31"> + android:theme="@style/Theme.Pingle.Splash" + tools:ignore="LockedOrientationActivity"> @@ -72,7 +72,7 @@ tools:ignore="LockedOrientationActivity" /> > suspend fun getJoinGroupInfo(teamId: Int): BaseResponse suspend fun postJoinGroupCode( teamId: Int, diff --git a/app/src/main/java/org/sopt/pingle/data/datasourceimpl/remote/JoinGroupRemoteDataSourceImpl.kt b/app/src/main/java/org/sopt/pingle/data/datasourceimpl/remote/JoinGroupRemoteDataSourceImpl.kt index 3bc8ab12..7db4dfa6 100644 --- a/app/src/main/java/org/sopt/pingle/data/datasourceimpl/remote/JoinGroupRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/sopt/pingle/data/datasourceimpl/remote/JoinGroupRemoteDataSourceImpl.kt @@ -5,12 +5,16 @@ import org.sopt.pingle.data.datasource.remote.JoinGroupRemoteDataSource import org.sopt.pingle.data.model.remote.request.RequestJoinGroupCodeDto import org.sopt.pingle.data.model.remote.response.ResponseJoinGroupCodeDto import org.sopt.pingle.data.model.remote.response.ResponseJoinGroupInfoDto +import org.sopt.pingle.data.model.remote.response.ResponseJoinGroupSearchDto import org.sopt.pingle.data.service.JoinGroupService import org.sopt.pingle.util.base.BaseResponse class JoinGroupRemoteDataSourceImpl @Inject constructor( private val joinGroupService: JoinGroupService ) : JoinGroupRemoteDataSource { + override suspend fun getJoinGroupSearch(teamName: String): BaseResponse> = + joinGroupService.getJoinGroupSearch(teamName = teamName) + override suspend fun getJoinGroupInfo(teamId: Int): BaseResponse = joinGroupService.getJoinGroupInfo(teamId = teamId) diff --git a/app/src/main/java/org/sopt/pingle/data/model/remote/response/ResponseJoinGroupSearchDto.kt b/app/src/main/java/org/sopt/pingle/data/model/remote/response/ResponseJoinGroupSearchDto.kt index fb0739d2..f19f8220 100644 --- a/app/src/main/java/org/sopt/pingle/data/model/remote/response/ResponseJoinGroupSearchDto.kt +++ b/app/src/main/java/org/sopt/pingle/data/model/remote/response/ResponseJoinGroupSearchDto.kt @@ -1,28 +1,21 @@ package org.sopt.pingle.data.model.remote.response import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import org.sopt.pingle.domain.model.JoinGroupSearchEntity +@Serializable data class ResponseJoinGroupSearchDto( - @SerialName("code") - val code: Int, - @SerialName("code") - val message: String, - @SerialName("code") - val data: List + @SerialName("id") + val id: Int, + @SerialName("name") + val name: String, + @SerialName("keyword") + val keyword: String ) { - data class Data( - @SerialName("code") - val id: Int, - @SerialName("code") - val name: String, - @SerialName("code") - val keyword: String - ) { - fun toJoinGroupSearchEntity() = JoinGroupSearchEntity( - id = this.id, - keyword = this.keyword, - name = this.name - ) - } + fun toJoinGroupSearchEntity() = JoinGroupSearchEntity( + id = this.id, + keyword = this.keyword, + name = this.name + ) } diff --git a/app/src/main/java/org/sopt/pingle/data/repository/JoinGroupRepositoryImpl.kt b/app/src/main/java/org/sopt/pingle/data/repository/JoinGroupRepositoryImpl.kt index 5beb558f..f60685b7 100644 --- a/app/src/main/java/org/sopt/pingle/data/repository/JoinGroupRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/pingle/data/repository/JoinGroupRepositoryImpl.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.flow import org.sopt.pingle.data.datasource.remote.JoinGroupRemoteDataSource import org.sopt.pingle.data.mapper.toRequestJoinGroupCode import org.sopt.pingle.domain.model.JoinGroupInfoEntity +import org.sopt.pingle.domain.model.JoinGroupSearchEntity import org.sopt.pingle.domain.model.RequestJoinGroupCodeEntity import org.sopt.pingle.domain.model.ResponseJoinGroupCodeEntity import org.sopt.pingle.domain.repository.JoinGroupRepository @@ -13,6 +14,15 @@ import org.sopt.pingle.domain.repository.JoinGroupRepository class JoinGroupRepositoryImpl @Inject constructor( private val joinGroupRemoteDataSource: JoinGroupRemoteDataSource ) : JoinGroupRepository { + override fun getJoinGroupSearch(teamName: String): Flow> = flow { + val result = runCatching { + joinGroupRemoteDataSource.getJoinGroupSearch(teamName = teamName).data.map { joinGroupSearch -> + joinGroupSearch.toJoinGroupSearchEntity() + } + } + emit(result.getOrThrow()) + } + override fun getJoinGroupInfo(teamId: Int): Flow = flow { val result = runCatching { joinGroupRemoteDataSource.getJoinGroupInfo(teamId = teamId).data.toJoinGroupCodeEntity() diff --git a/app/src/main/java/org/sopt/pingle/data/service/JoinGroupService.kt b/app/src/main/java/org/sopt/pingle/data/service/JoinGroupService.kt index 0dcf2f4f..1e2f9428 100644 --- a/app/src/main/java/org/sopt/pingle/data/service/JoinGroupService.kt +++ b/app/src/main/java/org/sopt/pingle/data/service/JoinGroupService.kt @@ -3,13 +3,20 @@ package org.sopt.pingle.data.service import org.sopt.pingle.data.model.remote.request.RequestJoinGroupCodeDto import org.sopt.pingle.data.model.remote.response.ResponseJoinGroupCodeDto import org.sopt.pingle.data.model.remote.response.ResponseJoinGroupInfoDto +import org.sopt.pingle.data.model.remote.response.ResponseJoinGroupSearchDto import org.sopt.pingle.util.base.BaseResponse import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Path +import retrofit2.http.Query interface JoinGroupService { + @GET("$VERSION/$TEAMS") + suspend fun getJoinGroupSearch( + @Query("$NAME") teamName: String + ): BaseResponse> + @GET("$VERSION/$TEAMS/{$TEAM_ID}") suspend fun getJoinGroupInfo( @Path("$TEAM_ID") teamId: Int @@ -24,6 +31,7 @@ interface JoinGroupService { companion object { const val VERSION = "v1" const val TEAMS = "teams" + const val NAME = "name" const val TEAM_ID = "teamId" const val REGISTER = "register" } diff --git a/app/src/main/java/org/sopt/pingle/di/UseCaseModule.kt b/app/src/main/java/org/sopt/pingle/di/UseCaseModule.kt index 451fd32e..1537b45a 100644 --- a/app/src/main/java/org/sopt/pingle/di/UseCaseModule.kt +++ b/app/src/main/java/org/sopt/pingle/di/UseCaseModule.kt @@ -14,6 +14,7 @@ import org.sopt.pingle.domain.repository.PlanMeetingRepository import org.sopt.pingle.domain.repository.PlanRepository import org.sopt.pingle.domain.usecase.GetDummyUserListUseCase import org.sopt.pingle.domain.usecase.GetJoinGroupInfoUseCase +import org.sopt.pingle.domain.usecase.GetJoinGroupSearchUseCase import org.sopt.pingle.domain.usecase.GetPinListWithoutFilteringUseCase import org.sopt.pingle.domain.usecase.GetPingleListUseCase import org.sopt.pingle.domain.usecase.GetPlanLocationListUseCase @@ -67,6 +68,11 @@ class UseCaseModule { fun providesPostPlanMeetingUseCase(planMeetingRepository: PlanMeetingRepository): PostPlanMeetingUseCase = PostPlanMeetingUseCase(planMeetingRepository = planMeetingRepository) + @Provides + @Singleton + fun providesGetJoinGroupSearchUseCase(joinGroupRepository: JoinGroupRepository): GetJoinGroupSearchUseCase = + GetJoinGroupSearchUseCase(joinGroupRepository = joinGroupRepository) + @Provides @Singleton fun providesGetJoinGroupInfoUseCase(joinGroupRepository: JoinGroupRepository): GetJoinGroupInfoUseCase = diff --git a/app/src/main/java/org/sopt/pingle/domain/repository/JoinGroupRepository.kt b/app/src/main/java/org/sopt/pingle/domain/repository/JoinGroupRepository.kt index 364054a9..7d2cbe2c 100644 --- a/app/src/main/java/org/sopt/pingle/domain/repository/JoinGroupRepository.kt +++ b/app/src/main/java/org/sopt/pingle/domain/repository/JoinGroupRepository.kt @@ -2,10 +2,12 @@ package org.sopt.pingle.domain.repository import kotlinx.coroutines.flow.Flow import org.sopt.pingle.domain.model.JoinGroupInfoEntity +import org.sopt.pingle.domain.model.JoinGroupSearchEntity import org.sopt.pingle.domain.model.RequestJoinGroupCodeEntity import org.sopt.pingle.domain.model.ResponseJoinGroupCodeEntity interface JoinGroupRepository { + fun getJoinGroupSearch(teamName: String): Flow> fun getJoinGroupInfo(teamId: Int): Flow fun postJoinGroupCode( teamId: Int, diff --git a/app/src/main/java/org/sopt/pingle/domain/usecase/GetJoinGroupSearchUseCase.kt b/app/src/main/java/org/sopt/pingle/domain/usecase/GetJoinGroupSearchUseCase.kt new file mode 100644 index 00000000..de45c75d --- /dev/null +++ b/app/src/main/java/org/sopt/pingle/domain/usecase/GetJoinGroupSearchUseCase.kt @@ -0,0 +1,12 @@ +package org.sopt.pingle.domain.usecase + +import kotlinx.coroutines.flow.Flow +import org.sopt.pingle.domain.model.JoinGroupSearchEntity +import org.sopt.pingle.domain.repository.JoinGroupRepository + +class GetJoinGroupSearchUseCase( + private val joinGroupRepository: JoinGroupRepository +) { + operator fun invoke(teamName: String): Flow> = + joinGroupRepository.getJoinGroupSearch(teamName = teamName) +} diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinGroupCodeActivity.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinGroupCodeActivity.kt index a73d9ad5..36c15a17 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinGroupCodeActivity.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinGroupCodeActivity.kt @@ -20,6 +20,7 @@ import timber.log.Timber class JoinGroupCodeActivity : BindingActivity(R.layout.activity_join_group_code) { private val viewModel by viewModels() + private var teamId = -1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -32,13 +33,14 @@ class JoinGroupCodeActivity : } private fun initLayout() { - viewModel.joinGroupInfoState(TEAM_ID) + teamId = intent.getIntExtra(TEAM_ID, -1) + viewModel.joinGroupInfoState(teamId) } private fun addListeners() { binding.btnJoinGroupCodeNext.setOnClickListener { viewModel.joinGroupCodeState( - TEAM_ID, + teamId, RequestJoinGroupCodeEntity(viewModel.joinGroupCodeEditText.value.toString()) ) } @@ -109,8 +111,8 @@ class JoinGroupCodeActivity : } companion object { + const val TEAM_ID = "teamId" const val GROUP_NAME = "groupName" - const val TEAM_ID = 1 const val LOADING = "Loding" const val EMPTY = "Empty" const val JOIN_GROUP_CODE_ACTIVITY = "JoinGroupCodeActivity" diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinGroupSearchActivity.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinGroupSearchActivity.kt index c75e2cc9..ba3182ae 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinGroupSearchActivity.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinGroupSearchActivity.kt @@ -3,10 +3,11 @@ package org.sopt.pingle.presentation.ui.joingroup import android.content.Intent import android.os.Bundle import android.view.KeyEvent +import android.view.View import androidx.activity.viewModels -import androidx.core.view.isVisible import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.sopt.pingle.R @@ -15,11 +16,15 @@ import org.sopt.pingle.presentation.ui.onboarding.OnBoardingActivity import org.sopt.pingle.util.base.BindingActivity import org.sopt.pingle.util.context.hideKeyboard import org.sopt.pingle.util.context.navigateToWebView +import org.sopt.pingle.util.view.UiState +import timber.log.Timber +@AndroidEntryPoint class JoinGroupSearchActivity : BindingActivity(R.layout.activity_join_group_search) { private val viewModel by viewModels() private lateinit var joinGroupSearchAdapter: JoinGroupSearchAdapter + private var teamId = -1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -27,7 +32,7 @@ class JoinGroupSearchActivity : initLayout() addListeners() - addObservers() + collectData() } private fun initLayout() { @@ -36,22 +41,26 @@ class JoinGroupSearchActivity : } private fun addListeners() { + binding.includeJoinGroupSearchTopbar.ivAllTopbarArrowWithTitleArrowLeft.setOnClickListener { + finish() + } + binding.root.setOnClickListener { hideKeyboard(binding.etJoinGroupSearch) } binding.ivJoinGroupSearchIcon.setOnClickListener { - // TODO 서버통신 함수 호출 + viewModel.joinGroupSearchState(binding.etJoinGroupSearch.text.toString()) hideKeyboard(binding.etJoinGroupSearch) } binding.etJoinGroupSearch.setOnKeyListener { v, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) { - // TODO 서버통신 함수 호출 + viewModel.joinGroupSearchState(binding.etJoinGroupSearch.text.toString()) hideKeyboard(binding.etJoinGroupSearch) return@setOnKeyListener true } - true + false } binding.tvJoinGroupSearchCreate.setOnClickListener { @@ -61,22 +70,36 @@ class JoinGroupSearchActivity : binding.btnJoinGroupCodeNext.setOnClickListener { navigateToJoinGroupCode() } - - binding.includeJoinGroupSearchTopbar.ivAllTopbarArrowWithTitleArrowLeft.setOnClickListener { - finish() - } } - private fun addObservers() { - viewModel.joinGroupSearchData.flowWithLifecycle(lifecycle).onEach { joinGroupSearchList -> - // TODO 서버통신 시 옵저빙 체크 - joinGroupSearchAdapter.submitList(joinGroupSearchList) - - binding.tvJoinGroupSearchEmpty.isVisible = joinGroupSearchList.isEmpty() + private fun collectData() { + viewModel.joinGroupSearchState.flowWithLifecycle(lifecycle).onEach { uiState -> + when (uiState) { + is UiState.Success -> { + joinGroupSearchAdapter.submitList(uiState.data) + joinGroupSearchAdapter.currentList + binding.tvJoinGroupSearchEmpty.visibility = View.INVISIBLE + } + + is UiState.Error -> Timber.tag(JoinGroupCodeActivity.JOIN_GROUP_CODE_ACTIVITY) + .d(uiState.message) + + is UiState.Loading -> Timber.tag(JoinGroupCodeActivity.JOIN_GROUP_CODE_ACTIVITY).d( + JoinGroupCodeActivity.LOADING + ) + + is UiState.Empty -> { + joinGroupSearchAdapter.submitList(null) + binding.tvJoinGroupSearchEmpty.visibility = View.VISIBLE + } + } }.launchIn(lifecycleScope) viewModel.selectedJoinGroup.flowWithLifecycle(lifecycle).onEach { selectedJoinGroup -> binding.btnJoinGroupCodeNext.isEnabled = selectedJoinGroup != null + if (selectedJoinGroup != null) { + teamId = selectedJoinGroup.id + } }.launchIn(lifecycleScope) } @@ -86,6 +109,7 @@ class JoinGroupSearchActivity : private fun navigateToJoinGroupCode() { Intent(this, JoinGroupCodeActivity::class.java).apply { + putExtra(TEAM_ID, teamId) startActivity(this) } } @@ -94,4 +118,8 @@ class JoinGroupSearchActivity : binding.rvJoinGroupSearch.adapter = null super.onDestroy() } + + companion object { + const val TEAM_ID = "teamId" + } } diff --git a/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinViewModel.kt b/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinViewModel.kt index cab14b76..82f5d0e8 100644 --- a/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinViewModel.kt +++ b/app/src/main/java/org/sopt/pingle/presentation/ui/joingroup/JoinViewModel.kt @@ -5,7 +5,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.sopt.pingle.data.datasource.local.PingleLocalDataSource @@ -14,35 +16,52 @@ import org.sopt.pingle.domain.model.JoinGroupSearchEntity import org.sopt.pingle.domain.model.RequestJoinGroupCodeEntity import org.sopt.pingle.domain.model.ResponseJoinGroupCodeEntity import org.sopt.pingle.domain.usecase.GetJoinGroupInfoUseCase +import org.sopt.pingle.domain.usecase.GetJoinGroupSearchUseCase import org.sopt.pingle.domain.usecase.PostJoinGroupCodeUseCase import org.sopt.pingle.util.view.UiState @HiltViewModel class JoinViewModel @Inject constructor( private val localStorage: PingleLocalDataSource, + private val getJoinGroupSearchUseCase: GetJoinGroupSearchUseCase, private val getJoinGroupInfoUseCase: GetJoinGroupInfoUseCase, private val postJoinGroupCodeUseCase: PostJoinGroupCodeUseCase ) : ViewModel() { + private val _joinGroupSearchState = + MutableSharedFlow>>() + val joinGroupSearchState get() = _joinGroupSearchState.asSharedFlow() private val _selectedJoinGroup = MutableStateFlow(null) val selectedJoinGroup get() = _selectedJoinGroup.asStateFlow() - private val _joinGroupSearchData = MutableStateFlow>(emptyList()) val joinGroupSearchData get() = _joinGroupSearchData private val _joinGroupInfoState = MutableStateFlow>(UiState.Empty) val joinGroupInfoState get() = _joinGroupInfoState.asStateFlow() - - private var _isJoinGroupCodeBtn = MutableLiveData(false) - val isJoinGroupCodeBtn get() = _isJoinGroupCodeBtn - private var _joinGroupCodeState = MutableStateFlow>(UiState.Empty) val joinGroupCodeState get() = _joinGroupCodeState - val joinGroupCodeEditText = MutableLiveData() - val joinGroupSearchEditText = MutableLiveData("") + fun joinGroupSearchState(teamName: String) { + viewModelScope.launch { + _joinGroupSearchState.emit(UiState.Loading) + _joinGroupSearchData.value = emptyList() + _selectedJoinGroup.value = null + runCatching { + getJoinGroupSearchUseCase(teamName = teamName).collect { joinGroupSearch -> + if (joinGroupSearch.isEmpty()) { + _joinGroupSearchState.emit(UiState.Empty) + } else { + _joinGroupSearchData.value = joinGroupSearch + _joinGroupSearchState.emit(UiState.Success(joinGroupSearch)) + } + } + }.onFailure { + _joinGroupSearchState.emit(UiState.Error(it.message)) + } + } + } private var oldPosition = DEFAULT_OLD_POSITION fun updateJoinGroupSearchList(newPosition: Int) { @@ -75,7 +94,6 @@ class JoinViewModel @Inject constructor( fun joinGroupInfoState(teamId: Int) { _joinGroupInfoState.value = UiState.Loading viewModelScope.launch { - _joinGroupInfoState.value = UiState.Loading runCatching { getJoinGroupInfoUseCase(teamId = teamId).collect { joinGroupInfo -> _joinGroupInfoState.value = UiState.Success(joinGroupInfo) @@ -104,36 +122,6 @@ class JoinViewModel @Inject constructor( } } - init { - _joinGroupSearchData.value = listOf( - JoinGroupSearchEntity( - id = 0, - keyword = "연합동아리", - name = "SOPT1" - ), - JoinGroupSearchEntity( - id = 1, - keyword = "연합동아리", - name = "SOPT2" - ), - JoinGroupSearchEntity( - id = 2, - keyword = "연합동아리", - name = "SOPT3" - ), - JoinGroupSearchEntity( - id = 3, - keyword = "연합동아리", - name = "SOPT4" - ), - JoinGroupSearchEntity( - id = 4, - keyword = "연합동아리", - name = "SOPT5" - ) - ) - } - companion object { private const val DEFAULT_OLD_POSITION = -1 } diff --git a/app/src/main/res/layout/activity_join_group_search.xml b/app/src/main/res/layout/activity_join_group_search.xml index 0e6f9ea7..44a5745a 100644 --- a/app/src/main/res/layout/activity_join_group_search.xml +++ b/app/src/main/res/layout/activity_join_group_search.xml @@ -71,7 +71,6 @@ android:inputType="text" android:maxLength="100" android:paddingVertical="@dimen/spacing12" - android:text="@{joinViewModel.joinGroupSearchEditText}" android:textAppearance="@style/TextAppearance.Pingle.Body.Semi.14" android:textColor="@color/white" android:textColorHint="@color/g_07" @@ -153,7 +152,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/spacing25" - android:enabled="true" + android:enabled="false" android:text="@string/join_group_code_next" android:textAppearance="@style/TextAppearance.Pingle.Sub.Semi.16" app:layout_constraintBottom_toBottomOf="parent"