feat: Android login/register add mandatory agreement checkbox
- LoginScreen: add agreement checkbox, disabled until checked - RegisterScreen: add agreement checkbox, disabled until checked - RegisterViewModel: pass agreed_terms/agreed_terms_version to API - RegisterRequest DTO: add agreedTerms/agreedTermsVersion fields - Also include 3 template scripts missed in previous commit Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -266,7 +266,9 @@ data class RegisterRequest(
|
||||
val username: String,
|
||||
val password: String,
|
||||
val email: String = "",
|
||||
@SerializedName("display_name") val displayName: String? = null
|
||||
@SerializedName("display_name") val displayName: String? = null,
|
||||
@SerializedName("agreed_terms") val agreedTerms: Boolean = false,
|
||||
@SerializedName("agreed_terms_version") val agreedTermsVersion: String? = null
|
||||
)
|
||||
|
||||
data class RegisterResponse(
|
||||
|
||||
@@ -217,7 +217,42 @@ fun LoginScreen(
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Agreement checkbox
|
||||
val context = LocalContext.current
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = uiState.agreedTerms,
|
||||
onCheckedChange = viewModel::onAgreedTermsChanged,
|
||||
enabled = !uiState.isLoading
|
||||
)
|
||||
Text("我已阅读并同意", style = MaterialTheme.typography.bodySmall)
|
||||
TextButton(
|
||||
onClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://tiangong.ai/privacy"))
|
||||
context.startActivity(intent)
|
||||
},
|
||||
contentPadding = PaddingValues(horizontal = 2.dp)
|
||||
) {
|
||||
Text("《隐私政策》", style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Text("和", style = MaterialTheme.typography.bodySmall)
|
||||
TextButton(
|
||||
onClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://tiangong.ai/terms"))
|
||||
context.startActivity(intent)
|
||||
},
|
||||
contentPadding = PaddingValues(horizontal = 2.dp)
|
||||
) {
|
||||
Text("《用户协议》", style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Login button
|
||||
Button(
|
||||
@@ -228,7 +263,7 @@ fun LoginScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp),
|
||||
enabled = !uiState.isLoading
|
||||
enabled = !uiState.isLoading && uiState.agreedTerms
|
||||
) {
|
||||
if (uiState.isLoading) {
|
||||
CircularProgressIndicator(
|
||||
@@ -251,7 +286,6 @@ fun LoginScreen(
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Privacy policy & user agreement
|
||||
val context = LocalContext.current
|
||||
PrivacyPolicyRow(
|
||||
onPrivacyPolicy = {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://tiangong.ai/privacy"))
|
||||
|
||||
@@ -16,6 +16,7 @@ data class LoginUiState(
|
||||
val username: String = "",
|
||||
val password: String = "",
|
||||
val serverUrl: String = "",
|
||||
val agreedTerms: Boolean = false,
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
val isLoggedIn: Boolean = false
|
||||
@@ -50,6 +51,10 @@ class LoginViewModel @Inject constructor(
|
||||
_uiState.value = _uiState.value.copy(serverUrl = url, error = null)
|
||||
}
|
||||
|
||||
fun onAgreedTermsChanged(value: Boolean) {
|
||||
_uiState.value = _uiState.value.copy(agreedTerms = value, error = null)
|
||||
}
|
||||
|
||||
fun saveServerUrl(url: String) {
|
||||
val trimmed = url.trim().trimEnd('/')
|
||||
if (trimmed.isBlank()) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -178,13 +179,47 @@ fun RegisterScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Agreement checkbox
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = uiState.agreedTerms,
|
||||
onCheckedChange = viewModel::onAgreedTermsChanged,
|
||||
enabled = !uiState.isLoading
|
||||
)
|
||||
Text(
|
||||
text = "我已阅读并同意",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
TextButton(
|
||||
onClick = { /* TODO: open privacy policy in-app */ },
|
||||
contentPadding = PaddingValues(horizontal = 2.dp)
|
||||
) {
|
||||
Text("《隐私政策》", style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Text(
|
||||
text = "和",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
TextButton(
|
||||
onClick = { /* TODO: open user agreement in-app */ },
|
||||
contentPadding = PaddingValues(horizontal = 2.dp)
|
||||
) {
|
||||
Text("《用户协议》", style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Register button
|
||||
Button(
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.register()
|
||||
},
|
||||
enabled = !uiState.isLoading,
|
||||
enabled = !uiState.isLoading && uiState.agreedTerms,
|
||||
modifier = Modifier.fillMaxWidth().height(50.dp)
|
||||
) {
|
||||
if (uiState.isLoading) {
|
||||
|
||||
@@ -18,6 +18,7 @@ data class RegisterUiState(
|
||||
val confirmPassword: String = "",
|
||||
val email: String = "",
|
||||
val displayName: String = "",
|
||||
val agreedTerms: Boolean = false,
|
||||
|
||||
val isUsernameValid: Boolean = true,
|
||||
val isPasswordValid: Boolean = true,
|
||||
@@ -96,6 +97,10 @@ class RegisterViewModel @Inject constructor(
|
||||
_uiState.update { it.copy(displayName = value) }
|
||||
}
|
||||
|
||||
fun onAgreedTermsChanged(value: Boolean) {
|
||||
_uiState.update { it.copy(agreedTerms = value) }
|
||||
}
|
||||
|
||||
fun clearError() {
|
||||
_uiState.update { it.copy(errorMessage = null) }
|
||||
}
|
||||
@@ -137,6 +142,10 @@ class RegisterViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
if (!usernameOk || !passwordOk || !confirmOk || !emailOk) return
|
||||
if (!state.agreedTerms) {
|
||||
_uiState.update { it.copy(errorMessage = "请先阅读并同意用户协议和隐私政策") }
|
||||
return
|
||||
}
|
||||
|
||||
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
|
||||
|
||||
@@ -145,7 +154,9 @@ class RegisterViewModel @Inject constructor(
|
||||
username = state.username.trim(),
|
||||
password = state.password,
|
||||
email = state.email.trim(),
|
||||
displayName = state.displayName.trim().ifBlank { null }
|
||||
displayName = state.displayName.trim().ifBlank { null },
|
||||
agreedTerms = true,
|
||||
agreedTermsVersion = "1.0"
|
||||
)
|
||||
val result = authRepository.register(request)
|
||||
result.fold(
|
||||
|
||||
@@ -17,8 +17,8 @@ import time
|
||||
import requests
|
||||
|
||||
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
|
||||
USER = os.getenv("PLATFORM_USERNAME", "admin")
|
||||
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
|
||||
USER = os.getenv("PLATFORM_USERNAME")
|
||||
PWD = os.getenv("PLATFORM_PASSWORD")
|
||||
TEMPLATE_ID = "template_customer_service"
|
||||
DEFAULT_NAME = os.getenv("TARGET_NAME") or f"场景模板_客服_{int(time.time())}"
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ import time
|
||||
import requests
|
||||
|
||||
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
|
||||
USER = os.getenv("PLATFORM_USERNAME", "admin")
|
||||
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
|
||||
USER = os.getenv("PLATFORM_USERNAME")
|
||||
PWD = os.getenv("PLATFORM_PASSWORD")
|
||||
TEMPLATE_ID = "template_dev_codegen"
|
||||
DEFAULT_NAME = os.getenv("TARGET_NAME") or f"场景模板_研发_{int(time.time())}"
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ import time
|
||||
import requests
|
||||
|
||||
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
|
||||
USER = os.getenv("PLATFORM_USERNAME", "admin")
|
||||
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
|
||||
USER = os.getenv("PLATFORM_USERNAME")
|
||||
PWD = os.getenv("PLATFORM_PASSWORD")
|
||||
TEMPLATE_ID = "template_ops_log_analysis"
|
||||
DEFAULT_NAME = os.getenv("TARGET_NAME") or f"场景模板_运维_{int(time.time())}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user