更新 google 登录

This commit is contained in:
2024-08-23 18:31:14 +08:00
parent 5e65b7fe4d
commit 2dd2ee0281
21 changed files with 301 additions and 83 deletions

View File

@@ -68,7 +68,9 @@ dependencies {
implementation(libs.androidx.media3.ui) // UI组件可选
implementation(libs.androidx.media3.session)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.lifecycle.common.jvm) // 用于媒体会话(可选)
implementation(libs.androidx.lifecycle.common.jvm)
implementation(libs.googleid)
implementation(libs.identity.credential) // 用于媒体会话(可选)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@@ -83,12 +85,12 @@ dependencies {
implementation("com.google.android.gms:play-services-auth:21.2.0")
implementation("io.github.serpro69:kotlin-faker:2.0.0-rc.5")
implementation("androidx.compose.material:material:1.6.8")
implementation("com.facebook.android:facebook-android-sdk:17.0.0")
// platform("com.google.firebase:firebase-bom:33.1.2")
// implementation("com.google.firebase:firebase-analytics")
implementation("net.engawapg.lib:zoomable:1.6.1")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
implementation("androidx.credentials:credentials:1.2.2")
implementation("androidx.credentials:credentials-play-services-auth:1.2.2")
}

View File

@@ -2,5 +2,6 @@ package com.aiosman.riderpro
object ConstVars {
// api 地址
const val BASE_SERVER = "https://8.137.22.101:8088"
const val BASE_SERVER = "http://192.168.31.250:8088"
// const val BASE_SERVER = "https://8.137.22.101:8088"
}

View File

