申请入团

master
pan 3 years ago
parent acb519df85
commit a38dcf6587
  1. 3
      foreground/src/main/AndroidManifest.xml
  2. 3
      foreground/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt
  3. 42
      foreground/src/main/java/com/gyf/csams/association/model/AssociationViewModel.kt
  4. 53
      foreground/src/main/java/com/gyf/csams/association/model/AuditJoinViewModel.kt
  5. 275
      foreground/src/main/java/com/gyf/csams/association/model/ExamViewModel.kt
  6. 173
      foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt
  7. 133
      foreground/src/main/java/com/gyf/csams/association/ui/AuditJoinActivity.kt
  8. 240
      foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt
  9. 2
      foreground/src/main/java/com/gyf/csams/util/GsonUtil.kt
  10. 1
      foreground/src/main/res/values-en/strings.xml
  11. 1
      foreground/src/main/res/values-zh/strings.xml
  12. 1
      foreground/src/main/res/values/strings.xml
  13. 11
      foreground/src/test/java/com/gyf/csams/ExampleUnitTest.kt
  14. 13
      lib/src/main/java/com/gyf/lib/uikit/BaseTextField.kt
  15. 10
      lib/src/main/java/com/gyf/lib/uikit/Snackbar.kt
  16. 26
      lib/src/main/java/com/gyf/lib/util/Api.kt
  17. 2
      lib/src/main/res/values-en/strings.xml
  18. 2
      lib/src/main/res/values-zh/strings.xml
  19. 2
      lib/src/main/res/values/strings.xml

@ -86,6 +86,9 @@
<!--系统通知-->
<activity android:name=".message.ui.SysMessageActivity" />
<!--入团申请-->
<activity android:name=".association.ui.AuditJoinActivity" />
<service android:name="com.gyf.lib.service.MessageService" />
</application>

