更新 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.ui) // UI组件可选
implementation(libs.androidx.media3.session) implementation(libs.androidx.media3.session)
implementation(libs.androidx.activity.ktx) 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) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
@@ -83,12 +85,12 @@ dependencies {
implementation("com.google.android.gms:play-services-auth:21.2.0") implementation("com.google.android.gms:play-services-auth:21.2.0")
implementation("io.github.serpro69:kotlin-faker:2.0.0-rc.5") implementation("io.github.serpro69:kotlin-faker:2.0.0-rc.5")
implementation("androidx.compose.material:material:1.6.8") 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("net.engawapg.lib:zoomable:1.6.1")
implementation("com.squareup.retrofit2:retrofit:2.11.0") implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson: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 { object ConstVars {
// api 地址 // 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.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.ServiceException import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.ui.Navigation import com.aiosman.riderpro.ui.Navigation
import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.index.NavigationItem 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.HttpException
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val scope = CoroutineScope(Dispatchers.Main) private val scope = CoroutineScope(Dispatchers.Main)
suspend fun getAccount(): Boolean { suspend fun getAccount(): Boolean {
val accountService: AccountService = TestAccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
try { try {
val resp = accountService.getMyAccount() val resp = accountService.getMyAccount()
return true return true

View File

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

View File

@@ -27,14 +27,22 @@ data class RegisterRequestBody(
@SerializedName("username") @SerializedName("username")
val username: String, val username: String,
@SerializedName("password") @SerializedName("password")
val password: String val password: String,
)
)
data class LoginUserRequestBody( data class LoginUserRequestBody(
@SerializedName("username") @SerializedName("username")
val username: String, val username: String? = null,
@SerializedName("password") @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( data class AuthResult(
@@ -219,4 +227,7 @@ interface RiderProAPI {
@Query("nickname") search: String? = null, @Query("nickname") search: String? = null,
): Response<ListContainer<AccountProfile>> ): 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 androidx.compose.ui.unit.dp
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.TestAccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ChangePasswordViewModel { class ChangePasswordViewModel {
val accountService :AccountService = TestAccountServiceImpl() val accountService :AccountService = AccountServiceImpl()
suspend fun changePassword(currentPassword: String, newPassword: String) { suspend fun changePassword(currentPassword: String, newPassword: String) {
accountService.changeAccountPassword(currentPassword, newPassword) 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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.aiosman.riderpro.data.AccountProfileEntity import com.aiosman.riderpro.data.AccountProfileEntity
import com.aiosman.riderpro.data.AccountService 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.TestUserServiceImpl
import com.aiosman.riderpro.data.UploadImage import com.aiosman.riderpro.data.UploadImage
import com.aiosman.riderpro.data.UserService import com.aiosman.riderpro.data.UserService
@@ -49,7 +48,7 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun AccountEditScreen() { fun AccountEditScreen() {
val userService: UserService = TestUserServiceImpl() val userService: UserService = TestUserServiceImpl()
val accountService: AccountService = TestAccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
var name by remember { mutableStateOf("") } var name by remember { mutableStateOf("") }
var bio by remember { mutableStateOf("") } var bio by remember { mutableStateOf("") }
var imageUrl by remember { mutableStateOf<Uri?>(null) } var imageUrl by remember { mutableStateOf<Uri?>(null) }

View File

@@ -1,12 +1,45 @@
package com.aiosman.riderpro.ui.composables package com.aiosman.riderpro.ui.composables
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import androidx.compose.runtime.Composable 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.Modifier
import androidx.compose.ui.layout.ContentScale 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.compose.AsyncImage
import coil.request.ImageRequest
import coil.request.SuccessResult
import com.aiosman.riderpro.utils.Utils.getImageLoader 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 @Composable
fun CustomAsyncImage( fun CustomAsyncImage(
context: Context, context: Context,
@@ -15,9 +48,10 @@ fun CustomAsyncImage(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Crop contentScale: ContentScale = ContentScale.Crop
) { ) {
val bitmap = rememberImageBitmap(imageUrl, getImageLoader(context))
val imageLoader = getImageLoader(context) val imageLoader = getImageLoader(context)
AsyncImage( AsyncImage(
model = imageUrl, model = bitmap,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = modifier, modifier = modifier,
contentScale = contentScale, contentScale = contentScale,

View File

@@ -7,23 +7,19 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.aiosman.riderpro.data.AccountFavourite
import com.aiosman.riderpro.data.AccountFavouriteEntity import com.aiosman.riderpro.data.AccountFavouriteEntity
import com.aiosman.riderpro.data.AccountLike
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.FavoriteItemPagingSource import com.aiosman.riderpro.data.FavoriteItemPagingSource
import com.aiosman.riderpro.data.LikeItemPagingSource import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.api.ApiClient import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
import com.aiosman.riderpro.ui.like.LikePageViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
object FavouritePageViewModel : ViewModel() { object FavouritePageViewModel : ViewModel() {
private val accountService: AccountService = TestAccountServiceImpl() private val accountService: AccountService = AccountServiceImpl()
private val _favouriteItemsFlow = private val _favouriteItemsFlow =
MutableStateFlow<PagingData<AccountFavouriteEntity>>(PagingData.empty()) MutableStateFlow<PagingData<AccountFavouriteEntity>>(PagingData.empty())
val favouriteItemsFlow = _favouriteItemsFlow.asStateFlow() 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.AccountFollow
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.FollowItemPagingSource 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.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.data.api.ApiClient import com.aiosman.riderpro.data.api.ApiClient
@@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
object FollowerViewModel : ViewModel() { object FollowerViewModel : ViewModel() {
private val accountService: AccountService = TestAccountServiceImpl() private val accountService: AccountService = AccountServiceImpl()
private val userService: UserService = TestUserServiceImpl() private val userService: UserService = TestUserServiceImpl()
private val _followerItemsFlow = private val _followerItemsFlow =
MutableStateFlow<PagingData<AccountFollow>>(PagingData.empty()) 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.CommentPagingSource
import com.aiosman.riderpro.data.CommentRemoteDataSource import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.CommentService 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 com.aiosman.riderpro.data.TestCommentServiceImpl
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
object MessageListViewModel : ViewModel() { object MessageListViewModel : ViewModel() {
val accountService: AccountService = TestAccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
var noticeInfo by mutableStateOf<AccountNotice?>(null) var noticeInfo by mutableStateOf<AccountNotice?>(null)
private val commentService: CommentService = TestCommentServiceImpl() 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.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope 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.AnimatedCounter
import com.aiosman.riderpro.ui.composables.AnimatedFavouriteIcon import com.aiosman.riderpro.ui.composables.AnimatedFavouriteIcon
import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon 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.CustomAsyncImage
import com.aiosman.riderpro.ui.composables.RelPostCard import com.aiosman.riderpro.ui.composables.RelPostCard
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
@@ -102,7 +102,10 @@ fun MomentsList() {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) { ) {
items(moments.itemCount) { idx -> items(
moments.itemCount,
key = { idx -> moments[idx]?.id ?: idx }
) { idx ->
val momentItem = moments[idx] ?: return@items val momentItem = moments[idx] ?: return@items
MomentCard(momentEntity = momentItem, MomentCard(momentEntity = momentItem,
onAddComment = { onAddComment = {

View File

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

View File

@@ -9,7 +9,7 @@ import androidx.paging.PagingData
import com.aiosman.riderpro.AppStore import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.data.AccountProfileEntity import com.aiosman.riderpro.data.AccountProfileEntity
import com.aiosman.riderpro.data.AccountService 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.MomentPagingSource
import com.aiosman.riderpro.data.MomentRemoteDataSource import com.aiosman.riderpro.data.MomentRemoteDataSource
import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.data.TestMomentServiceImpl
@@ -18,7 +18,7 @@ import com.aiosman.riderpro.model.MomentEntity
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
object MyProfileViewModel { object MyProfileViewModel {
val service: AccountService = TestAccountServiceImpl() val service: AccountService = AccountServiceImpl()
val userService = TestUserServiceImpl() val userService = TestUserServiceImpl()
var profile by mutableStateOf<AccountProfileEntity?>(null) var profile by mutableStateOf<AccountProfileEntity?>(null)
var momentsFlow by mutableStateOf<Flow<PagingData<MomentEntity>>?>(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.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.aiosman.riderpro.data.AccountLike
import com.aiosman.riderpro.data.AccountLikeEntity import com.aiosman.riderpro.data.AccountLikeEntity
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.LikeItemPagingSource 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.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -21,7 +20,7 @@ import kotlinx.coroutines.launch
object LikePageViewModel : ViewModel() { object LikePageViewModel : ViewModel() {
private val accountService: AccountService = TestAccountServiceImpl() private val accountService: AccountService = AccountServiceImpl()
private val _likeItemsFlow = MutableStateFlow<PagingData<AccountLikeEntity>>(PagingData.empty()) private val _likeItemsFlow = MutableStateFlow<PagingData<AccountLikeEntity>>(PagingData.empty())
val likeItemsFlow = _likeItemsFlow.asStateFlow() val likeItemsFlow = _likeItemsFlow.asStateFlow()

View File

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

View File

@@ -1,9 +1,8 @@
package com.aiosman.riderpro.ui.login package com.aiosman.riderpro.ui.login
import android.app.Activity import android.content.ContentValues.TAG
import android.util.Log import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box 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.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip 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.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.AppStore
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R 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.NavigationRoute
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.gms.common.api.ApiException import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import com.google.android.gms.tasks.Task import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable @Composable
fun SignupScreen() { fun SignupScreen() {
val navController = LocalNavController.current val navController = LocalNavController.current
val context = LocalContext.current val context = LocalContext.current
val googleSignInClient = GoogleSignIn.getClient(context, AppStore.googleSignInOptions) val coroutineScope = rememberCoroutineScope()
fun handleSignInResult( val credentialManager = CredentialManager.create(context)
task: Task<GoogleSignInAccount>, val accountService: AccountService = AccountServiceImpl()
onSignInSuccess: (GoogleSignInAccount) -> Unit
) { fun registerWithGoogle(idToken: String) {
try { coroutineScope.launch {
val account = task.getResult(ApiException::class.java) try {
onSignInSuccess(account) accountService.regiterUserWithGoogleAccount(idToken)
} catch (e: ApiException) { } catch (e: Exception) {
// Handle sign-in failure Log.e(TAG, "Failed to register with google", e)
} return@launch
} }
val launcher = rememberLauncherForActivityResult( // 获取用户信息
contract = ActivityResultContracts.StartActivityForResult(), // 获取 token
onResult = { result -> val authResp = accountService.loginUserWithGoogle(idToken)
if (result.resultCode == Activity.RESULT_OK) { if (authResp.token != null) {
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data) coroutineScope.launch(Dispatchers.Main) {
handleSignInResult(task) {account -> Toast.makeText(context, "Successfully registered", Toast.LENGTH_SHORT).show()
// 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( Scaffold(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -162,8 +221,10 @@ fun SignupScreen() {
text = "CONTINUE WITH GOOGLE".uppercase(), text = "CONTINUE WITH GOOGLE".uppercase(),
backgroundImage = R.mipmap.rider_pro_signup_white_bg backgroundImage = R.mipmap.rider_pro_signup_white_bg
) { ) {
val signInIntent = googleSignInClient.signInIntent // val signInIntent = googleSignInClient.signInIntent
launcher.launch(signInIntent) // launcher.launch(signInIntent)
googleLogin()
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Box( Box(

View File

@@ -1,12 +1,16 @@
package com.aiosman.riderpro.ui.login package com.aiosman.riderpro.ui.login
import android.content.ContentValues.TAG
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding 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.text.input.VisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.AppStore
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.ServiceException import com.aiosman.riderpro.data.ServiceException
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.ui.NavigationRoute import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.modifiers.noRippleClickable 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 import kotlinx.coroutines.launch
@@ -57,10 +67,13 @@ fun UserAuthScreen() {
var email by remember { mutableStateOf("") } var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
var rememberMe by remember { mutableStateOf(false) } var rememberMe by remember { mutableStateOf(false) }
var accountService: AccountService = TestAccountServiceImpl() var accountService: AccountService = AccountServiceImpl()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val navController = LocalNavController.current val navController = LocalNavController.current
val context = LocalContext.current val context = LocalContext.current
val credentialManager = CredentialManager.create(context)
fun onLogin() { fun onLogin() {
scope.launch { scope.launch {
try { 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 { StatusBarMaskLayout {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@@ -150,6 +216,25 @@ fun UserAuthScreen() {
Spacer(modifier = Modifier.height(121.dp)) Spacer(modifier = Modifier.height(121.dp))
Text("or login with", color = Color(0xFF999999)) 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.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons 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.Edit
import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -72,7 +70,6 @@ import androidx.paging.cachedIn
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.map import androidx.paging.map
import coil.compose.AsyncImage
import com.aiosman.riderpro.LocalAnimatedContentScope import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope 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.CommentService
import com.aiosman.riderpro.data.TestCommentServiceImpl import com.aiosman.riderpro.data.TestCommentServiceImpl
import com.aiosman.riderpro.data.MomentService 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.TestMomentServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService import com.aiosman.riderpro.data.UserService
@@ -133,7 +130,7 @@ class PostViewModel(
var accountProfileEntity by mutableStateOf<AccountProfileEntity?>(null) var accountProfileEntity by mutableStateOf<AccountProfileEntity?>(null)
var moment by mutableStateOf<MomentEntity?>(null) var moment by mutableStateOf<MomentEntity?>(null)
var accountService: AccountService = TestAccountServiceImpl() var accountService: AccountService = AccountServiceImpl()
suspend fun initData() { suspend fun initData() {
moment = service.getMomentById(postId.toInt()) 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" accompanistSystemuicontroller = "0.27.0"
agp = "8.4.0" agp = "8.4.0"
animation = "1.7.0-beta05" animation = "1.7.0-beta05"
composeImageBlurhash = "3.0.2"
kotlin = "1.9.0" kotlin = "1.9.0"
coreKtx = "1.10.1" coreKtx = "1.10.1"
junit = "4.13.2" junit = "4.13.2"
@@ -18,6 +19,8 @@ pagingRuntime = "3.3.0"
activityKtx = "1.9.0" activityKtx = "1.9.0"
lifecycleCommonJvm = "2.8.2" lifecycleCommonJvm = "2.8.2"
places = "3.3.0" places = "3.3.0"
googleid = "1.1.1"
identityCredential = "20231002"
[libraries] [libraries]
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" } 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-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingRuntime" } androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingRuntime" }
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", 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" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } 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-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" } 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" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }