parent
0e2e960c31
commit
5f5f8bb45c
@ -0,0 +1,56 @@ |
||||
package com.gyf.csams |
||||
|
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import androidx.activity.ComponentActivity |
||||
import androidx.activity.compose.setContent |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.livedata.observeAsState |
||||
import androidx.compose.ui.platform.LocalContext |
||||
import androidx.lifecycle.viewmodel.compose.viewModel |
||||
import com.gyf.csams.account.ui.AccountActivity |
||||
import com.gyf.csams.ui.AnimationText |
||||
import com.gyf.csams.ui.MainActivity |
||||
import com.orhanobut.logger.Logger |
||||
|
||||
class InitActivity : ComponentActivity() { |
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
|
||||
// 检查网络 |
||||
setContent { |
||||
val initViewModel:InitViewModel= viewModel() |
||||
initViewModel.checkServer() |
||||
val isNetWorkWorking:Boolean? by initViewModel.isNetWorkWorking.observeAsState(null) |
||||
when(isNetWorkWorking){ |
||||
null-> AnimationText(text = "检查服务器网络状态中!!!") |
||||
true-> { |
||||
Init() |
||||
finish() |
||||
} |
||||
false->{ |
||||
TODO("无法连接到服务器,请检查本地网络或联系管理员") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Composable |
||||
private fun Init(initViewModel:InitViewModel= viewModel()){ |
||||
Logger.i("初始化。。。。") |
||||
val context= LocalContext.current |
||||
//后台检查token |
||||
initViewModel.hasOnlyUserToken(context) |
||||
//监听token校验状态 |
||||
val isValid: Boolean? by initViewModel.token.observeAsState(null) |
||||
|
||||
when (isValid) { |
||||
false -> context.startActivity(Intent(context, AccountActivity::class.java)) |
||||
true -> context.startActivity(Intent(context,MainActivity::class.java)) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,67 @@ |
||||
package com.gyf.csams |
||||
|
||||
import android.content.Context |
||||
import androidx.lifecycle.LiveData |
||||
import androidx.lifecycle.MutableLiveData |
||||
import androidx.lifecycle.ViewModel |
||||
import androidx.lifecycle.viewModelScope |
||||
import com.google.gson.Gson |
||||
import com.google.gson.reflect.TypeToken |
||||
import com.gyf.csams.util.* |
||||
import com.orhanobut.logger.Logger |
||||
import kotlinx.coroutines.launch |
||||
|
||||
data class TokenVo(val token:String,val studentId:String) |
||||
|
||||
class InitViewModel: ViewModel() { |
||||
/** |
||||
* 服务器网络状态是否正常,true=正常,false=不正常 |
||||
*/ |
||||
private val _isNetWorkWorking = MutableLiveData<Boolean>() |
||||
val isNetWorkWorking: LiveData<Boolean> = _isNetWorkWorking |
||||
|
||||
/** |
||||
* token |
||||
*/ |
||||
private val _token = MutableLiveData<Boolean>() |
||||
val token: LiveData<Boolean> = _token |
||||
|
||||
|
||||
fun checkServer(){ |
||||
Logger.i("测试连接到服务端") |
||||
_isNetWorkWorking.postValue(true) |
||||
} |
||||
|
||||
/** |
||||
* 查询本地是否有且只有一个用户token,如果有则自动登录 |
||||
*/ |
||||
fun hasOnlyUserToken(context: Context){ |
||||
viewModelScope.launch{ |
||||
val db=AppDatabase.getInstance(context) |
||||
val tokenList=db?.tokenDao()?.queryAll() |
||||
if (tokenList != null && tokenList.size == 1) { |
||||
val currentToken: Token = tokenList[0] |
||||
val url=Api.buildUrl(AccountApi.loginToken) |
||||
val action="校验token" |
||||
Logger.i("${action}api=$url") |
||||
HttpClient.post(url,SimpleCallback<Boolean>( |
||||
action=action, |
||||
onSuccess = { |
||||
_token.postValue(it.body) |
||||
Logger.i("token校验结果:${it.body}") |
||||
}, |
||||
onFail = { TODO("token校验失败")}, |
||||
type = object : TypeToken<ApiResponse<Boolean>>(){}.type |
||||
),jsonBody = Gson().toJson(TokenVo(token=currentToken.token,studentId = currentToken.studentId))) |
||||
}else if(tokenList != null && tokenList.size > 1){ |
||||
//TODO 实现切换历史登录帐号 |
||||
Logger.i("token数量大于一,需要手动登录") |
||||
_token.postValue(false) |
||||
}else{ |
||||
Logger.i("本地没有任何token,跳转到登录界面") |
||||
_token.postValue(false) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -1,377 +0,0 @@ |
||||
package com.gyf.csams |
||||
|
||||
import android.os.Bundle |
||||
import androidx.activity.ComponentActivity |
||||
import androidx.activity.compose.setContent |
||||
import androidx.compose.animation.animateColor |
||||
import androidx.compose.animation.core.* |
||||
import androidx.compose.foundation.layout.* |
||||
import androidx.compose.foundation.text.KeyboardActions |
||||
import androidx.compose.foundation.text.KeyboardOptions |
||||
import androidx.compose.material.* |
||||
import androidx.compose.runtime.* |
||||
import androidx.compose.runtime.livedata.observeAsState |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.platform.LocalFocusManager |
||||
import androidx.compose.ui.text.buildAnnotatedString |
||||
import androidx.compose.ui.text.input.ImeAction |
||||
import androidx.compose.ui.text.input.KeyboardType |
||||
import androidx.compose.ui.text.withStyle |
||||
import androidx.compose.ui.tooling.preview.Preview |
||||
import androidx.compose.ui.unit.dp |
||||
import androidx.lifecycle.viewmodel.compose.viewModel |
||||
import com.gyf.csams.account.model.DialogMessage |
||||
import com.gyf.csams.account.model.RegisterViewModel |
||||
import com.gyf.csams.ui.theme.CSAMSTheme |
||||
|
||||
class MainActivity : ComponentActivity() { |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
|
||||
setContent { |
||||
|
||||
CSAMSTheme { |
||||
// A surface container using the 'background' color from the theme |
||||
Surface(color = MaterialTheme.colors.background) { |
||||
Register() |
||||
RegisterDialog() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
* 注册表单 |
||||
* |
||||
*/ |
||||
@Composable |
||||
fun Register(registerViewModel: RegisterViewModel=viewModel()){ |
||||
val scaffoldState = rememberScaffoldState() |
||||
Scaffold(scaffoldState=scaffoldState) { |
||||
Row( |
||||
horizontalArrangement = Arrangement.Center, |
||||
verticalAlignment = Alignment.CenterVertically, |
||||
modifier = Modifier.fillMaxSize() |
||||
) { |
||||
Column(modifier = Modifier.width(IntrinsicSize.Min)) { |
||||
val name: String by registerViewModel.name.observeAsState("") |
||||
Text(buildAnnotatedString { |
||||
withStyle( |
||||
style = MaterialTheme.typography.subtitle1.toSpanStyle() |
||||
.copy(color = MaterialTheme.colors.primary) |
||||
) { |
||||
append(name) |
||||
} |
||||
withStyle(style = MaterialTheme.typography.subtitle1.toSpanStyle()) { |
||||
append(registerViewModel.welcomeStart) |
||||
} |
||||
withStyle(style = MaterialTheme.typography.subtitle2.toSpanStyle()) { |
||||
append(registerViewModel.welcomeEnd) |
||||
} |
||||
withStyle( |
||||
style = MaterialTheme.typography.subtitle2.toSpanStyle() |
||||
.copy(color = MaterialTheme.colors.secondary) |
||||
) { |
||||
append(BuildConfig.APP_NAME) |
||||
} |
||||
}) |
||||
|
||||
StudentId() |
||||
Spacer(modifier = Modifier.height(10.dp)) |
||||
Name(name) |
||||
Spacer(modifier = Modifier.height(10.dp)) |
||||
|
||||
val isValidForm: Boolean by registerViewModel.isValidForm.observeAsState(false) |
||||
if(isValidForm) { |
||||
Password() |
||||
} |
||||
Spacer(modifier = Modifier.height(10.dp)) |
||||
RegisterButton(isValidForm,scaffoldState = scaffoldState) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 学号 |
||||
* |
||||
* @param registerViewModel |
||||
*/ |
||||
@Composable |
||||
fun StudentId(registerViewModel: RegisterViewModel=viewModel()){ |
||||
Column { |
||||
|
||||
val studentId: String by registerViewModel.studentId.observeAsState("") |
||||
val isValidStudentId : Boolean by registerViewModel.isValidStudentId.observeAsState(false) |
||||
val focusManager = LocalFocusManager.current |
||||
OutlinedTextField( |
||||
value = studentId, |
||||
onValueChange = { registerViewModel.onStudentIdChange(it) }, |
||||
label = { Text(text = registerViewModel.studentIdDesc) }, |
||||
placeholder = { Text(text = registerViewModel.studentIdPlaceholder) }, |
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), |
||||
keyboardOptions = KeyboardOptions.Default.copy( keyboardType = KeyboardType.Number,imeAction = ImeAction.Done), |
||||
singleLine = true, |
||||
isError = !isValidStudentId |
||||
) |
||||
if (isValidStudentId) { |
||||
val isRepeat:Boolean? by registerViewModel.isRepeat.observeAsState(null) |
||||
when(isRepeat){ |
||||
null-> AnimationText(text = registerViewModel.checkRegTip) |
||||
true-> |
||||
Text(buildAnnotatedString { |
||||
append(registerViewModel.studentIdDesc) |
||||
withStyle(style = MaterialTheme.typography.body1.toSpanStyle().copy( |
||||
color=MaterialTheme.colors.error)){ |
||||
append(studentId) |
||||
} |
||||
append(registerViewModel.registered) |
||||
}) |
||||
false-> |
||||
Text(buildAnnotatedString { |
||||
append(registerViewModel.studentIdDesc) |
||||
withStyle(style = MaterialTheme.typography.body1.toSpanStyle().copy( |
||||
color=MaterialTheme.colors.primary)){ |
||||
append(studentId) |
||||
} |
||||
append(registerViewModel.canRegister) |
||||
}) |
||||
} |
||||
}else{ |
||||
Text( |
||||
text = registerViewModel.studentIdFormat, |
||||
color = MaterialTheme.colors.error, |
||||
style = MaterialTheme.typography.body1 |
||||
) |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 淡入淡出并且颜色变化文本 |
||||
* |
||||
* @param text |
||||
*/ |
||||
@Composable |
||||
fun AnimationText(text:String){ |
||||
val infiniteTransition = rememberInfiniteTransition() |
||||
val color by infiniteTransition.animateColor( |
||||
initialValue = Color.Red, |
||||
targetValue = Color.Green, |
||||
animationSpec = infiniteRepeatable( |
||||
animation = tween(1000, easing = LinearEasing), |
||||
repeatMode = RepeatMode.Reverse |
||||
) |
||||
) |
||||
|
||||
Text( |
||||
text = text, |
||||
color = color, |
||||
style = MaterialTheme.typography.body1 |
||||
) |
||||
} |
||||
|
||||
/** |
||||
* 注册弹窗 |
||||
* |
||||
* @param registerViewModel |
||||
*/ |
||||
@Composable |
||||
fun RegisterDialog(registerViewModel: RegisterViewModel=viewModel()){ |
||||
val dialogMsg:DialogMessage? by registerViewModel.dialogMsg.observeAsState(null) |
||||
|
||||
val message=dialogMsg?.userResDto?.password |
||||
if(message?.isNotEmpty() == true){ |
||||
PasswordDialog(message = message) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 密码弹窗 |
||||
* |
||||
* @param registerViewModel |
||||
* @param message |
||||
*/ |
||||
@Composable |
||||
fun PasswordDialog(registerViewModel: RegisterViewModel=viewModel(),message:String){ |
||||
val button:@Composable () -> Unit = { |
||||
Row(horizontalArrangement=Arrangement.Center,modifier = Modifier |
||||
.fillMaxWidth() |
||||
.padding(bottom = 10.dp)) { |
||||
OutlinedButton(onClick = { registerViewModel.resetDialogMsg() }, |
||||
modifier = Modifier.padding(end = 10.dp)) { |
||||
Text(text = registerViewModel.confirmDesc) |
||||
} |
||||
OutlinedButton(onClick = { TODO() }, |
||||
colors = ButtonDefaults.outlinedButtonColors( |
||||
contentColor = MaterialTheme.colors.onBackground)) { |
||||
Text(text = registerViewModel.backDesc) |
||||
} |
||||
} |
||||
} |
||||
AlertDialog(onDismissRequest = { registerViewModel.resetDialogMsg() }, |
||||
buttons = button, |
||||
title = { Text(text = registerViewModel.title) }, |
||||
text = { |
||||
Text(buildAnnotatedString { |
||||
append(registerViewModel.passwordDialogStart) |
||||
withStyle(style = MaterialTheme.typography.body1.toSpanStyle() |
||||
.copy(color = MaterialTheme.colors.secondary)){ |
||||
append(message) |
||||
} |
||||
append(registerViewModel.passwordDialogEnd) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 姓名 |
||||
* |
||||
* @param registerViewModel |
||||
*/ |
||||
@Composable |
||||
fun Name(name:String,registerViewModel: RegisterViewModel=viewModel()){ |
||||
Column { |
||||
|
||||
val isValidName:Boolean by registerViewModel.isValidName.observeAsState(false) |
||||
val focusManager = LocalFocusManager.current |
||||
OutlinedTextField(value = name, |
||||
onValueChange = {registerViewModel.onNameChange(it)}, |
||||
label={ Text(text = registerViewModel.nameDesc)}, |
||||
placeholder = { Text(text = registerViewModel.namePlaceholder)}, |
||||
singleLine = true, |
||||
isError = !isValidName, |
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), |
||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done)) |
||||
|
||||
if (!isValidName){ |
||||
Text(text = registerViewModel.nameFormat, |
||||
color=MaterialTheme.colors.error) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
@Composable |
||||
fun Password(registerViewModel: RegisterViewModel=viewModel()) { |
||||
Text(text = registerViewModel.passwordTip |
||||
,color=MaterialTheme.colors.primary, |
||||
modifier = Modifier.fillMaxWidth()) |
||||
} |
||||
|
||||
/** |
||||
* 注册按钮 |
||||
* |
||||
* @param registerViewModel |
||||
*/ |
||||
@Composable |
||||
fun RegisterButton(isValidForm:Boolean,scaffoldState:ScaffoldState,registerViewModel: RegisterViewModel=viewModel()){ |
||||
|
||||
OutlinedButton(onClick = { registerViewModel.register()}, |
||||
enabled = isValidForm, |
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp)) { |
||||
Text(text = registerViewModel.regBtnDesc) |
||||
} |
||||
|
||||
OutlinedButton(onClick = { TODO()}, |
||||
modifier = Modifier.fillMaxWidth(), |
||||
colors = ButtonDefaults.outlinedButtonColors( |
||||
contentColor = MaterialTheme.colors.onBackground)) { |
||||
Text(text = registerViewModel.backDesc) |
||||
} |
||||
|
||||
val snackBarMsg:String by registerViewModel.snackBarMsg.observeAsState("") |
||||
|
||||
if(snackBarMsg!=""){ |
||||
LaunchedEffect(scaffoldState) { |
||||
scaffoldState.snackbarHostState.showSnackbar( |
||||
message = snackBarMsg |
||||
) |
||||
registerViewModel.resetRegisterResMsg() |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
@Preview(showBackground = true) |
||||
|
||||
@Composable |
||||
fun DefaultPreview() { |
||||
CSAMSTheme { |
||||
|
||||
Row ( |
||||
horizontalArrangement=Arrangement.Center, |
||||
verticalAlignment = Alignment.CenterVertically, |
||||
modifier = Modifier.fillMaxSize()) { |
||||
Column(modifier = Modifier.width(IntrinsicSize.Min)) { |
||||
val model=RegisterViewModel() |
||||
|
||||
StudentId(model) |
||||
Spacer(modifier = Modifier.height(10.dp)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Preview |
||||
@Composable |
||||
fun AnimationTextPreview(){ |
||||
AnimationText(text = "6666") |
||||
} |
||||
|
||||
@Preview |
||||
@Composable |
||||
fun PasswordDial(){ |
||||
CSAMSTheme { |
||||
// A surface container using the 'background' color from the theme |
||||
Surface(color = MaterialTheme.colors.background) { |
||||
// val model=RegisterViewModel() |
||||
// PasswordDialog(registerViewModel=model,message = "99999") |
||||
val openDialog = remember { mutableStateOf(true) } |
||||
AlertDialog( |
||||
onDismissRequest = { |
||||
// Dismiss the dialog when the user clicks outside the dialog or on the back |
||||
// button. If you want to disable that functionality, simply use an empty |
||||
// onCloseRequest. |
||||
openDialog.value = false |
||||
}, |
||||
title = { |
||||
Text(text = "Title") |
||||
}, |
||||
text = { |
||||
Text( |
||||
"This area typically contains the supportive text " + |
||||
"which presents the details regarding the Dialog's purpose." |
||||
) |
||||
}, |
||||
confirmButton = { |
||||
TextButton( |
||||
onClick = { |
||||
openDialog.value = false |
||||
} |
||||
) { |
||||
Text("Confirm") |
||||
} |
||||
}, |
||||
dismissButton = { |
||||
TextButton( |
||||
onClick = { |
||||
openDialog.value = false |
||||
} |
||||
) { |
||||
Text("Dismiss") |
||||
} |
||||
} |
||||
) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,391 @@ |
||||
package com.gyf.csams.account.ui |
||||
|
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import androidx.activity.ComponentActivity |
||||
import androidx.activity.compose.setContent |
||||
import androidx.compose.foundation.layout.* |
||||
import androidx.compose.foundation.text.KeyboardActions |
||||
import androidx.compose.foundation.text.KeyboardOptions |
||||
import androidx.compose.material.* |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.LaunchedEffect |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.livedata.observeAsState |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.platform.LocalContext |
||||
import androidx.compose.ui.platform.LocalFocusManager |
||||
import androidx.compose.ui.text.buildAnnotatedString |
||||
import androidx.compose.ui.text.input.ImeAction |
||||
import androidx.compose.ui.text.input.KeyboardType |
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation |
||||
import androidx.compose.ui.text.withStyle |
||||
import androidx.compose.ui.tooling.preview.Preview |
||||
import androidx.compose.ui.unit.dp |
||||
import androidx.lifecycle.viewmodel.compose.viewModel |
||||
import androidx.navigation.compose.NavHost |
||||
import androidx.navigation.compose.composable |
||||
import androidx.navigation.compose.navigate |
||||
import androidx.navigation.compose.rememberNavController |
||||
import com.gyf.csams.BuildConfig |
||||
import com.gyf.csams.account.model.AccountViewModel |
||||
import com.gyf.csams.account.model.DialogMessage |
||||
import com.gyf.csams.ui.AnimationText |
||||
import com.gyf.csams.ui.theme.CSAMSTheme |
||||
|
||||
|
||||
enum class AccountRoute{ |
||||
login, |
||||
register |
||||
} |
||||
|
||||
class AccountActivity: ComponentActivity() { |
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
|
||||
setContent { |
||||
CSAMSTheme { |
||||
// A surface container using the 'background' color from the theme |
||||
Surface(color = MaterialTheme.colors.background) { |
||||
val navController = rememberNavController() |
||||
val scaffoldState = rememberScaffoldState() |
||||
|
||||
Scaffold(scaffoldState=scaffoldState) { |
||||
NavHost(navController, startDestination = AccountRoute.login.name) { |
||||
composable(AccountRoute.login.name) { |
||||
Account(scaffoldState=scaffoldState,route = AccountRoute.login) { isValidForm: Boolean, accountViewModel: AccountViewModel -> |
||||
Spacer(modifier = Modifier.height(10.dp)) |
||||
|
||||
OutlinedButton(onClick = {accountViewModel.login()}, |
||||
enabled = isValidForm, |
||||
modifier = Modifier |
||||
.fillMaxWidth() |
||||
.padding(bottom = 10.dp)) { |
||||
|
||||
Text(text = accountViewModel.loginDesc) |
||||
} |
||||
|
||||
val finishLogin:Boolean? by accountViewModel.finishLogin.observeAsState() |
||||
if(finishLogin==true){ |
||||
finish() |
||||
} |
||||
|
||||
OutlinedButton(onClick = { navController.navigate(AccountRoute.register.name)}, |
||||
modifier = Modifier.fillMaxWidth(), |
||||
colors = ButtonDefaults.outlinedButtonColors( |
||||
contentColor = MaterialTheme.colors.onBackground)) { |
||||
Text(text = accountViewModel.goRegister) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
composable(AccountRoute.register.name) { |
||||
Account(scaffoldState=scaffoldState,route = AccountRoute.register) { isValidForm: Boolean, accountViewModel: AccountViewModel -> |
||||
Spacer(modifier = Modifier.height(10.dp)) |
||||
|
||||
OutlinedButton(onClick = { accountViewModel.register()}, |
||||
enabled = isValidForm, |
||||
modifier = Modifier |
||||
.fillMaxWidth() |
||||
.padding(bottom = 10.dp)) { |
||||
Text(text = accountViewModel.regBtnDesc) |
||||
} |
||||
|
||||
OutlinedButton(onClick = { navController.navigate(AccountRoute.login.name)}, |
||||
modifier = Modifier.fillMaxWidth(), |
||||
colors = ButtonDefaults.outlinedButtonColors( |
||||
contentColor = MaterialTheme.colors.onBackground)) { |
||||
Text(text = accountViewModel.backLogin) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 帐号表单 |
||||
* |
||||
* @param accountViewModel |
||||
* @param Action 表单操作区域 |
||||
*/ |
||||
@Composable |
||||
private fun Account(accountViewModel: AccountViewModel = viewModel(), |
||||
scaffoldState: ScaffoldState, |
||||
route: AccountRoute, |
||||
Action: @Composable (isValidForm:Boolean,accountViewModel: AccountViewModel) -> Unit){ |
||||
accountViewModel.route=route |
||||
Row( |
||||
horizontalArrangement = Arrangement.Center, |
||||
verticalAlignment = Alignment.CenterVertically, |
||||
modifier = Modifier.fillMaxSize() |
||||
) { |
||||
Column(modifier = Modifier.width(IntrinsicSize.Min)) { |
||||
val name: String by accountViewModel.name.observeAsState("") |
||||
Text(buildAnnotatedString { |
||||
withStyle( |
||||
style = MaterialTheme.typography.subtitle1.toSpanStyle() |
||||
.copy(color = MaterialTheme.colors.primary) |
||||
) { |
||||
append(name) |
||||
} |
||||
withStyle(style = MaterialTheme.typography.subtitle1.toSpanStyle()) { |
||||
append(accountViewModel.welcomeStart) |
||||
} |
||||
withStyle(style = MaterialTheme.typography.subtitle2.toSpanStyle()) { |
||||
append(accountViewModel.welcomeEnd) |
||||
} |
||||
withStyle( |
||||
style = MaterialTheme.typography.subtitle2.toSpanStyle() |
||||
.copy(color = MaterialTheme.colors.secondary) |
||||
) { |
||||
append(BuildConfig.APP_NAME) |
||||
} |
||||
}) |
||||
|
||||
StudentId(checkRepeat=route==AccountRoute.register) |
||||
Spacer(modifier = Modifier.height(10.dp)) |
||||
|
||||
|
||||
if (route==AccountRoute.register) Name(name=name) else Password() |
||||
|
||||
Spacer(modifier = Modifier.height(10.dp)) |
||||
PasswordTip() |
||||
val isValidForm: Boolean by accountViewModel.isValidForm.observeAsState(false) |
||||
Action(isValidForm=isValidForm,accountViewModel=accountViewModel) |
||||
|
||||
val snackBarMsg:String by accountViewModel.snackBarMsg.observeAsState("") |
||||
|
||||
if(snackBarMsg!=""){ |
||||
LaunchedEffect(scaffoldState) { |
||||
scaffoldState.snackbarHostState.showSnackbar( |
||||
message = snackBarMsg |
||||
) |
||||
accountViewModel.resetRegisterResMsg() |
||||
} |
||||
} |
||||
|
||||
RegisterDialog() |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 学号 |
||||
* |
||||
* @param accountViewModel |
||||
*/ |
||||
@Composable |
||||
private fun StudentId(accountViewModel: AccountViewModel = viewModel(),checkRepeat:Boolean){ |
||||
Column { |
||||
|
||||
val studentId: String by accountViewModel.studentId.observeAsState("") |
||||
val isValidStudentId : Boolean by accountViewModel.isValidStudentId.observeAsState(false) |
||||
val focusManager = LocalFocusManager.current |
||||
OutlinedTextField( |
||||
value = studentId, |
||||
onValueChange = { accountViewModel.onStudentIdChange(it) }, |
||||
label = { Text(text = accountViewModel.studentIdDesc) }, |
||||
placeholder = { Text(text = accountViewModel.studentIdPlaceholder) }, |
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), |
||||
keyboardOptions = KeyboardOptions.Default.copy( keyboardType = KeyboardType.Number,imeAction = ImeAction.Done), |
||||
singleLine = true, |
||||
isError = !isValidStudentId |
||||
) |
||||
if (isValidStudentId) { |
||||
if(checkRepeat) { |
||||
val isRepeat: Boolean? by accountViewModel.isRepeat.observeAsState(null) |
||||
when (isRepeat) { |
||||
null -> AnimationText(text = accountViewModel.checkRegTip) |
||||
true -> |
||||
Text(buildAnnotatedString { |
||||
append(accountViewModel.studentIdDesc) |
||||
withStyle( |
||||
style = MaterialTheme.typography.body1.toSpanStyle().copy( |
||||
color = MaterialTheme.colors.error |
||||
) |
||||
) { |
||||
append(studentId) |
||||
} |
||||
append(accountViewModel.registered) |
||||
}) |
||||
false -> |
||||
Text(buildAnnotatedString { |
||||
append(accountViewModel.studentIdDesc) |
||||
withStyle( |
||||
style = MaterialTheme.typography.body1.toSpanStyle().copy( |
||||
color = MaterialTheme.colors.primary |
||||
) |
||||
) { |
||||
append(studentId) |
||||
} |
||||
append(accountViewModel.canRegister) |
||||
}) |
||||
} |
||||
} |
||||
}else{ |
||||
Text( |
||||
text = accountViewModel.studentIdFormat, |
||||
color = MaterialTheme.colors.error, |
||||
style = MaterialTheme.typography.body1 |
||||
) |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 注册弹窗 |
||||
* |
||||
* @param accountViewModel |
||||
*/ |
||||
@Composable |
||||
private fun RegisterDialog(accountViewModel: AccountViewModel = viewModel()){ |
||||
val dialogMsg: DialogMessage? by accountViewModel.dialogMsg.observeAsState(null) |
||||
|
||||
val message=dialogMsg?.userResDto?.password |
||||
if(message?.isNotEmpty() == true){ |
||||
PasswordDialog(message = message) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 密码弹窗 |
||||
* |
||||
* @param accountViewModel |
||||
* @param message |
||||
*/ |
||||
@Composable |
||||
private fun PasswordDialog(accountViewModel: AccountViewModel = viewModel(), message:String){ |
||||
val context= LocalContext.current |
||||
val button:@Composable () -> Unit = { |
||||
Row(horizontalArrangement= Arrangement.Center,modifier = Modifier |
||||
.fillMaxWidth() |
||||
.padding(bottom = 10.dp)) { |
||||
OutlinedButton(onClick = { accountViewModel.resetDialogMsg() }, |
||||
modifier = Modifier.padding(end = 10.dp)) { |
||||
Text(text = accountViewModel.confirmDesc) |
||||
} |
||||
OutlinedButton(onClick = { |
||||
context.startActivity(Intent(context,AccountActivity::class.java)) |
||||
}, |
||||
colors = ButtonDefaults.outlinedButtonColors( |
||||
contentColor = MaterialTheme.colors.onBackground)) { |
||||
Text(text = accountViewModel.backLogin) |
||||
} |
||||
} |
||||
} |
||||
AlertDialog(onDismissRequest = { accountViewModel.resetDialogMsg() }, |
||||
buttons = button, |
||||
title = { Text(text = accountViewModel.title) }, |
||||
text = { |
||||
Text(buildAnnotatedString { |
||||
append(accountViewModel.passwordDialogStart) |
||||
withStyle(style = MaterialTheme.typography.body1.toSpanStyle() |
||||
.copy(color = MaterialTheme.colors.secondary)){ |
||||
append(message) |
||||
} |
||||
append(accountViewModel.passwordDialogEnd) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 姓名文本框 |
||||
* |
||||
* @param name 姓名 |
||||
* @param accountViewModel |
||||
*/ |
||||
@Composable |
||||
private fun Name(name:String, accountViewModel: AccountViewModel = viewModel()){ |
||||
Column { |
||||
|
||||
val isValidName:Boolean by accountViewModel.isValidName.observeAsState(false) |
||||
val focusManager = LocalFocusManager.current |
||||
OutlinedTextField(value = name, |
||||
onValueChange = {accountViewModel.onNameChange(it)}, |
||||
label={ Text(text = accountViewModel.nameDesc)}, |
||||
placeholder = { Text(text = accountViewModel.namePlaceholder)}, |
||||
singleLine = true, |
||||
isError = !isValidName, |
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), |
||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done)) |
||||
|
||||
if (!isValidName){ |
||||
Text(text = accountViewModel.nameFormat, |
||||
color=MaterialTheme.colors.error) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 密码框 |
||||
* |
||||
* @param accountViewModel |
||||
*/ |
||||
@Composable |
||||
private fun Password(accountViewModel: AccountViewModel= viewModel()){ |
||||
Column { |
||||
val isValidPwd:Boolean by accountViewModel.isValidPwd.observeAsState(false) |
||||
val focusManager = LocalFocusManager.current |
||||
val password:String by accountViewModel.password.observeAsState("") |
||||
OutlinedTextField(value = password, |
||||
visualTransformation=PasswordVisualTransformation(), |
||||
onValueChange = {accountViewModel.onPasswordChange(it)}, |
||||
label={ Text(text = accountViewModel.passwordDesc)}, |
||||
placeholder = { Text(text = accountViewModel.passwordPlaceholder)}, |
||||
singleLine = true, |
||||
isError = !isValidPwd, |
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), |
||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done,keyboardType = KeyboardType.Number)) |
||||
|
||||
if(!isValidPwd){ |
||||
Text(text = accountViewModel.passwordFormat, |
||||
color=MaterialTheme.colors.error) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 提示自动生成密码 |
||||
* |
||||
* @param accountViewModel |
||||
*/ |
||||
@Composable |
||||
private fun PasswordTip(accountViewModel: AccountViewModel = viewModel()) { |
||||
if(accountViewModel.isValidForm.value==true) { |
||||
Text( |
||||
text = accountViewModel.passwordTip, color = MaterialTheme.colors.primary, |
||||
modifier = Modifier.fillMaxWidth() |
||||
) |
||||
} |
||||
} |
||||
|
||||
|
||||
@Preview(showBackground = true) |
||||
@Composable |
||||
fun DefaultPreview() { |
||||
CSAMSTheme { |
||||
|
||||
Row ( |
||||
horizontalArrangement=Arrangement.Center, |
||||
verticalAlignment = Alignment.CenterVertically, |
||||
modifier = Modifier.fillMaxSize()) { |
||||
Column(modifier = Modifier.width(IntrinsicSize.Min)) { |
||||
val model:AccountViewModel= viewModel() |
||||
|
||||
StudentId(model,false) |
||||
Spacer(modifier = Modifier.height(10.dp)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,41 @@ |
||||
package com.gyf.csams.ui |
||||
|
||||
import androidx.compose.animation.animateColor |
||||
import androidx.compose.animation.core.* |
||||
import androidx.compose.material.MaterialTheme |
||||
import androidx.compose.material.Text |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.tooling.preview.Preview |
||||
|
||||
/** |
||||
* 淡入淡出并且颜色变化文本 |
||||
* |
||||
* @param text |
||||
*/ |
||||
@Composable |
||||
fun AnimationText(text:String){ |
||||
val infiniteTransition = rememberInfiniteTransition() |
||||
val color by infiniteTransition.animateColor( |
||||
initialValue = Color.Red, |
||||
targetValue = Color.Green, |
||||
animationSpec = infiniteRepeatable( |
||||
animation = tween(1000, easing = LinearEasing), |
||||
repeatMode = RepeatMode.Reverse |
||||
) |
||||
) |
||||
|
||||
Text( |
||||
text = text, |
||||
color = color, |
||||
style = MaterialTheme.typography.body1 |
||||
) |
||||
} |
||||
|
||||
|
||||
@Preview |
||||
@Composable |
||||
fun AnimationTextPreview(){ |
||||
AnimationText(text = "6666") |
||||
} |
@ -0,0 +1,42 @@ |
||||
package com.gyf.csams.ui |
||||
|
||||
import android.os.Bundle |
||||
import androidx.activity.ComponentActivity |
||||
import androidx.activity.compose.setContent |
||||
import androidx.compose.foundation.layout.Arrangement |
||||
import androidx.compose.foundation.layout.Row |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.material.MaterialTheme |
||||
import androidx.compose.material.Surface |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.tooling.preview.Preview |
||||
import com.gyf.csams.ui.theme.CSAMSTheme |
||||
|
||||
class MainActivity: ComponentActivity() { |
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContent { |
||||
CSAMSTheme { |
||||
// A surface container using the 'background' color from the theme |
||||
Surface(color = MaterialTheme.colors.background) { |
||||
Row(horizontalArrangement = Arrangement.Center, |
||||
verticalAlignment = Alignment.CenterVertically, |
||||
modifier = Modifier.fillMaxSize()){ |
||||
AnimationText(text = "主界面设计中。。。。") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Preview(showBackground = true) |
||||
@Composable |
||||
fun DefaultPreview() { |
||||
CSAMSTheme { |
||||
// A surface container using the 'background' color from the theme |
||||
AnimationText(text = "sdfsdf") |
||||
} |
||||
} |
@ -0,0 +1,102 @@ |
||||
package com.gyf.csams.util |
||||
|
||||
import android.content.Context |
||||
import androidx.lifecycle.LiveData |
||||
import androidx.lifecycle.MutableLiveData |
||||
import androidx.room.* |
||||
import kotlinx.serialization.Serializable |
||||
|
||||
|
||||
/** |
||||
* 登陆令牌 |
||||
*/ |
||||
@Entity |
||||
@Serializable |
||||
data class Token(@PrimaryKey val studentId:String,@ColumnInfo val token:String,@ColumnInfo val createTime:Long) |
||||
|
||||
/** |
||||
* 令牌传输 |
||||
* |
||||
* @property isValid |
||||
* @property token |
||||
*/ |
||||
@Serializable |
||||
data class TokenResDto(val isValid:Boolean,val token: Token?) |
||||
|
||||
@Dao |
||||
interface TokenDao { |
||||
@Query("select * from token") |
||||
suspend fun queryAll(): List<Token> |
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE) |
||||
suspend fun save(token: Token) |
||||
|
||||
@Delete |
||||
suspend fun delete(user: Token) |
||||
} |
||||
|
||||
class TokenManager private constructor(private var token: Token?) { |
||||
companion object { |
||||
@Volatile |
||||
private var instance: TokenManager? = null |
||||
|
||||
fun getInstance(token: Token?=null) = |
||||
instance ?: synchronized(this) { |
||||
instance ?: TokenManager(token).also { instance = it } |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
@Database(entities = [Token::class], version = 1, exportSchema = false) |
||||
abstract class AppDatabase : RoomDatabase() { |
||||
abstract fun tokenDao(): TokenDao |
||||
private val mIsDatabaseCreated = MutableLiveData<Boolean>() |
||||
|
||||
/** |
||||
* Check whether the database already exists and expose it via [.getDatabaseCreated] |
||||
*/ |
||||
private fun updateDatabaseCreated(context: Context) { |
||||
if (context.getDatabasePath(DATABASE_NAME).exists()) { |
||||
setDatabaseCreated() |
||||
} |
||||
} |
||||
|
||||
private fun setDatabaseCreated() { |
||||
mIsDatabaseCreated.postValue(true) |
||||
} |
||||
|
||||
val databaseCreated: LiveData<Boolean> |
||||
get() = mIsDatabaseCreated |
||||
|
||||
companion object { |
||||
private var sInstance: AppDatabase? = null |
||||
const val DATABASE_NAME = "basic-sample-db" |
||||
fun getInstance(context: Context): AppDatabase? { |
||||
if (sInstance == null) { |
||||
synchronized(AppDatabase::class.java) { |
||||
if (sInstance == null) { |
||||
sInstance = |
||||
buildDatabase(context.applicationContext) |
||||
sInstance!!.updateDatabaseCreated(context.applicationContext) |
||||
} |
||||
} |
||||
} |
||||
return sInstance |
||||
} |
||||
|
||||
/** |
||||
* Build the database. [Builder.build] only sets up the database configuration and |
||||
* creates a new instance of the database. |
||||
* The SQLite database is only created when it's accessed for the first time. |
||||
*/ |
||||
private fun buildDatabase(appContext: Context): AppDatabase { |
||||
return Room.databaseBuilder(appContext, AppDatabase::class.java, DATABASE_NAME) |
||||
.build() |
||||
} |
||||
|
||||
|
||||
} |
||||
} |
Loading…
Reference in new issue