Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AN/USER] feat: 학생 인증 화면 구현 (#370) #429

Merged
merged 35 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c6dedda
feat: 학생 인증 레포지토리 정의
SeongHoonC Aug 24, 2023
f64f815
feat: 학교 레포지토리 임시 정의
SeongHoonC Aug 24, 2023
90ca80f
feat: 학교 인증 화면 뷰모델 구현
SeongHoonC Aug 24, 2023
306b1dc
test: 학생 인증 화면 뷰모델 테스트 작성
SeongHoonC Aug 25, 2023
fa3831d
refactor: LiveData stateFlow 로 리팩터링
SeongHoonC Sep 5, 2023
a36f8e1
feat: StudentVerificationActivity 생성
SeongHoonC Sep 5, 2023
b3a81da
feat: 학생 인증 화면 xml 그리기
SeongHoonC Sep 5, 2023
9b84313
feat: 재사용 가능한 문장 검증 로직 생성 및 테스트
SeongHoonC Sep 7, 2023
5b42940
feat: 학생 인증 검증 로직 생성 및 테스트
SeongHoonC Sep 7, 2023
024c3d2
feat: SchoolId Long 으로 변경
SeongHoonC Sep 7, 2023
1600176
feat: 인증 코드 확인 요청 기능 작성
SeongHoonC Sep 7, 2023
ee4175b
feat: 코드 형식 확인 인증
SeongHoonC Sep 7, 2023
cb765d4
feat: 임시 학교 인증 화면 이동
SeongHoonC Sep 7, 2023
5b853fc
refator: ktlint check
SeongHoonC Sep 7, 2023
4c50f49
refator: 클래스 및 변수 명 변경
SeongHoonC Sep 8, 2023
12faf1a
fix: 화면 회전 시 학교 이메일을 재요청하지 않음
SeongHoonC Sep 8, 2023
e8bd130
refactor: 사용하지않는 코드 주석 제거
SeongHoonC Sep 11, 2023
3b896e4
refactor: repeatOnStartedState util 추가
SeongHoonC Sep 11, 2023
9165d3b
refactor: StudentVerificationActivity 전체 리팩터링
SeongHoonC Sep 11, 2023
981fdfe
refactor: Students 를 Student 로 통일
SeongHoonC Sep 11, 2023
56056f4
refactor: State 성공 시 처리 함수 분리 및 네이밍 변경
SeongHoonC Sep 11, 2023
b4eface
refactor: 글자 30개까지 입력으로 변경
SeongHoonC Sep 11, 2023
56d7ca2
feat: sealed interface 학생 인증 이벤트 추가
SeongHoonC Sep 11, 2023
3743d5b
refactor: 학생 인증 ViewModel 리팩터링
SeongHoonC Sep 11, 2023
4dcf7c3
test: 학생 인증 이벤트 테스트 추가
SeongHoonC Sep 11, 2023
da5a951
refactor: 사용하지 않는 뷰 제거
SeongHoonC Sep 11, 2023
2c09605
test: 학생 인증 코드 예외 테스트 추가
SeongHoonC Sep 11, 2023
738f96e
feat: Event observing 추가
SeongHoonC Sep 11, 2023
e522752
refactor: 테스트 리팩터링
SeongHoonC Sep 12, 2023
917d18a
refactor: 테스트 네이밍 리팩터링
SeongHoonC Sep 12, 2023
c25372f
refactor: turbine 추가 및 sharedFlow 테스트 리팩터링
SeongHoonC Sep 13, 2023
f135fcd
refactor: Text 검증 테스트 네이밍 및 구조 리팩터링
SeongHoonC Sep 14, 2023
c2615df
refactor: TextValidator 생성 로직 통일
SeongHoonC Sep 14, 2023
84e963e
refactor: given when 함수 분리 및 테스트 리팩터링
SeongHoonC Sep 14, 2023
6e6ce87
refactor: seal interface 는 isExactlyInstanceOf 로 테스트
SeongHoonC Sep 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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