@@ -32,10 +32,7 @@ import androidx.core.view.WindowCompat
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.ServiceException
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.ui.Navigation
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.index.NavigationItem
@@ -44,12 +41,11 @@ import com.google.android.libraries.places.api.Places
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.HttpException
class MainActivity : ComponentActivity() {
private val scope = CoroutineScope(Dispatchers.Main)
suspend fun getAccount(): Boolean {
val accountService: AccountService = TestAccountServiceImpl()
val accountService: AccountService = AccountServiceImpl()
try {
val resp = accountService.getMyAccount()
return true

View File

@@ -2,9 +2,9 @@ package com.aiosman.riderpro.data
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.ChangePasswordRequestBody
import com.aiosman.riderpro.data.api.GoogleRegisterRequestBody
import com.aiosman.riderpro.data.api.LoginUserRequestBody
import com.aiosman.riderpro.data.api.RegisterRequestBody
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
@@ -24,11 +24,13 @@ data class AccountLikeEntity(
val user: NoticeUserEntity,
val likeTime: Date,
)
data class AccountFavouriteEntity(
val post: NoticePostEntity,
val user: NoticeUserEntity,
val favoriteTime: Date,
)
data class AccountProfileEntity(
val id: Int,
val followerCount: Int,
@@ -281,10 +283,12 @@ interface AccountService {
suspend fun getAccountProfileById(id: Int): AccountProfileEntity
suspend fun getMyAccount(): UserAuth
suspend fun loginUserWithPassword(loginName: String, password: String): UserAuth
suspend fun loginUserWithGoogle(googleId: String): UserAuth
suspend fun logout()
suspend fun updateAvatar(uri: String)
suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?)
suspend fun registerUserWithPassword(loginName: String, password: String)
suspend fun regiterUserWithGoogleAccount(idToken: String)
suspend fun changeAccountPassword(oldPassword: String, newPassword: String)
suspend fun getMyLikeNotice(page: Int, pageSize: Int): ListContainer<AccountLike>
suspend fun getMyFollowNotice(page: Int, pageSize: Int): ListContainer<AccountFollow>
@@ -293,7 +297,7 @@ interface AccountService {
suspend fun updateNotice(payload: UpdateNoticeRequestBody)
}
class TestAccountServiceImpl : AccountService {
class AccountServiceImpl : AccountService {
override suspend fun getMyAccountProfile(): AccountProfileEntity {
val resp = ApiClient.api.getMyAccount()
val body = resp.body() ?: throw ServiceException("Failed to get account")
@@ -318,6 +322,19 @@ class TestAccountServiceImpl : AccountService {
return UserAuth(0, body.token)
}
override suspend fun loginUserWithGoogle(googleId: String): UserAuth {
val resp = ApiClient.api.login(LoginUserRequestBody(googleId=googleId))
val body = resp.body() ?: throw ServiceException("Failed to login")
return UserAuth(0, body.token)
}
override suspend fun regiterUserWithGoogleAccount(idToken: String) {
val resp = ApiClient.api.registerWithGoogle(GoogleRegisterRequestBody(idToken))
if (resp.code() != 200) {
throw ServiceException("Failed to register")
}
}
override suspend fun logout() {
// do nothing
}

View File

@@ -27,14 +27,22 @@ data class RegisterRequestBody(
@SerializedName("username")
val username: String,
@SerializedName("password")
val password: String
val password: String,
)
data class LoginUserRequestBody(
@SerializedName("username")
val username: String,
val username: String? = null,
@SerializedName("password")
val password: String
val password: String? = null,
@SerializedName("googleId")
val googleId: String? = null,
)
data class GoogleRegisterRequestBody(
@SerializedName("idToken")
val idToken: String
)
data class AuthResult(
@@ -219,4 +227,7 @@ interface RiderProAPI {
@Query("nickname") search: String? = null,
): Response<ListContainer<AccountProfile>>
@POST("register/google")
suspend fun registerWithGoogle(@Body body: GoogleRegisterRequestBody): Response<AuthResult>
}

View File

@@ -6,11 +6,11 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import kotlinx.coroutines.launch
class ChangePasswordViewModel {
val accountService :AccountService = TestAccountServiceImpl()
val accountService :AccountService = AccountServiceImpl()
suspend fun changePassword(currentPassword: String, newPassword: String) {
accountService.changeAccountPassword(currentPassword, newPassword)
}

View File

@@ -33,10 +33,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.aiosman.riderpro.data.AccountProfileEntity
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UploadImage
import com.aiosman.riderpro.data.UserService
@@ -49,7 +48,7 @@ import kotlinx.coroutines.launch
@Composable
fun AccountEditScreen() {
val userService: UserService = TestUserServiceImpl()
val accountService: AccountService = TestAccountServiceImpl()
val accountService: AccountService = AccountServiceImpl()
var name by remember { mutableStateOf("") }
var bio by remember { mutableStateOf("") }
var imageUrl by remember { mutableStateOf<Uri?>(null) }

View File

@@ -1,12 +1,45 @@
package com.aiosman.riderpro.ui.composables
import android.content.Context
import android.graphics.Bitmap
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.core.graphics.drawable.toBitmap
import coil.ImageLoader
import coil.compose.AsyncImage
import coil.request.ImageRequest
import coil.request.SuccessResult
import com.aiosman.riderpro.utils.Utils.getImageLoader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun rememberImageBitmap(imageUrl: String, imageLoader: ImageLoader): Bitmap? {
val context = LocalContext.current
var bitmap by remember { mutableStateOf<Bitmap?>(null) }
LaunchedEffect(imageUrl) {
val request = ImageRequest.Builder(context)
.data(imageUrl)
.crossfade(true)
.build()
val result = withContext(Dispatchers.IO) {
(imageLoader.execute(request) as? SuccessResult)?.drawable?.toBitmap()
}
bitmap = result
}
return bitmap
}
@Composable
fun CustomAsyncImage(
context: Context,
@@ -15,9 +48,10 @@ fun CustomAsyncImage(
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Crop
) {
val bitmap = rememberImageBitmap(imageUrl, getImageLoader(context))
val imageLoader = getImageLoader(context)
AsyncImage(
model = imageUrl,
model = bitmap,
contentDescription = contentDescription,
modifier = modifier,
contentScale = contentScale,

View File

@@ -7,23 +7,19 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.aiosman.riderpro.data.AccountFavourite
import com.aiosman.riderpro.data.AccountFavouriteEntity
import com.aiosman.riderpro.data.AccountLike
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.FavoriteItemPagingSource
import com.aiosman.riderpro.data.LikeItemPagingSource
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
import com.aiosman.riderpro.ui.like.LikePageViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object FavouritePageViewModel : ViewModel() {
private val accountService: AccountService = TestAccountServiceImpl()
private val accountService: AccountService = AccountServiceImpl()
private val _favouriteItemsFlow =
MutableStateFlow<PagingData<AccountFavouriteEntity>>(PagingData.empty())
val favouriteItemsFlow = _favouriteItemsFlow.asStateFlow()

View File

@@ -11,7 +11,7 @@ import androidx.paging.map
import com.aiosman.riderpro.data.AccountFollow
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.FollowItemPagingSource
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.data.api.ApiClient
@@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object FollowerViewModel : ViewModel() {
private val accountService: AccountService = TestAccountServiceImpl()
private val accountService: AccountService = AccountServiceImpl()
private val userService: UserService = TestUserServiceImpl()
private val _followerItemsFlow =
MutableStateFlow<PagingData<AccountFollow>>(PagingData.empty())

View File

@@ -16,7 +16,7 @@ import com.aiosman.riderpro.data.CommentEntity
import com.aiosman.riderpro.data.CommentPagingSource
import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.CommentService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestCommentServiceImpl
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object MessageListViewModel : ViewModel() {
val accountService: AccountService = TestAccountServiceImpl()
val accountService: AccountService = AccountServiceImpl()
var noticeInfo by mutableStateOf<AccountNotice?>(null)
private val commentService: CommentService = TestCommentServiceImpl()

View File

@@ -41,6 +41,7 @@ import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -70,7 +71,6 @@ import com.aiosman.riderpro.ui.comment.CommentModalContent
import com.aiosman.riderpro.ui.composables.AnimatedCounter
import com.aiosman.riderpro.ui.composables.AnimatedFavouriteIcon
import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon
import com.aiosman.riderpro.ui.composables.AsyncBlurImage
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.composables.RelPostCard
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
@@ -102,7 +102,10 @@ fun MomentsList() {
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
items(moments.itemCount) { idx ->
items(
moments.itemCount,
key = { idx -> moments[idx]?.id ?: idx }
) { idx ->
val momentItem = moments[idx] ?: return@items
MomentCard(momentEntity = momentItem,
onAddComment = {

View File

@@ -11,7 +11,7 @@ import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.MomentPagingSource
import com.aiosman.riderpro.data.MomentRemoteDataSource
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestMomentServiceImpl
import com.aiosman.riderpro.model.MomentEntity
import kotlinx.coroutines.flow.MutableStateFlow
@@ -24,7 +24,7 @@ object MomentViewModel : ViewModel() {
private val momentService: MomentService = TestMomentServiceImpl()
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
val momentsFlow = _momentsFlow.asStateFlow()
val accountService: AccountService = TestAccountServiceImpl()
val accountService: AccountService = AccountServiceImpl()
init {
viewModelScope.launch {
val profile = accountService.getMyAccountProfile()

View File

@@ -9,7 +9,7 @@ import androidx.paging.PagingData
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.data.AccountProfileEntity
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.MomentPagingSource
import com.aiosman.riderpro.data.MomentRemoteDataSource
import com.aiosman.riderpro.data.TestMomentServiceImpl
@@ -18,7 +18,7 @@ import com.aiosman.riderpro.model.MomentEntity
import kotlinx.coroutines.flow.Flow
object MyProfileViewModel {
val service: AccountService = TestAccountServiceImpl()
val service: AccountService = AccountServiceImpl()
val userService = TestUserServiceImpl()
var profile by mutableStateOf<AccountProfileEntity?>(null)
var momentsFlow by mutableStateOf<Flow<PagingData<MomentEntity>>?>(null)

View File

@@ -7,11 +7,10 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.aiosman.riderpro.data.AccountLike
import com.aiosman.riderpro.data.AccountLikeEntity
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.LikeItemPagingSource
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
import kotlinx.coroutines.flow.MutableStateFlow
@@ -21,7 +20,7 @@ import kotlinx.coroutines.launch
object LikePageViewModel : ViewModel() {
private val accountService: AccountService = TestAccountServiceImpl()
private val accountService: AccountService = AccountServiceImpl()
private val _likeItemsFlow = MutableStateFlow<PagingData<AccountLikeEntity>>(PagingData.empty())
val likeItemsFlow = _likeItemsFlow.asStateFlow()

View File

@@ -29,7 +29,7 @@ import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.ServiceException
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
@@ -47,7 +47,7 @@ fun EmailSignupScreen() {
val scope = rememberCoroutineScope()
val navController = LocalNavController.current
val context = LocalContext.current
val accountService: AccountService = TestAccountServiceImpl()
val accountService: AccountService = AccountServiceImpl()
fun validateForm(): Boolean {
if (email.isEmpty()) {
Toast.makeText(context, "Email is required", Toast.LENGTH_SHORT).show()

View File

@@ -1,9 +1,8 @@
package com.aiosman.riderpro.ui.login
import android.app.Activity
import android.content.ContentValues.TAG
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
@@ -20,6 +19,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -29,44 +29,103 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.credentials.Credential
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.ServiceException
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun SignupScreen() {
val navController = LocalNavController.current
val context = LocalContext.current
val googleSignInClient = GoogleSignIn.getClient(context, AppStore.googleSignInOptions)
fun handleSignInResult(
task: Task<GoogleSignInAccount>,
onSignInSuccess: (GoogleSignInAccount) -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val credentialManager = CredentialManager.create(context)
val accountService: AccountService = AccountServiceImpl()
fun registerWithGoogle(idToken: String) {
coroutineScope.launch {
try {
val account = task.getResult(ApiException::class.java)
onSignInSuccess(account)
} catch (e: ApiException) {
// Handle sign-in failure
accountService.regiterUserWithGoogleAccount(idToken)
} catch (e: Exception) {
Log.e(TAG, "Failed to register with google", e)
return@launch
}
// 获取用户信息
// 获取 token
val authResp = accountService.loginUserWithGoogle(idToken)
if (authResp.token != null) {
coroutineScope.launch(Dispatchers.Main) {
Toast.makeText(context, "Successfully registered", Toast.LENGTH_SHORT).show()
}
}
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = { result ->
if (result.resultCode == Activity.RESULT_OK) {
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
handleSignInResult(task) {account ->
// Handle sign-in success
Log.d("SignupScreen", "handleSignInResult: $account")
AppStore.apply {
token = authResp.token
this.rememberMe = true
saveData()
}
// 获取token 信息
try {
accountService.getMyAccount()
} catch (e: ServiceException) {
coroutineScope.launch(Dispatchers.Main) {
Toast.makeText(context, "Failed to get account", Toast.LENGTH_SHORT).show()
}
}
coroutineScope.launch(Dispatchers.Main) {
navController.navigate(NavigationRoute.Index.route) {
popUpTo(NavigationRoute.Login.route) { inclusive = true }
}
}
}
)
}
fun handleGoogleSignIn(result: GetCredentialResponse) {
val credential: Credential = result.credential
if (credential is CustomCredential) {
if (GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.equals(credential.type)) {
try {
val googleIdTokenCredential: GoogleIdTokenCredential =
GoogleIdTokenCredential.createFrom(credential.data)
registerWithGoogle(googleIdTokenCredential.idToken)
} catch (e: GoogleIdTokenParsingException) {
Log.e(TAG, "Received an invalid google id token response", e);
}
}
}
}
fun googleLogin() {
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId("754277015802-pnua6tg8ibnjq69lv8qdcmsdhbe97ag9.apps.googleusercontent.com")
.build()
val request = GetCredentialRequest.Builder().addCredentialOption(googleIdOption)
.build()
coroutineScope.launch {
credentialManager.getCredential(context, request).let {
// Use the credential
handleGoogleSignIn(it)
}
}
}
Scaffold(
modifier = Modifier
.fillMaxSize()
@@ -162,8 +221,10 @@ fun SignupScreen() {
text = "CONTINUE WITH GOOGLE".uppercase(),
backgroundImage = R.mipmap.rider_pro_signup_white_bg
) {
val signInIntent = googleSignInClient.signInIntent
launcher.launch(signInIntent)
// val signInIntent = googleSignInClient.signInIntent
// launcher.launch(signInIntent)
googleLogin()
}
Spacer(modifier = Modifier.height(16.dp))
Box(

View File

@@ -1,12 +1,16 @@
package com.aiosman.riderpro.ui.login
import android.content.ContentValues.TAG
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -36,18 +40,24 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.credentials.Credential
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.ServiceException
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import kotlinx.coroutines.launch
@@ -57,10 +67,13 @@ fun UserAuthScreen() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var rememberMe by remember { mutableStateOf(false) }
var accountService: AccountService = TestAccountServiceImpl()
var accountService: AccountService = AccountServiceImpl()
val scope = rememberCoroutineScope()
val navController = LocalNavController.current
val context = LocalContext.current
val credentialManager = CredentialManager.create(context)
fun onLogin() {
scope.launch {
try {
@@ -82,6 +95,59 @@ fun UserAuthScreen() {
}
}
fun onLoginWithGoogle(googleId: String) {
scope.launch {
try {
val authResp = accountService.loginUserWithGoogle(googleId)
if (authResp.token != null) {
AppStore.apply {
token = authResp.token
this.rememberMe = rememberMe
saveData()
}
navController.navigate(NavigationRoute.Index.route) {
popUpTo(NavigationRoute.Login.route) { inclusive = true }
}
}
} catch (e: ServiceException) {
// handle error
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}
}
}
fun handleGoogleSignIn(result: GetCredentialResponse) {
val credential: Credential = result.credential
if (credential is CustomCredential) {
if (GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.equals(credential.type)) {
try {
val googleIdTokenCredential: GoogleIdTokenCredential =
GoogleIdTokenCredential.createFrom(credential.data)
onLoginWithGoogle(googleIdTokenCredential.idToken)
} catch (e: GoogleIdTokenParsingException) {
Log.e(TAG, "Received an invalid google id token response", e);
}
}
}
}
fun googleLogin() {
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setFilterByAuthorizedAccounts(true)
.setServerClientId("754277015802-pnua6tg8ibnjq69lv8qdcmsdhbe97ag9.apps.googleusercontent.com")
.build()
val request = GetCredentialRequest.Builder().addCredentialOption(googleIdOption)
.build()
scope.launch {
credentialManager.getCredential(context, request).let {
// Use the credential
handleGoogleSignIn(it)
}
}
}
StatusBarMaskLayout {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -150,6 +216,25 @@ fun UserAuthScreen() {
Spacer(modifier = Modifier.height(121.dp))
Text("or login with", color = Color(0xFF999999))
Spacer(modifier = Modifier.height(16.dp))
Row {
Box(
modifier = Modifier
.size(96.dp)
.padding(16.dp)
.border(2.dp, Color(0xFFEBEBEB))
.noRippleClickable {
// login with facebook
googleLogin()
}
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_google),
contentDescription = "Google",
modifier = Modifier.fillMaxSize()
)
}
}
}
}

View File

@@ -29,10 +29,8 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -72,7 +70,6 @@ import androidx.paging.cachedIn
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.map
import coil.compose.AsyncImage
import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope
@@ -85,7 +82,7 @@ import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.CommentService
import com.aiosman.riderpro.data.TestCommentServiceImpl
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestMomentServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService
@@ -133,7 +130,7 @@ class PostViewModel(
var accountProfileEntity by mutableStateOf<AccountProfileEntity?>(null)
var moment by mutableStateOf<MomentEntity?>(null)
var accountService: AccountService = TestAccountServiceImpl()
var accountService: AccountService = AccountServiceImpl()
suspend fun initData() {
moment = service.getMomentById(postId.toInt())

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:alpha="0.77" android:height="150dp" android:viewportHeight="150" android:viewportWidth="150" android:width="150dp">
<path android:fillColor="#4280EF" android:pathData="M120,76.1c0,-3.1 -0.3,-6.3 -0.8,-9.3H75.9v17.7h24.8c-1,5.7 -4.3,10.7 -9.2,13.9l14.8,11.5C115,101.8 120,90 120,76.1L120,76.1z"/>
<path android:fillColor="#34A353" android:pathData="M75.9,120.9c12.4,0 22.8,-4.1 30.4,-11.1L91.5,98.4c-4.1,2.8 -9.4,4.4 -15.6,4.4c-12,0 -22.1,-8.1 -25.8,-18.9L34.9,95.6C42.7,111.1 58.5,120.9 75.9,120.9z"/>
<path android:fillColor="#F6B704" android:pathData="M50.1,83.8c-1.9,-5.7 -1.9,-11.9 0,-17.6L34.9,54.4c-6.5,13 -6.5,28.3 0,41.2L50.1,83.8z"/>
<path android:fillColor="#E54335" android:pathData="M75.9,47.3c6.5,-0.1 12.9,2.4 17.6,6.9L106.6,41C98.3,33.2 87.3,29 75.9,29.1c-17.4,0 -33.2,9.8 -41,25.3l15.2,11.8C53.8,55.3 63.9,47.3 75.9,47.3z"/>
</vector>

View File

@@ -2,6 +2,7 @@
accompanistSystemuicontroller = "0.27.0"
agp = "8.4.0"
animation = "1.7.0-beta05"
composeImageBlurhash = "3.0.2"
kotlin = "1.9.0"
coreKtx = "1.10.1"
junit = "4.13.2"
@@ -18,6 +19,8 @@ pagingRuntime = "3.3.0"
activityKtx = "1.9.0"
lifecycleCommonJvm = "2.8.2"
places = "3.3.0"
googleid = "1.1.1"
identityCredential = "20231002"
[libraries]
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
@@ -29,6 +32,7 @@ androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "medi
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingRuntime" }
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" }
compose-image-blurhash = { module = "com.github.orlando-dev-code:compose-image-blurhash", version.ref = "composeImageBlurhash" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@@ -47,6 +51,8 @@ maps-compose = { module = "com.google.maps.android:maps-compose", version.ref =
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
androidx-lifecycle-common-jvm = { group = "androidx.lifecycle", name = "lifecycle-common-jvm", version.ref = "lifecycleCommonJvm" }
places = { module = "com.google.android.libraries.places:places", version.ref = "places" }
googleid = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "googleid" }
identity-credential = { group = "com.android.identity", name = "identity-credential", version.ref = "identityCredential" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }