Skip to content

Commit

Permalink
Merge pull request #16 from gaeun5744/refactor/#40-source-dataStore
Browse files Browse the repository at this point in the history
Refactor/#40 source data store
  • Loading branch information
gaeun5744 authored Sep 28, 2023
2 parents 2afdfe1 + 9a70255 commit 3f1a6ea
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 30 deletions.
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ dependencies {
implementation "com.kakao.sdk:v2-share:2.15.0" // 메시지(카카오톡 공유)
//EncryptedSharedPreference
implementation "androidx.security:security-crypto-ktx:1.1.0-alpha06"
// dataStore
implementation "androidx.datastore:datastore-preferences:1.0.0"


}
// hilt dependency와 함께 추가
kapt {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.puzzling.puzzlingaos.data.datasource.local

import android.os.Build
import androidx.annotation.RequiresApi
import androidx.datastore.core.Serializer
import com.puzzling.puzzlingaos.data.entity.Token
import com.puzzling.puzzlingaos.data.service.CryptoService
import kotlinx.serialization.json.Json
import org.apache.commons.lang3.SerializationException
import java.io.InputStream
import java.io.OutputStream
import javax.inject.Inject

@RequiresApi(Build.VERSION_CODES.M)
class TokenDataSource @Inject constructor(private val cryptoService: CryptoService) : Serializer<Token> {
override val defaultValue: Token
get() = Token()

override suspend fun readFrom(input: InputStream): Token {
val decryptedBytes = cryptoService.decrypt(input)
return try {
Json.decodeFromString(
deserializer = Token.serializer(),
string = decryptedBytes.decodeToString(),
)
} catch (e: SerializationException) {
e.printStackTrace()
defaultValue
}
}

override suspend fun writeTo(t: Token, output: OutputStream) {
cryptoService.encrypt(
bytes = Json.encodeToString(
serializer = Token.serializer(),
value = t,
).encodeToByteArray(),
outputStream = output,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.puzzling.puzzlingaos.data.entity

import kotlinx.serialization.Serializable

@Serializable
data class Token(
val accessToken: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.puzzling.puzzlingaos.data.repository

import androidx.datastore.core.DataStore
import com.puzzling.puzzlingaos.data.entity.Token
import com.puzzling.puzzlingaos.domain.repository.TokenRepository
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class TokenRepositoryImpl @Inject constructor(private val dataStore: DataStore<Token>) :
TokenRepository {
override suspend fun setToken(token: String) {
dataStore.updateData { Token(token) }
}

override suspend fun getToken(): Token = dataStore.data.first()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.puzzling.puzzlingaos.data.service

import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.annotation.RequiresApi
import java.io.InputStream
import java.io.OutputStream
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import javax.inject.Inject

@RequiresApi(Build.VERSION_CODES.M)
class CryptoService @Inject constructor() {

private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}

private val encryptCipher = Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.ENCRYPT_MODE, getKey())
}

private fun getDecryptCipher(iv: ByteArray): Cipher {
return Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.DECRYPT_MODE, getKey(), IvParameterSpec(iv))
}
}

private fun getKey(): SecretKey {
val existingKey = keyStore.getEntry("secret", null) as? KeyStore.SecretKeyEntry
return existingKey?.secretKey ?: createKey()
}

private fun createKey(): SecretKey {
return KeyGenerator.getInstance(ALGORITHM).apply {
init(
KeyGenParameterSpec.Builder(
"secret",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT,
).setBlockModes(BLOCK_MODE).setEncryptionPaddings(
PADDING,
).setUserAuthenticationRequired(false) // 지문인식 같은거 false
.setRandomizedEncryptionRequired(true).build(),
)
}.generateKey()
}

fun encrypt(bytes: ByteArray, outputStream: OutputStream): ByteArray {
val encryptedBytes = encryptCipher.doFinal(bytes)
outputStream.use {
it.write(encryptCipher.iv.size)
it.write(encryptCipher.iv)
it.write(encryptedBytes.size)
it.write(encryptedBytes)
}

return encryptedBytes
}

fun decrypt(inputStream: InputStream): ByteArray {
return inputStream.use {
val ivSize = it.read()
val iv = ByteArray(ivSize)
it.read(iv)

val encryptedBytesSize = it.read()
val encryptedBytes = ByteArray(encryptedBytesSize)
it.read(encryptedBytes)

getDecryptCipher(iv).doFinal(encryptedBytes)
}
}

companion object {
private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package com.puzzling.puzzlingaos.data.service
import android.content.Context
import com.kakao.sdk.auth.model.OAuthToken
import com.kakao.sdk.user.UserApiClient
import dagger.hilt.android.qualifiers.ActivityContext
import javax.inject.Inject

class KakaoLoginService(private val context: Context) {
class KakaoAuthService @Inject constructor(@ActivityContext private val context: Context) {
fun startKakaoLogin(kakaoLoginCallBack: (OAuthToken?, Throwable?) -> Unit) {
val kakaoLoginState =
if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) {
Expand All @@ -20,6 +22,7 @@ class KakaoLoginService(private val context: Context) {
callback = kakaoLoginCallBack,
)
}

KAKAO_ACCOUNT_LOGIN -> {
UserApiClient.instance.loginWithKakaoAccount(
context,
Expand Down
35 changes: 35 additions & 0 deletions app/src/main/java/com/puzzling/puzzlingaos/di/DataStoreModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.puzzling.puzzlingaos.di

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.puzzling.puzzlingaos.data.datasource.local.TokenDataSource
import com.puzzling.puzzlingaos.data.entity.Token
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {
private const val USER_PREFERENCES_NAME = "user_preferences"
private const val DATA_STORE_FILE_NAME = "user_prefs.pb"

@Provides
@Singleton
fun providePreferencesDataStore(
@ApplicationContext appContext: Context,
tokenDataSource: TokenDataSource,
): DataStore<Token> {
return DataStoreFactory.create(
serializer = tokenDataSource,
produceFile = {
appContext.dataStoreFile(DATA_STORE_FILE_NAME)
},
)
}
}
16 changes: 6 additions & 10 deletions app/src/main/java/com/puzzling/puzzlingaos/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
package com.puzzling.puzzlingaos.di

import com.puzzling.puzzlingaos.data.repository.MyBoardRepositoryImpl
import com.puzzling.puzzlingaos.data.repository.MyPageRepositoryImpl
import com.puzzling.puzzlingaos.data.repository.ProjectRepositoryImpl
import com.puzzling.puzzlingaos.data.repository.TeamDashBoardRepositoryImpl
import com.puzzling.puzzlingaos.data.repository.WriteReviewRepositoryImpl
import com.puzzling.puzzlingaos.domain.repository.MyBoardRepository
import com.puzzling.puzzlingaos.domain.repository.MyPageRepository
import com.puzzling.puzzlingaos.domain.repository.ProjectRepository
import com.puzzling.puzzlingaos.domain.repository.TeamDashBoardRepository
import com.puzzling.puzzlingaos.domain.repository.WriteReviewRepository
import com.puzzling.puzzlingaos.data.repository.*
import com.puzzling.puzzlingaos.domain.repository.*
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand Down Expand Up @@ -38,4 +30,8 @@ abstract class RepositoryModule {
@Singleton
@Binds
abstract fun providesWriteReviewRepository(repoImpl: WriteReviewRepositoryImpl): WriteReviewRepository

@Singleton
@Binds
abstract fun providesTokenRepository(repoImpl: TokenRepositoryImpl): TokenRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.puzzling.puzzlingaos.domain.repository

import com.puzzling.puzzlingaos.data.entity.Token

interface TokenRepository {
suspend fun setToken(token: String)

suspend fun getToken(): Token
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.puzzling.puzzlingaos.domain.usecase.onboarding

import com.puzzling.puzzlingaos.domain.repository.TokenRepository
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class GetTokenUseCase @Inject constructor(
private val tokenRepository: TokenRepository,
) {

suspend operator fun invoke() = tokenRepository.getToken()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.puzzling.puzzlingaos.domain.usecase.onboarding

import com.puzzling.puzzlingaos.domain.repository.TokenRepository
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class PostTokenUseCase @Inject constructor(private val tokenRepository: TokenRepository) {
suspend operator fun invoke(token: String) = tokenRepository.setToken(token)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@ package com.puzzling.puzzlingaos.presentation.onboarding

import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.puzzling.puzzlingaos.R
import com.puzzling.puzzlingaos.base.BaseActivity
import com.puzzling.puzzlingaos.data.service.KakaoAuthService
import com.puzzling.puzzlingaos.databinding.ActivityLoginBinding
import com.puzzling.puzzlingaos.util.ViewModelFactory
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject

@AndroidEntryPoint
class LoginActivity : BaseActivity<ActivityLoginBinding>(R.layout.activity_login) {

private val viewModel: LoginViewModel by viewModels { ViewModelFactory(this) }
@Inject
lateinit var kakakoAuthService: KakaoAuthService

private val viewModel: LoginViewModel by viewModels()


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -24,13 +32,13 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(R.layout.activity_login

private fun startKakaoLogin() {
binding.ibLoginKakao.setOnClickListener {
viewModel.kakaoLogin()
kakakoAuthService.startKakaoLogin(viewModel.kakaoLoginCallback)
}
}

private fun isKakaoLoginSuccess() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isKakaoLogin.collect { isLoginSuccess ->
if (isLoginSuccess) {
val intent =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
package com.puzzling.puzzlingaos.presentation.onboarding

import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kakao.sdk.auth.model.OAuthToken
import com.puzzling.puzzlingaos.data.datasource.local.LocalDataSource
import com.puzzling.puzzlingaos.data.service.KakaoLoginService
import com.puzzling.puzzlingaos.domain.usecase.onboarding.GetTokenUseCase
import com.puzzling.puzzlingaos.domain.usecase.onboarding.PostTokenUseCase
import com.puzzling.puzzlingaos.util.KakaoLoginCallback
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject

class LoginViewModel(private val kakaoLoginService: KakaoLoginService) : ViewModel() {
@HiltViewModel
class LoginViewModel @Inject constructor(
private val postTokenUseCase: PostTokenUseCase,
private val getTokenUseCase: GetTokenUseCase,
) :
ViewModel() {
private val _isKakaoLogin = MutableStateFlow(false)
val isKakaoLogin = _isKakaoLogin.asStateFlow()

val kakaoLoginCallback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
KakaoLoginCallback {
_isKakaoLogin.value = true
Timber.d("토큰!!!! $token")
Log.d("LoginViewModel", "토큰!! $token")
LocalDataSource.setAccessToken("$token")
viewModelScope.launch {
postTokenUseCase.invoke(it)
Log.d("LoginViewModel", "토큰!! usecase ${getTokenUseCase.invoke()}")
}
}.handleResult(token, error)
}

fun kakaoLogin() = viewModelScope.launch {
kakaoLoginService.startKakaoLogin(kakaoLoginCallback)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package com.puzzling.puzzlingaos.util
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.puzzling.puzzlingaos.data.service.KakaoLoginService
import com.puzzling.puzzlingaos.presentation.invitationCode.InvitationCodeViewModel
import com.puzzling.puzzlingaos.presentation.onboarding.LoginViewModel

class ViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
Expand All @@ -14,10 +11,10 @@ class ViewModelFactory(private val context: Context) : ViewModelProvider.Factory
// InvitationCodeViewModel(context) as T
// }

modelClass.isAssignableFrom(LoginViewModel::class.java) -> {
val repository = KakaoLoginService(context)
LoginViewModel(repository) as T
}
// modelClass.isAssignableFrom(LoginViewModel::class.java) -> {
// val repository = KakaoAuthService(context)
// LoginViewModel(repository) as T
// }

else -> {
throw java.lang.IllegalArgumentException("Unknown ViewModel")
Expand Down

0 comments on commit 3f1a6ea

Please sign in to comment.