diff --git a/app/build.gradle b/app/build.gradle index 059aed0..14c687c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -52,6 +52,7 @@ android { buildConfigField 'String', 'NOTIFLY_PROJECT_ID', '"765102ec85855aa680da35f1b0f55712"' buildConfigField 'String', 'NOTIFLY_USERNAME', '"voiceon"' buildConfigField 'String', 'NOTIFLY_PASSWORD', '"c6c585db0aaa4189be44d0467c7d66b6@A"' + buildConfigField 'String', 'GOOGLE_CLIENT_ID', '"983594297130-5hrmkh6vpskeq6v34350kmilf74574h2.apps.googleusercontent.com"' manifestPlaceholders = [ URISCHEME : "voiceon", APPLINK_HOST : "voiceon.onelink.me", @@ -74,6 +75,7 @@ android { buildConfigField 'String', 'NOTIFLY_PROJECT_ID', '"5f7ebe90d1ce5f0392164b8a53a662bc"' buildConfigField 'String', 'NOTIFLY_USERNAME', '"voiceon"' buildConfigField 'String', 'NOTIFLY_PASSWORD', '"c6c585db0aaa4189be44d0467c7d66b6@A"' + buildConfigField 'String', 'GOOGLE_CLIENT_ID', '"758414412471-mosodbj2chno7l1j0iihldh6edmk0gk9.apps.googleusercontent.com"' manifestPlaceholders = [ URISCHEME : "voiceon-test", APPLINK_HOST : "voiceon-test.onelink.me", @@ -154,6 +156,10 @@ dependencies { implementation 'com.google.firebase:firebase-messaging-ktx' implementation 'com.google.firebase:firebase-config-ktx' + implementation 'androidx.credentials:credentials:1.3.0' + implementation 'androidx.credentials:credentials-play-services-auth:1.3.0' + implementation 'com.google.android.libraries.identity.googleid:googleid:1.1.1' + // bootpay implementation "io.github.bootpay:android:4.4.3" diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt index 61520a3..7734122 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt @@ -17,6 +17,7 @@ import kr.co.vividnext.sodalive.settings.signout.SignOutRequest import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest import kr.co.vividnext.sodalive.user.login.LoginRequest import kr.co.vividnext.sodalive.user.login.LoginResponse +import kr.co.vividnext.sodalive.user.login.SocialLoginRequest import kr.co.vividnext.sodalive.user.signup.SignUpRequest import okhttp3.MultipartBody import retrofit2.http.Body @@ -162,4 +163,10 @@ interface UserApi { @Path("id") id: Long, @Header("Authorization") authHeader: String ): Single> + + @POST("/member/login/google") + fun loginGoogle( + @Body request: SocialLoginRequest, + @Header("Authorization") authHeader: String + ): Single> } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt index 1ae028c..b0caa46 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt @@ -13,6 +13,7 @@ import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingR import kr.co.vividnext.sodalive.settings.signout.SignOutRequest import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest import kr.co.vividnext.sodalive.user.login.LoginRequest +import kr.co.vividnext.sodalive.user.login.SocialLoginRequest import kr.co.vividnext.sodalive.user.signup.SignUpRequest import okhttp3.MultipartBody @@ -124,4 +125,12 @@ class UserRepository(private val userApi: UserApi) { id: Long, token: String ) = userApi.getMemberProfile(id = id, authHeader = token) + + fun googleLogin( + request: SocialLoginRequest, + token: String + ) = userApi.loginGoogle( + request = request, + authHeader = token + ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/login/LoginActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/login/LoginActivity.kt index 782f7b0..a9ce6e3 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/login/LoginActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/login/LoginActivity.kt @@ -14,10 +14,22 @@ import android.widget.Toast import androidx.annotation.OptIn import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.credentials.Credential +import androidx.credentials.CredentialManager +import androidx.credentials.CustomCredential +import androidx.credentials.GetCredentialRequest +import androidx.credentials.exceptions.GetCredentialException +import androidx.lifecycle.lifecycleScope import androidx.media3.common.util.UnstableApi +import com.google.android.libraries.identity.googleid.GetGoogleIdOption +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential.Companion.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL import com.jakewharton.rxbinding4.widget.textChanges +import com.orhanobut.logger.Logger import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers +import kotlinx.coroutines.launch +import kr.co.vividnext.sodalive.BuildConfig import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.LoadingDialog @@ -101,6 +113,67 @@ class LoginActivity : BaseActivity(ActivityLoginBinding::i ) ) } + + binding.ivLoginGoogle.setOnClickListener { + loadingDialog.show(width = screenWidth) + val credentialManager = CredentialManager.create(this) + + val googleIdOption = GetGoogleIdOption.Builder() + .setServerClientId(BuildConfig.GOOGLE_CLIENT_ID) + .setFilterByAuthorizedAccounts(false) + .setAutoSelectEnabled(false) + .build() + + // Create the Credential Manager request + val request = GetCredentialRequest.Builder() + .addCredentialOption(googleIdOption) + .build() + + lifecycleScope.launch { + try { + // Launch Credential Manager UI + val result = credentialManager.getCredential( + context = this@LoginActivity, + request = request + ) + loadingDialog.dismiss() + + // Extract credential from the result returned by Credential Manager + handleSignIn(result.credential) + } catch (e: GetCredentialException) { + showToast("로그인을 하지 못했습니다. 다시 시도해 주세요") + Logger.e("Couldn't retrieve user's credentials: ${e.localizedMessage}") + loadingDialog.dismiss() + } + } + } + } + + private fun handleSignIn(credential: Credential) { + // Check if credential is of type Google ID + if (credential is CustomCredential && credential.type == TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { + // Create Google ID Token + val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data) + viewModel.googleLogin(idToken = googleIdTokenCredential.idToken) { + finishAffinity() + val nextIntent = Intent(applicationContext, MainActivity::class.java) + val extras = intent.getBundleExtra(Constants.EXTRA_DATA) + ?: if (intent.extras != null) { + intent.extras + } else { + null + } + if (extras != null) { + nextIntent.putExtra(Constants.EXTRA_DATA, extras) + } + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + startActivity(nextIntent) + } + } else { + showToast("로그인을 하지 못했습니다. 다시 시도해 주세요") + Logger.e("Credential is not of type Google ID!") + } } private fun login() { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/login/LoginViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/login/LoginViewModel.kt index 6c4ecc1..26cc1cb 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/login/LoginViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/login/LoginViewModel.kt @@ -22,6 +22,45 @@ class LoginViewModel(private val repository: UserRepository) : BaseViewModel() { val isLoading: LiveData get() = _isLoading + fun googleLogin(idToken: String, onSuccess: () -> Unit) { + _isLoading.value = true + + compositeDisposable.add( + repository.googleLogin( + request = SocialLoginRequest(marketingPid = SharedPreferenceManager.marketingPid), + token = "Bearer $idToken" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success && it.data != null) { + SharedPreferenceManager.token = it.data.token + SharedPreferenceManager.email = it.data.email + SharedPreferenceManager.userId = it.data.userId + SharedPreferenceManager.nickname = it.data.nickname + SharedPreferenceManager.profileImage = it.data.profileImage + onSuccess() + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + fun login(onSuccess: (String?) -> Unit) { if (email.isBlank()) { _toastLiveData.postValue("이메일을 입력하세요.") diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/login/SocialLoginRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/login/SocialLoginRequest.kt new file mode 100644 index 0000000..3e0450b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/login/SocialLoginRequest.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.user.login + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class SocialLoginRequest( + @SerializedName("container") val container: String = "aos", + @SerializedName("marketingPid") val marketingPid: String +) diff --git a/app/src/main/res/drawable-xxhdpi/ic_login_google.png b/app/src/main/res/drawable-xxhdpi/ic_login_google.png new file mode 100755 index 0000000..8ede9b6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_login_google.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_login_kakao.png b/app/src/main/res/drawable-xxhdpi/ic_login_kakao.png new file mode 100755 index 0000000..fe1b24e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_login_kakao.png differ diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 0c388ef..7bf6871 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -96,6 +96,21 @@ android:textColor="@color/white" android:textSize="15sp" /> + + + + +