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

SplashScreen 구현, 로그인 & 회원가입 시 토큰 저장 #93

Merged
merged 10 commits into from
Nov 21, 2023
7 changes: 5 additions & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
android:theme="@style/Theme.PriceGuard"
tools:targetApi="34">
<activity
android:name=".ui.intro.IntroActivity"
android:exported="true" >
android:name=".ui.splash.SplashScreenActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.intro.IntroActivity"
android:exported="false" />
<activity
android:name=".ui.login.LoginActivity"
android:exported="false" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class LoginActivity : AppCompatActivity() {
private fun initListener() {
with(binding) {
btnLoginLogin.setOnClickListener {
(binding.btnLoginLogin as MaterialButton).icon = getCircularProgressIndicatorDrawable(this@LoginActivity)
(binding.btnLoginLogin as MaterialButton).icon =
getCircularProgressIndicatorDrawable(this@LoginActivity)
}
btnLoginSignup.setOnClickListener {
gotoSignUp()
Expand All @@ -47,7 +48,8 @@ class LoginActivity : AppCompatActivity() {
loginViewModel.event.collect { eventType ->
when (eventType) {
LoginEvent.LoginStart -> {
(binding.btnLoginLogin as MaterialButton).icon = getCircularProgressIndicatorDrawable(this@LoginActivity)
(binding.btnLoginLogin as MaterialButton).icon =
getCircularProgressIndicatorDrawable(this@LoginActivity)
}

else -> {
Expand All @@ -73,7 +75,20 @@ class LoginActivity : AppCompatActivity() {
}

is LoginEvent.LoginSuccess -> {
gotoHome()
val response = eventType.response

if (response == null) {
showDialog(
getString(R.string.login_fail),
getString(R.string.login_fail_message)
)
} else {
loginViewModel.saveTokens(response.accessToken, response.refreshToken)
Taewan-P marked this conversation as resolved.
Show resolved Hide resolved
}
}

is LoginEvent.LoginInfoSaved -> {
gotoHomeActivity()
}

else -> {}
Expand All @@ -93,7 +108,7 @@ class LoginActivity : AppCompatActivity() {
startActivity(Intent(this, SignupActivity::class.java))
}

private fun gotoHome() {
private fun gotoHomeActivity() {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.priceguard.data.dto.LoginResponse
import app.priceguard.data.dto.LoginState
import app.priceguard.data.repository.TokenRepository
import app.priceguard.data.repository.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
Expand All @@ -18,7 +19,8 @@ import kotlinx.coroutines.launch

@HiltViewModel
class LoginViewModel @Inject constructor(
private val userRepository: UserRepository
private val userRepository: UserRepository,
private val tokenRepository: TokenRepository
) : ViewModel() {

data class State(
Expand All @@ -33,6 +35,7 @@ class LoginViewModel @Inject constructor(
data object Invalid : LoginEvent()
data class LoginSuccess(val response: LoginResponse?) : LoginEvent()
data class LoginFailure(val status: LoginState) : LoginEvent()
data object LoginInfoSaved : LoginEvent()
}

private val emailPattern =
Expand Down Expand Up @@ -87,6 +90,13 @@ class LoginViewModel @Inject constructor(
}
}

fun saveTokens(accessToken: String, refreshToken: String) {
viewModelScope.launch {
tokenRepository.storeTokens(accessToken, refreshToken)
sendLoginEvent(LoginEvent.LoginInfoSaved)
}
}

private suspend fun sendLoginEvent(event: LoginEvent) {
_event.emit(event)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,7 @@ class SignupActivity : AppCompatActivity() {
if (response == null) {
showDialog(getString(R.string.error), getString(R.string.undefined_error))
} else {
// TODO: DataStore에 저장하기
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)

// TODO: 뒤에 액티비티 스택 다 날리기
finish()
signupViewModel.saveTokens(response.accessToken, response.refreshToken)
Taewan-P marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -106,9 +100,20 @@ class SignupActivity : AppCompatActivity() {
else -> {}
}
}

SignupEvent.SignupInfoSaved -> {
gotoHomeActivity()
}
}
}

private fun gotoHomeActivity() {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
}

private fun setNavigationButton() {
binding.mtSignupTopbar.setNavigationOnClickListener {
finish()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.priceguard.data.dto.SignUpResponse
import app.priceguard.data.dto.SignUpState
import app.priceguard.data.repository.TokenRepository
import app.priceguard.data.repository.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
Expand All @@ -18,7 +19,8 @@ import kotlinx.coroutines.launch

@HiltViewModel
class SignupViewModel @Inject constructor(
private val userRepository: UserRepository
private val userRepository: UserRepository,
private val tokenRepository: TokenRepository
) : ViewModel() {

data class SignupUIState(
Expand All @@ -39,6 +41,7 @@ class SignupViewModel @Inject constructor(
data object SignupStart : SignupEvent()
data class SignupSuccess(val response: SignUpResponse?) : SignupEvent()
data class SignupFailure(val errorState: SignUpState) : SignupEvent()
data object SignupInfoSaved : SignupEvent()
}

private val emailPattern =
Expand All @@ -52,6 +55,13 @@ class SignupViewModel @Inject constructor(
private val _eventFlow: MutableSharedFlow<SignupEvent> = MutableSharedFlow(replay = 0)
val eventFlow: SharedFlow<SignupEvent> = _eventFlow.asSharedFlow()

fun saveTokens(accessToken: String, refreshToken: String) {
viewModelScope.launch {
tokenRepository.storeTokens(accessToken, refreshToken)
sendSignupEvent(SignupEvent.SignupInfoSaved)
}
}

fun signup() {
if (_state.value.isSignupStarted || _state.value.isSignupFinished) {
Log.d("Signup", "Signup already requested. Skipping")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package app.priceguard.ui.splash

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.lifecycleScope
import app.priceguard.R
import app.priceguard.data.repository.TokenRepository
import app.priceguard.databinding.ActivitySplashScreenBinding
import app.priceguard.ui.intro.IntroActivity
import app.priceguard.ui.main.MainActivity
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.launch

@SuppressLint("CustomSplashScreen")
@AndroidEntryPoint
class SplashScreenActivity : AppCompatActivity() {

@Inject
lateinit var tokenRepository: TokenRepository
private lateinit var binding: ActivitySplashScreenBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_splash_screen)

lifecycleScope.launch {
val accessToken = tokenRepository.getAccessToken()
val refreshToken = tokenRepository.getRefreshToken()

if (accessToken == null || refreshToken == null) {
// Intro Activity로 이동
tokenRepository.clearTokens()
launchActivityAndExit(this@SplashScreenActivity, IntroActivity::class.java)
return@launch
}

// TODO: Token 갱신하기

// Main Activity로 이동
launchActivityAndExit(this@SplashScreenActivity, MainActivity::class.java)
}
}

private fun <T : Activity> launchActivityAndExit(context: Context, clazz: Class<T>) {
val intent = Intent(context, clazz)
startActivity(intent)
finish()
}
}
23 changes: 23 additions & 0 deletions android/app/src/main/res/layout/activity_splash_screen.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
tools:context=".ui.splash.SplashScreenActivity">

<ImageView
android:id="@+id/iv_splash_logo"
android:layout_width="160dp"
android:layout_height="160dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:src="@drawable/ic_priceguard_original" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
8 changes: 8 additions & 0 deletions android/app/src/main/res/values-v31/styles.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.PriceGuard" parent="Base.Theme.PriceGuard">
<item name="android:windowSplashScreenBackground">@android:color/transparent</item>
<item name="android:windowSplashScreenAnimatedIcon">@android:color/transparent</item>
<item name="android:windowSplashScreenAnimationDuration">0</item>
</style>
</resources>