Skip to content

Commit

Permalink
[AN/USER] feat: 학교 선택 화면 구현(#374) (#461)
Browse files Browse the repository at this point in the history
* feat: 학교 선택 화면 구현

* feat: 학교 조회 api 연결

* feat: 학교 선택 화면 이동

* refactor: 뷰모델 init 대신 액티비티에서 호출하도록 수정

* test: 학교 선택 화면 테스트 작성

* refactor: SchoolUiState 제거

* feat: 로딩, 에러 화면 처리

* refactor: 불필요한 프로퍼티 제거

* feat: 학교 선택 상단 제목 추가

* test: analyticsHelper relaxed true 설정

* refactor: 파라미터 순서 변경

* fix: 화면 회전 시 schoolSelected 값 유지되도록 수정
  • Loading branch information
EmilyCh0 authored and BGuga committed Oct 17, 2023
1 parent ce3ad3e commit 29103a9
Show file tree
Hide file tree
Showing 19 changed files with 512 additions and 12 deletions.
5 changes: 4 additions & 1 deletion android/festago/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
android:theme="@style/Theme.Festago"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".presentation.ui.selectschool.SelectSchoolActivity"
android:exported="false" />
<activity
android:name=".presentation.ui.studentverification.StudentVerificationActivity"
android:exported="false" />
Expand Down Expand Up @@ -61,4 +64,4 @@
android:exported="false" />
</application>

</manifest>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.festago.festago.data.di

import com.festago.festago.data.repository.ReservationTicketDefaultRepository
import com.festago.festago.data.repository.SchoolDefaultRepository
import com.festago.festago.data.repository.StudentVerificationDefaultRepository
import com.festago.festago.data.repository.UserDefaultRepository
import com.festago.festago.repository.ReservationTicketRepository
import com.festago.festago.repository.SchoolRepository
import com.festago.festago.repository.StudentVerificationRepository
import com.festago.festago.repository.UserRepository
import dagger.Binds
Expand All @@ -26,5 +28,9 @@ interface ViewModelScopeModule {

@Binds
@ViewModelScoped
fun binsUserDefaultRepository(userRepository: UserDefaultRepository): UserRepository
fun bindsUserDefaultRepository(userRepository: UserDefaultRepository): UserRepository

@Binds
@ViewModelScoped
fun bindsSelectSchoolRepository(schoolRepository: SchoolDefaultRepository): SchoolRepository
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ package com.festago.festago.data.di.singletonscope

import com.festago.festago.data.repository.AuthDefaultRepository
import com.festago.festago.data.repository.FestivalDefaultRepository
import com.festago.festago.data.repository.SchoolDefaultRepository
import com.festago.festago.data.repository.TicketDefaultRepository
import com.festago.festago.data.repository.TokenDefaultRepository
import com.festago.festago.repository.AuthRepository
import com.festago.festago.repository.FestivalRepository
import com.festago.festago.repository.SchoolRepository
import com.festago.festago.repository.TicketRepository
import com.festago.festago.repository.TokenRepository
import dagger.Binds
Expand All @@ -30,10 +28,6 @@ interface RepositoryModule {
@Singleton
fun bindsFestivalDefaultRepository(festivalRepository: FestivalDefaultRepository): FestivalRepository

@Binds
@Singleton
fun bindsSchoolDefaultRepository(schoolRepository: SchoolDefaultRepository): SchoolRepository

@Binds
@Singleton
fun bindsTicketDefaultRepository(ticketRepository: TicketDefaultRepository): TicketRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.festago.festago.data.di.singletonscope

import com.festago.festago.data.service.FestivalRetrofitService
import com.festago.festago.data.service.ReservationTicketRetrofitService
import com.festago.festago.data.service.SchoolRetrofitService
import com.festago.festago.data.service.StudentVerificationRetrofitService
import com.festago.festago.data.service.TicketRetrofitService
import com.festago.festago.data.service.TokenRetrofitService
Expand Down Expand Up @@ -64,4 +65,12 @@ object ServiceModule {
): StudentVerificationRetrofitService {
return retrofit.create(StudentVerificationRetrofitService::class.java)
}

@Provides
@Singleton
fun providesSchoolRetrofitService(
@NormalRetrofitQualifier retrofit: Retrofit
): SchoolRetrofitService {
return retrofit.create(SchoolRetrofitService::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.festago.festago.data.dto

import com.festago.festago.model.School
import kotlinx.serialization.Serializable

@Serializable
data class SchoolResponse(
val id: Int,
val domain: String,
val name: String
) {
fun toDomain(): School = School(
id = id.toLong(),
domain = domain,
name = name
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.festago.festago.data.dto

import com.festago.festago.model.School
import kotlinx.serialization.Serializable

@Serializable
data class SchoolsResponse(
val schools: List<SchoolResponse>
) {
fun toDomain(): List<School> = schools.map { it.toDomain() }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
package com.festago.festago.data.repository

import com.festago.festago.data.service.SchoolRetrofitService
import com.festago.festago.data.util.runCatchingWithErrorHandler
import com.festago.festago.model.School
import com.festago.festago.repository.SchoolRepository
import javax.inject.Inject

class SchoolDefaultRepository @Inject constructor() : SchoolRepository {
class SchoolDefaultRepository @Inject constructor(
private val schoolRetrofitService: SchoolRetrofitService
) : SchoolRepository {

override suspend fun loadSchools(): Result<List<School>> {
schoolRetrofitService.getSchools()
.runCatchingWithErrorHandler()
.getOrElse { error -> return Result.failure(error) }
.let { return Result.success(it.toDomain()) }
}

override suspend fun loadSchoolEmail(schoolId: Long): Result<String> {
// TODO: API 연동 작업 필요
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.festago.festago.data.service

import com.festago.festago.data.dto.SchoolsResponse
import retrofit2.Response
import retrofit2.http.GET

interface SchoolRetrofitService {

@GET("/schools")
suspend fun getSchools(): Response<SchoolsResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import androidx.fragment.app.viewModels
import com.festago.festago.R
import com.festago.festago.databinding.FragmentMyPageBinding
import com.festago.festago.presentation.ui.home.HomeActivity
import com.festago.festago.presentation.ui.selectschool.SelectSchoolActivity
import com.festago.festago.presentation.ui.signin.SignInActivity
import com.festago.festago.presentation.ui.studentverification.StudentVerificationActivity
import com.festago.festago.presentation.ui.tickethistory.TicketHistoryActivity
import dagger.hilt.android.AndroidEntryPoint

Expand Down Expand Up @@ -104,9 +104,9 @@ class MyPageFragment : Fragment(R.layout.fragment_my_page) {
binding.srlMyPage.setOnRefreshListener {
vm.loadUserInfo()
}
// TODO: 학교 선택 화면 변경 필요

binding.tvSchoolAuthorization.setOnClickListener {
startActivity(StudentVerificationActivity.getIntent(requireContext(), 1L))
startActivity(SelectSchoolActivity.getIntent(requireContext()))
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.festago.festago.presentation.ui.selectschool

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.ArrayAdapter
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.festago.festago.R
import com.festago.festago.databinding.ActivitySelectSchoolBinding
import com.festago.festago.presentation.ui.studentverification.StudentVerificationActivity
import com.festago.festago.presentation.util.repeatOnStarted
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class SelectSchoolActivity : AppCompatActivity() {

private val binding: ActivitySelectSchoolBinding by lazy {
ActivitySelectSchoolBinding.inflate(layoutInflater)
}

private val vm: SelectSchoolViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initBinding()
initObserve()
initView()
}

private fun initBinding() {
setContentView(binding.root)
binding.lifecycleOwner = this
binding.vm = vm
}

private fun initObserve() {
repeatOnStarted {
vm.uiState.collect { uiState ->
handleUiState(uiState)
}
}
repeatOnStarted {
vm.event.collect { event ->
handleEvent(event)
}
}
}

private fun initView() {
vm.loadSchools()
}

private fun handleUiState(uiState: SelectSchoolUiState) {
binding.uiState = uiState
when (uiState) {
is SelectSchoolUiState.Loading, is SelectSchoolUiState.Error -> Unit
is SelectSchoolUiState.Success -> handleSuccess(uiState)
}
}

private fun handleSuccess(uiState: SelectSchoolUiState.Success) {
val adapter =
ArrayAdapter(this, R.layout.item_select_school, uiState.schools.map { it.name })
binding.actvSelectSchool.setAdapter(adapter)
binding.actvSelectSchool.setOnItemClickListener { _, _, position, _ ->
val selectedSchool = uiState.schools.firstOrNull {
it.name == adapter.getItem(position)
}
selectedSchool?.let { vm.selectSchool(it.id) }
}
}

private fun handleEvent(event: SelectSchoolEvent) {
when (event) {
is SelectSchoolEvent.ShowStudentVerification -> {
startActivity(StudentVerificationActivity.getIntent(this, event.schoolId))
}
}
}

companion object {
fun getIntent(context: Context): Intent {
return Intent(context, SelectSchoolActivity::class.java)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.festago.festago.presentation.ui.selectschool

interface SelectSchoolEvent {
class ShowStudentVerification(val schoolId: Long) : SelectSchoolEvent
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.festago.festago.presentation.ui.selectschool

import com.festago.festago.model.School

interface SelectSchoolUiState {
object Loading : SelectSchoolUiState

data class Success(
val schools: List<School>,
val selectedSchoolId: Long? = null
) : SelectSchoolUiState {
val schoolSelected = selectedSchoolId != null
}

object Error : SelectSchoolUiState

val enableNext get() = (this is Success) && schoolSelected
val shouldShowSuccess get() = this is Success
val shouldShowLoading get() = this is Loading
val shouldShowError get() = this is Error
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.festago.festago.presentation.ui.selectschool

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.festago.festago.analytics.AnalyticsHelper
import com.festago.festago.analytics.logNetworkFailure
import com.festago.festago.repository.SchoolRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class SelectSchoolViewModel @Inject constructor(
private val schoolRepository: SchoolRepository,
private val analyticsHelper: AnalyticsHelper
) : ViewModel() {

private val _uiState = MutableStateFlow<SelectSchoolUiState>(SelectSchoolUiState.Loading)
val uiState: StateFlow<SelectSchoolUiState> = _uiState.asStateFlow()

private val _event = MutableSharedFlow<SelectSchoolEvent>()
val event: SharedFlow<SelectSchoolEvent> = _event.asSharedFlow()

fun loadSchools() {
viewModelScope.launch {
schoolRepository.loadSchools()
.onSuccess { schools ->
if (uiState.value is SelectSchoolUiState.Success) {
val successState = (uiState.value as SelectSchoolUiState.Success)
_uiState.value = successState.copy(schools = schools)
} else {
_uiState.value = SelectSchoolUiState.Success(schools)
}
}
.onFailure {
_uiState.value = SelectSchoolUiState.Error
analyticsHelper.logNetworkFailure(KEY_LOAD_SCHOOLS_LOG, it.message.toString())
}
}
}

fun selectSchool(schoolId: Long) {
if (uiState.value is SelectSchoolUiState.Success) {
_uiState.value =
(uiState.value as SelectSchoolUiState.Success).copy(selectedSchoolId = schoolId)
}
}

fun showStudentVerification() {
viewModelScope.launch {
if (uiState.value is SelectSchoolUiState.Success) {
val success = uiState.value as SelectSchoolUiState.Success
success.selectedSchoolId?.let { schoolId ->
_event.emit(
SelectSchoolEvent.ShowStudentVerification(schoolId)
)
}
}
}
}

companion object {
private const val KEY_LOAD_SCHOOLS_LOG = "load_schools"
}
}
Loading

0 comments on commit 29103a9

Please sign in to comment.