Skip to content

Commit

Permalink
[AN/USER] feat: 학생 인증 화면 구현 (#370) (#429)
Browse files Browse the repository at this point in the history
* feat: 학생 인증 레포지토리 정의

* feat: 학교 레포지토리 임시 정의

* feat: 학교 인증 화면 뷰모델 구현

* test: 학생 인증 화면 뷰모델 테스트 작성

* refactor: LiveData stateFlow 로 리팩터링

* feat: StudentVerificationActivity 생성

* feat: 학생 인증 화면 xml 그리기

* feat: 재사용 가능한 문장 검증 로직 생성 및 테스트

* feat: 학생 인증 검증 로직 생성 및 테스트

* feat: SchoolId Long 으로 변경

* feat: 인증 코드 확인 요청 기능 작성

* feat: 코드 형식 확인 인증

* feat: 임시 학교 인증 화면 이동

* refator: ktlint check

* refator: 클래스 및 변수 명 변경

* fix: 화면 회전 시 학교 이메일을 재요청하지 않음

* refactor: 사용하지않는 코드 주석 제거

* refactor: repeatOnStartedState util 추가

* refactor: StudentVerificationActivity 전체 리팩터링

* refactor: Students 를 Student 로 통일

* refactor: State 성공 시 처리 함수 분리 및 네이밍 변경

* refactor: 글자 30개까지 입력으로 변경

* feat: sealed interface 학생 인증 이벤트 추가

* refactor: 학생 인증 ViewModel 리팩터링

* test: 학생 인증 이벤트 테스트 추가

* refactor: 사용하지 않는 뷰 제거

* test: 학생 인증 코드 예외 테스트 추가

* feat: Event observing 추가

* refactor: 테스트 리팩터링

* refactor: 테스트 네이밍 리팩터링

* refactor: turbine 추가 및 sharedFlow 테스트 리팩터링

* refactor: Text 검증 테스트 네이밍 및 구조 리팩터링

* refactor: TextValidator 생성 로직 통일

* refactor: given when 함수 분리 및 테스트 리팩터링

* refactor: seal interface 는 isExactlyInstanceOf 로 테스트
  • Loading branch information
SeongHoonC authored and BGuga committed Oct 17, 2023
1 parent fb68116 commit dbe8b39
Show file tree
Hide file tree
Showing 24 changed files with 946 additions and 5 deletions.
3 changes: 3 additions & 0 deletions android/festago/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ dependencies {
// Encrypted SharedPreference
implementation("androidx.security:security-crypto-ktx:1.1.0-alpha06")

// turbine
testImplementation("app.cash.turbine:turbine:1.0.0")

// domain
implementation(project(":domain"))
}
Expand Down
10 changes: 5 additions & 5 deletions android/festago/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<uses-permission android:name="android.permission.INTERNET" />

<application
Expand All @@ -18,6 +17,9 @@
android:theme="@style/Theme.Festago"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".presentation.ui.studentverification.StudentVerificationActivity"
android:exported="false" />
<activity
android:name=".presentation.ui.signin.SignInActivity"
android:exported="false" />
Expand Down Expand Up @@ -54,11 +56,9 @@
android:scheme="@string/kakao_redirection_scheme" />
</intent-filter>
</activity>

<activity
android:name=".presentation.ui.tickethistory.TicketHistoryActivity"
android:exported="false">
</activity>
android:exported="false" />
</application>

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

import kotlinx.serialization.Serializable

@Serializable
data class SendVerificationRequest(
val userName: String,
val schoolId: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.festago.festago.data.repository

import com.festago.festago.repository.SchoolRepository

class SchoolDefaultRepository() : SchoolRepository {

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

import com.festago.festago.data.service.StudentVerificationRetrofitService
import com.festago.festago.model.StudentVerificationCode
import com.festago.festago.repository.StudentVerificationRepository

class StudentVerificationDefaultRepository(
private val studentVerificationRetrofitService: StudentVerificationRetrofitService,
) : StudentVerificationRepository {

override suspend fun sendVerificationCode(userName: String, schoolId: Long): Result<Unit> {
// TODO: API 연동 작업 필요
return Result.success(Unit)
}

override suspend fun requestVerificationCodeConfirm(code: StudentVerificationCode): Result<Unit> {
// TODO: API 연동 작업 필요
return Result.success(Unit)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.festago.festago.data.service

import com.festago.festago.data.dto.SendVerificationRequest
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST

interface StudentVerificationRetrofitService {
@POST("/students/send-verification")
suspend fun sendVerificationCode(
@Body sendVerificationRequest: SendVerificationRequest,
): Response<Unit>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.festago.festago.di

import com.festago.festago.data.retrofit.AuthInterceptor
import com.festago.festago.data.service.StudentVerificationRetrofitService
import com.festago.festago.data.service.TicketRetrofitService
import com.festago.festago.data.service.UserRetrofitService
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
Expand Down Expand Up @@ -29,4 +30,8 @@ class AuthServiceContainer(baseUrl: String, tokenContainer: TokenContainer) {
val userRetrofitService: UserRetrofitService by lazy {
authRetrofit.create(UserRetrofitService::class.java)
}

val studentVerificationRetrofitService: StudentVerificationRetrofitService by lazy {
authRetrofit.create(StudentVerificationRetrofitService::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package com.festago.festago.di
import com.festago.festago.data.repository.AuthDefaultRepository
import com.festago.festago.data.repository.FestivalDefaultRepository
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.TicketDefaultRepository
import com.festago.festago.data.repository.UserDefaultRepository
import com.festago.festago.repository.AuthRepository
import com.festago.festago.repository.FestivalRepository
import com.festago.festago.repository.ReservationTicketRepository
import com.festago.festago.repository.SchoolRepository
import com.festago.festago.repository.StudentVerificationRepository
import com.festago.festago.repository.TicketRepository
import com.festago.festago.repository.UserRepository

Expand Down Expand Up @@ -40,4 +44,12 @@ class RepositoryContainer(
get() = ReservationTicketDefaultRepository(
reservationTicketRetrofitService = normalServiceContainer.reservationTicketRetrofitService,
)

val studentVerificationRepository: StudentVerificationRepository
get() = StudentVerificationDefaultRepository(
studentVerificationRetrofitService = authServiceContainer.studentVerificationRetrofitService,
)

val schoolRepository: SchoolRepository
get() = SchoolDefaultRepository()
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.festago.festago.presentation.ui.home.festivallist.FestivalListViewMod
import com.festago.festago.presentation.ui.home.mypage.MyPageViewModel
import com.festago.festago.presentation.ui.home.ticketlist.TicketListViewModel
import com.festago.festago.presentation.ui.signin.SignInViewModel
import com.festago.festago.presentation.ui.studentverification.StudentVerificationViewModel
import com.festago.festago.presentation.ui.ticketentry.TicketEntryViewModel
import com.festago.festago.presentation.ui.tickethistory.TicketHistoryViewModel
import com.festago.festago.presentation.ui.ticketreserve.TicketReserveViewModel
Expand Down Expand Up @@ -63,6 +64,12 @@ val FestagoViewModelFactory: ViewModelProvider.Factory = object : ViewModelProvi
analyticsHelper = analysisContainer.analyticsHelper,
)

modelClass.isAssignableFrom(StudentVerificationViewModel::class.java) -> StudentVerificationViewModel(
schoolRepository = repositoryContainer.schoolRepository,
studentVerificationRepository = repositoryContainer.studentVerificationRepository,
analyticsHelper = analysisContainer.analyticsHelper,
)

else -> throw IllegalArgumentException("ViewModelFactory에 정의되지않은 뷰모델을 생성하였습니다 : ${modelClass.name}")
} as T
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.festago.festago.databinding.FragmentMyPageBinding
import com.festago.festago.presentation.ui.FestagoViewModelFactory
import com.festago.festago.presentation.ui.home.HomeActivity
import com.festago.festago.presentation.ui.signin.SignInActivity
import com.festago.festago.presentation.ui.studentverification.StudentVerificationActivity
import com.festago.festago.presentation.ui.tickethistory.TicketHistoryActivity

class MyPageFragment : Fragment(R.layout.fragment_my_page) {
Expand Down Expand Up @@ -102,6 +103,10 @@ class MyPageFragment : Fragment(R.layout.fragment_my_page) {
binding.srlMyPage.setOnRefreshListener {
vm.loadUserInfo()
}
// TODO: 학교 선택 화면 변경 필요
binding.tvSchoolAuthorization.setOnClickListener {
startActivity(StudentVerificationActivity.getIntent(requireContext(), 1L))
}
}

private fun handleSuccess(uiState: MyPageUiState.Success) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.festago.festago.presentation.ui.studentverification

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.festago.festago.R
import com.festago.festago.databinding.ActivityStudentVerificationBinding
import com.festago.festago.presentation.ui.FestagoViewModelFactory
import com.festago.festago.presentation.util.repeatOnStarted
import java.time.LocalTime
import java.time.format.DateTimeFormatter

class StudentVerificationActivity : AppCompatActivity() {

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

private val vm: StudentVerificationViewModel by viewModels { FestagoViewModelFactory }

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

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

private fun initView() {
val schoolId = intent.getLongExtra(KEY_SCHOOL_ID, -1L)
vm.loadSchoolEmail(schoolId)
initRequestVerificationCodeBtn(schoolId)
}

private fun initRequestVerificationCodeBtn(schoolId: Long) {
binding.btnRequestVerificationCode.setOnClickListener {
vm.sendVerificationCode(binding.tieVerificationCode.text.toString(), schoolId)
}
}

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

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

private fun handleSuccess(uiState: StudentVerificationUiState.Success) {
binding.tvSchoolEmail.text =
getString(R.string.student_verification_tv_email_format, uiState.schoolEmail)

val format =
DateTimeFormatter.ofPattern(getString(R.string.student_verification_tv_timer_format))
binding.tvTimerVerificationCode.text = LocalTime.ofSecondOfDay(uiState.remainTime.toLong())
.format(format)

binding.btnVerificationConfirm.isEnabled = uiState.isValidateCode
}

private fun handleEvent(event: StudentVerificationEvent) {
when (event) {
is StudentVerificationEvent.VerificationSuccess -> Unit
is StudentVerificationEvent.VerificationFailure -> Unit
is StudentVerificationEvent.CodeTimeOut -> Unit
}
}

companion object {

private const val KEY_SCHOOL_ID = "KEY_SCHOOL_ID"

fun getIntent(context: Context, schoolId: Long): Intent {
return Intent(context, StudentVerificationActivity::class.java).apply {
putExtra(KEY_SCHOOL_ID, schoolId)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.festago.festago.presentation.ui.studentverification

sealed interface StudentVerificationEvent {
object CodeTimeOut : StudentVerificationEvent
object VerificationFailure : StudentVerificationEvent
object VerificationSuccess : StudentVerificationEvent
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.festago.festago.presentation.ui.studentverification

sealed interface StudentVerificationUiState {
object Loading : StudentVerificationUiState

data class Success(
val schoolEmail: String,
val remainTime: Int,
val isValidateCode: Boolean = false,
) : StudentVerificationUiState

object Error : StudentVerificationUiState

val shouldShowSuccess get() = this is Success
val shouldShowLoading get() = this is Loading
val shouldShowError get() = this is Error
}
Loading

0 comments on commit dbe8b39

Please sign in to comment.