diff --git a/.github/workflows/pr_checker.yml b/.github/workflows/pr_checker.yml
index 6b9f85d0..ffe16277 100644
--- a/.github/workflows/pr_checker.yml
+++ b/.github/workflows/pr_checker.yml
@@ -43,8 +43,11 @@ jobs:
- name: Add Local Properties
env:
AUTH_BASE_URL: ${{ secrets.AUTH_BASE_URL }}
+ KAKAO_NATIVE_KEY: ${{ secrets.KAKAO_NATIVE_KEY }}
run: |
echo auth.base.url=\"$AUTH_BASE_URL\" >> ./local.properties
+ echo kakao.native.key=\"$KAKAO_NATIVE_KEY\" >> ./local.properties
+ echo kakaoNativeKey=$KAKAO_NATIVE_KEY >> ./local.properties
# - name: Access Firebase Service
# run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./app/google-services.json
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d3bd1606..47cddbf0 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -26,6 +26,13 @@ android {
"AUTH_BASE_URL",
gradleLocalProperties(rootDir).getProperty("auth.base.url")
)
+ buildConfigField(
+ "String",
+ "KAKAO_NATIVE_KEY",
+ gradleLocalProperties(rootDir).getProperty("kakao.native.key")
+ )
+
+ manifestPlaceholders["KAKAO_NATIVE_KEY"] = gradleLocalProperties(rootDir).getProperty("kakaoNativeKey")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -84,6 +91,8 @@ dependencies {
implementation(workManager)
implementation(hiltWorkManager)
implementation(exif)
+ implementation(dataStore)
+ implementation(dataStoreCore)
}
TestDependencies.run {
@@ -112,6 +121,7 @@ dependencies {
implementation(balloon)
implementation(lottie)
implementation(circleImageView)
+ implementation(kakaoLogin)
debugImplementation(flipper)
debugImplementation(flipperNetwork)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c182e214..db8c2188 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -51,6 +51,25 @@
android:exported="false"
android:screenOrientation="portrait" />
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/android/go/sopt/winey/WineyApplication.kt b/app/src/main/java/com/android/go/sopt/winey/WineyApplication.kt
index 3d63fb04..3d26f949 100644
--- a/app/src/main/java/com/android/go/sopt/winey/WineyApplication.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/WineyApplication.kt
@@ -1,6 +1,8 @@
package com.android.go.sopt.winey
import android.app.Application
+import com.android.go.sopt.winey.BuildConfig.KAKAO_NATIVE_KEY
+import com.kakao.sdk.common.KakaoSdk
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
@@ -9,5 +11,6 @@ class WineyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
+ KakaoSdk.init(this, KAKAO_NATIVE_KEY)
}
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/interceptor/AuthInterceptor.kt b/app/src/main/java/com/android/go/sopt/winey/data/interceptor/AuthInterceptor.kt
index f7450e0f..4ff7cea0 100644
--- a/app/src/main/java/com/android/go/sopt/winey/data/interceptor/AuthInterceptor.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/data/interceptor/AuthInterceptor.kt
@@ -1,26 +1,89 @@
package com.android.go.sopt.winey.data.interceptor
import android.content.Context
+import com.android.go.sopt.winey.BuildConfig.AUTH_BASE_URL
+import com.android.go.sopt.winey.data.model.remote.response.ResponseReIssueTokenDto
+import com.android.go.sopt.winey.data.model.remote.response.base.BaseResponse
+import com.android.go.sopt.winey.domain.repository.DataStoreRepository
import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.Json
import okhttp3.Interceptor
+import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
+import timber.log.Timber
import javax.inject.Inject
class AuthInterceptor @Inject constructor(
- @ApplicationContext context: Context
+ @ApplicationContext context: Context,
+ private val json: Json,
+ private val dataStoreRepository: DataStoreRepository
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
- val headerRequest = originalRequest.newBuilder()
- .addHeader(HEADER_TOKEN, USER_ID)
+ val headerRequest = originalRequest.newAuthBuilder()
.build()
- return chain.proceed(headerRequest)
+ val response = chain.proceed(headerRequest)
+
+ when (response.code) {
+ CODE_TOKEN_EXPIRED -> {
+ try {
+ val refreshTokenRequest = originalRequest.newBuilder().post("".toRequestBody())
+ .url("$AUTH_BASE_URL/auth/token")
+ .addHeader(REFRESH_TOKEN, runBlocking(Dispatchers.IO) { getRefreshToken() })
+ .build()
+ val refreshTokenResponse = chain.proceed(refreshTokenRequest)
+ Timber.e("리프레시 토큰 : $refreshTokenResponse")
+
+ if (refreshTokenResponse.isSuccessful) {
+ val responseToken = json.decodeFromString(
+ refreshTokenResponse.body?.string().toString()
+ ) as BaseResponse
+
+ if (responseToken.data != null) {
+ saveAccessToken(
+ responseToken.data.accessToken,
+ responseToken.data.refreshToken
+ )
+ }
+ refreshTokenResponse.close()
+ val newRequest = originalRequest.newAuthBuilder().build()
+ return chain.proceed(newRequest)
+ }
+ saveAccessToken("", "")
+ } catch (t: Throwable) {
+ Timber.e(t)
+ saveAccessToken("", "")
+ }
+ }
+ }
+ return response
}
+ private fun Request.newAuthBuilder() =
+ this.newBuilder().addHeader(HEADER_TOKEN, runBlocking(Dispatchers.IO) { getAccessToken() })
+
+ private suspend fun getAccessToken(): String {
+ return dataStoreRepository.getAccessToken().first() ?: ""
+ }
+
+ private suspend fun getRefreshToken(): String {
+ return dataStoreRepository.getAccessToken().first() ?: ""
+ }
+
+ private fun saveAccessToken(accessToken: String, refreshToken: String) =
+ runBlocking {
+ dataStoreRepository.saveAccessToken(accessToken, refreshToken)
+ }
+
companion object {
private const val HEADER_TOKEN = "accessToken"
- const val USER_ID = "1"
+ private const val CODE_TOKEN_EXPIRED = 401
+ private const val REFRESH_TOKEN = "refreshToken"
}
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/model/remote/request/RequestLoginDto.kt b/app/src/main/java/com/android/go/sopt/winey/data/model/remote/request/RequestLoginDto.kt
new file mode 100644
index 00000000..fb3da3f8
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/data/model/remote/request/RequestLoginDto.kt
@@ -0,0 +1,10 @@
+package com.android.go.sopt.winey.data.model.remote.request
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RequestLoginDto(
+ @SerialName("socialType")
+ val socialType: String
+)
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/model/remote/response/ResponseLoginDto.kt b/app/src/main/java/com/android/go/sopt/winey/data/model/remote/response/ResponseLoginDto.kt
new file mode 100644
index 00000000..804ca1ad
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/data/model/remote/response/ResponseLoginDto.kt
@@ -0,0 +1,16 @@
+package com.android.go.sopt.winey.data.model.remote.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponseLoginDto(
+ @SerialName("userId")
+ val userId: Int,
+ @SerialName("accessToken")
+ val accessToken: String,
+ @SerialName("refreshToken")
+ val refreshToken: String,
+ @SerialName("isRegistered")
+ val isRegistered: Boolean
+)
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/model/remote/response/ResponseReIssueTokenDto.kt b/app/src/main/java/com/android/go/sopt/winey/data/model/remote/response/ResponseReIssueTokenDto.kt
new file mode 100644
index 00000000..60a6fc49
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/data/model/remote/response/ResponseReIssueTokenDto.kt
@@ -0,0 +1,12 @@
+package com.android.go.sopt.winey.data.model.remote.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponseReIssueTokenDto(
+ @SerialName("accessToken")
+ val accessToken: String,
+ @SerialName("refreshToken")
+ val refreshToken: String
+)
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/android/go/sopt/winey/data/repository/AuthRepositoryImpl.kt
index 7bb59e9e..22d2f353 100644
--- a/app/src/main/java/com/android/go/sopt/winey/data/repository/AuthRepositoryImpl.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/data/repository/AuthRepositoryImpl.kt
@@ -1,8 +1,11 @@
package com.android.go.sopt.winey.data.repository
import com.android.go.sopt.winey.data.model.remote.request.RequestCreateGoalDto
+import com.android.go.sopt.winey.data.model.remote.request.RequestLoginDto
import com.android.go.sopt.winey.data.model.remote.request.RequestPostLikeDto
+import com.android.go.sopt.winey.data.model.remote.response.ResponseLoginDto
import com.android.go.sopt.winey.data.model.remote.response.ResponsePostWineyFeedDto
+import com.android.go.sopt.winey.data.model.remote.response.ResponseReIssueTokenDto
import com.android.go.sopt.winey.data.source.AuthDataSource
import com.android.go.sopt.winey.domain.entity.Goal
import com.android.go.sopt.winey.domain.entity.Like
@@ -17,9 +20,9 @@ import javax.inject.Inject
class AuthRepositoryImpl @Inject constructor(
private val authDataSource: AuthDataSource
) : AuthRepository {
- override suspend fun getUser(): Result =
+ override suspend fun getUser(): Result =
runCatching {
- authDataSource.getUser().data!!.toUser()
+ authDataSource.getUser().data?.toUser()
}
override suspend fun getWineyFeedList(page: Int): Result> =
@@ -46,18 +49,34 @@ class AuthRepositoryImpl @Inject constructor(
authDataSource.deleteFeed(feedId)
}
- override suspend fun postFeedLike(feedId: Int, requestPostLikeDto: RequestPostLikeDto): Result =
+ override suspend fun postFeedLike(
+ feedId: Int,
+ requestPostLikeDto: RequestPostLikeDto
+ ): Result =
runCatching {
authDataSource.postFeedLike(feedId, requestPostLikeDto).toLike()
}
- override suspend fun postCreateGoal(requestCreateGoalDto: RequestCreateGoalDto): Result =
+ override suspend fun postCreateGoal(requestCreateGoalDto: RequestCreateGoalDto): Result =
runCatching {
- authDataSource.postCreateGoal(requestCreateGoalDto).data!!.toGoal()
+ authDataSource.postCreateGoal(requestCreateGoalDto).data?.toGoal()
}
- override suspend fun getRecommendList(page: Int): Result> =
+ override suspend fun getRecommendList(page: Int): Result?> =
runCatching {
- authDataSource.getRecommendList(page).data!!.convertToRecommend()
+ authDataSource.getRecommendList(page).data?.convertToRecommend()
+ }
+
+ override suspend fun postLogin(
+ socialAccessToken: String,
+ requestLoginDto: RequestLoginDto
+ ): Result =
+ runCatching {
+ authDataSource.postLogin(socialAccessToken, requestLoginDto).data
+ }
+
+ override suspend fun postReIssueToken(refreshToken: String): Result =
+ runCatching {
+ authDataSource.postReIssueToken(refreshToken).data
}
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/repository/DataStoreRepositoryImpl.kt b/app/src/main/java/com/android/go/sopt/winey/data/repository/DataStoreRepositoryImpl.kt
new file mode 100644
index 00000000..4a56bdfb
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/data/repository/DataStoreRepositoryImpl.kt
@@ -0,0 +1,121 @@
+package com.android.go.sopt.winey.data.repository
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.emptyPreferences
+import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.stringPreferencesKey
+import com.android.go.sopt.winey.domain.entity.User
+import com.android.go.sopt.winey.domain.repository.DataStoreRepository
+import com.google.gson.Gson
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+import java.io.IOException
+import javax.inject.Inject
+
+class DataStoreRepositoryImpl @Inject constructor(
+ val datastore: DataStore
+) : DataStoreRepository {
+
+ override suspend fun saveSocialToken(socialAccessToken: String, socialRefreshToken: String) {
+ datastore.edit {
+ it[SOCIAL_ACCESS_TOKEN] = socialAccessToken
+ it[SOCIAL_REFRESH_TOKEN] = socialRefreshToken
+ }
+ }
+
+ override suspend fun getSocialAccessToken(): Flow {
+ return getStringValue(SOCIAL_ACCESS_TOKEN)
+ }
+
+ override suspend fun saveAccessToken(accessToken: String, refreshToken: String) {
+ datastore.edit {
+ it[ACCESS_TOKEN] = accessToken
+ it[REFRESH_TOKEN] = refreshToken
+ }
+ }
+
+ override suspend fun saveUserId(userId: Int) {
+ datastore.edit {
+ it[USER_ID] = userId
+ }
+ }
+
+ override suspend fun getAccessToken(): Flow {
+ return getStringValue(ACCESS_TOKEN)
+ }
+
+ override suspend fun getRefreshToken(): Flow {
+ return getStringValue(REFRESH_TOKEN)
+ }
+
+ override suspend fun getStringValue(key: Preferences.Key): Flow {
+ return datastore.data
+ .catch { exception ->
+ if (exception is IOException) {
+ exception.printStackTrace()
+ emit(emptyPreferences())
+ } else {
+ throw exception
+ }
+ }
+ .map {
+ it[key]
+ }
+ }
+
+ override suspend fun getUserId(): Flow {
+ return datastore.data
+ .catch { exception ->
+ if (exception is IOException) {
+ exception.printStackTrace()
+ emit(emptyPreferences())
+ } else {
+ throw exception
+ }
+ }
+ .map {
+ it[USER_ID]
+ }
+ }
+
+ override suspend fun saveUserInfo(userInfo: User?) {
+ datastore.edit {
+ val json = Gson().toJson(userInfo)
+ it[USER_INFO] = json
+ }
+ }
+
+ override suspend fun getUserInfo(): Flow {
+ return datastore.data
+ .catch { exception ->
+ if (exception is IOException) {
+ exception.printStackTrace()
+ emit(emptyPreferences())
+ } else {
+ throw exception
+ }
+ }
+ .map {
+ val json = it[USER_INFO]
+ try {
+ Gson().fromJson(json, User::class.java)
+ } catch (e: Exception) {
+ User()
+ }
+ }
+ }
+
+ companion object PreferencesKeys {
+ private val SOCIAL_ACCESS_TOKEN: Preferences.Key =
+ stringPreferencesKey("social_access_token")
+ private val SOCIAL_REFRESH_TOKEN: Preferences.Key =
+ stringPreferencesKey("social_refresh_token")
+ private val ACCESS_TOKEN: Preferences.Key = stringPreferencesKey("access_token")
+ private val REFRESH_TOKEN: Preferences.Key = stringPreferencesKey("refresh_token")
+ private val USER_ID: Preferences.Key = intPreferencesKey("user_id")
+ private val USER_INFO: Preferences.Key = stringPreferencesKey("user_info")
+ }
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/repository/KakaoLoginRepositoryImpl.kt b/app/src/main/java/com/android/go/sopt/winey/data/repository/KakaoLoginRepositoryImpl.kt
new file mode 100644
index 00000000..3f4a99a7
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/data/repository/KakaoLoginRepositoryImpl.kt
@@ -0,0 +1,23 @@
+package com.android.go.sopt.winey.data.repository
+
+import android.content.Context
+import com.android.go.sopt.winey.data.source.KakaoLoginDataSource
+import com.android.go.sopt.winey.domain.repository.KakaoLoginRepository
+import com.kakao.sdk.auth.model.OAuthToken
+import javax.inject.Inject
+
+class KakaoLoginRepositoryImpl @Inject constructor(
+ private val kakaoLoginDataSource: KakaoLoginDataSource
+) : KakaoLoginRepository {
+ override fun loginKakao(kakaoLoginCallBack: (OAuthToken?, Throwable?) -> Unit, context: Context) {
+ kakaoLoginDataSource.loginKakao(kakaoLoginCallBack, context)
+ }
+
+ override fun logoutKakao(kakaoLogoutCallBack: (Throwable?) -> Unit) {
+ kakaoLoginDataSource.logoutKakao(kakaoLogoutCallBack)
+ }
+
+ override fun deleteKakaoAccount(kakaoLogoutCallBack: (Throwable?) -> Unit) {
+ kakaoLoginDataSource.deleteKakaoAccount(kakaoLogoutCallBack)
+ }
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/service/AuthService.kt b/app/src/main/java/com/android/go/sopt/winey/data/service/AuthService.kt
index 3f80455b..c64493e7 100644
--- a/app/src/main/java/com/android/go/sopt/winey/data/service/AuthService.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/data/service/AuthService.kt
@@ -1,19 +1,23 @@
package com.android.go.sopt.winey.data.service
import com.android.go.sopt.winey.data.model.remote.request.RequestCreateGoalDto
+import com.android.go.sopt.winey.data.model.remote.request.RequestLoginDto
import com.android.go.sopt.winey.data.model.remote.request.RequestPostLikeDto
import com.android.go.sopt.winey.data.model.remote.response.ResponseCreateGoalDto
import com.android.go.sopt.winey.data.model.remote.response.ResponseGetRecommendListDto
import com.android.go.sopt.winey.data.model.remote.response.ResponseGetUserDto
import com.android.go.sopt.winey.data.model.remote.response.ResponseGetWineyFeedListDto
+import com.android.go.sopt.winey.data.model.remote.response.ResponseLoginDto
import com.android.go.sopt.winey.data.model.remote.response.ResponsePostLikeDto
import com.android.go.sopt.winey.data.model.remote.response.ResponsePostWineyFeedDto
+import com.android.go.sopt.winey.data.model.remote.response.ResponseReIssueTokenDto
import com.android.go.sopt.winey.data.model.remote.response.base.BaseResponse
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
+import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
@@ -23,7 +27,7 @@ import retrofit2.http.Query
interface AuthService {
@GET("user")
- suspend fun getUser(): BaseResponse
+ suspend fun getUser(): BaseResponse
@GET("feed")
suspend fun getWineyFeedList(
@@ -62,4 +66,15 @@ interface AuthService {
suspend fun deleteFeed(
@Path("feedId") feedId: Int
): BaseResponse
+
+ @POST("auth")
+ suspend fun postLogin(
+ @Header("Authorization") socialAccessToken: String,
+ @Body requestLoginDto: RequestLoginDto
+ ): BaseResponse
+
+ @POST("auth/token")
+ suspend fun postReIssueToken(
+ @Header("refreshToken") refreshToken: String
+ ): BaseResponse
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/service/KakaoLoginService.kt b/app/src/main/java/com/android/go/sopt/winey/data/service/KakaoLoginService.kt
new file mode 100644
index 00000000..cda7dc46
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/data/service/KakaoLoginService.kt
@@ -0,0 +1,10 @@
+package com.android.go.sopt.winey.data.service
+
+import android.content.Context
+import com.kakao.sdk.auth.model.OAuthToken
+
+interface KakaoLoginService {
+ fun loginKakao(kakaoLoginCallBack: (OAuthToken?, Throwable?) -> Unit, context: Context)
+ fun logoutKakao(kakaoLogoutCallBack: (Throwable?) -> Unit)
+ fun deleteKakaoAccount(kakaoLogoutCallBack: (Throwable?) -> Unit)
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/source/AuthDataSource.kt b/app/src/main/java/com/android/go/sopt/winey/data/source/AuthDataSource.kt
index c338870c..532fb32a 100644
--- a/app/src/main/java/com/android/go/sopt/winey/data/source/AuthDataSource.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/data/source/AuthDataSource.kt
@@ -1,13 +1,16 @@
package com.android.go.sopt.winey.data.source
import com.android.go.sopt.winey.data.model.remote.request.RequestCreateGoalDto
+import com.android.go.sopt.winey.data.model.remote.request.RequestLoginDto
import com.android.go.sopt.winey.data.model.remote.request.RequestPostLikeDto
import com.android.go.sopt.winey.data.model.remote.response.ResponseCreateGoalDto
import com.android.go.sopt.winey.data.model.remote.response.ResponseGetRecommendListDto
import com.android.go.sopt.winey.data.model.remote.response.ResponseGetUserDto
import com.android.go.sopt.winey.data.model.remote.response.ResponseGetWineyFeedListDto
+import com.android.go.sopt.winey.data.model.remote.response.ResponseLoginDto
import com.android.go.sopt.winey.data.model.remote.response.ResponsePostLikeDto
import com.android.go.sopt.winey.data.model.remote.response.ResponsePostWineyFeedDto
+import com.android.go.sopt.winey.data.model.remote.response.ResponseReIssueTokenDto
import com.android.go.sopt.winey.data.model.remote.response.base.BaseResponse
import com.android.go.sopt.winey.data.service.AuthService
import okhttp3.MultipartBody
@@ -17,7 +20,7 @@ import javax.inject.Inject
class AuthDataSource @Inject constructor(
private val authService: AuthService
) {
- suspend fun getUser(): BaseResponse = authService.getUser()
+ suspend fun getUser(): BaseResponse = authService.getUser()
suspend fun getWineyFeedList(page: Int): ResponseGetWineyFeedListDto =
authService.getWineyFeedList(page)
@@ -25,7 +28,10 @@ class AuthDataSource @Inject constructor(
suspend fun getMyFeedList(page: Int): ResponseGetWineyFeedListDto =
authService.getMyFeedList(page)
- suspend fun postFeedLike(feedId: Int, requestPostLikeDto: RequestPostLikeDto): ResponsePostLikeDto =
+ suspend fun postFeedLike(
+ feedId: Int,
+ requestPostLikeDto: RequestPostLikeDto
+ ): ResponsePostLikeDto =
authService.postFeedLike(feedId, requestPostLikeDto)
suspend fun postWineyFeedList(
@@ -39,6 +45,18 @@ class AuthDataSource @Inject constructor(
suspend fun getRecommendList(page: Int): BaseResponse =
authService.getRecommendList(page)
+
suspend fun deleteFeed(feedId: Int): BaseResponse =
authService.deleteFeed(feedId)
+
+ suspend fun postLogin(
+ socialAccessToken: String,
+ requestLoginDto: RequestLoginDto
+ ): BaseResponse =
+ authService.postLogin(socialAccessToken, requestLoginDto)
+
+ suspend fun postReIssueToken(
+ refreshToken: String
+ ): BaseResponse =
+ authService.postReIssueToken(refreshToken)
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/data/source/KakaoLoginDataSource.kt b/app/src/main/java/com/android/go/sopt/winey/data/source/KakaoLoginDataSource.kt
new file mode 100644
index 00000000..ee959f6e
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/data/source/KakaoLoginDataSource.kt
@@ -0,0 +1,19 @@
+package com.android.go.sopt.winey.data.source
+
+import android.content.Context
+import com.android.go.sopt.winey.data.service.KakaoLoginService
+import com.kakao.sdk.auth.model.OAuthToken
+import javax.inject.Inject
+
+class KakaoLoginDataSource @Inject constructor(
+ private val kakaoLoginService: KakaoLoginService
+) {
+ fun loginKakao(kakaoLoginCallBack: (OAuthToken?, Throwable?) -> Unit, context: Context) =
+ kakaoLoginService.loginKakao(kakaoLoginCallBack, context)
+
+ fun logoutKakao(kakaoLogoutCallBack: (Throwable?) -> Unit) =
+ kakaoLoginService.logoutKakao(kakaoLogoutCallBack)
+
+ fun deleteKakaoAccount(kakaoLogoutCallBack: (Throwable?) -> Unit) =
+ kakaoLoginService.deleteKakaoAccount(kakaoLogoutCallBack)
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/di/DataStoreModule.kt b/app/src/main/java/com/android/go/sopt/winey/di/DataStoreModule.kt
new file mode 100644
index 00000000..62941fa9
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/di/DataStoreModule.kt
@@ -0,0 +1,22 @@
+package com.android.go.sopt.winey.di
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.preferencesDataStore
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+object DataStoreModule {
+ private val Context.dataStore: DataStore by preferencesDataStore(name = "winey_data_store")
+
+ @Provides
+ fun provideDataStore(@ApplicationContext context: Context): DataStore {
+ return context.dataStore
+ }
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/di/KakaoModule.kt b/app/src/main/java/com/android/go/sopt/winey/di/KakaoModule.kt
new file mode 100644
index 00000000..2061569a
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/di/KakaoModule.kt
@@ -0,0 +1,16 @@
+package com.android.go.sopt.winey.di
+
+import com.kakao.sdk.user.UserApiClient
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object KakaoModule {
+ @Provides
+ @Singleton
+ fun provideKakaoApiClient(): UserApiClient = UserApiClient.instance
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/di/RepositoryModule.kt b/app/src/main/java/com/android/go/sopt/winey/di/RepositoryModule.kt
index 83cfaadc..ebfde822 100644
--- a/app/src/main/java/com/android/go/sopt/winey/di/RepositoryModule.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/di/RepositoryModule.kt
@@ -1,7 +1,11 @@
package com.android.go.sopt.winey.di
import com.android.go.sopt.winey.data.repository.AuthRepositoryImpl
+import com.android.go.sopt.winey.data.repository.DataStoreRepositoryImpl
+import com.android.go.sopt.winey.data.repository.KakaoLoginRepositoryImpl
import com.android.go.sopt.winey.domain.repository.AuthRepository
+import com.android.go.sopt.winey.domain.repository.DataStoreRepository
+import com.android.go.sopt.winey.domain.repository.KakaoLoginRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -16,4 +20,16 @@ abstract class RepositoryModule {
abstract fun bindsAuthRepository(
authRepository: AuthRepositoryImpl
): AuthRepository
+
+ @Singleton
+ @Binds
+ abstract fun bindsKakaoLoginRepository(
+ kakaoLoginRepository: KakaoLoginRepositoryImpl
+ ): KakaoLoginRepository
+
+ @Singleton
+ @Binds
+ abstract fun bindsDataStoreRepository(
+ dataStoreRepository: DataStoreRepositoryImpl
+ ): DataStoreRepository
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/di/ServiceModule.kt b/app/src/main/java/com/android/go/sopt/winey/di/ServiceModule.kt
index e13a6d8e..9f36e3b1 100644
--- a/app/src/main/java/com/android/go/sopt/winey/di/ServiceModule.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/di/ServiceModule.kt
@@ -1,6 +1,10 @@
package com.android.go.sopt.winey.di
+import android.content.Context
import com.android.go.sopt.winey.data.service.AuthService
+import com.android.go.sopt.winey.data.service.KakaoLoginService
+import com.kakao.sdk.auth.model.OAuthToken
+import com.kakao.sdk.user.UserApiClient
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -11,8 +15,54 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object ServiceModule {
+ private const val KAKAO_TALK_LOGIN = 0
+ private const val KAKAO_ACCOUNT_LOGIN = 1
+
@Provides
@Singleton
fun provideAuthService(retrofit: Retrofit): AuthService =
retrofit.create(AuthService::class.java)
+
+ @Provides
+ fun provideKakaoLoginService(
+ client: UserApiClient
+ ): KakaoLoginService {
+ return object : KakaoLoginService {
+ override fun loginKakao(
+ kakaoLoginCallBack: (OAuthToken?, Throwable?) -> Unit,
+ context: Context
+ ) {
+ val kakaoLoginState =
+ if (client.isKakaoTalkLoginAvailable(context)) {
+ KAKAO_TALK_LOGIN
+ } else {
+ KAKAO_ACCOUNT_LOGIN
+ }
+
+ when (kakaoLoginState) {
+ KAKAO_TALK_LOGIN -> {
+ client.loginWithKakaoTalk(
+ context,
+ callback = kakaoLoginCallBack
+ )
+ }
+
+ KAKAO_ACCOUNT_LOGIN -> {
+ client.loginWithKakaoAccount(
+ context,
+ callback = kakaoLoginCallBack
+ )
+ }
+ }
+ }
+
+ override fun logoutKakao(kakaoLogoutCallBack: (Throwable?) -> Unit) {
+ client.logout(kakaoLogoutCallBack)
+ }
+
+ override fun deleteKakaoAccount(kakaoLogoutCallBack: (Throwable?) -> Unit) {
+ client.unlink(kakaoLogoutCallBack)
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/domain/entity/Login.kt b/app/src/main/java/com/android/go/sopt/winey/domain/entity/Login.kt
new file mode 100644
index 00000000..2ddbe675
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/domain/entity/Login.kt
@@ -0,0 +1,8 @@
+package com.android.go.sopt.winey.domain.entity
+
+data class Login(
+ val userId: Int,
+ val accessToken: String,
+ val refreshToken: String,
+ val isRegistered: Boolean
+)
diff --git a/app/src/main/java/com/android/go/sopt/winey/domain/entity/ReIssueToken.kt b/app/src/main/java/com/android/go/sopt/winey/domain/entity/ReIssueToken.kt
new file mode 100644
index 00000000..cf5e6588
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/domain/entity/ReIssueToken.kt
@@ -0,0 +1,6 @@
+package com.android.go.sopt.winey.domain.entity
+
+data class ReIssueToken(
+ val accessToken: String,
+ val refreshToken: String
+)
diff --git a/app/src/main/java/com/android/go/sopt/winey/domain/entity/User.kt b/app/src/main/java/com/android/go/sopt/winey/domain/entity/User.kt
index 6d8ca287..421eb13f 100644
--- a/app/src/main/java/com/android/go/sopt/winey/domain/entity/User.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/domain/entity/User.kt
@@ -1,13 +1,13 @@
package com.android.go.sopt.winey.domain.entity
data class User(
- val nickname: String,
- val userLevel: String,
- val duringGoalAmount: Long,
- val duringGoalCount: Long,
- val targetMoney: Int,
- val targetDay: Int,
- val dday: Int,
- val isOver: Boolean,
- val isAttained: Boolean
+ val nickname: String = "",
+ val userLevel: String = "",
+ val duringGoalAmount: Long = 0,
+ val duringGoalCount: Long = 0,
+ val targetMoney: Int = 0,
+ val targetDay: Int = 0,
+ val dday: Int = 0,
+ val isOver: Boolean = false,
+ val isAttained: Boolean = false
)
diff --git a/app/src/main/java/com/android/go/sopt/winey/domain/repository/AuthRepository.kt b/app/src/main/java/com/android/go/sopt/winey/domain/repository/AuthRepository.kt
index 13c4e00a..951abdf3 100644
--- a/app/src/main/java/com/android/go/sopt/winey/domain/repository/AuthRepository.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/domain/repository/AuthRepository.kt
@@ -1,8 +1,11 @@
package com.android.go.sopt.winey.domain.repository
import com.android.go.sopt.winey.data.model.remote.request.RequestCreateGoalDto
+import com.android.go.sopt.winey.data.model.remote.request.RequestLoginDto
import com.android.go.sopt.winey.data.model.remote.request.RequestPostLikeDto
+import com.android.go.sopt.winey.data.model.remote.response.ResponseLoginDto
import com.android.go.sopt.winey.data.model.remote.response.ResponsePostWineyFeedDto
+import com.android.go.sopt.winey.data.model.remote.response.ResponseReIssueTokenDto
import com.android.go.sopt.winey.domain.entity.Goal
import com.android.go.sopt.winey.domain.entity.Like
import com.android.go.sopt.winey.domain.entity.Recommend
@@ -12,7 +15,7 @@ import okhttp3.MultipartBody
import okhttp3.RequestBody
interface AuthRepository {
- suspend fun getUser(): Result
+ suspend fun getUser(): Result
suspend fun getWineyFeedList(page: Int): Result>
@@ -25,8 +28,15 @@ interface AuthRepository {
requestMap: HashMap
): Result
- suspend fun postCreateGoal(requestCreateGoalDto: RequestCreateGoalDto): Result
+ suspend fun postCreateGoal(requestCreateGoalDto: RequestCreateGoalDto): Result
- suspend fun getRecommendList(page: Int): Result>
+ suspend fun getRecommendList(page: Int): Result?>
suspend fun deleteFeed(feedId: Int): Result
+
+ suspend fun postLogin(
+ socialAccessToken: String,
+ requestLoginDto: RequestLoginDto
+ ): Result
+
+ suspend fun postReIssueToken(refreshToken: String): Result
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/domain/repository/DataStoreRepository.kt b/app/src/main/java/com/android/go/sopt/winey/domain/repository/DataStoreRepository.kt
new file mode 100644
index 00000000..77ddf2c6
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/domain/repository/DataStoreRepository.kt
@@ -0,0 +1,27 @@
+package com.android.go.sopt.winey.domain.repository
+
+import androidx.datastore.preferences.core.Preferences
+import com.android.go.sopt.winey.domain.entity.User
+import kotlinx.coroutines.flow.Flow
+
+interface DataStoreRepository {
+ suspend fun saveSocialToken(socialAccessToken: String, socialRefreshToken: String)
+
+ suspend fun getSocialAccessToken(): Flow
+
+ suspend fun saveAccessToken(accessToken: String = "", refreshToken: String = "")
+
+ suspend fun saveUserId(userId: Int = 0)
+
+ suspend fun getAccessToken(): Flow
+
+ suspend fun getRefreshToken(): Flow
+
+ suspend fun getStringValue(key: Preferences.Key): Flow
+
+ suspend fun getUserId(): Flow
+
+ suspend fun saveUserInfo(userInfo: User?)
+
+ suspend fun getUserInfo(): Flow
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/domain/repository/KakaoLoginRepository.kt b/app/src/main/java/com/android/go/sopt/winey/domain/repository/KakaoLoginRepository.kt
new file mode 100644
index 00000000..c5be5d72
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/domain/repository/KakaoLoginRepository.kt
@@ -0,0 +1,12 @@
+package com.android.go.sopt.winey.domain.repository
+
+import android.content.Context
+import com.kakao.sdk.auth.model.OAuthToken
+
+interface KakaoLoginRepository {
+ fun loginKakao(kakaoLoginCallBack: (OAuthToken?, Throwable?) -> Unit, context: Context)
+
+ fun logoutKakao(kakaoLogoutCallBack: (Throwable?) -> Unit)
+
+ fun deleteKakaoAccount(kakaoLogoutCallBack: (Throwable?) -> Unit)
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/main/MainViewModel.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/main/MainViewModel.kt
index a247e440..39378ce3 100644
--- a/app/src/main/java/com/android/go/sopt/winey/presentation/main/MainViewModel.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/main/MainViewModel.kt
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android.go.sopt.winey.domain.entity.User
import com.android.go.sopt.winey.domain.repository.AuthRepository
+import com.android.go.sopt.winey.domain.repository.DataStoreRepository
import com.android.go.sopt.winey.util.view.UiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -16,10 +17,11 @@ import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
- private val authRepository: AuthRepository
+ private val authRepository: AuthRepository,
+ private val dataStoreRepository: DataStoreRepository
) : ViewModel() {
- private val _getUserState = MutableStateFlow>(UiState.Loading)
- val getUserState: StateFlow> get() = _getUserState.asStateFlow()
+ private val _getUserState = MutableStateFlow>(UiState.Loading)
+ val getUserState: StateFlow> = _getUserState.asStateFlow()
init {
getUser()
@@ -31,6 +33,7 @@ class MainViewModel @Inject constructor(
authRepository.getUser()
.onSuccess { response ->
+ dataStoreRepository.saveUserInfo(response)
_getUserState.value = UiState.Success(response)
Timber.e("메인뷰모델 성공")
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/main/feed/WineyFeedFragment.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/main/feed/WineyFeedFragment.kt
index bbe0cc3b..327d311f 100644
--- a/app/src/main/java/com/android/go/sopt/winey/presentation/main/feed/WineyFeedFragment.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/main/feed/WineyFeedFragment.kt
@@ -12,10 +12,10 @@ import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.go.sopt.winey.R
-import com.android.go.sopt.winey.data.interceptor.AuthInterceptor
import com.android.go.sopt.winey.databinding.FragmentWineyFeedBinding
import com.android.go.sopt.winey.domain.entity.User
import com.android.go.sopt.winey.domain.entity.WineyFeed
+import com.android.go.sopt.winey.domain.repository.DataStoreRepository
import com.android.go.sopt.winey.presentation.main.MainViewModel
import com.android.go.sopt.winey.presentation.main.feed.upload.UploadActivity
import com.android.go.sopt.winey.util.binding.BindingFragment
@@ -26,11 +26,14 @@ import com.android.go.sopt.winey.util.view.UiState
import com.android.go.sopt.winey.util.view.setOnSingleClickListener
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import timber.log.Timber
+import javax.inject.Inject
@AndroidEntryPoint
class WineyFeedFragment : BindingFragment(R.layout.fragment_winey_feed) {
@@ -39,6 +42,10 @@ class WineyFeedFragment : BindingFragment(R.layout.fra
private lateinit var wineyFeedDialogFragment: WineyFeedDialogFragment
private lateinit var wineyFeedAdapter: WineyFeedAdapter
private lateinit var wineyFeedHeaderAdapter: WineyFeedHeaderAdapter
+
+ @Inject
+ lateinit var dataStoreRepository: DataStoreRepository
+
private var totalPage = Int.MAX_VALUE
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -70,7 +77,7 @@ class WineyFeedFragment : BindingFragment(R.layout.fra
val menuDelete = popupMenu.menu.findItem(R.id.menu_delete)
val menuReport = popupMenu.menu.findItem(R.id.menu_report)
//TODO: 로그인 완료되면 리팩토링
- if (wineyFeed.userId == AuthInterceptor.USER_ID.toInt()) {
+ if (wineyFeed.userId == runBlocking { dataStoreRepository.getUserId().first() }) {
menuReport.isVisible = false
} else {
menuDelete.isVisible = false
@@ -142,7 +149,8 @@ class WineyFeedFragment : BindingFragment(R.layout.fra
mainViewModel.getUserState.collect { state ->
when (state) {
is UiState.Success -> {
- isGoalValid(state.data)
+ val data = dataStoreRepository.getUserInfo().firstOrNull()
+ isGoalValid(data)
}
is UiState.Failure -> {
@@ -155,8 +163,8 @@ class WineyFeedFragment : BindingFragment(R.layout.fra
}
}
- private fun isGoalValid(data: User) {
- if (data.isOver) {
+ private fun isGoalValid(data: User?) {
+ if (data?.isOver == true) {
wineyFeedDialogFragment = WineyFeedDialogFragment()
wineyFeedDialogFragment.show(parentFragmentManager, TAG_WINEYFEED_DIALOG)
} else {
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/MyPageFragment.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/MyPageFragment.kt
index 2f11bec3..9c98c8bf 100644
--- a/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/MyPageFragment.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/MyPageFragment.kt
@@ -8,10 +8,12 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.commit
import androidx.fragment.app.replace
+import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.android.go.sopt.winey.R
import com.android.go.sopt.winey.databinding.FragmentMyPageBinding
import com.android.go.sopt.winey.domain.entity.User
+import com.android.go.sopt.winey.domain.repository.DataStoreRepository
import com.android.go.sopt.winey.presentation.main.MainViewModel
import com.android.go.sopt.winey.presentation.main.mypage.myfeed.MyFeedFragment
import com.android.go.sopt.winey.util.binding.BindingFragment
@@ -19,23 +21,28 @@ import com.android.go.sopt.winey.util.fragment.snackBar
import com.android.go.sopt.winey.util.view.UiState
import com.android.go.sopt.winey.util.view.setOnSingleClickListener
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
@AndroidEntryPoint
class MyPageFragment : BindingFragment(R.layout.fragment_my_page) {
private val viewModel by activityViewModels()
+ @Inject
+ lateinit var dataStoreRepository: DataStoreRepository
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
init1On1ButtonClickListener()
initLevelHelpButtonClickListener()
initToMyFeedButtonClickListener()
setupGetUserState()
+ viewModel.getUser()
}
override fun onResume() {
super.onResume()
- viewModel.getUser()
}
private fun initToMyFeedButtonClickListener() {
@@ -46,7 +53,7 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
private fun init1On1ButtonClickListener() {
binding.clMypageTo1on1.setOnClickListener {
- val url = "https://open.kakao.com/o/s751Susf"
+ val url = ONE_ON_ONE_URL
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
}
@@ -60,30 +67,29 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
}
private fun setupGetUserState() {
- lifecycleScope.launch {
- viewModel.getUserState.collect { state ->
+ viewModel.getUserState.flowWithLifecycle(lifecycle).onEach { state ->
- when (state) {
- is UiState.Loading -> {
- }
+ when (state) {
+ is UiState.Loading -> {
+ }
- is UiState.Success -> {
- handleSuccessState(state.data)
- handleTargetModifyButtonState(state.data)
- }
+ is UiState.Success -> {
+ val data = dataStoreRepository.getUserInfo().firstOrNull()
+ handleSuccessState(data)
+ handleTargetModifyButtonState(data)
+ }
- is UiState.Failure -> {
- snackBar(binding.root) { state.msg }
- }
+ is UiState.Failure -> {
+ snackBar(binding.root) { state.msg }
+ }
- is UiState.Empty -> {
- }
+ is UiState.Empty -> {
}
}
- }
+ }.launchIn(lifecycleScope)
}
- private fun handleTargetModifyButtonState(data: User) {
+ private fun handleTargetModifyButtonState(data: User?) {
binding.btnMypageTargetModify.setOnSingleClickListener {
val bottomSheet = TargetAmountBottomSheetFragment()
bottomSheet.show(this.childFragmentManager, bottomSheet.tag)
@@ -101,10 +107,10 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
}
}
- private fun handleSuccessState(data: User) {
+ private fun handleSuccessState(data: User?) {
binding.data = data
- when (data.isOver) {
+ when (data?.isOver) {
true -> {
binding.tvMypageTargetAmount.text = getString(R.string.mypage_not_yet_set)
binding.tvMypagePeriodValue.text = getString(R.string.mypage_not_yet_set)
@@ -119,8 +125,11 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
binding.dday = data
}
}
+
+ null -> {
+ }
}
- when (data.userLevel) {
+ when (data?.userLevel) {
LEVEL_COMMON -> {
binding.ivMypageProgressbar.setImageResource(R.drawable.ic_mypage_lv1_progressbar)
binding.ivMypageProfile.setImageResource(R.drawable.ic_mypage_lv1_profile)
@@ -157,5 +166,6 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
private const val LEVEL_KNIGHT = "기사"
private const val LEVEL_NOBLESS = "귀족"
private const val LEVEL_KING = "황제"
+ private const val ONE_ON_ONE_URL = "https://open.kakao.com/o/s751Susf"
}
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/TargetAmountBottomSheetFragment.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/TargetAmountBottomSheetFragment.kt
index f0c11869..d504faa0 100644
--- a/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/TargetAmountBottomSheetFragment.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/TargetAmountBottomSheetFragment.kt
@@ -9,7 +9,7 @@ import android.view.ViewGroup
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
-import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.flowWithLifecycle
import com.android.go.sopt.winey.R
import com.android.go.sopt.winey.databinding.FragmentTargetAmountBottomSheetBinding
import com.android.go.sopt.winey.presentation.main.MainViewModel
@@ -17,10 +17,13 @@ import com.android.go.sopt.winey.util.binding.BindingBottomSheetDialogFragment
import com.android.go.sopt.winey.util.context.colorOf
import com.android.go.sopt.winey.util.context.hideKeyboard
import com.android.go.sopt.winey.util.fragment.snackBar
+import com.android.go.sopt.winey.util.fragment.viewLifeCycle
+import com.android.go.sopt.winey.util.fragment.viewLifeCycleScope
import com.android.go.sopt.winey.util.view.UiState
import com.google.android.material.bottomsheet.BottomSheetBehavior
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import java.text.DecimalFormat
@AndroidEntryPoint
@@ -56,26 +59,24 @@ class TargetAmountBottomSheetFragment :
}
fun initCreateGoalObserver() {
- lifecycleScope.launch {
- viewModel.createGoalState.collect {
- when (it) {
- is UiState.Loading -> {
- }
+ viewModel.createGoalState.flowWithLifecycle(viewLifeCycle).onEach {
+ when (it) {
+ is UiState.Loading -> {
+ }
- is UiState.Success -> {
- mainViewModel.getUser()
- this@TargetAmountBottomSheetFragment.dismiss()
- }
+ is UiState.Success -> {
+ mainViewModel.getUser()
+ this@TargetAmountBottomSheetFragment.dismiss()
+ }
- is UiState.Failure -> {
- snackBar(binding.root) { it.msg }
- }
+ is UiState.Failure -> {
+ snackBar(binding.root) { it.msg }
+ }
- is UiState.Empty -> {
- }
+ is UiState.Empty -> {
}
}
- }
+ }.launchIn(viewLifeCycleScope)
}
fun initCancelButtonClickListener() {
@@ -141,75 +142,69 @@ class TargetAmountBottomSheetFragment :
}
private fun initAmountCheckObserver() {
- lifecycleScope.launch {
- viewModel.amountCheck.collect {
- when (it) {
- true -> {
- binding.tilTargetAmountSetAmount.error = " "
- binding.etTargetAmountSetAmount.setTextColor(
- requireContext().colorOf(R.color.red_500)
- )
- binding.tvTargetAmountWarningAmount.setTextColor(
- requireContext().colorOf(R.color.red_500)
- )
- }
+ viewModel.amountCheck.flowWithLifecycle(viewLifeCycle).onEach {
+ when (it) {
+ true -> {
+ binding.tilTargetAmountSetAmount.error = " "
+ binding.etTargetAmountSetAmount.setTextColor(
+ requireContext().colorOf(R.color.red_500)
+ )
+ binding.tvTargetAmountWarningAmount.setTextColor(
+ requireContext().colorOf(R.color.red_500)
+ )
+ }
- false -> {
- binding.tilTargetAmountSetAmount.error = null
- binding.etTargetAmountSetAmount.setTextColor(
- requireContext().colorOf(R.color.purple_400)
- )
- binding.tvTargetAmountWarningAmount.setTextColor(
- requireContext().colorOf(R.color.gray_400)
- )
- }
+ false -> {
+ binding.tilTargetAmountSetAmount.error = null
+ binding.etTargetAmountSetAmount.setTextColor(
+ requireContext().colorOf(R.color.purple_400)
+ )
+ binding.tvTargetAmountWarningAmount.setTextColor(
+ requireContext().colorOf(R.color.gray_400)
+ )
}
}
- }
+ }.launchIn(viewLifeCycleScope)
}
private fun initDayCheckObserver() {
- lifecycleScope.launch {
- viewModel.dayCheck.collect {
- when (it) {
- true -> {
- binding.tilTargetAmountSetDay.error = " "
- binding.etTargetAmountSetDay.setTextColor(
- requireContext().colorOf(R.color.red_500)
- )
- binding.tvTargetAmountWarningDay.setTextColor(
- requireContext().colorOf(R.color.red_500)
- )
- }
+ viewModel.dayCheck.flowWithLifecycle(viewLifeCycle).onEach {
+ when (it) {
+ true -> {
+ binding.tilTargetAmountSetDay.error = " "
+ binding.etTargetAmountSetDay.setTextColor(
+ requireContext().colorOf(R.color.red_500)
+ )
+ binding.tvTargetAmountWarningDay.setTextColor(
+ requireContext().colorOf(R.color.red_500)
+ )
+ }
- false -> {
- binding.tilTargetAmountSetDay.error = null
- binding.etTargetAmountSetDay.setTextColor(
- requireContext().colorOf(R.color.purple_400)
- )
- binding.tvTargetAmountWarningDay.setTextColor(
- requireContext().colorOf(R.color.gray_400)
- )
- }
+ false -> {
+ binding.tilTargetAmountSetDay.error = null
+ binding.etTargetAmountSetDay.setTextColor(
+ requireContext().colorOf(R.color.purple_400)
+ )
+ binding.tvTargetAmountWarningDay.setTextColor(
+ requireContext().colorOf(R.color.gray_400)
+ )
}
}
- }
+ }.launchIn(viewLifeCycleScope)
}
private fun initButtonStateCheckObserver() {
- lifecycleScope.launch {
- viewModel.buttonStateCheck.collect {
- when (it) {
- true -> {
- binding.btnTargetAmountSave.isEnabled = true
- }
+ viewModel.buttonStateCheck.flowWithLifecycle(viewLifeCycle).onEach {
+ when (it) {
+ true -> {
+ binding.btnTargetAmountSave.isEnabled = true
+ }
- false -> {
- binding.btnTargetAmountSave.isEnabled = false
- }
+ false -> {
+ binding.btnTargetAmountSave.isEnabled = false
}
}
- }
+ }.launchIn(viewLifeCycleScope)
}
private fun makeCommaString(input: Long): String {
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/TargetAmountViewModel.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/TargetAmountViewModel.kt
index 77756233..a97e825a 100644
--- a/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/TargetAmountViewModel.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/main/mypage/TargetAmountViewModel.kt
@@ -28,16 +28,16 @@ class TargetAmountViewModel @Inject constructor(
val day: LiveData get() = _day
private val _amountCheck = MutableStateFlow(false)
- val amountCheck: StateFlow get() = _amountCheck.asStateFlow()
+ val amountCheck: StateFlow = _amountCheck.asStateFlow()
private val _dayCheck = MutableStateFlow(false)
- val dayCheck: StateFlow get() = _dayCheck.asStateFlow()
+ val dayCheck: StateFlow = _dayCheck.asStateFlow()
private val _buttonStatecheck = MutableStateFlow(false)
- val buttonStateCheck: StateFlow get() = _buttonStatecheck.asStateFlow()
+ val buttonStateCheck: StateFlow = _buttonStatecheck.asStateFlow()
- private val _createGoalState = MutableStateFlow>(UiState.Empty)
- val createGoalState: StateFlow> get() = _createGoalState.asStateFlow()
+ private val _createGoalState = MutableStateFlow>(UiState.Empty)
+ val createGoalState: StateFlow> = _createGoalState.asStateFlow()
fun postCreateGoal() {
val money: Any? = amount.value
@@ -93,7 +93,7 @@ class TargetAmountViewModel @Inject constructor(
val day = _day.value
val amount = _amount.value
_buttonStatecheck.value =
- !day.isNullOrEmpty() && !amount.isNullOrEmpty() && _dayCheck.value && _amountCheck.value
+ !day.isNullOrEmpty() && !amount.isNullOrEmpty() && !_dayCheck.value && !_amountCheck.value
}
companion object {
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/main/recommend/RecommendFragment.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/main/recommend/RecommendFragment.kt
index 64e4390d..77ea8ea6 100644
--- a/app/src/main/java/com/android/go/sopt/winey/presentation/main/recommend/RecommendFragment.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/main/recommend/RecommendFragment.kt
@@ -3,6 +3,7 @@ package com.android.go.sopt.winey.presentation.main.recommend
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
+import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter
import com.android.go.sopt.winey.R
@@ -12,7 +13,8 @@ import com.android.go.sopt.winey.util.binding.BindingFragment
import com.android.go.sopt.winey.util.fragment.snackBar
import com.android.go.sopt.winey.util.view.UiState
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
@AndroidEntryPoint
class RecommendFragment : BindingFragment(R.layout.fragment_recommend) {
@@ -35,24 +37,22 @@ class RecommendFragment : BindingFragment(R.layout.fra
}
private fun getRecommendListStateObserver() {
- lifecycleScope.launch {
- viewModel.getRecommendListState.collect { state ->
- when (state) {
- is UiState.Loading -> {
- }
-
- is UiState.Success -> {
- recommendAdapter.submitList(state.data)
- }
-
- is UiState.Failure -> {
- snackBar(binding.root) { state.msg }
- }
-
- is UiState.Empty -> {
- }
+ viewModel.getRecommendListState.flowWithLifecycle(lifecycle).onEach { state ->
+ when (state) {
+ is UiState.Loading -> {
+ }
+
+ is UiState.Success -> {
+ recommendAdapter.submitList(state.data)
+ }
+
+ is UiState.Failure -> {
+ snackBar(binding.root) { state.msg }
+ }
+
+ is UiState.Empty -> {
}
}
- }
+ }.launchIn(lifecycleScope)
}
}
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/main/recommend/RecommendViewModel.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/main/recommend/RecommendViewModel.kt
index 570825bd..ad70953d 100644
--- a/app/src/main/java/com/android/go/sopt/winey/presentation/main/recommend/RecommendViewModel.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/main/recommend/RecommendViewModel.kt
@@ -18,8 +18,8 @@ import javax.inject.Inject
class RecommendViewModel @Inject constructor(
private val authRepository: AuthRepository
) : ViewModel() {
- private val _getRecommendListState = MutableStateFlow>>(UiState.Loading)
- val getRecommendListState: StateFlow>> =
+ private val _getRecommendListState = MutableStateFlow?>>(UiState.Loading)
+ val getRecommendListState: StateFlow?>> =
_getRecommendListState.asStateFlow()
init {
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/KakaoLoginCallback.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/KakaoLoginCallback.kt
new file mode 100644
index 00000000..aa22fab8
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/KakaoLoginCallback.kt
@@ -0,0 +1,71 @@
+package com.android.go.sopt.winey.presentation.onboarding
+
+import com.kakao.sdk.auth.model.OAuthToken
+import com.kakao.sdk.common.model.AuthErrorCause
+import com.kakao.sdk.common.model.ClientError
+import com.kakao.sdk.common.model.ClientErrorCause
+import timber.log.Timber
+
+class KakaoLoginCallback(private val onSuccess: (accessToken: String) -> Unit) {
+ fun handleResult(token: OAuthToken?, error: Throwable?) {
+ if (error != null) {
+ when {
+ error.toString() == AuthErrorCause.AccessDenied.toString() -> {
+ Timber.e(error, ACCESS_DENIED)
+ }
+
+ error.toString() == AuthErrorCause.InvalidClient.toString() -> {
+ Timber.e(error, INVALID_CLIENT)
+ }
+
+ error.toString() == AuthErrorCause.InvalidGrant.toString() -> {
+ Timber.e(error, INVALID_GRANT)
+ }
+
+ error.toString() == AuthErrorCause.InvalidRequest.toString() -> {
+ Timber.e(error, INVALID_REQUEST)
+ }
+
+ error.toString() == AuthErrorCause.InvalidScope.toString() -> {
+ Timber.e(error, INVALID_SCOPE)
+ }
+
+ error.toString() == AuthErrorCause.Misconfigured.toString() -> {
+ Timber.e(error, MISCONFIGURED)
+ }
+
+ error.toString() == AuthErrorCause.ServerError.toString() -> {
+ Timber.e(error, SERVER_ERROR)
+ }
+
+ error.toString() == AuthErrorCause.Unauthorized.toString() -> {
+ Timber.e(error, UNAUTHORIZED)
+ }
+
+ error is ClientError && error.reason == ClientErrorCause.Cancelled -> {
+ Timber.e(error, CANCELLED)
+ }
+
+ else -> { //
+ Timber.e(error, ELSE)
+ }
+ }
+ } else if (token != null) {
+ Timber.d(ON_SUCCESS)
+ onSuccess(token.accessToken)
+ }
+ }
+ companion object {
+ private const val ACCESS_DENIED = "접근이 거부 됨(동의 취소)"
+ private const val INVALID_CLIENT = "유효하지 않은 앱"
+ private const val INVALID_GRANT = "인증 수단이 유효하지 않아 인증할 수 없는 상태"
+ private const val INVALID_REQUEST = "요청 파라미터 오류"
+ private const val INVALID_SCOPE = "유효하지 않은 scope ID"
+ private const val MISCONFIGURED = "설정이 올바르지 않음(android key hash)"
+ private const val SERVER_ERROR = "서버 내부 에러"
+ private const val UNAUTHORIZED = "앱이 요청 권한이 없음"
+ private const val CANCELLED = "의도적인 로그인 취소로 보고 카카오계정으로 로그인 시도 없이 로그인 취소로 처리"
+ private const val ELSE = "기타 에러"
+ private const val ON_SUCCESS = "로그인 성공"
+ }
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginActivity.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginActivity.kt
new file mode 100644
index 00000000..253a04c3
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginActivity.kt
@@ -0,0 +1,64 @@
+package com.android.go.sopt.winey.presentation.onboarding
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.android.go.sopt.winey.R
+import com.android.go.sopt.winey.databinding.ActivityLoginBinding
+import com.android.go.sopt.winey.presentation.main.MainActivity
+import com.android.go.sopt.winey.util.binding.BindingActivity
+import com.android.go.sopt.winey.util.context.snackBar
+import com.android.go.sopt.winey.util.view.UiState
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@AndroidEntryPoint
+class LoginActivity :
+ BindingActivity(R.layout.activity_login) {
+ val viewModel: LoginViewModel by viewModels()
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ initKakaoLoginButtonClickListener()
+ initLoginObserver()
+ }
+
+ private fun initKakaoLoginButtonClickListener() {
+ binding.btnLoginKakao.setOnClickListener {
+ viewModel.loginKakao(this)
+ }
+ }
+
+ private fun initLoginObserver() {
+ viewModel.loginState.flowWithLifecycle(lifecycle).onEach { state ->
+ when (state) {
+ is UiState.Loading -> {
+ binding.btnLoginKakao.isEnabled = false
+ }
+
+ is UiState.Success -> {
+ if (state.data?.isRegistered == true) {
+ val intent = Intent(this@LoginActivity, MainActivity::class.java)
+ startActivity(intent)
+ finish()
+ } else {
+ //TODO : isRegistered false일경우 닉네임 설정화면으로
+ val intent = Intent(this@LoginActivity, MainActivity::class.java)
+ startActivity(intent)
+ finish()
+ }
+ }
+
+ is UiState.Failure -> {
+ snackBar(binding.root) { state.msg }
+ }
+
+ is UiState.Empty -> {
+ }
+ }
+ }.launchIn(lifecycleScope)
+ }
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginViewModel.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginViewModel.kt
new file mode 100644
index 00000000..bff76e53
--- /dev/null
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/onboarding/LoginViewModel.kt
@@ -0,0 +1,110 @@
+package com.android.go.sopt.winey.presentation.onboarding
+
+import android.content.Context
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.android.go.sopt.winey.data.model.remote.request.RequestLoginDto
+import com.android.go.sopt.winey.data.model.remote.response.ResponseLoginDto
+import com.android.go.sopt.winey.domain.repository.AuthRepository
+import com.android.go.sopt.winey.domain.repository.DataStoreRepository
+import com.android.go.sopt.winey.domain.repository.KakaoLoginRepository
+import com.android.go.sopt.winey.util.view.UiState
+import com.kakao.sdk.auth.model.OAuthToken
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import retrofit2.HttpException
+import timber.log.Timber
+import javax.inject.Inject
+
+@HiltViewModel
+class LoginViewModel @Inject constructor(
+ private val kakaoLoginRepository: KakaoLoginRepository,
+ private val authRepository: AuthRepository,
+ private val dataStoreRepository: DataStoreRepository
+) : ViewModel() {
+ private val _isKakaoLogin = MutableStateFlow(false)
+ val isKakaoLogin = _isKakaoLogin.asStateFlow()
+
+ private val _loginState = MutableStateFlow>(UiState.Empty)
+ val loginState: StateFlow> = _loginState.asStateFlow()
+
+ val kakaoLoginCallback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
+ KakaoLoginCallback {
+ _isKakaoLogin.value = true
+ Timber.d("액세스토큰 ${token?.accessToken}")
+ Timber.d("리프레시토큰 ${token?.refreshToken}")
+ if (token != null) {
+ saveSocialToken(token.accessToken, token.refreshToken)
+ postLogin(token.accessToken, KAKAO)
+ } else {
+ Timber.e("token is null")
+ }
+ }.handleResult(token, error)
+ }
+
+ fun loginKakao(context: Context) = viewModelScope.launch {
+ kakaoLoginRepository.loginKakao(kakaoLoginCallback, context)
+ }
+
+ fun postLogin(socialToken: String, socialType: String) {
+ viewModelScope.launch {
+ _loginState.value = UiState.Loading
+
+ authRepository.postLogin(socialToken, RequestLoginDto(socialType))
+ .onSuccess { response ->
+ Timber.e("로그인 성공")
+ if (response != null) {
+ saveAccessToken(response.accessToken, response.refreshToken)
+ saveUserId(response.userId)
+ _loginState.value = UiState.Success(response)
+ } else {
+ Timber.e("response is null")
+ }
+ }
+ .onFailure { t ->
+ if (t is HttpException) {
+ Timber.e("HTTP 실패 ${t.code()}, ${t.message()}")
+ }
+ Timber.e("${t.message}")
+ _loginState.value = UiState.Failure("${t.message}")
+ }
+ }
+ }
+
+ fun saveSocialToken(socialAccessToken: String, socialRefreshToken: String) =
+ viewModelScope.launch(Dispatchers.IO) {
+ dataStoreRepository.saveSocialToken(socialAccessToken, socialRefreshToken)
+ }
+
+ suspend fun getSocialToken() = withContext(Dispatchers.IO) {
+ dataStoreRepository.getSocialAccessToken().first()
+ }
+
+ fun saveAccessToken(accessToken: String, refreshToken: String) =
+ viewModelScope.launch(Dispatchers.IO) {
+ dataStoreRepository.saveAccessToken(accessToken, refreshToken)
+ }
+
+ fun saveUserId(userId: Int) =
+ viewModelScope.launch(Dispatchers.IO) {
+ dataStoreRepository.saveUserId(userId)
+ }
+
+ suspend fun getAccessToken() = withContext(Dispatchers.IO) {
+ dataStoreRepository.getAccessToken().first()
+ }
+
+ suspend fun getRefreshToken() = withContext(Dispatchers.IO) {
+ dataStoreRepository.getRefreshToken().first()
+ }
+
+ companion object {
+ private const val KAKAO = "KAKAO"
+ }
+}
diff --git a/app/src/main/java/com/android/go/sopt/winey/presentation/splash/SplashActivity.kt b/app/src/main/java/com/android/go/sopt/winey/presentation/splash/SplashActivity.kt
index ff85ca41..99fd9617 100644
--- a/app/src/main/java/com/android/go/sopt/winey/presentation/splash/SplashActivity.kt
+++ b/app/src/main/java/com/android/go/sopt/winey/presentation/splash/SplashActivity.kt
@@ -5,23 +5,47 @@ import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.android.go.sopt.winey.R
import com.android.go.sopt.winey.databinding.ActivitySplashBinding
+import com.android.go.sopt.winey.domain.repository.DataStoreRepository
import com.android.go.sopt.winey.presentation.main.MainActivity
+import com.android.go.sopt.winey.presentation.onboarding.LoginActivity
import com.android.go.sopt.winey.util.binding.BindingActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import javax.inject.Inject
@AndroidEntryPoint
class SplashActivity : BindingActivity(R.layout.activity_splash) {
+ @Inject
+ lateinit var dataStoreRepository: DataStoreRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
delay(DELAY_TIME)
+ checkAutoLogin()
+ }
+ }
+
+ private fun checkAutoLogin() {
+ val accessToken = runBlocking { dataStoreRepository.getAccessToken().firstOrNull() }
+ if (accessToken.isNullOrBlank()) {
+ navigateToOnBoardingScreen()
+ } else {
navigateToMainScreen()
}
}
+ private fun navigateToOnBoardingScreen() {
+ Intent(this@SplashActivity, LoginActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ startActivity(this)
+ }
+ finish()
+ }
+
private fun navigateToMainScreen() {
Intent(this@SplashActivity, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
diff --git a/app/src/main/res/drawable/ic_login_kakao.xml b/app/src/main/res/drawable/ic_login_kakao.xml
new file mode 100644
index 00000000..9d3c0ed3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_login_kakao.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_login_saver.xml b/app/src/main/res/drawable/ic_login_saver.xml
new file mode 100644
index 00000000..1a9ba337
--- /dev/null
+++ b/app/src/main/res/drawable/ic_login_saver.xml
@@ -0,0 +1,289 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_login_title.xml b/app/src/main/res/drawable/ic_login_title.xml
new file mode 100644
index 00000000..43baf68b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_login_title.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 00000000..9682480b
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5b0bac15..f36c07a9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -92,5 +92,9 @@
0
위니가 알려주는 추천 절약법
쉿, 그대를 위한 절약방법이 도착했어.\n황실 내에서도 비밀인데 특별히 알려줄게:)
+
6자 이상 작성해주세요
+
+ 절약을 더 쉽고 재밌게
+ 카카오톡으로 3초만에 시작하기
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 25e0c8e9..9a8f8e13 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -31,6 +31,8 @@ object AndroidXDependencies {
const val workManager = "androidx.work:work-runtime-ktx:${Versions.workManagerVersion}"
const val hiltWorkManager = "androidx.hilt:hilt-work:1.0.0"
const val exif = "androidx.exifinterface:exifinterface:${Versions.exifVersion}"
+ const val dataStore = "androidx.datastore:datastore-preferences:${Versions.dataStoreVersion}"
+ const val dataStoreCore = "androidx.datastore:datastore-preferences-core:${Versions.dataStoreVersion}"
}
object TestDependencies {
@@ -75,6 +77,7 @@ object ThirdPartyDependencies {
"com.squareup.leakcanary:leakcanary-android:${Versions.leakCanaryVersion}"
const val soloader = "com.facebook.soloader:soloader:${Versions.soloaderVersion}"
const val circleImageView = "de.hdodenhof:circleimageview:${Versions.circleImageViewVersion}"
+ const val kakaoLogin = "com.kakao.sdk:v2-user:${Versions.kakaoLoginVersion}"
}
object FirebaseDependencies {
diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt
index 1036ba22..ce0113c1 100644
--- a/buildSrc/src/main/java/Versions.kt
+++ b/buildSrc/src/main/java/Versions.kt
@@ -26,6 +26,7 @@ object Versions {
const val splashVersion = "1.0.1"
const val workManagerVersion = "2.8.1"
const val exifVersion = "1.3.2"
+ const val dataStoreVersion = "1.0.0"
const val coilVersion = "2.4.0"
const val retrofitVersion = "2.9.0"
@@ -41,7 +42,8 @@ object Versions {
const val leakCanaryVersion = "2.11"
const val circleImageViewVersion = "3.1.0"
+ const val kakaoLoginVersion = "2.10.0"
val javaVersion = JavaVersion.VERSION_17
const val jvmVersion = "17"
-}
\ No newline at end of file
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index cc9a4639..5f0d6b0b 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -10,6 +10,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven("https://devrepo.kakao.com/nexus/content/groups/public/")
}
}
rootProject.name = "Winey"