@ -176,6 +176,7 @@ class AccountViewModel(application: Application) : AbstractLoginViewModel(applic
password = it.body ?: throw IllegalArgumentException("无法获取生成密码")
)
)
resetForm()
},
onFail = onFail,
typeToken = object : TypeToken<ApiResponse<String>>() {}.type
@ -185,7 +186,6 @@ class AccountViewModel(application: Application) : AbstractLoginViewModel(applic
name = name.getValue()
)
)
resetForm()
} else {
Logger.wtf("表单校验失败,无法$regBtnDesc!!!")
}
@ -198,6 +198,7 @@ class AccountViewModel(application: Application) : AbstractLoginViewModel(applic
private fun resetForm() {
id.clean()
name.clean()
checkForm()
}

@ -21,10 +21,6 @@ class AssociationViewModel : ViewModel(), TopMenuInterface<AssociationMenu> {
override val _currentMenu: MutableLiveData<AssociationMenu> = MutableLiveData()
override val currentMenu: LiveData<AssociationMenu> = _currentMenu
private val _dropDownMenuResult = MutableLiveData<String?>()
val dropDownMenuResult: LiveData<String?> = _dropDownMenuResult
/**
* 下拉菜单状态
*/
@ -34,10 +30,21 @@ class AssociationViewModel : ViewModel(), TopMenuInterface<AssociationMenu> {
private val _associationVo = MutableLiveData<AssociationMainVo>()
val associationVo: LiveData<AssociationMainVo> = _associationVo
fun update(message: String?) {
_dropDownMenuResult.postValue(message)
private val _applyAssociationResultVo = MutableLiveData<ApplyAssociationResultVo?>()
val applyAssociationResultVo: LiveData<ApplyAssociationResultVo?> = _applyAssociationResultVo
private val _dropMenuMessage = MutableLiveData<String?>()
val dropMenuMessage: LiveData<String?> = _dropMenuMessage
fun update(message: String? = null) {
_dropMenuMessage.postValue(message)
}
fun update(applyAssociationResultVo: ApplyAssociationResultVo? = null) {
_applyAssociationResultVo.postValue(applyAssociationResultVo)
}
fun load(id: Int) {
viewModelScope.launch {
HttpClient.post(
@ -64,6 +71,29 @@ class AssociationViewModel : ViewModel(), TopMenuInterface<AssociationMenu> {
_expanded.value = false
}
/**
* 申请入团
*
*/
fun applyAssociation(associationId: Int, callback: (result: ApplyAssociationResultVo) -> Unit) {
viewModelScope.launch {
HttpClient.post(
url = Api.buildUrl(AssociationApi.CheckApply),
callback = HttpCallback<ApplyAssociationResultVo>(
action = "申请入团",
onSuccess = {
it.body?.let {
callback(it)
}
},
typeToken = object : TypeToken<ApiResponse<ApplyAssociationResultVo>>() {}.type
),
jsonParam =
SearchExamVo(associationId = associationId, token = TokenManager.getToken())
)
}
}
}
/**

@ -0,0 +1,53 @@
package com.gyf.csams.association.model
import android.app.Application
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.module.ApiResponse
import com.gyf.csams.module.AuditJoinVo
import com.gyf.csams.module.JoinAssociationVo
import com.gyf.csams.module.SearchExamVo
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.util.*
import kotlinx.coroutines.launch
class AuditJoinViewModel(application: Application) :
ScrollViewModel<JoinAssociationVo>(application = application) {
fun load(associationId: Int) {
viewModelScope.launch {
HttpClient.post(
url = Api.buildUrl(AssociationApi.CheckJoin),
callback = HttpCallback<MutableList<JoinAssociationVo>>(
action = "查看入团申请记录",
onSuccess = {
it.body?.let {
_data.postValue(it)
}
},
typeToken = object :
TypeToken<ApiResponse<MutableList<JoinAssociationVo>>>() {}.type
), jsonParam =
SearchExamVo(associationId = associationId, token = TokenManager.getToken())
)
}
}
/**
* 审核结果
*
* @param joinId
* @param result
*/
fun audit(joinId: Int, result: Boolean, callback: (message: String) -> Unit) {
viewModelScope.launch {
HttpClient.post(
url = Api.buildUrl(AssociationApi.AuditJoin),
callback = HttpCallback<Boolean>(action = "审核入团申请", onSuccess = {
callback(it.message)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type), jsonParam =
AuditJoinVo(joinId = joinId, result = result, token = TokenManager.getToken())
)
}
}
}

@ -3,10 +3,15 @@ package com.gyf.csams.association.model
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.module.*
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.uikit.AsyncStringForm
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.NOT_IMPL_TIP
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
/**
* 题库界面类型
@ -17,7 +22,10 @@ enum class ExamActivityType(val menuName: String) {
SET_EXAM("入团题库"),
//入团申请表
JOIN_Association("入团申请表")
JOIN_Association("入团申请表"),
//查看答卷
Answer("查看答卷")
}
@ -51,48 +59,57 @@ enum class ExamType(val type: String) {
OQ("开放题")
}
abstract class Exam {
abstract class Exam() {
abstract val examType: ExamType
abstract val _question: StringForm
//**TODO 题目反序列化
abstract val question: String?
abstract val _question: StringForm?
abstract fun check(): Boolean
abstract val questionId: Int?
}
/**
* 选择题
*
* @property examType 题型描述
* @property answers 答案
* @property rightAnswer 正确答案
* @property question 问题
* TODO 题目反序列化
*
*/
data class ChoiceQuestionVo(
val id: Int? = null,
override val examType: ExamType = ExamType.CQ,
val _answers: MutableList<StringForm> = mutableListOf(),
val answers: List<String> = ('A'..'D').map { "选项${it}" }.toList(),
val _answers: MutableList<StringForm>,
val rightAnswer: Int,
override val question: String? = null,
override val _question: StringForm?,
val chooseOption: Int? = null,
override val _question: StringForm,
override val questionId: Int? = null,
) : Exam() {
) : Exam()
override fun check(): Boolean {
if (_question.formValue.value?.isEmpty() == true || _question.formValue.value == null) {
return false
}
_answers.forEach {
if (it.formValue.value == null || it.formValue.value?.isEmpty() == true) {
return false
}
}
return true
}
}
/**
* 开放题
*
* @property examType 题型描述
* @property question 问题
* TODO 题目反序列化
*/
data class OpenQuestionsVo(
override val examType: ExamType = ExamType.OQ,
override val question: String? = null,
override val _question: StringForm?
override val _question: StringForm,
override val questionId: Int?,
) : Exam()
) : Exam() {
override fun check(): Boolean {
TODO("Not yet implemented")
}
}
/**
* 题库状态管理
@ -100,46 +117,45 @@ data class OpenQuestionsVo(
*/
class ExamViewModel(application: Application) : ScrollViewModel<Exam>(application) {
val questionIsNull: String = "问题不能为空"
val deleteLeastOne: String = "至少保留一道题目"
val questionIsNull: String = "问题或答案为空"
val deleteTip = "确定删除此题目?"
val addTip = "确定添加此题目?"
val actionLabel = "确定"
private val _newExam: MutableLiveData<Exam> = MutableLiveData(createExam(ExamType.CQ))
private val _newExam: MutableLiveData<Exam> = MutableLiveData()
val newExam: LiveData<Exam> = _newExam
init {
load()
}
/**
* 切换题型
*
*/
fun switchType(exam: Exam) {
if (exam is ChoiceQuestionVo) _newExam.value = createExam(ExamType.OQ) else _newExam.value =
createExam(ExamType.CQ)
}
private val _deleteIds = mutableListOf<Int>()
private val _allowPostAnswer = MutableLiveData<Boolean>()
val allowPostAnswer: LiveData<Boolean> = _allowPostAnswer
/**
* 创建题目
*
* @param type
* @return
*/
private fun createExam(type: ExamType): Exam {
val questionForm = ValidStringForm(formDesc = "", textLength = QUESTION_TEXT_LENGTH)
val answerA = ValidStringForm(formDesc = "选项A", textLength = ANSWER_TEXT_LENGTH)
val answerB = ValidStringForm(formDesc = "选项B", textLength = ANSWER_TEXT_LENGTH)
val answerC = ValidStringForm(formDesc = "选项C", textLength = ANSWER_TEXT_LENGTH)
val answerD = ValidStringForm(formDesc = "选项D", textLength = ANSWER_TEXT_LENGTH)
private fun createExam(examVo: ExamVo? = null, answer: Int? = null): Exam {
val questionForm = AsyncStringForm(formDesc = "问题", textLength = QUESTION_TEXT_LENGTH)
val answerA = AsyncStringForm(formDesc = "选项A", textLength = ANSWER_TEXT_LENGTH)
val answerB = AsyncStringForm(formDesc = "选项B", textLength = ANSWER_TEXT_LENGTH)
val answerC = AsyncStringForm(formDesc = "选项C", textLength = ANSWER_TEXT_LENGTH)
val answerD = AsyncStringForm(formDesc = "选项D", textLength = ANSWER_TEXT_LENGTH)
if (examVo != null) {
questionForm.setValue(examVo.question)
answerA.setValue(examVo.optionsA)
answerB.setValue(examVo.optionsB)
answerC.setValue(examVo.optionsC)
answerD.setValue(examVo.optionsD)
return ChoiceQuestionVo(
_answers = mutableListOf(answerA, answerB, answerC, answerD),
rightAnswer = examVo.rightOption,
_question = questionForm,
questionId = examVo.questionId,
chooseOption = answer
)
} else {
return ChoiceQuestionVo(
_answers = mutableListOf(answerA, answerB, answerC, answerD),
rightAnswer = 0,
@ -148,6 +164,18 @@ class ExamViewModel(application: Application) : ScrollViewModel<Exam>(applicatio
}
}
fun checkChooseAnswer() {
_data.value?.forEach {
if (it is ChoiceQuestionVo && it.chooseOption == null) {
_allowPostAnswer.postValue(false)
return
}
}
_allowPostAnswer.postValue(true)
}
/**
* 更新题目
*
@ -167,32 +195,153 @@ class ExamViewModel(application: Application) : ScrollViewModel<Exam>(applicatio
}
/**
* TODO 更新题库
* 更新题库
*
* @param callback
*/
fun updateExam(callback: (message: String) -> Unit) {
fun updateExam(associationId: Int, callback: (message: String) -> Unit) {
viewModelScope.launch {
_data.value?.dropLast(1)?.mapNotNull { it ->
if (it is ChoiceQuestionVo) {
toExamVo(choiceQuestionVo = it)
} else {
null
}
}?.apply {
HttpClient.post(url = Api.buildUrl(AssociationApi.UpdateExam),
callback = HttpCallback<Boolean>(action = "更新题库",
onSuccess = {
callback(it.message)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type
),
jsonParam = AddExamVo(
questions = this,
associationId = associationId,
token = TokenManager.getToken(),
deleteIds = _deleteIds
)
)
}
}
}
private fun toExamVo(choiceQuestionVo: ChoiceQuestionVo): ExamVo? {
return choiceQuestionVo.let {
val options = it._answers.map {
it.getValue()
}
if (options.size == 4) {
ExamVo(
question = it._question.getValue(), optionsA = options[0],
optionsB = options[1], optionsC = options[2], optionsD = options[3],
rightOption = it.rightAnswer,
questionId = it.questionId
)
} else {
null
}
}
}
/**
* TODO 提交答案
*
* 提交答卷
* @param callback
*/
fun postAnswer(callback: (message: String) -> Unit) {
callback(NOT_IMPL_TIP)
fun postAnswer(associationId: Int, callback: (message: String) -> Unit) {
viewModelScope.launch {
_data.value?.mapNotNull { it ->
if (it is ChoiceQuestionVo) {
toExamVo(choiceQuestionVo = it)?.let { choice ->
ApplyAnswerVo(
examVo = choice,
answer = it.chooseOption
?: throw IllegalArgumentException("题目[id=${choice.questionId}]没有填写答案???")
)
}.apply {
Logger.i("$this")
}
} else {
null
}
}?.let {
Logger.i("提交${it.size}道题目答案")
HttpClient.post(url = Api.buildUrl(AssociationApi.ApplyAnswer),
callback = HttpCallback<Boolean>(action = "提交答案",
onSuccess = {
callback(it.message)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type
),
jsonParam = AddAnswerVo(
answers = it, token = TokenManager.getToken(),
associationId = associationId
)
)
}
}
}
/**
* 加载题目
* 加载题
*
*/
fun load() {
fun load(associationId: Int, activityType: ExamActivityType) {
viewModelScope.launch {
HttpClient.post(
url = Api.buildUrl(
when (activityType) {
ExamActivityType.SET_EXAM -> AssociationApi.LoadExam
ExamActivityType.JOIN_Association -> AssociationApi.CreatePaper
else -> throw IllegalArgumentException("非法类型:${activityType}")
}
),
callback = HttpCallback<MutableList<ExamVo>>(action = "加载题库", onSuccess = { it ->
it.body?.let { it1 ->
_data.postValue(it1.map {
createExam(examVo = it)
}.toMutableList().apply {
if (activityType == ExamActivityType.SET_EXAM) {
add(createExam())
}
})
}
}, typeToken = object : TypeToken<ApiResponse<MutableList<ExamVo>>>() {}.type),
jsonParam =
SearchExamVo(associationId = associationId, token = TokenManager.getToken())
)
}
}
/**
* 加载答卷
*
* @param joinId
* @param callback
*/
fun showAnswer(joinId: Int, callback: (message: String) -> Unit) {
viewModelScope.launch {
HttpClient.post(
url = Api.buildUrl(AssociationApi.ShowAnswers),
callback = HttpCallback<List<ApplyAnswerVo>>(action = "查看答案", onSuccess = { it ->
it.body?.apply {
val exam = map {
createExam(examVo = it.examVo, answer = it.answer)
}
_data.postValue(exam.toMutableList())
}
callback(it.message)
}, typeToken = object : TypeToken<ApiResponse<List<ApplyAnswerVo>>>() {}.type),
jsonParam = ShowAnswerVo(joinId = joinId, token = TokenManager.getToken())
)
}
}
/**
* 添加题目
*
*/
fun addQuestion() {
_data.value?.apply {
_newExam.value?.let {
@ -201,20 +350,24 @@ class ExamViewModel(application: Application) : ScrollViewModel<Exam>(applicatio
list.add(it)
_data.postValue(list)
}
_newExam.value = createExam(ExamType.CQ)
_newExam.value = createExam()
}
}
/**
* TODO 删除题目
*
*删除题目
*/
fun deleteQuestion(exam: Exam) {
_data.value?.apply {
val list = mutableListOf<Exam>()
remove(exam)
list.addAll(this)
Logger.i("size=${list.size}")
_data.value?.clear()
_data.postValue(list)
exam.questionId?.let {
_deleteIds.add(it)
}
}
}
}

@ -3,6 +3,7 @@ package com.gyf.csams.association.ui
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
@ -62,21 +63,27 @@ class AssociationActivity : ComponentActivity() {
val currentMenuName: AssociationMenu by model.currentMenu.observeAsState(
AssociationMenu.startMenu
)
val intent = Intent(this, ExamActivity::class.java)
val expanded by model.expanded.observeAsState(false)
var message: String? by remember {
mutableStateOf(null)
}
//TODO重命名操作反馈
val rename =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val m = it.data?.getStringExtra(RenameActivity::class.java.name)
Logger.i("社团重命名返回:${m}")
message = m
model.update(message = m)
}
}
val examResult =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val m = it.data?.getStringExtra(ExamActivity::class.java.name)
Logger.i("试卷提交结果:${m}")
model.update(message = m)
}
}
Column {
TextTopAppBar(nav = nav,
currentMenuName = currentMenuName.menuName,
@ -90,7 +97,6 @@ class AssociationActivity : ComponentActivity() {
onDismissRequest = { },
properties = PopupProperties()
) {
when {
TokenManager.getUserInfo()?.associationVo?.associationId == associationId && TokenManager.getUserInfo()?.isHead == true -> {
DropdownMenuItem(onClick = {
@ -118,13 +124,20 @@ class AssociationActivity : ComponentActivity() {
}
}
DropdownMenuItem(onClick = {
intent.apply {
startActivity(
Intent(
this@AssociationActivity,
ExamActivity::class.java
).apply {
putExtra(
AssociationActivity::class.java.name,
associationId
)
putExtra(
ExamActivityType::name.name,
ExamActivityType.SET_EXAM
)
}
startActivity(intent)
})
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
@ -135,6 +148,27 @@ class AssociationActivity : ComponentActivity() {
)
}
}
DropdownMenuItem(onClick = {
startActivity(
Intent(
this@AssociationActivity,
AuditJoinActivity::class.java
).apply {
putExtra(
AssociationActivity::class.java.name,
associationId
)
})
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = ("入团审核"))
Icon(
painter = painterResource(id = R.drawable.ic_exchange_rate),
contentDescription = null
)
}
}
DropdownMenuItem(onClick = {
rename.launch(Intent(
this@AssociationActivity,
@ -159,13 +193,9 @@ class AssociationActivity : ComponentActivity() {
}
TokenManager.getUserInfo()?.associationVo == null -> DropdownMenuItem(
onClick = {
intent.apply {
putExtra(
ExamActivityType::name.name,
ExamActivityType.JOIN_Association
)
model.applyAssociation(associationId = associationId) {
model.update(applyAssociationResultVo = it)
}
startActivity(intent)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
@ -177,7 +207,6 @@ class AssociationActivity : ComponentActivity() {
}
}
}
DropdownMenuItem(onClick = {
model.close()
}) {
@ -202,32 +231,19 @@ class AssociationActivity : ComponentActivity() {
composable(AssociationMenu.Member.name) {
model.clickMenu(AssociationMenu.Member)
Member()
ShowTip(model = model, examResult = examResult)
ShowSnackbar(scaffoldState = scaffoldState)
}
composable(AssociationMenu.Main.name) {
model.clickMenu(AssociationMenu.Main)
Main()
//TODO 提示
val scaffoldModel: ScaffoldModel = viewModel()
message?.let {
scaffoldModel.update(message = message, actionLabel = "刷新")
val s by scaffoldModel.data.observeAsState()
if (s == null) {
message = null
// nav.navigate(AssociationMenu.Main.name)
}
LaunchedEffect(message) {
delay(it.length * 300L)
message = null
// nav.navigate(AssociationMenu.Main.name)
}
}
ShowTip(model = model, examResult = examResult)
ShowSnackbar(scaffoldState = scaffoldState)
}
composable(AssociationMenu.ActivityList.name) {
model.clickMenu(AssociationMenu.ActivityList)
AssociationList()
ShowTip(model = model, examResult = examResult)
ShowSnackbar(scaffoldState = scaffoldState)
}
}
@ -238,16 +254,71 @@ class AssociationActivity : ComponentActivity() {
}
}
//TODO下拉菜单操作反馈
/**
* 显示提示
*
* @param model
* @param examResult
*/
@Composable
private fun ShowTip(
model: AssociationViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel()
examResult: ManagedActivityResultLauncher<Intent, *>
) {
val m by model.dropDownMenuResult.observeAsState()
m?.let {
Logger.i("收到${it}")
scaffoldModel.update(message = it, actionLabel = "关闭提示")
val scaffoldModel: ScaffoldModel = viewModel()
val message by model.dropMenuMessage.observeAsState()
message?.let {
scaffoldModel.update(message = message, actionLabel = "刷新")
val s by scaffoldModel.data.observeAsState()
if (s == null) {
model.update(message = null)
}
LaunchedEffect(message) {
delay(it.length * 300L)
model.update(message = null)
}
}
val applyAssociationResultVo by model.applyAssociationResultVo.observeAsState()
applyAssociationResultVo?.let {
when {
it.result == true && it.hasPaper == true ->
scaffoldModel.update(
message = "申请此社团需要完成一份笔试题,点击确认获取试卷",
actionLabel = "确认"
) {
examResult.launch(
Intent(
this@AssociationActivity,
ExamActivity::class.java
).apply {
putExtra(AssociationActivity::class.java.name, associationId)
putExtra(
ExamActivityType::name.name,
ExamActivityType.JOIN_Association
)
})
}
it.result == true ->
scaffoldModel.update(
message = "入团申请发送成功,请耐心等待审核结果",
actionLabel = "收到"
)
it.associationVo != null ->
scaffoldModel.update(
message = "您已申请进入${it.associationVo?.name},请耐心等待审核结果",
actionLabel = "收到"
)
else ->
scaffoldModel.update(
message = "入团申请发送失败!",
actionLabel = "确认"
)
}
model.update(applyAssociationResultVo = null)
}
}
@ -491,16 +562,15 @@ class AssociationActivity : ComponentActivity() {
alpha = 07F
)
}) {
val onGoWeight = 0.3F
OngoingActivity(
modifier = Modifier
.weight(onGoWeight)
.fillMaxWidth()
)
// val onGoWeight = 0.3F
// OngoingActivity(
// modifier = Modifier
// .weight(onGoWeight)
// .fillMaxWidth()
// )
HistoryActivityList(
modifier = Modifier
.fillMaxWidth()
.weight(1 - onGoWeight)
.border(width = 1.dp, color = MaterialTheme.colors.onBackground)
)
}
@ -540,13 +610,21 @@ class AssociationActivity : ComponentActivity() {
val listState = rememberLazyListState()
val list by model.data.observeAsState()
if (list?.size == 0) {
Row(modifier = Modifier.fillMaxWidth()) {
Text(text = "目前社团没有任何活动")
}
} else {
LazyColumn(state = listState, modifier = modifier) {
list?.chunked(2)?.forEach {
item {
Row(modifier = Modifier.fillMaxWidth()) {
HistoryActivity(modifier = Modifier.weight(0.4F), it[0])
Spacer(modifier = Modifier.weight(0.2F))
if (it.size == 2) HistoryActivity(modifier = Modifier.weight(0.4F), it[1])
if (it.size == 2) HistoryActivity(
modifier = Modifier.weight(0.4F),
it[1]
)
else Box(modifier = Modifier.weight(0.4F))
}
Spacer(modifier = Modifier.height(10.dp))
@ -554,9 +632,8 @@ class AssociationActivity : ComponentActivity() {
}
}
}
if (listState.layoutInfo.totalItemsCount - listState.firstVisibleItemIndex == model.initSize / 2 - 1) {
TODO("加载更多")
}
}
/**

@ -0,0 +1,133 @@
package com.gyf.csams.association.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.lazy.LazyColumn
import androidx.compose.material.Card
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
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.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.R
import com.gyf.csams.association.model.AuditJoinViewModel
import com.gyf.csams.association.model.ExamActivityType
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.BottomButton
import com.gyf.lib.util.DateTimeUtil.datetimeFormat
import java.util.*
/**
* 审核入团申请
*
*/
class AuditJoinActivity : ComponentActivity() {
private val associationId: Int
get() {
val id = intent.getIntExtra(
AssociationActivity::class.java.name,
0
)
return if (id == 0) throw IllegalArgumentException("社团id:${id}不合法,初始化失败") else id
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Body {
MainColumnFrame(background = { }) {
val model: AuditJoinViewModel = viewModel()
val data by model.data.observeAsState()
val scaffoldModel: ScaffoldModel = viewModel()
LaunchedEffect(associationId) {
model.load(associationId = associationId)
}
LazyColumn(
verticalArrangement = Arrangement.spacedBy(
10.dp,
Alignment.CenterVertically
)
) {
data?.forEach {
item {
Card(elevation = 5.dp) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "申请人:${it.user.name}")
Text(text = "申请时间:${Date(it.applyTime).datetimeFormat()}")
if (it.hasPaper) {
Spacer(modifier = Modifier.height(5.dp))
OutlinedButton(onClick = {
startActivity(
Intent(
this@AuditJoinActivity,
ExamActivity::class.java
).apply {
putExtra(
ExamActivityType::name.name,
ExamActivityType.Answer
)
putExtra(
AuditJoinActivity::class.java.name,
it.id
)
putExtra(
AssociationActivity::class.java.name,
it.associationVo.associationId
)
})
}) {
Text(text = "查看申请答卷")
}
Spacer(modifier = Modifier.height(5.dp))
}
when (val result = it.result) {
null -> BottomButton(confirmDesc = R.string.allow_btn,
backDesc = R.string.refuse_btn,
modifier = Modifier.fillMaxWidth(),
onBack = {
model.audit(joinId = it.id, result = false) {
scaffoldModel.update(
message = it,
actionLabel = "关闭提示"
)
model.load(associationId = associationId)
}
},
onConfirm = {
model.audit(joinId = it.id, result = true) {
scaffoldModel.update(
message = it,
actionLabel = "关闭提示"
)
model.load(associationId = associationId)
}
})
true, false -> {
Text("审核结果:${if (result) "通过" else "不通过"}")
Text("审核时间:${it.auditTime?.let { Date(it).datetimeFormat() }}")
}
}
}
}
}
}
}
}
}
}
}
}

@ -1,5 +1,6 @@
package com.gyf.csams.association.ui
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@ -9,6 +10,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
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
@ -19,6 +21,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.R
import com.gyf.csams.association.model.*
import com.gyf.csams.module.QUESTION_MIN_SIZE
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.BaseTextField
@ -26,6 +29,7 @@ import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.BottomButton
import com.orhanobut.logger.Logger
/**
@ -34,13 +38,32 @@ import com.gyf.lib.util.BottomButton
*/
class ExamActivity : ComponentActivity() {
lateinit var activityType: ExamActivityType
private val activityType: ExamActivityType
get() {
return intent?.getSerializableExtra(ExamActivityType::name.name) as ExamActivityType
}
private val associationId: Int
get() {
val id = intent.getIntExtra(
AssociationActivity::class.java.name,
0
)
return if (id == 0) throw IllegalArgumentException("不存在社团id,获取失败") else id
}
private val joinId: Int
get() {
val id = intent.getIntExtra(
AuditJoinActivity::class.java.name,
0
)
return if (id == 0) throw IllegalArgumentException("不存在申请记录id,获取失败") else id
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityType = intent?.getSerializableExtra(ExamActivityType::name.name) as ExamActivityType
setContent {
Body {
MainColumnFrame(background = {
@ -49,14 +72,35 @@ class ExamActivity : ComponentActivity() {
alpha = 0.6F
)
}) {
Spacer(modifier = Modifier.weight(0.1F))
Title(modifier = Modifier.weight(0.1F))
Title()
Spacer(modifier = Modifier.height(10.dp))
Exam(modifier = Modifier.weight(0.8F))
}
}
}
}
@Composable
private fun ErrorRadioButton(onClick: () -> Unit = {}) {
RadioButton(
selected = true,
colors =
RadioButtonDefaults.colors(disabledColor = MaterialTheme.colors.error),
onClick = onClick,
enabled = false
)
}
@Composable
private fun RightRadioButton(selected: Boolean, onClick: () -> Unit = {}) {
RadioButton(
selected = selected,
colors =
RadioButtonDefaults.colors(disabledColor = MaterialTheme.colors.primary),
onClick = onClick,
enabled = false
)
}
/**
* 标题
@ -64,12 +108,44 @@ class ExamActivity : ComponentActivity() {
* @param modifier
*/
@Composable
private fun Title(modifier: Modifier = Modifier) {
Row(horizontalArrangement = Arrangement.Center, modifier = modifier.fillMaxWidth()) {
Text(text = activityType.menuName, style = MaterialTheme.typography.h4)
private fun Title(modifier: Modifier = Modifier, model: ExamViewModel = viewModel()) {
val data by model.data.observeAsState()
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = activityType.menuName, style = MaterialTheme.typography.h5)
data?.size?.let {
Text(buildString {
append("共有${if (activityType == ExamActivityType.SET_EXAM) it - 1 else it}道题目")
if (it < QUESTION_MIN_SIZE) {
append(",还差${QUESTION_MIN_SIZE - it}道题目才可以生成入团笔试题")
}
}, style = MaterialTheme.typography.h6.copy(color = MaterialTheme.colors.primary))
}
when (activityType) {
ExamActivityType.Answer -> Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Row {
RightRadioButton(selected = true)
Text(text = "表示正确选项")
}
Row {
ErrorRadioButton()
Text(text = "表示错误选项")
}
}
ExamActivityType.SET_EXAM -> Row(modifier = Modifier.fillMaxWidth()) {
Text(text = "通过单选按钮设置正确答案")
}
ExamActivityType.JOIN_Association -> Row(modifier = Modifier.fillMaxWidth()) {
Text(text = "通过单选按钮选择正确答案")
}
}
}
}
@Composable
private fun ExamChild(it: Exam, examHeight: Dp, isAdd: Boolean = false) {
val questionWeight = 0.3F
@ -77,7 +153,7 @@ class ExamActivity : ComponentActivity() {
is OpenQuestionsVo -> ExamOQ(
openQuestionsVo = it,
modifier = Modifier.height(examHeight * questionWeight),
isAdd = isAdd
isAdd = isAdd,
)
is ChoiceQuestionVo -> ExamCQ(
choiceQuestionVo = it,
@ -101,10 +177,23 @@ class ExamActivity : ComponentActivity() {
scaffoldModel: ScaffoldModel = viewModel(),
examHeight: Dp = 350.dp
) {
LaunchedEffect(associationId) {
when (activityType) {
ExamActivityType.JOIN_Association, ExamActivityType.SET_EXAM -> model.load(
associationId = associationId,
activityType = activityType
)
ExamActivityType.Answer -> model.showAnswer(joinId = joinId) {
scaffoldModel.update(message = "答卷加载成功", actionLabel = "关闭提示")
}
}
}
val listState = rememberLazyListState()
val data by model.data.observeAsState()
val newExam by model.newExam.observeAsState()
LazyColumn(state = listState, modifier = modifier) {
Column(modifier = modifier) {
LazyColumn(state = listState, modifier = Modifier.weight(0.8F)) {
data?.forEach {
item {
ExamChild(it = it, examHeight = examHeight)
@ -122,43 +211,54 @@ class ExamActivity : ComponentActivity() {
}
}
item {
Column {
}
Divider(color = MaterialTheme.colors.background)
Spacer(modifier = Modifier.height(30.dp))
when (activityType) {
ExamActivityType.SET_EXAM -> {
BottomButton(
modifier = Modifier.fillMaxWidth(),
confirmDesc = R.string.update_exam
confirmDesc = R.string.update_exam,
enabled = data?.size ?: 0 > 0
) {
model.updateExam { scaffoldModel.update(message = it) }
model.updateExam(associationId = associationId) {
scaffoldModel.update(
message = it, actionLabel = "关闭提示"
)
}
}
}
ExamActivityType.JOIN_Association -> {
val allowPostAnswer by model.allowPostAnswer.observeAsState(false)
BottomButton(
modifier = Modifier.fillMaxWidth(),
confirmDesc = R.string.post_answer
confirmDesc = R.string.post_answer,
backDesc = R.string.cancel_apply,
enabled = allowPostAnswer
) {
model.postAnswer { scaffoldModel.update(message = it) }
model.postAnswer(associationId = associationId) {
scaffoldModel.update(message = it)
setResult(RESULT_OK, Intent().apply {
putExtra(ExamActivity::class.java.name, it)
})
finish()
}
}
}
ExamActivityType.Answer -> {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
OutlinedButton(onClick = { onBackPressed() }) {
Text(text = "返回")
}
}
}
// if (listState.layoutInfo.totalItemsCount - listState.firstVisibleItemIndex == model.initSize / 2 - 1) {
// model.loadMore { scaffoldModel.update(message = it) }
// }
}
}
}
/**
@ -172,12 +272,14 @@ class ExamActivity : ComponentActivity() {
modifier: Modifier = Modifier,
exam: Exam
) {
exam._question?.let {
exam._question.let {
BaseTextField(
form = it,
modifier = modifier
.fillMaxSize()
.background(color = MaterialTheme.colors.background)
.background(color = MaterialTheme.colors.background),
readOnly = activityType != ExamActivityType.SET_EXAM,
showTrailingIcon = activityType == ExamActivityType.SET_EXAM
)
}
@ -194,16 +296,13 @@ class ExamActivity : ComponentActivity() {
scaffoldModel: ScaffoldModel = viewModel(),
exam: Exam
) {
val list by model.data.observeAsState()
model.newExam.value?._question?.let {
val value by it.formValue.observeAsState()
Box(
contentAlignment = Alignment.Center,
modifier = modifier
) {
IconButton(onClick = {
if (isAdd) {
if ((value?.isNotEmpty() == true)) {
if (exam.check()) {
scaffoldModel.update(
message = model.addTip,
actionLabel = model.actionLabel
@ -211,12 +310,9 @@ class ExamActivity : ComponentActivity() {
model.addQuestion()
}
} else {
scaffoldModel.update(message = model.questionIsNull)
scaffoldModel.update(message = model.questionIsNull, actionLabel = "知道了")
}
} else {
if (list?.size == 1) {
scaffoldModel.update(model.deleteLeastOne)
} else {
scaffoldModel.update(
message = model.deleteTip,
@ -225,7 +321,6 @@ class ExamActivity : ComponentActivity() {
model.deleteQuestion(exam = exam)
}
}
}
}) {
Icon(
painter = painterResource(id = if (isAdd) R.drawable.ic_add_select else R.drawable.ic_sami_select),
@ -233,7 +328,6 @@ class ExamActivity : ComponentActivity() {
)
}
}
}
}
@ -247,9 +341,15 @@ class ExamActivity : ComponentActivity() {
private fun ExamOQ(
modifier: Modifier = Modifier,
openQuestionsVo: OpenQuestionsVo,
isAdd: Boolean = false
isAdd: Boolean = false,
model: ExamViewModel = viewModel()
) {
val data by model.data.observeAsState()
Row(modifier = modifier) {
Logger.i("index=${data?.indexOf(openQuestionsVo)}")
data?.indexOf(openQuestionsVo)?.let {
Text(text = "${it}.", modifier = Modifier.weight(0.05F))
}
Question(
exam = openQuestionsVo, modifier =
if (activityType == ExamActivityType.SET_EXAM)
@ -262,7 +362,7 @@ class ExamActivity : ComponentActivity() {
if (activityType == ExamActivityType.SET_EXAM) {
ActionButton(
modifier = Modifier
.weight(0.2F)
.weight(0.15F)
.fillMaxHeight(),
isAdd = isAdd,
exam = openQuestionsVo
@ -285,6 +385,16 @@ class ExamActivity : ComponentActivity() {
questionWeight: Float
) {
Row(modifier = modifier) {
model.data.value?.let {
Text(
text = "${
if (it.indexOf(choiceQuestionVo) == -1) it.size else it.indexOf(
choiceQuestionVo
) + 1
}."
)
}
Column(
modifier = if (activityType == ExamActivityType.SET_EXAM)
Modifier
@ -315,16 +425,52 @@ class ExamActivity : ComponentActivity() {
.weight(1F / ANSWER_SIZE)
) {
val answerIndex: Int = indexOf(it)
val click = {
when (activityType) {
ExamActivityType.SET_EXAM -> {
RadioButton(
selected = choiceQuestionVo.rightAnswer == answerIndex,
onClick = {
model.update(
oldExam = choiceQuestionVo,
newExam = choiceQuestionVo.copy(rightAnswer = answerIndex)
)
})
}
ExamActivityType.JOIN_Association -> {
RadioButton(
selected = choiceQuestionVo.chooseOption == answerIndex,
onClick = {
model.update(
oldExam = choiceQuestionVo,
newExam = choiceQuestionVo.copy(chooseOption = answerIndex)
)
model.checkChooseAnswer()
})
}
ExamActivityType.Answer -> {
when {
choiceQuestionVo.rightAnswer == answerIndex -> RightRadioButton(
selected = true
)
choiceQuestionVo.chooseOption != choiceQuestionVo.rightAnswer && choiceQuestionVo.chooseOption == answerIndex ->
ErrorRadioButton()
else -> RadioButton(
selected = false,
onClick = {},
enabled = false
)
}
val isRightAnswer =
choiceQuestionVo.rightAnswer == answerIndex
RadioButton(selected = isRightAnswer, onClick = click)
BaseTextField(form = it)
}
}
BaseTextField(
form = it,
enabled = activityType != ExamActivityType.Answer,
showTrailingIcon = activityType == ExamActivityType.SET_EXAM
)
}
}
@ -333,7 +479,6 @@ class ExamActivity : ComponentActivity() {
}
}
if (activityType == ExamActivityType.SET_EXAM) {
ActionButton(
modifier = Modifier
.weight(0.2F)
@ -341,7 +486,6 @@ class ExamActivity : ComponentActivity() {
isAdd = isAdd,
exam = choiceQuestionVo
)
}
}
}

@ -50,7 +50,7 @@ class ChoiceQuestionVoSerializer : JsonSerializer<ChoiceQuestionVo> {
val c = JsonObject()
c.add("examType", gson.toJsonTree(vo?.examType?.name))
// c.add("question", gson.toJsonTree(vo?.question?.formValue?.value))
c.add("answers", gson.toJsonTree(vo?.answers))
// c.add("answers", gson.toJsonTree(vo?.answers))
c.add("rightAnswer", gson.toJsonTree(vo?.rightAnswer))
return c
}

@ -20,4 +20,5 @@
<string name="collect_btn">收藏</string>
<string name="upload_btn">上传</string>
<string name="close_btn">关闭</string>
<string name="cancel_apply">放弃申请</string>
</resources>

@ -20,4 +20,5 @@
<string name="collect_btn">收藏</string>
<string name="upload_btn">上传</string>
<string name="close_btn">关闭</string>
<string name="cancel_apply">放弃申请</string>
</resources>

@ -20,4 +20,5 @@
<string name="collect_btn">收藏</string>
<string name="upload_btn">上传</string>
<string name="close_btn">关闭</string>
<string name="cancel_apply">放弃申请</string>
</resources>

@ -14,6 +14,17 @@ import org.junit.Test
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
abstract class TestA {
val i: Int = 3
}
data class TestB(val b: Int = 3) : TestA() {
fun test() {
println("${i}")
}
}
class ExampleUnitTest {
@Test
fun addition_isCorrect() {

@ -131,6 +131,7 @@ fun <T : StringForm> BaseTextField(
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
readOnly: Boolean = false,
enabled: Boolean = true,
textStyle: TextStyle = LocalTextStyle.current,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
@ -151,7 +152,8 @@ fun <T : StringForm> BaseTextField(
isError = isError,
visualTransformation = visualTransformation,
readOnly = readOnly,
textStyle = textStyle
textStyle = textStyle,
enabled = enabled
)
}
@ -174,7 +176,9 @@ fun <T : StringForm> BaseTextField(
keyboardOptions: KeyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
readOnly: Boolean = false
readOnly: Boolean = false,
showTrailingIcon: Boolean = true,
enabled: Boolean = true
) {
val name: String by form.formValue.observeAsState("")
BaseTextField(
@ -184,8 +188,9 @@ fun <T : StringForm> BaseTextField(
keyboardOptions,
isError,
visualTransformation,
trailingIcon = { Text(text = "${name.length}/${form.textLength}") },
readOnly = readOnly
trailingIcon = { if (showTrailingIcon) Text(text = "${name.length}/${form.textLength}") },
readOnly = readOnly,
enabled = enabled
)
}

@ -17,7 +17,7 @@ import kotlinx.coroutines.launch
data class SnackBar(
val message: String?,
val actionLabel: String? = null,
val actionLabel: String,
val duration: SnackbarDuration = SnackbarDuration.Short,
val callback: () -> Unit?
)
@ -30,7 +30,7 @@ class ScaffoldModel : ViewModel() {
private val _data = MutableLiveData<SnackBar?>()
val data: LiveData<SnackBar?> = _data
fun update(message: String? = null, actionLabel: String? = null, callback: () -> Unit? = {}) {
fun update(message: String? = null, actionLabel: String = "关闭提示", callback: () -> Unit? = {}) {
if (message == null) {
_data.postValue(null)
} else {
@ -55,7 +55,6 @@ fun ShowSnackbar(model: ScaffoldModel = viewModel(), scaffoldState: ScaffoldStat
if (message != null) {
LaunchedEffect(scaffoldState) {
launch {
if (actionLabel != null) {
val result = scaffoldState.snackbarHostState.showSnackbar(
message = message, actionLabel = actionLabel,
duration = duration
@ -63,16 +62,13 @@ fun ShowSnackbar(model: ScaffoldModel = viewModel(), scaffoldState: ScaffoldStat
)
when (result) {
SnackbarResult.ActionPerformed -> {
Logger.i("点击操作按钮")
Logger.d("点击操作按钮")
callback()
}
SnackbarResult.Dismissed -> {
Logger.d("窗口消失")
}
}
} else {
scaffoldState.snackbarHostState.showSnackbar(message = message)
}
model.update()
}
}

@ -131,7 +131,31 @@ enum class AssociationApi(val path: String) : UrlPath {
RenameAudit("${Rename.path}${Audit.path}"),
//换名申请表审核进度
RenameRead("${Rename.path}${Read.path}");
RenameRead("${Rename.path}${Read.path}"),
//更新题库
UpdateExam("/update/exam"),
//加载题库
LoadExam("/load/exam"),
//申请入团
CheckApply("/check/apply"),
//创建试卷
CreatePaper("/create/paper"),
//提交答卷
ApplyAnswer("/apply/paper"),
//检查入团申请
CheckJoin("/check/join"),
//审核入团申请
AuditJoin("/audit/join"),
//显示答案
ShowAnswers("/show/answer");
override fun build(): String {
return "/api/association${this.path}"

@ -31,4 +31,6 @@
<string name="audit_phases">审核阶段</string>
<string name="last_approver_origin">复审意见</string>
<string name="activity_date">活动日期</string>
<string name="allow_btn">通过</string>
<string name="refuse_btn">拒绝</string>
</resources>

@ -31,4 +31,6 @@
<string name="audit_phases">审核阶段</string>
<string name="last_approver_origin">复审意见</string>
<string name="activity_date">活动日期</string>
<string name="allow_btn">通过</string>
<string name="refuse_btn">拒绝</string>
</resources>

@ -31,4 +31,6 @@
<string name="audit_phases">审核阶段</string>
<string name="last_approver_origin">复审意见</string>
<string name="activity_date">活动日期</string>
<string name="allow_btn">通过</string>
<string name="refuse_btn">拒绝</string>
</resources>
Loading…
Cancel
Save