新增验证码
This commit is contained in:
@@ -3,6 +3,7 @@ package com.aiosman.riderpro.data
|
||||
import com.aiosman.riderpro.AppState
|
||||
import com.aiosman.riderpro.data.api.ApiClient
|
||||
import com.aiosman.riderpro.data.api.AppConfig
|
||||
import com.aiosman.riderpro.data.api.CaptchaInfo
|
||||
import com.aiosman.riderpro.data.api.ChangePasswordRequestBody
|
||||
import com.aiosman.riderpro.data.api.GoogleRegisterRequestBody
|
||||
import com.aiosman.riderpro.data.api.LoginUserRequestBody
|
||||
@@ -266,8 +267,13 @@ interface AccountService {
|
||||
* 使用用户名密码登录
|
||||
* @param loginName 用户名
|
||||
* @param password 密码
|
||||
* @param captchaInfo 验证码信息
|
||||
*/
|
||||
suspend fun loginUserWithPassword(loginName: String, password: String): UserAuth
|
||||
suspend fun loginUserWithPassword(
|
||||
loginName: String,
|
||||
password: String,
|
||||
captchaInfo: CaptchaInfo? = null
|
||||
): UserAuth
|
||||
|
||||
/**
|
||||
* 使用google登录
|
||||
@@ -383,8 +389,16 @@ class AccountServiceImpl : AccountService {
|
||||
return UserAuth(body.id)
|
||||
}
|
||||
|
||||
override suspend fun loginUserWithPassword(loginName: String, password: String): UserAuth {
|
||||
val resp = ApiClient.api.login(LoginUserRequestBody(loginName, password))
|
||||
override suspend fun loginUserWithPassword(
|
||||
loginName: String,
|
||||
password: String,
|
||||
captchaInfo: CaptchaInfo?
|
||||
): UserAuth {
|
||||
val resp = ApiClient.api.login(LoginUserRequestBody(
|
||||
username = loginName,
|
||||
password = password,
|
||||
captcha = captchaInfo,
|
||||
))
|
||||
if (!resp.isSuccessful) {
|
||||
parseErrorResponse(resp.errorBody())?.let {
|
||||
throw it.toServiceException()
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.aiosman.riderpro.data
|
||||
|
||||
import com.aiosman.riderpro.data.api.ApiClient
|
||||
import com.aiosman.riderpro.data.api.CaptchaRequestBody
|
||||
import com.aiosman.riderpro.data.api.CaptchaResponseBody
|
||||
import com.aiosman.riderpro.data.api.CheckLoginCaptchaRequestBody
|
||||
import com.aiosman.riderpro.data.api.GenerateLoginCaptchaRequestBody
|
||||
|
||||
|
||||
interface CaptchaService {
|
||||
suspend fun generateCaptcha(source: String): CaptchaResponseBody
|
||||
suspend fun checkLoginCaptcha(username: String): Boolean
|
||||
suspend fun generateLoginCaptcha(username: String): CaptchaResponseBody
|
||||
}
|
||||
|
||||
class CaptchaServiceImpl : CaptchaService {
|
||||
override suspend fun generateCaptcha(source: String): CaptchaResponseBody {
|
||||
val resp = ApiClient.api.generateCaptcha(
|
||||
CaptchaRequestBody(source)
|
||||
)
|
||||
val data = resp.body() ?: throw Exception("Failed to generate captcha")
|
||||
return data.data.copy(
|
||||
masterBase64 = data.data.masterBase64.replace("data:image/jpeg;base64,", ""),
|
||||
thumbBase64 = data.data.thumbBase64.replace("data:image/png;base64,", "")
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun checkLoginCaptcha(username: String): Boolean {
|
||||
val resp = ApiClient.api.checkLoginCaptcha(
|
||||
CheckLoginCaptchaRequestBody(username)
|
||||
)
|
||||
return resp.body()?.data ?: true
|
||||
}
|
||||
|
||||
override suspend fun generateLoginCaptcha(username: String): CaptchaResponseBody {
|
||||
val resp = ApiClient.api.generateLoginCaptcha(
|
||||
GenerateLoginCaptchaRequestBody(username)
|
||||
)
|
||||
val data = resp.body() ?: throw Exception("Failed to generate captcha")
|
||||
return data.data.copy(
|
||||
masterBase64 = data.data.masterBase64.replace("data:image/jpeg;base64,", ""),
|
||||
thumbBase64 = data.data.thumbBase64.replace("data:image/png;base64,", "")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,8 @@ data class LoginUserRequestBody(
|
||||
val password: String? = null,
|
||||
@SerializedName("googleId")
|
||||
val googleId: String? = null,
|
||||
@SerializedName("captcha")
|
||||
val captcha: CaptchaInfo? = null,
|
||||
)
|
||||
|
||||
data class GoogleRegisterRequestBody(
|
||||
@@ -128,6 +130,69 @@ data class DictItem(
|
||||
val desc: String,
|
||||
)
|
||||
|
||||
data class CaptchaRequestBody(
|
||||
@SerializedName("source")
|
||||
val source: String,
|
||||
)
|
||||
|
||||
data class CaptchaResponseBody(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("thumb_base64")
|
||||
val thumbBase64: String,
|
||||
@SerializedName("master_base64")
|
||||
val masterBase64: String,
|
||||
@SerializedName("count")
|
||||
val count: Int,
|
||||
)
|
||||
|
||||
data class CheckLoginCaptchaRequestBody(
|
||||
@SerializedName("username")
|
||||
val username: String,
|
||||
)
|
||||
data class GenerateLoginCaptchaRequestBody(
|
||||
@SerializedName("username")
|
||||
val username: String,
|
||||
)
|
||||
//{
|
||||
// "id":48,
|
||||
// "dot": [
|
||||
// {
|
||||
// "index": 0,
|
||||
// "x": 76,
|
||||
// "y": 165
|
||||
// },
|
||||
// {
|
||||
// "index": 1,
|
||||
// "x": 144,
|
||||
// "y": 21
|
||||
// },
|
||||
// {
|
||||
// "index": 2,
|
||||
// "x": 220,
|
||||
// "y": 42
|
||||
// },
|
||||
// {
|
||||
// "index": 3,
|
||||
// "x": 10,
|
||||
// "y": 10
|
||||
// }
|
||||
// ]
|
||||
//}
|
||||
data class DotPosition(
|
||||
@SerializedName("index")
|
||||
val index: Int,
|
||||
@SerializedName("x")
|
||||
val x: Int,
|
||||
@SerializedName("y")
|
||||
val y: Int,
|
||||
)
|
||||
data class CaptchaInfo(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("dot")
|
||||
val dot: List<DotPosition>
|
||||
)
|
||||
interface RiderProAPI {
|
||||
@POST("register")
|
||||
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
||||
@@ -336,4 +401,20 @@ interface RiderProAPI {
|
||||
suspend fun getDict(
|
||||
@Query("key") key: String
|
||||
): Response<DataContainer<DictItem>>
|
||||
|
||||
@POST("captcha/generate")
|
||||
suspend fun generateCaptcha(
|
||||
@Body body: CaptchaRequestBody
|
||||
): Response<DataContainer<CaptchaResponseBody>>
|
||||
|
||||
@POST("login/needCaptcha")
|
||||
suspend fun checkLoginCaptcha(
|
||||
@Body body: CheckLoginCaptchaRequestBody
|
||||
): Response<DataContainer<Boolean>>
|
||||
|
||||
@POST("captcha/login/generate")
|
||||
suspend fun generateLoginCaptcha(
|
||||
@Body body: GenerateLoginCaptchaRequestBody
|
||||
): Response<DataContainer<CaptchaResponseBody>>
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
|
||||
@Composable
|
||||
fun ActionButton(
|
||||
modifier: Modifier,
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
color: Color = Color.Black,
|
||||
@DrawableRes backgroundImage: Int? = null,
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
package com.aiosman.riderpro.ui.composables
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.material.AlertDialog
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.nativeCanvas
|
||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.data.api.CaptchaResponseBody
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun ClickCaptchaView(
|
||||
captchaData: CaptchaResponseBody,
|
||||
onPositionClicked: (Offset) -> Unit
|
||||
) {
|
||||
var clickPositions by remember { mutableStateOf(listOf<Offset>()) }
|
||||
|
||||
val context = LocalContext.current
|
||||
val imageBitmap = remember(captchaData.masterBase64) {
|
||||
val decodedString = Base64.decode(captchaData.masterBase64, Base64.DEFAULT)
|
||||
val inputStream = ByteArrayInputStream(decodedString)
|
||||
BitmapFactory.decodeStream(inputStream).asImageBitmap()
|
||||
}
|
||||
val thumbnailBitmap = remember(captchaData.thumbBase64) {
|
||||
val decodedString = Base64.decode(captchaData.thumbBase64, Base64.DEFAULT)
|
||||
val inputStream = ByteArrayInputStream(decodedString)
|
||||
BitmapFactory.decodeStream(inputStream).asImageBitmap()
|
||||
}
|
||||
var boxWidth by remember { mutableStateOf(0) }
|
||||
var boxHeightInDp by remember { mutableStateOf(0.dp) }
|
||||
var scale by remember { mutableStateOf(1f) }
|
||||
val density = LocalDensity.current
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
|
||||
Text(stringResource(R.string.captcha_hint))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Image(
|
||||
bitmap = thumbnailBitmap,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onGloballyPositioned {
|
||||
boxWidth = it.size.width
|
||||
scale = imageBitmap.width.toFloat() / boxWidth
|
||||
boxHeightInDp = with(density) { (imageBitmap.height.toFloat() / scale).toDp() }
|
||||
}
|
||||
.background(Color.Gray)
|
||||
) {
|
||||
if (boxWidth != 0 && boxHeightInDp != 0.dp) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(boxHeightInDp)
|
||||
) {
|
||||
Image(
|
||||
bitmap = imageBitmap,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.pointerInteropFilter { event ->
|
||||
if (event.action == android.view.MotionEvent.ACTION_DOWN) {
|
||||
val newPosition = Offset(event.x, event.y)
|
||||
clickPositions = clickPositions + newPosition
|
||||
// 计算出点击的位置在图片上的坐标
|
||||
val imagePosition = Offset(
|
||||
newPosition.x * scale,
|
||||
newPosition.y * scale
|
||||
)
|
||||
onPositionClicked(imagePosition)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Draw markers at click positions
|
||||
clickPositions.forEachIndexed { index, position ->
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
drawCircle(
|
||||
color = Color(0xaada3832).copy(),
|
||||
radius = 40f,
|
||||
center = position
|
||||
)
|
||||
drawContext.canvas.nativeCanvas.apply {
|
||||
drawText(
|
||||
(index + 1).toString(),
|
||||
position.x,
|
||||
position.y + 15f, // Adjusting the y position to center the text
|
||||
android.graphics.Paint().apply {
|
||||
color = android.graphics.Color.WHITE
|
||||
textSize = 50f
|
||||
textAlign = android.graphics.Paint.Align.CENTER
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ClickCaptchaDialog(
|
||||
captchaData: CaptchaResponseBody,
|
||||
onLoadCaptcha: () -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
onPositionClicked: (Offset) -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
onDismissRequest()
|
||||
},
|
||||
title = {
|
||||
Text("Captcha")
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
ClickCaptchaView(
|
||||
captchaData = captchaData!!,
|
||||
onPositionClicked = onPositionClicked
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
ActionButton(
|
||||
text = "Refresh",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
onLoadCaptcha()
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package com.aiosman.riderpro.ui.login
|
||||
|
||||
import android.icu.util.Calendar
|
||||
import android.icu.util.TimeZone
|
||||
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.PaddingValues
|
||||
@@ -16,7 +13,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -35,35 +31,41 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.riderpro.AppState
|
||||
import com.aiosman.riderpro.AppStore
|
||||
import com.aiosman.riderpro.ErrorCode
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.Messaging
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.data.AccountService
|
||||
import com.aiosman.riderpro.data.AccountServiceImpl
|
||||
import com.aiosman.riderpro.data.CaptchaService
|
||||
import com.aiosman.riderpro.data.CaptchaServiceImpl
|
||||
import com.aiosman.riderpro.data.ServiceException
|
||||
import com.aiosman.riderpro.data.api.CaptchaInfo
|
||||
import com.aiosman.riderpro.data.api.CaptchaResponseBody
|
||||
import com.aiosman.riderpro.data.api.DotPosition
|
||||
import com.aiosman.riderpro.ui.NavigationRoute
|
||||
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
|
||||
import com.aiosman.riderpro.ui.composables.ActionButton
|
||||
import com.aiosman.riderpro.ui.composables.ClickCaptchaDialog
|
||||
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.riderpro.ui.composables.TextInputField
|
||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.riderpro.utils.GoogleLogin
|
||||
import com.aiosman.riderpro.utils.Utils
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun UserAuthScreen() {
|
||||
var email by remember { mutableStateOf("") }
|
||||
var password by remember { mutableStateOf("") }
|
||||
var email by remember { mutableStateOf("root") }
|
||||
var password by remember { mutableStateOf("password") }
|
||||
var rememberMe by remember { mutableStateOf(false) }
|
||||
var accountService: AccountService = AccountServiceImpl()
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
val captchaService: CaptchaService = CaptchaServiceImpl()
|
||||
val scope = rememberCoroutineScope()
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
var emailError by remember { mutableStateOf<String?>(null) }
|
||||
var passwordError by remember { mutableStateOf<String?>(null) }
|
||||
var captchaInfo by remember { mutableStateOf<CaptchaInfo?>(null) }
|
||||
fun validateForm(): Boolean {
|
||||
emailError =
|
||||
if (email.isEmpty()) context.getString(R.string.text_error_email_required) else null
|
||||
@@ -72,13 +74,37 @@ fun UserAuthScreen() {
|
||||
return emailError == null && passwordError == null
|
||||
}
|
||||
|
||||
fun onLogin() {
|
||||
var captchaData by remember { mutableStateOf<CaptchaResponseBody?>(null) }
|
||||
fun loadLoginCaptcha() {
|
||||
scope.launch {
|
||||
try {
|
||||
captchaData = captchaService.generateLoginCaptcha(email)
|
||||
captchaData?.let {
|
||||
captchaInfo = CaptchaInfo(
|
||||
id = it.id,
|
||||
dot = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
} catch (e: ServiceException) {
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onLogin(captchaInfo: CaptchaInfo? = null) {
|
||||
if (!validateForm()) {
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
try {
|
||||
val authResp = accountService.loginUserWithPassword(email, password)
|
||||
// 检查是否需要验证码
|
||||
if (captchaInfo == null && captchaService.checkLoginCaptcha(email)) {
|
||||
loadLoginCaptcha()
|
||||
return@launch
|
||||
}
|
||||
// 获取用户凭证
|
||||
val authResp = accountService.loginUserWithPassword(email, password, captchaInfo)
|
||||
if (authResp.token != null) {
|
||||
AppStore.apply {
|
||||
token = authResp.token
|
||||
@@ -92,13 +118,25 @@ fun UserAuthScreen() {
|
||||
}
|
||||
} catch (e: ServiceException) {
|
||||
// handle error
|
||||
|
||||
if (e.code == 12005) {
|
||||
emailError = context.getString(R.string.error_invalidate_username_password)
|
||||
passwordError = context.getString(R.string.error_invalidate_username_password)
|
||||
} else if (e.code == ErrorCode.InvalidateCaptcha.code) {
|
||||
loadLoginCaptcha()
|
||||
Toast.makeText(
|
||||
context,
|
||||
"incorrect captcha,please try again",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
} finally {
|
||||
captchaData = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,10 +170,37 @@ fun UserAuthScreen() {
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
captchaData?.let {
|
||||
ClickCaptchaDialog(
|
||||
onDismissRequest = {
|
||||
captchaData = null
|
||||
},
|
||||
captchaData = it,
|
||||
onLoadCaptcha = {
|
||||
loadLoginCaptcha()
|
||||
},
|
||||
onPositionClicked = { offset ->
|
||||
captchaInfo?.let { info ->
|
||||
val dots = info.dot.toMutableList()
|
||||
val lastDotIndex = dots.size - 1
|
||||
dots += DotPosition(
|
||||
index = lastDotIndex + 1,
|
||||
x = offset.x.toInt(),
|
||||
y = offset.y.toInt()
|
||||
)
|
||||
captchaInfo = info.copy(dot = dots)
|
||||
// 检查是否完成
|
||||
if (dots.size == it.count) {
|
||||
onLogin(captchaInfo)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
@@ -204,26 +269,6 @@ fun UserAuthScreen() {
|
||||
navController.navigate(NavigationRoute.ResetPassword.route)
|
||||
}
|
||||
)
|
||||
// CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
|
||||
// Checkbox(
|
||||
// checked = rememberMe,
|
||||
// onCheckedChange = {
|
||||
// rememberMe = it
|
||||
// },
|
||||
// colors = CheckboxDefaults.colors(
|
||||
// checkedColor = Color.Black
|
||||
// ),
|
||||
// )
|
||||
// Text(
|
||||
// stringResource(R.string.remember_me),
|
||||
// modifier = Modifier.padding(start = 8.dp),
|
||||
// fontSize = 12.sp
|
||||
// )
|
||||
// Spacer(modifier = Modifier.weight(1f))
|
||||
// Text(stringResource(R.string.forgot_password), fontSize = 12.sp, modifier = Modifier.noRippleClickable {
|
||||
// navController.navigate(NavigationRoute.ResetPassword.route)
|
||||
// })
|
||||
// }
|
||||
|
||||
}
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
@@ -251,7 +296,7 @@ fun UserAuthScreen() {
|
||||
},
|
||||
expandText = true,
|
||||
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 8.dp)
|
||||
){
|
||||
) {
|
||||
googleLogin()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,4 +81,5 @@
|
||||
<string name="like_your_comment">喜欢了你的评论</string>
|
||||
<string name="resend">重新发送 %s</string>
|
||||
<string name="error_40002_user_not_exist">用户不存在</string>
|
||||
<string name="captcha_hint">请依次点击图片中的元素</string>
|
||||
</resources>
|
||||
@@ -80,4 +80,5 @@
|
||||
<string name="like_your_comment">Like your comment</string>
|
||||
<string name="resend">Resend in %s</string>
|
||||
<string name="error_40002_user_not_exist">user not exist</string>
|
||||
<string name="captcha_hint">Please click on the dots in the image in the correct order.</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user