From 5f5f8bb45ca9540e0d7e7d851e055df970199b9f Mon Sep 17 00:00:00 2001
From: pan <1029559041@qq.com>
Date: Mon, 3 May 2021 05:48:43 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E7=99=BB=E5=BD=95=E5=AF=B9?=
=?UTF-8?q?=E6=8E=A5=E6=9C=8D=E5=8A=A1=E7=AB=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/build.gradle.kts | 14 +
app/src/main/AndroidManifest.xml | 16 +-
app/src/main/java/com/gyf/csams/APP.kt | 2 +
app/src/main/java/com/gyf/csams/Api.kt | 7 +-
.../main/java/com/gyf/csams/InitActivity.kt | 56 +++
.../main/java/com/gyf/csams/InitViewModel.kt | 67 +++
.../main/java/com/gyf/csams/MainActivity.kt | 377 -----------------
...gisterViewModel.kt => AccountViewModel.kt} | 164 ++++++--
.../gyf/csams/account/ui/AccountActivity.kt | 391 ++++++++++++++++++
app/src/main/java/com/gyf/csams/ui/Base.kt | 41 ++
.../java/com/gyf/csams/ui/MainActivity.kt | 42 ++
.../main/java/com/gyf/csams/util/HttpUtil.kt | 1 +
.../main/java/com/gyf/csams/util/TokenUtil.kt | 102 +++++
.../java/com/gyf/csams/ExampleUnitTest.kt | 7 +
build.gradle.kts | 1 +
15 files changed, 882 insertions(+), 406 deletions(-)
create mode 100644 app/src/main/java/com/gyf/csams/InitActivity.kt
create mode 100644 app/src/main/java/com/gyf/csams/InitViewModel.kt
delete mode 100644 app/src/main/java/com/gyf/csams/MainActivity.kt
rename app/src/main/java/com/gyf/csams/account/model/{RegisterViewModel.kt => AccountViewModel.kt} (53%)
create mode 100644 app/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt
create mode 100644 app/src/main/java/com/gyf/csams/ui/Base.kt
create mode 100644 app/src/main/java/com/gyf/csams/ui/MainActivity.kt
create mode 100644 app/src/main/java/com/gyf/csams/util/TokenUtil.kt
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index fed6354..0ceb916 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,6 +2,7 @@ plugins {
id("com.android.application")
id("kotlin-android")
id("org.jetbrains.kotlin.plugin.serialization") version "1.4.32"
+ id("kotlin-kapt")
}
android {
@@ -117,6 +118,19 @@ dependencies {
* https://kotlinlang.org/docs/serialization.html
*/
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
+ /**
+ * https://developer.android.com/jetpack/androidx/releases/navigation
+ */
+ implementation("androidx.navigation:navigation-compose:1.0.0-alpha10")
+ /**
+ * https://developer.android.com/jetpack/androidx/releases/room
+ */
+ implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
+ kapt("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
+ // optional - Kotlin Extensions and Coroutines support for Room
+ implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
+ // optional - Test helpers
+ testImplementation("androidx.room:room-testing:${rootProject.extra["room_version"]}")
//测试
testImplementation("junit:junit:4.13.2")
/**
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eab844f..34d063a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,16 +16,26 @@
+ android:theme="@style/Theme.CSAMS.NoActionBar">
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/gyf/csams/APP.kt b/app/src/main/java/com/gyf/csams/APP.kt
index ba69430..5c41d89 100644
--- a/app/src/main/java/com/gyf/csams/APP.kt
+++ b/app/src/main/java/com/gyf/csams/APP.kt
@@ -6,6 +6,8 @@ import com.orhanobut.logger.DiskLogAdapter
import com.orhanobut.logger.Logger
class APP : Application() {
+
+
override fun onCreate() {
super.onCreate()
//初始化日志
diff --git a/app/src/main/java/com/gyf/csams/Api.kt b/app/src/main/java/com/gyf/csams/Api.kt
index b9b80b8..3c62a5f 100644
--- a/app/src/main/java/com/gyf/csams/Api.kt
+++ b/app/src/main/java/com/gyf/csams/Api.kt
@@ -5,9 +5,12 @@ interface UrlPath{
fun build():String
}
-enum class RegisterApi(val path: String):UrlPath{
+enum class AccountApi(val path: String):UrlPath{
register("/register"),
- checkId("/register/checkId");
+ checkId("/register/checkId"),
+ login("/login"),
+ loginToken("/login/token");
+
override fun build(): String {
return "/api/account${this.path}"
diff --git a/app/src/main/java/com/gyf/csams/InitActivity.kt b/app/src/main/java/com/gyf/csams/InitActivity.kt
new file mode 100644
index 0000000..e1899a5
--- /dev/null
+++ b/app/src/main/java/com/gyf/csams/InitActivity.kt
@@ -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))
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gyf/csams/InitViewModel.kt b/app/src/main/java/com/gyf/csams/InitViewModel.kt
new file mode 100644
index 0000000..a0ac3f7
--- /dev/null
+++ b/app/src/main/java/com/gyf/csams/InitViewModel.kt
@@ -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()
+ val isNetWorkWorking: LiveData = _isNetWorkWorking
+
+ /**
+ * token
+ */
+ private val _token = MutableLiveData()
+ val token: LiveData = _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(
+ action=action,
+ onSuccess = {
+ _token.postValue(it.body)
+ Logger.i("token校验结果:${it.body}")
+ },
+ onFail = { TODO("token校验失败")},
+ type = object : TypeToken>(){}.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)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gyf/csams/MainActivity.kt b/app/src/main/java/com/gyf/csams/MainActivity.kt
deleted file mode 100644
index 455117b..0000000
--- a/app/src/main/java/com/gyf/csams/MainActivity.kt
+++ /dev/null
@@ -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")
- }
- }
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/gyf/csams/account/model/RegisterViewModel.kt b/app/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt
similarity index 53%
rename from app/src/main/java/com/gyf/csams/account/model/RegisterViewModel.kt
rename to app/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt
index fe38811..c7a123c 100644
--- a/app/src/main/java/com/gyf/csams/account/model/RegisterViewModel.kt
+++ b/app/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt
@@ -1,33 +1,64 @@
package com.gyf.csams.account.model
+import android.app.Application
+import android.content.Intent
+import android.os.Build
+import androidx.lifecycle.AndroidViewModel
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.AccountApi
import com.gyf.csams.Api
-import com.gyf.csams.RegisterApi
-import com.gyf.csams.util.ApiResponse
-import com.gyf.csams.util.HttpClient
-import com.gyf.csams.util.SimpleCallback
+import com.gyf.csams.InitActivity
+import com.gyf.csams.account.ui.AccountRoute
+import com.gyf.csams.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
+/**
+ * 响应自动生成密码
+ *
+ * @property password
+ */
@Serializable
data class UserResDto(val password:String)
+/**
+ * 构造登录、注册信息实体表单
+ *
+ * @property studentId 学号
+ * @property name 姓名
+ */
data class UserVo(val studentId:String,val name:String)
+/**
+ * 用户登陆表单
+ *
+ * @property studentId 学号
+ * @property password 密码
+ * @property device 设备型号
+ */
+data class UserLoginVo(val studentId: String,val password: String,val device: String)
+
+/**
+ * 密码弹窗信息
+ *
+ * @property message
+ * @property userResDto
+ */
data class DialogMessage(val message:String,val userResDto: UserResDto?)
+typealias Token= TokenResDto
+
/**
* 注册表单
*/
-class RegisterViewModel:ViewModel() {
+class AccountViewModel(application: Application) : AndroidViewModel(application) {
+
//欢迎信息
val welcomeStart="同学您好\n"
@@ -39,30 +70,43 @@ class RegisterViewModel:ViewModel() {
private val _isValidStudentId=MutableLiveData()
val isValidStudentId:LiveData = _isValidStudentId
val studentIdDesc="学号"
- val studentIdPlaceholder="学号纯数字"
+ val studentIdPlaceholder="请输入$studentIdDesc"
val studentIdFormat="入学年份(四位)+班级代码(两位)+学生代码(两位)"
//学号已存在
private val _isRepeat=MutableLiveData()
+
+ val regBtnDesc="注册"
+
//已注册
- val registered="已注册"
+ val registered="已$regBtnDesc"
//可注册
- val canRegister="可注册"
+ val canRegister="可$regBtnDesc"
//提示信息
- val checkRegTip="检测学号是否已注册。。。"
+ val checkRegTip="检测学号是否已${regBtnDesc}。。。"
val isRepeat:LiveData = _isRepeat
private var checkJob: Job? = null
//姓名
private val _name=MutableLiveData()
val name:LiveData = _name
val nameDesc="姓名"
- val namePlaceholder=nameDesc
+ val namePlaceholder="请输入$nameDesc"
private val _isValidName=MutableLiveData()
val isValidName:LiveData = _isValidName
val nameFormat="姓名不能为空"
+ //密码
+ private val _password=MutableLiveData()
+ val password:LiveData = _password
+ val passwordDesc="密码"
+ val passwordPlaceholder="请输入$passwordDesc"
+ private val _isValidPwd=MutableLiveData()
+ val isValidPwd:LiveData = _isValidPwd
+ val passwordFormat="八位纯数字"
+
+
//注册按钮
private val _isValidForm=MutableLiveData()
- val regBtnDesc="注册"
+
val isValidForm:LiveData = _isValidForm
//注册请求响应信息
private val _snackBarMsg=MutableLiveData()
@@ -71,16 +115,28 @@ class RegisterViewModel:ViewModel() {
private val _dialogMsg=MutableLiveData()
val dialogMsg:LiveData = _dialogMsg
+ val loginDesc="登陆"
+
//返回登陆
- val backDesc="返回登陆"
+ val backLogin="返回$loginDesc"
//确定按钮
val confirmDesc="确定"
//显示密码提示
val title="提示信息"
- val passwordTip="密码会在点击注册以后,在后台自动生成,请留意系统提示。"
- val passwordDialogStart="注册成功,后台为您自动生成的密码是"
- val passwordDialogEnd="\n密码有且只有这里显示一次,请在记住密码后点击确定或${backDesc}。"
+ val passwordTip="密码会在点击${regBtnDesc}以后,在后台自动生成,请留意系统提示。"
+ val passwordDialogStart="${regBtnDesc}成功,后台为您自动生成的密码是"
+ val passwordDialogEnd="\n密码有且只有这里显示一次,请在记住密码后点击确定或${backLogin}。"
+ //转到注册
+ var goRegister="转到$regBtnDesc"
+
+ /**
+ * 完成登录状态
+ */
+ private val _finishLogin=MutableLiveData()
+ val finishLogin:LiveData = _finishLogin
+
+ lateinit var route:AccountRoute
/**
* 更新学号
@@ -101,6 +157,7 @@ class RegisterViewModel:ViewModel() {
*
*/
private fun checkStudentId(): Boolean {
+
_isValidStudentId.value= _studentId.value?.matches(Regex("\\d{8}"))
return _isValidStudentId.value==true
}
@@ -109,14 +166,14 @@ class RegisterViewModel:ViewModel() {
* 检查学号是否已注册
*
*/
- suspend fun checkRepeat(){
+ private suspend fun checkRepeat(){
if (checkStudentId()) {
if (checkJob?.isActive == true) {
checkJob?.join()
}else {
_isRepeat.postValue(null)
checkJob = viewModelScope.launch {
- val url = Api.buildUrl(RegisterApi.checkId)
+ val url = Api.buildUrl(AccountApi.checkId)
Logger.i("检测$studentIdDesc,请求接口$url")
HttpClient.get(
url, SimpleCallback(
@@ -155,12 +212,31 @@ class RegisterViewModel:ViewModel() {
return _isValidName.value==true
}
+ /**
+ * 更新密码
+ *
+ * @param password 密码
+ */
+ fun onPasswordChange(password: String){
+ _password.value=password
+ checkForm()
+ }
+
+ /**
+ * 检测密码
+ *
+ * @return
+ */
+ private fun checkPassword():Boolean{
+ _isValidPwd.value= _password.value?.matches(Regex("\\d{8}"))
+ return _isValidPwd.value==true
+ }
private fun checkForm(): Boolean {
if(checkJob?.isActive==true){
_isValidForm.value = false
}else{
- _isValidForm.value = checkName() && checkStudentId() && isRepeat.value==false
+ _isValidForm.value = checkStudentId() && (if (route==AccountRoute.register) checkName()&&isRepeat.value==false else checkPassword())
}
return _isValidForm.value == true
}
@@ -171,20 +247,24 @@ class RegisterViewModel:ViewModel() {
*/
fun register(){
if(checkForm()){
- val url= Api.buildUrl(RegisterApi.register)
- Logger.i("开始注册,请求接口:$url")
+ val url= Api.buildUrl(AccountApi.register)
+ Logger.i("开始$regBtnDesc,请求接口:$url")
HttpClient.post(url,SimpleCallback(
- action = "注册",
+ action = regBtnDesc,
onSuccess = { _dialogMsg.postValue(DialogMessage(message = it.message,userResDto = it.body)) },
onFail = { _snackBarMsg.postValue(it)},
type = object : TypeToken>() {}.type),
jsonBody = Gson().toJson(UserVo(studentId = "${studentId.value}",name = "${name.value}")))
resetForm()
}else{
- Logger.wtf("表单校验失败,无法注册!!!")
+ Logger.wtf("表单校验失败,无法$regBtnDesc!!!")
}
}
+ /**
+ *
+ */
+
/**
* 重置信息
*
@@ -201,4 +281,40 @@ class RegisterViewModel:ViewModel() {
_studentId.value=""
_name.value=""
}
+
+ /**
+ * 登录
+ *
+ */
+ fun login(){
+ if(checkForm()){
+ val url = Api.buildUrl(AccountApi.login)
+ Logger.i("开始$loginDesc,请求接口:$url")
+ HttpClient.post(url,SimpleCallback(
+ action = loginDesc,
+ onSuccess = {
+ _snackBarMsg.postValue(it.message)
+
+ val context= getApplication().applicationContext
+ val token = it.body?.token
+ if(token!=null){
+ val db= AppDatabase.getInstance(context)
+ viewModelScope.launch {
+ db?.tokenDao()?.save(token = token)
+ }.invokeOnCompletion {
+ context.startActivity(Intent(context, InitActivity::class.java))
+ _finishLogin.postValue(true)
+ }
+ }
+ },
+ onFail = {_snackBarMsg.postValue(it)},
+ type = object : TypeToken>(){}.type
+ ),jsonBody = Gson().toJson(UserLoginVo(studentId = "${studentId.value}",password = "${password.value}",device = "${Build.MANUFACTURER} ${Build.MODEL}")))
+ }else{
+ Logger.wtf("表单校验失败,无法$loginDesc!!!")
+ }
+ }
+
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt b/app/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt
new file mode 100644
index 0000000..9804cd0
--- /dev/null
+++ b/app/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt
@@ -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))
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/gyf/csams/ui/Base.kt b/app/src/main/java/com/gyf/csams/ui/Base.kt
new file mode 100644
index 0000000..007e902
--- /dev/null
+++ b/app/src/main/java/com/gyf/csams/ui/Base.kt
@@ -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")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gyf/csams/ui/MainActivity.kt b/app/src/main/java/com/gyf/csams/ui/MainActivity.kt
new file mode 100644
index 0000000..aed0362
--- /dev/null
+++ b/app/src/main/java/com/gyf/csams/ui/MainActivity.kt
@@ -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")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gyf/csams/util/HttpUtil.kt b/app/src/main/java/com/gyf/csams/util/HttpUtil.kt
index 10d3947..aa8b9a4 100644
--- a/app/src/main/java/com/gyf/csams/util/HttpUtil.kt
+++ b/app/src/main/java/com/gyf/csams/util/HttpUtil.kt
@@ -78,6 +78,7 @@ object HttpClient{
* @param jsonBody
*/
fun post(url:String, callback: Callback, jsonBody:String){
+ Logger.json(jsonBody)
val request = Request.Builder()
.url(url)
.post(body = jsonBody.toRequestBody(contentType = JSON_CONTENT_TYPE))
diff --git a/app/src/main/java/com/gyf/csams/util/TokenUtil.kt b/app/src/main/java/com/gyf/csams/util/TokenUtil.kt
new file mode 100644
index 0000000..5e6e8d6
--- /dev/null
+++ b/app/src/main/java/com/gyf/csams/util/TokenUtil.kt
@@ -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
+
+ @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()
+
+ /**
+ * 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
+ 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()
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/gyf/csams/ExampleUnitTest.kt b/app/src/test/java/com/gyf/csams/ExampleUnitTest.kt
index f0435f9..070132b 100644
--- a/app/src/test/java/com/gyf/csams/ExampleUnitTest.kt
+++ b/app/src/test/java/com/gyf/csams/ExampleUnitTest.kt
@@ -5,6 +5,8 @@ import com.google.gson.reflect.TypeToken
import com.gyf.csams.util.ApiResponse
import org.junit.Assert.assertEquals
import org.junit.Test
+import java.time.LocalDateTime
+import java.time.temporal.ChronoField
/**
@@ -32,5 +34,10 @@ class ExampleUnitTest {
println(e.body)
}
+ @Test
+ fun testYear(){
+ println( LocalDateTime.now().get(ChronoField.YEAR))
+ }
+
}
diff --git a/build.gradle.kts b/build.gradle.kts
index b7b47a5..29f0ac1 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,6 +7,7 @@ buildscript {
//APP应用名字
val APP_NAME by extra("大学生社团管理系统")
val SERVER_ADDRESS by extra("http://192.168.50.107:8080")
+ val room_version by extra("2.2.6")
repositories {
maven("https://maven.aliyun.com/repository/google")
maven("https://maven.aliyun.com/repository/public")