申请活动

master
pan 3 years ago
parent d6d3193837
commit f093f47c37
  1. 2
      background/src/main/AndroidManifest.xml
  2. 2
      background/src/main/java/com/gyf/csams/InitActivity.kt
  3. 5
      background/src/main/java/com/gyf/csams/account/model/LoginViewModel.kt
  4. 6
      background/src/main/java/com/gyf/csams/main/model/AssociationManagementViewModel.kt
  5. 29
      background/src/main/java/com/gyf/csams/main/model/AuditActViewModel.kt
  6. 76
      background/src/main/java/com/gyf/csams/main/model/AuditAssociationViewModel.kt
  7. 2
      background/src/main/java/com/gyf/csams/main/model/BackgroundViewModel.kt
  8. 89
      background/src/main/java/com/gyf/csams/main/model/BaseAuditViewModel.kt
  9. 46
      background/src/main/java/com/gyf/csams/main/model/CheckActViewModel.kt
  10. 3
      background/src/main/java/com/gyf/csams/main/model/CheckQualityReportViewModel.kt
  11. 5
      background/src/main/java/com/gyf/csams/main/model/ManagementOfficerModel.kt
  12. 22
      background/src/main/java/com/gyf/csams/main/model/ManagerActViewModel.kt
  13. 2
      background/src/main/java/com/gyf/csams/main/model/MenuViewModel.kt
  14. 3
      background/src/main/java/com/gyf/csams/main/model/RenameViewModel.kt
  15. 4
      background/src/main/java/com/gyf/csams/main/ui/AssociationManagementActivity.kt
  16. 96
      background/src/main/java/com/gyf/csams/main/ui/AuditActActivity.kt
  17. 241
      background/src/main/java/com/gyf/csams/main/ui/AuditAssociationActivity.kt
  18. 92
      background/src/main/java/com/gyf/csams/main/ui/CheckActActivity.kt
  19. 5
      background/src/main/java/com/gyf/csams/main/ui/CheckQualityReportActivity.kt
  20. 5
      background/src/main/java/com/gyf/csams/main/ui/MainActivity.kt
  21. 2
      background/src/main/java/com/gyf/csams/main/ui/ManagementOfficerActivity.kt
  22. 22
      background/src/main/java/com/gyf/csams/main/ui/ManagerActActivity.kt
  23. 7
      background/src/main/java/com/gyf/csams/main/ui/NotificationActivity.kt
  24. 2
      background/src/main/java/com/gyf/csams/main/ui/RenameActivity.kt
  25. 266
      background/src/main/java/com/gyf/csams/uikit/CheckForm.kt
  26. 15
      background/src/main/java/com/gyf/csams/uikit/Table.kt
  27. 3
      foreground/src/main/java/com/gyf/csams/InitActivity.kt
  28. 44
      foreground/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt
  29. 2
      foreground/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt
  30. 19
      foreground/src/main/java/com/gyf/csams/activity/model/ActivityDetailViewModel.kt
  31. 171
      foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt
  32. 26
      foreground/src/main/java/com/gyf/csams/activity/ui/ActivityDetailActivity.kt
  33. 224
      foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt
  34. 14
      foreground/src/main/java/com/gyf/csams/association/model/AssociationViewModel.kt
  35. 65
      foreground/src/main/java/com/gyf/csams/association/model/ExamViewModel.kt
  36. 49
      foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt
  37. 151
      foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt
  38. 21
      foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt
  39. 155
      foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt
  40. 13
      foreground/src/main/java/com/gyf/csams/main/model/MainViewModel.kt
  41. 69
      foreground/src/main/java/com/gyf/csams/main/ui/MainActivity.kt
  42. 3
      foreground/src/main/java/com/gyf/csams/message/model/ForegroundViewModel.kt
  43. 7
      foreground/src/main/java/com/gyf/csams/message/ui/SysMessageActivity.kt
  44. 52
      foreground/src/main/java/com/gyf/csams/uikit/BaseView.kt
  45. 39
      foreground/src/main/java/com/gyf/csams/util/GsonUtil.kt
  46. 3
      foreground/src/main/java/com/gyf/csams/util/HttpCallback.kt
  47. 9
      foreground/src/main/res/drawable/ic_clock.xml
  48. 3
      foreground/src/test/java/com/gyf/csams/ExampleUnitTest.kt
  49. 2
      lib/build.gradle.kts
  50. 2
      lib/src/androidTest/java/com/gyf/lib/ExampleInstrumentedTest.kt
  51. 23
      lib/src/main/java/com/gyf/lib/model/AbstractLoginViewModel.kt
  52. 8
      lib/src/main/java/com/gyf/lib/model/InitViewModel.kt
  53. 9
      lib/src/main/java/com/gyf/lib/model/SysMessageViewModel.kt
  54. 3
      lib/src/main/java/com/gyf/lib/service/BaseActivity.kt
  55. 29
      lib/src/main/java/com/gyf/lib/service/MessageService.kt
  56. 3
      lib/src/main/java/com/gyf/lib/uikit/AbstractInitActivity.kt
  57. 13
      lib/src/main/java/com/gyf/lib/uikit/BaseTextField.kt
  58. 8
      lib/src/main/java/com/gyf/lib/uikit/Profile.kt
  59. 33
      lib/src/main/java/com/gyf/lib/util/Api.kt
  60. 53
      lib/src/main/java/com/gyf/lib/util/DateTimeUtil.kt
  61. 53
      lib/src/main/java/com/gyf/lib/util/HttpUtil.kt
  62. 22
      lib/src/main/java/com/gyf/lib/util/RandomUtil.kt
  63. 61
      lib/src/main/java/com/gyf/lib/util/TokenUtil.kt
  64. 527
      lib/src/main/java/com/gyf/lib/util/Vo.kt
  65. 1
      lib/src/main/res/values-en/integers.xml
  66. 1
      lib/src/main/res/values-en/strings.xml
  67. 1
      lib/src/main/res/values-zh/integers.xml
  68. 1
      lib/src/main/res/values-zh/strings.xml
  69. 1
      lib/src/main/res/values/integers.xml
  70. 1
      lib/src/main/res/values/strings.xml
  71. 8
      lib/src/test/java/com/gyf/lib/ExampleUnitTest.kt
  72. 4
      settings.gradle.kts

@ -47,7 +47,7 @@
android:name=".main.ui.AuditAssociationActivity"
android:windowSoftInputMode="adjustResize" />
<!--活动申请书-->
<activity android:name=".main.ui.CheckActActivity" />
<activity android:name=".main.ui.AuditActActivity" />
<!--活动质量汇报单-->
<activity android:name=".main.ui.CheckQualityReportActivity" />
<!--通知界面-->

@ -4,10 +4,10 @@ import android.app.Activity
import com.google.gson.reflect.TypeToken
import com.gyf.csams.account.ui.LoginActivity
import com.gyf.csams.main.ui.MainActivity
import com.gyf.csams.module.ManagerVo
import com.gyf.lib.uikit.AbstractInitActivity
import com.gyf.lib.util.AccountApi
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.ManagerVo
import java.lang.reflect.Type
class InitActivity : AbstractInitActivity<ManagerVo>() {

@ -4,12 +4,13 @@ import android.app.Activity
import android.app.Application
import android.os.Build
import com.gyf.csams.account.ui.LoginActivity
import com.gyf.csams.module.ClientType
import com.gyf.csams.module.ManagerLoginVo
import com.gyf.lib.model.AbstractLoginViewModel
import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.AccountApi
import com.gyf.lib.util.ClientType
import com.gyf.lib.util.ManagerLoginVo
class LoginViewModel(application: Application) : AbstractLoginViewModel(application) {
override val id = ValidStringForm(formDesc = "管理帐号", textLength = 8)

@ -1,11 +1,9 @@
package com.gyf.csams.main.model
import android.app.Application
import com.gyf.csams.module.AssociationLevel
import com.gyf.csams.module.AssociationVo
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.util.AssociationLevel
import com.gyf.lib.util.AssociationVo
/**

@ -0,0 +1,29 @@
package com.gyf.csams.main.model
import android.app.Application
import com.google.gson.reflect.TypeToken
import com.gyf.csams.module.AuditActVo
import com.gyf.lib.util.ActivityApi
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.UrlPath
import java.lang.reflect.Type
/**
* 活动审核
*
*/
class AuditActViewModel(application: Application) : BaseAuditViewModel<AuditActVo>(application) {
override val auditApi: UrlPath = ActivityApi.Audit
override val acceptApi: UrlPath = ActivityApi.Accept
override val checkApi: UrlPath = ActivityApi.Check
override val typeToken: Type =
object : TypeToken<ApiResponse<MutableList<AuditActVo>>>() {}.type
init {
load { }
}
}

@ -1,78 +1,28 @@
package com.gyf.csams.main.model
import android.app.Application
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.util.*
import kotlinx.coroutines.launch
import com.gyf.csams.module.AuditAssociationVo
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.AssociationApi
import com.gyf.lib.util.UrlPath
import java.lang.reflect.Type
class AuditAssociationViewModel(application: Application) : ScrollViewModel<DisposeRegInfoVo>(
class AuditAssociationViewModel(application: Application) : BaseAuditViewModel<AuditAssociationVo>(
application
) {
override val initSize: Int = 10
override val auditApi: UrlPath = AssociationApi.Audit
override val acceptApi: UrlPath = AssociationApi.Accept
override val checkApi: UrlPath = AssociationApi.Check
override val typeToken: Type =
object : TypeToken<ApiResponse<MutableList<AuditAssociationVo>>>() {}.type
init {
load { }
}
fun load(callback: (message: String) -> Unit) {
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(AssociationApi.Audit), HttpCallback<MutableList<DisposeRegInfoVo>>(
action = "获取审核列表",
onSuccess = { it ->
it.body?.let {
_data.postValue(it)
}
},
typeToken = object :
TypeToken<ApiResponse<MutableList<DisposeRegInfoVo>>>() {}.type
),
jsonParam = OnlyToken(clientType = ClientType.Background)
)
}
}
/**
* 受理注册资料
*
* @param callback
*/
fun accept(regId: Int, isFirstAccept: Boolean, callback: (message: String) -> Unit) {
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(AssociationApi.Accept),
HttpCallback<Boolean>(action = "受理社团注册资料", onSuccess = {
callback(it.message)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type),
jsonParam = AcceptRegAssociation(regId = regId, isFirstAccept = isFirstAccept)
)
}
}
/**
* 提交审核结果
*
* @param regId
* @param result
* @param callback
*/
fun check(regId: Int, cause: String, result: Boolean, callback: (message: String) -> Unit) {
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(AssociationApi.Check),
HttpCallback<Boolean>(action = "提交审核结果", onSuccess = {
callback(it.message)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type),
jsonParam = CheckRegVo(
regId = regId, result = result,
cause = cause
)
)
}
}
}

@ -1,8 +1,8 @@
package com.gyf.csams.main.model
import android.app.Application
import com.gyf.csams.module.ClientType
import com.gyf.lib.model.SysMessageViewModel
import com.gyf.lib.util.ClientType
class BackgroundViewModel(application: Application) : SysMessageViewModel(application) {
override fun clientType(): ClientType {

@ -0,0 +1,89 @@
package com.gyf.csams.main.model
import android.app.Application
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.module.AcceptVo
import com.gyf.csams.module.AuditVo
import com.gyf.csams.module.CheckVo
import com.gyf.csams.module.ClientType
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.util.*
import kotlinx.coroutines.launch
import java.lang.reflect.Type
abstract class BaseAuditViewModel<T : AuditVo>(application: Application) :
ScrollViewModel<T>(application = application) {
abstract val auditApi: UrlPath
abstract val acceptApi: UrlPath
abstract val checkApi: UrlPath
abstract val typeToken: Type
/**
* 加载审核记录
*
* @param callback
*/
fun load(callback: (message: String) -> Unit) {
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(auditApi), HttpCallback<MutableList<T>>(
action = "加载资料",
onSuccess = { it ->
it.body?.let {
_data.postValue(it)
}
},
typeToken = typeToken
),
jsonParam = OnlyToken(clientType = ClientType.Background)
)
}
}
/**
* 受理资料
*
* @param callback
*/
fun accept(auditId: Int, callback: (message: String) -> Unit) {
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(acceptApi),
HttpCallback<Boolean>(action = "受理资料", onSuccess = {
callback(it.message)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type),
jsonParam = AcceptVo(auditId = auditId, token = TokenManager.getToken())
)
}
}
/**
* 审核资料
*
* @param auditId
* @param result
* @param callback
*/
fun check(auditId: Int, cause: String, result: Boolean, callback: (message: String) -> Unit) {
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(checkApi),
HttpCallback<Boolean>(action = "审核资料", onSuccess = {
callback(it.message)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type),
jsonParam = CheckVo(
auditId = auditId,
result = result,
cause = cause,
token = TokenManager.getToken()
)
)
}
}
}

@ -1,46 +0,0 @@
package com.gyf.csams.main.model
import android.app.Application
import androidx.lifecycle.viewModelScope
import com.gyf.lib.model.ApplyViewModel
import com.gyf.lib.util.ApplyActVo
import com.gyf.lib.util.format
import com.gyf.lib.util.randomChinese
import com.gyf.lib.util.randomDateTime
import kotlinx.coroutines.launch
/**
* 活动数据管理
*
*/
class CheckActViewModel(application: Application) : ApplyViewModel<ApplyActVo>(application) {
override val initSize: Int = 10
init {
load()
}
fun load() {
viewModelScope.launch {
_data.value?.apply {
repeat(initSize) {
add(
ApplyActVo(
activityName = randomChinese(5),
activityTime = randomDateTime().format(),
location = randomChinese(10),
desc = randomChinese(10),
size = 10
)
)
}
}
}
}
fun loadMore(callback: (message: String) -> Unit) {
TODO("Not yet implemented")
}
}

@ -2,8 +2,9 @@ package com.gyf.csams.main.model
import android.app.Application
import androidx.lifecycle.viewModelScope
import com.gyf.csams.module.QualityReportVo
import com.gyf.lib.model.ApplyViewModel
import com.gyf.lib.util.QualityReportVo
import com.gyf.lib.util.randomChinese
import kotlinx.coroutines.launch

@ -3,8 +3,9 @@ package com.gyf.csams.main.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gyf.lib.util.AllOfficerVo
import com.gyf.lib.util.ManagerInfoVo
import com.gyf.csams.module.AllOfficerVo
import com.gyf.csams.module.ManagerInfoVo
enum class ColumnType {
Text,

@ -1,11 +1,8 @@
package com.gyf.csams.main.model
import android.app.Application
import com.gyf.csams.module.ManagerActVo
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.util.ActivityVo
import com.gyf.lib.util.randomChinese
import com.gyf.lib.util.randomDateTime
import com.gyf.lib.util.randomNum
/**
@ -15,7 +12,7 @@ import com.gyf.lib.util.randomNum
*
* @param application
*/
class ManagerActViewModel(application: Application) : ScrollViewModel<ActivityVo>(application) {
class ManagerActViewModel(application: Application) : ScrollViewModel<ManagerActVo>(application) {
override val initSize: Int = 10
init {
@ -23,20 +20,7 @@ class ManagerActViewModel(application: Application) : ScrollViewModel<ActivityVo
}
fun load() {
_data.value?.apply {
repeat(initSize) {
add(
ActivityVo(
activityId = randomNum(8).toLong(),
activityName = randomChinese(5),
association = randomChinese(5),
score = (1..5).random(),
activityTime = randomDateTime(),
location = randomChinese(10)
)
)
}
}
TODO("活动信息管理")
}
fun loadMore(callback: (message: String) -> Unit) {

@ -15,7 +15,7 @@ enum class MenuType(val desc: String, val clazz: Map<String, Class<out Activity>
),
Act(
"活动管理", mapOf(
"审核社团活动" to CheckActActivity::class.java,
"审核社团活动" to AuditActActivity::class.java,
"审核质量报告单" to CheckQualityReportActivity::class.java,
"查看社团活动" to ManagerActActivity::class.java
)

@ -3,9 +3,10 @@ package com.gyf.csams.main.model
import android.app.Application
import androidx.lifecycle.viewModelScope
import com.gyf.csams.R
import com.gyf.csams.module.RenameVo
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.util.RenameVo
import com.gyf.lib.util.randomChinese
import com.gyf.lib.util.randomNum
import kotlinx.coroutines.launch

@ -17,9 +17,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.main.model.AssociationManagementViewModel
import com.gyf.csams.module.AssociationLevel
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainBoxFrame
import com.gyf.lib.util.AssociationLevel
/**
* 社团管理
@ -52,7 +52,7 @@ class AssociationManagementActivity : ComponentActivity() {
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "$id",
text = "$associationId",
style = MaterialTheme.typography.h6
)
Text(

@ -0,0 +1,96 @@
package com.gyf.csams.main.ui
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.insets.ExperimentalAnimatedInsets
import com.gyf.csams.R
import com.gyf.csams.main.model.AuditActViewModel
import com.gyf.csams.module.AuditActVo
import com.gyf.csams.uikit.BASE_HEIGHT
import com.gyf.csams.uikit.CheckForm
import com.gyf.csams.uikit.RowItem
import com.gyf.csams.uikit.TestTableImeSimple
import com.gyf.lib.uikit.ImeBody
import com.gyf.lib.util.DateTimeUtil.datetimeFormat
import java.util.*
/**
* 审批社团活动
*
*/
class AuditActActivity : ComponentActivity() {
@ExperimentalMaterialApi
@ExperimentalAnimatedInsets
@ExperimentalComposeApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ImeBody {
val model: AuditActViewModel = viewModel()
val data by model.data.observeAsState()
TestTableImeSimple(
title = R.string.activity_association
) {
data?.forEach {
item {
RegisterForm(vo = it)
CheckForm<AuditActVo, AuditActViewModel>(vo = it)
Spacer(modifier = Modifier.height(10.dp))
}
}
}
}
}
}
@Composable
private fun RegisterForm(
modifier: Modifier = Modifier,
vo: AuditActVo
) {
Column(modifier = modifier) {
vo.activityVo.activityTime
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.activity_name,
value = vo.activityVo.activityName
)
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.activity_time,
value = Date(vo.activityVo.activityTime).datetimeFormat()
)
RowItem(
modifier = Modifier.height(BASE_HEIGHT * 1.5F),
key = R.string.activity_address,
value = vo.activityVo.activityAddress
)
RowItem(
modifier = Modifier.height(BASE_HEIGHT * 3),
key = R.string.activity_desc,
value = vo.activityVo.activityDesc
)
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.activity_size,
value = "${vo.activityVo.activitySize}"
)
}
}
}

@ -5,7 +5,9 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
@ -18,13 +20,15 @@ import androidx.core.view.WindowCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.coil.rememberCoilPainter
import com.google.accompanist.insets.ExperimentalAnimatedInsets
import com.google.accompanist.insets.navigationBarsWithImePadding
import com.gyf.csams.R
import com.gyf.csams.main.model.AuditAssociationViewModel
import com.gyf.csams.module.AuditAssociationVo
import com.gyf.csams.uikit.BASE_HEIGHT
import com.gyf.csams.uikit.CheckForm
import com.gyf.csams.uikit.RowItem
import com.gyf.csams.uikit.TestTableImeSimple
import com.gyf.lib.uikit.*
import com.gyf.lib.util.*
import com.gyf.lib.uikit.ImeBody
import com.gyf.lib.util.Api
class AuditAssociationActivity : ComponentActivity() {
@ExperimentalMaterialApi
@ -43,6 +47,7 @@ class AuditAssociationActivity : ComponentActivity() {
data?.forEach {
item {
RegisterForm(vo = it)
CheckForm<AuditAssociationVo, AuditAssociationViewModel>(vo = it)
Spacer(modifier = Modifier.height(10.dp))
}
}
@ -52,208 +57,9 @@ class AuditAssociationActivity : ComponentActivity() {
}
@Composable
private fun CheckForm(
vo: DisposeRegInfoVo,
scaffoldModel: ScaffoldModel = viewModel(),
auditAssociationViewModel: AuditAssociationViewModel = viewModel(),
first: @Composable () -> Unit,
last: @Composable () -> Unit
) {
val baseHeight = 50.dp
(TokenManager.getOwnInfo() as? ManagerVo)?.let { it ->
var confirmDesc: Int? = null
var backDesc: Int? = null
var onConfirm: (() -> Unit)? = null
var onBack: (() -> Unit)? = null
val check: (result: Boolean, cause: StringForm) -> Unit =
{ result: Boolean, cause: StringForm ->
scaffoldModel.update(
message = "确认${if (result) "上报" else "驳回"}",
actionLabel = "确认"
) {
auditAssociationViewModel.check(
regId = vo.log.id,
result = result,
cause = cause.formValue.value
?: throw IllegalArgumentException("无法获取审核理由")
) {
scaffoldModel.update(message = it, actionLabel = "刷新") {
auditAssociationViewModel.load { }
}
}
}
}
val accept: (m: String, isFirstAccept: Boolean) -> Unit =
{ m: String, isFirstAccept: Boolean ->
scaffoldModel.update("确认${m}", actionLabel = "确认") {
auditAssociationViewModel.accept(
regId = vo.log.id,
isFirstAccept = isFirstAccept
) {
scaffoldModel.update(message = it, actionLabel = "刷新") {
auditAssociationViewModel.load { }
}
}
}
}
val doCheck: @Composable () -> Unit = {
val cause = ValidStringForm(
formDesc = "审核理由",
textLength = 20
)
val statusForm by cause.statusForm.observeAsState()
onConfirm = {
if (statusForm == FormStatus.Empty) {
scaffoldModel.update("${cause.formDesc}不能为空", actionLabel = "知道了")
} else {
check(true, cause)
}
}
onBack = {
if (statusForm == FormStatus.Empty) {
scaffoldModel.update("${cause.formDesc}不能为空", actionLabel = "知道了")
} else {
check(false, cause)
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
BaseTextField(
modifier = Modifier.navigationBarsWithImePadding(),
form = cause
)
}
}
when {
//初审记录,负责人为空 等待初审
vo.log.nextAudit == null && vo.log.manager == null -> {
if (it.duty == Duty.PamphaBhusal) {
confirmDesc = R.string.accept_btn
onConfirm = { accept("受理", true) }
}
first()
RowItem(key = R.string.first_approver_origin, value = "")
RowItem(key = R.string.first_result, value = "")
last()
RowItem(key = R.string.last_approver_origin, value = "")
RowItem(key = R.string.last_result, value = "")
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.audit_phases, value = "等待初审"
)
}
//初审记录,负责人不为空 初审受理
vo.log.nextAudit == null && vo.log.manager != null -> {
first()
if (it.duty == Duty.PamphaBhusal) {
confirmDesc = R.string.reported_btn
backDesc = R.string.reject_btn
doCheck()
} else {
RowItem(key = R.string.first_approver_origin, value = "")
}
RowItem(key = R.string.first_result, value = "")
last()
RowItem(key = R.string.last_approver_origin, value = "")
RowItem(key = R.string.last_result, value = "")
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.audit_phases, value = "初审受理"
)
}
//初审记录,审核通过(上报) 等待复审
vo.log.nextAudit != null && vo.log.nextAudit?.manager == null -> {
if (it.duty == Duty.Teacher) {
confirmDesc = R.string.recheck_btn
onConfirm = { accept("复审", false) }
}
first()
RowItem(key = R.string.first_approver_origin, value = vo.log.cause)
RowItem(key = R.string.first_result, value = "通过")
last()
RowItem(key = R.string.last_approver_origin, value = "")
RowItem(key = R.string.last_result, value = "")
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.audit_phases, value = "等待复审"
)
}
//复审记录,审核结果为空 复审受理
vo.log.nextAudit != null && vo.log.nextAudit?.result == null -> {
first()
RowItem(key = R.string.first_approver_origin, value = vo.log.cause)
RowItem(key = R.string.first_result, value = "通过")
last()
if (it.duty == Duty.Teacher) {
confirmDesc = R.string.allow_btn
backDesc = R.string.reject_btn
doCheck()
} else {
RowItem(key = R.string.last_approver_origin, value = "")
}
RowItem(key = R.string.last_result, value = "")
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.audit_phases, value = "复审受理"
)
}
else -> {
first()
RowItem(key = R.string.first_approver_origin, value = vo.log.cause)
RowItem(
key = R.string.first_result, value = when (vo.log.result) {
null -> ""
true -> "通过"
false -> "不通过"
}
)
last()
RowItem(
key = R.string.last_approver_origin,
value = vo.log.nextAudit?.cause ?: ""
)
RowItem(
key = R.string.last_result, value = when (vo.log.nextAudit?.result) {
null -> ""
true -> "通过"
false -> "不通过"
}
)
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.audit_phases, value = "完成审核"
)
}
}
if (confirmDesc != null) {
BottomButton(
confirmDesc = confirmDesc,
backDesc = backDesc,
modifier = Modifier.fillMaxWidth(),
onBack = onBack
) {
onConfirm?.let { it() }
}
}
}
}
@Composable
private fun RegisterForm(
modifier: Modifier = Modifier, vo: DisposeRegInfoVo
modifier: Modifier = Modifier, vo: AuditAssociationVo
) {
Column(
modifier = modifier.border(
@ -261,28 +67,26 @@ class AuditAssociationActivity : ComponentActivity() {
color = MaterialTheme.colors.onBackground
)
) {
val baseHeight = 50.dp
RowItem(
modifier = Modifier.height(baseHeight),
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.petitioner,
value = vo.log.user.name
value = vo.audit.user.name
)
RowItem(
modifier = Modifier.height(baseHeight),
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.association_name,
value = vo.name
)
RowItem(
modifier = Modifier.height(baseHeight),
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.association_desc,
value = vo.desc
)
RowItem(
modifier = Modifier.height(baseHeight * 3),
modifier = Modifier.height(BASE_HEIGHT * 3),
key = R.string.association_logo
) {
//TODO 图片全屏显示
@ -290,21 +94,6 @@ class AuditAssociationActivity : ComponentActivity() {
rememberCoilPainter(Api.buildUrl(vo.logo))
Image(painter = painter, contentDescription = null)
}
CheckForm(vo = vo, first = {
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.first_approver,
value = vo.log.manager?.name ?: ""
)
}, last = {
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.last_approver,
value = vo.log.nextAudit?.manager?.name ?: ""
)
})
}
}
}

@ -1,92 +0,0 @@
package com.gyf.csams.main.ui
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.insets.ExperimentalAnimatedInsets
import com.gyf.csams.R
import com.gyf.csams.main.model.CheckActViewModel
import com.gyf.csams.uikit.RowItem
import com.gyf.csams.uikit.TestTable
import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.ApplyActVo
import com.gyf.lib.util.BottomButton
/**
* 审批社团活动
*
*/
class CheckActActivity : ComponentActivity() {
@ExperimentalAnimatedInsets
@ExperimentalComposeApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TestTable(
clazz = CheckActViewModel::class.java,
title = R.string.activity_application
) {
ApplyActForm(vo = it)
}
}
}
@Composable
private fun ApplyActForm(
modifier: Modifier = Modifier, model: CheckActViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel(), vo: ApplyActVo
) {
Column(modifier = modifier) {
val baseHeight = 50.dp
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.activity_name,
value = vo.activityName
)
RowItem(
modifier = Modifier.height(baseHeight * 1.5F),
key = R.string.activity_address,
value = vo.location
)
RowItem(
modifier = Modifier.height(baseHeight * 3),
key = R.string.activity_desc,
value = vo.desc
)
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.activity_size,
value = "${vo.size}"
)
RowItem(
modifier = Modifier.height(baseHeight), key = R.string.first_approver, value = ""
/**TODO 获取审批人**/
)
RowItem(
modifier = Modifier.height(baseHeight * 3),
key = R.string.first_approver_origin
) {
BaseTextField(modifier = Modifier.fillMaxSize(), form = model.approveOrigin)
}
val message = stringResource(id = R.string.not_impl_error)
BottomButton(
confirmDesc = R.string.reported_btn, backDesc = R.string.reject_btn,
modifier = Modifier.fillMaxWidth()
) {
scaffoldModel.update(message = message)
}
}
}
}

@ -15,13 +15,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.insets.ExperimentalAnimatedInsets
import com.gyf.csams.R
import com.gyf.csams.main.model.CheckQualityReportViewModel
import com.gyf.csams.module.MAX_SCORE
import com.gyf.csams.module.QualityReportVo
import com.gyf.csams.uikit.RowItem
import com.gyf.csams.uikit.TestTable
import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.BottomButton
import com.gyf.lib.util.MAX_SCORE
import com.gyf.lib.util.QualityReportVo
/**
* 审批质量报告单

@ -16,10 +16,11 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.R
import com.gyf.csams.account.model.LoginViewModel
import com.gyf.csams.main.model.MenuType
import com.gyf.csams.module.ClientType
import com.gyf.csams.module.ManagerInfoVo
import com.gyf.lib.service.BaseActivity
import com.gyf.lib.uikit.*
import com.gyf.lib.util.ClientType
import com.gyf.lib.util.ManagerInfoVo
import com.gyf.lib.util.TokenManager
class MainActivity : BaseActivity() {

@ -21,11 +21,11 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.R
import com.gyf.csams.main.model.ManagementOfficerModel
import com.gyf.csams.module.ManagerInfoVo
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.Profile
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.ManagerInfoVo
import com.orhanobut.logger.Logger
/**

@ -16,11 +16,13 @@ import androidx.compose.ui.unit.dp
import com.google.accompanist.insets.ExperimentalAnimatedInsets
import com.gyf.csams.R
import com.gyf.csams.main.model.ManagerActViewModel
import com.gyf.csams.module.MAX_SCORE
import com.gyf.csams.module.ManagerActVo
import com.gyf.csams.uikit.RowItem
import com.gyf.csams.uikit.TestTable
import com.gyf.lib.util.ActivityVo
import com.gyf.lib.util.MAX_SCORE
import com.gyf.lib.util.format
import com.gyf.lib.util.DateTimeUtil.datetimeFormat
import java.util.*
/**
* 查看活动信息
@ -40,19 +42,15 @@ class ManagerActActivity : ComponentActivity() {
}
@Composable
private fun ActivityTable(vo: ActivityVo) {
private fun ActivityTable(vo: ManagerActVo) {
val baseHeight = 50.dp
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.activity_id, value = "${vo.activityId}"
)
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.activity_name, value = vo.activityName
key = R.string.activity_name, value = vo.activityVo.activityName
)
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.activity_association, value = vo.association
key = R.string.activity_association, value = vo.association.name
)
RowItem(modifier = Modifier.height(baseHeight), key = R.string.activity_evaluate) {
Row(
@ -70,11 +68,11 @@ class ManagerActActivity : ComponentActivity() {
}
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.activity_time, value = vo.activityTime.format()
key = R.string.activity_time, value = Date(vo.activityVo.activityTime).datetimeFormat()
)
RowItem(
modifier = Modifier.height(baseHeight * 2),
key = R.string.activity_location, value = vo.location
key = R.string.activity_location, value = vo.activityVo.activityAddress
)
}
}

@ -15,10 +15,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.main.model.BackgroundViewModel
import com.gyf.csams.module.NotificationVo
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.util.NotificationVo
import com.gyf.lib.util.format
import com.gyf.lib.util.DateTimeUtil.datetimeFormat
import java.util.*
/**
@ -72,7 +73,7 @@ class NotificationActivity : ComponentActivity() {
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
Text(text = Date(content.createTime).format())
Text(text = Date(content.createTime).datetimeFormat())
}
}
}

@ -16,12 +16,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.insets.ExperimentalAnimatedInsets
import com.gyf.csams.R
import com.gyf.csams.main.model.RenameViewModel
import com.gyf.csams.module.RenameVo
import com.gyf.csams.uikit.RowItem
import com.gyf.csams.uikit.TestTable
import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.BottomButton
import com.gyf.lib.util.RenameVo
class RenameActivity : ComponentActivity() {

@ -0,0 +1,266 @@
package com.gyf.csams.uikit
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.*
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.insets.ExperimentalAnimatedInsets
import com.gyf.csams.R
import com.gyf.csams.main.model.BaseAuditViewModel
import com.gyf.csams.module.AuditVo
import com.gyf.csams.module.Duty
import com.gyf.csams.module.ManagerVo
import com.gyf.lib.uikit.*
import com.gyf.lib.util.BottomButton
import com.gyf.lib.util.TokenManager
val BASE_HEIGHT = 50.dp
@ExperimentalMaterialApi
@ExperimentalAnimatedInsets
@Composable
inline fun <reified T : AuditVo, reified E : BaseAuditViewModel<T>> ContentCheckForm(
@StringRes title: Int,
crossinline RegisterForm: @Composable (vo: T) -> Unit
) {
ImeBody {
val model: E = viewModel()
val data by model.data.observeAsState()
TestTableImeSimple(
title = title
) {
data?.forEach {
item {
RegisterForm(vo = it)
CheckForm<T, E>(vo = it)
Spacer(modifier = Modifier.height(10.dp))
}
}
}
}
}
/**
* 表格审核组件
*
* @param VO
* @param M
* @param vo
* @param scaffoldModel
* @param model
* @param first
* @param last
*/
@Composable
inline fun <reified VO : AuditVo, reified M : BaseAuditViewModel<VO>> CheckForm(
vo: VO,
scaffoldModel: ScaffoldModel = viewModel(),
model: M = viewModel(),
first: @Composable () -> Unit = {
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.first_approver,
value = vo.audit.manager?.name ?: ""
)
},
last: @Composable () -> Unit = {
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.last_approver,
value = vo.audit.nextAudit?.manager?.name ?: ""
)
}
) {
(TokenManager.getOwnInfo() as? ManagerVo)?.let { it ->
var confirmDesc: Int? = null
var backDesc: Int? = null
var onConfirm: (() -> Unit)? = null
var onBack: (() -> Unit)? = null
val check: (result: Boolean, cause: StringForm) -> Unit =
{ result: Boolean, cause: StringForm ->
scaffoldModel.update(
message = "确认${if (result) "上报" else "驳回"}",
actionLabel = "确认"
) {
model.check(
auditId = vo.audit.id,
result = result,
cause = cause.formValue.value
?: throw IllegalArgumentException("无法获取审核理由")
) {
scaffoldModel.update(message = it, actionLabel = "刷新") {
model.load { }
}
}
}
}
val accept: (m: String) -> Unit =
{ m: String ->
scaffoldModel.update("确认${m}", actionLabel = "确认") {
model.accept(
auditId = vo.audit.id
) {
scaffoldModel.update(message = it, actionLabel = "刷新") {
model.load { }
}
}
}
}
val doCheck: @Composable () -> Unit = {
val cause = ValidStringForm(
formDesc = "审核理由",
textLength = 20
)
val statusForm by cause.statusForm.observeAsState()
onConfirm = {
if (statusForm == FormStatus.Empty) {
scaffoldModel.update("${cause.formDesc}不能为空", actionLabel = "知道了")
} else {
check(true, cause)
}
}
onBack = {
if (statusForm == FormStatus.Empty) {
scaffoldModel.update("${cause.formDesc}不能为空", actionLabel = "知道了")
} else {
check(false, cause)
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
BaseTextField(
form = cause
)
}
}
when {
//初审记录,负责人为空 等待初审
vo.audit.nextAudit == null && vo.audit.manager == null -> {
if (it.duty == Duty.PamphaBhusal) {
confirmDesc = R.string.accept_btn
onConfirm = { accept("受理") }
}
first()
RowItem(key = R.string.first_approver_origin, value = "")
RowItem(key = R.string.first_result, value = "")
last()
RowItem(key = R.string.last_approver_origin, value = "")
RowItem(key = R.string.last_result, value = "")
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.audit_phases, value = "等待初审"
)
}
//初审记录,负责人不为空 初审受理
vo.audit.nextAudit == null && vo.audit.manager != null -> {
first()
if (it.duty == Duty.PamphaBhusal) {
confirmDesc = R.string.reported_btn
backDesc = R.string.reject_btn
doCheck()
} else {
RowItem(key = R.string.first_approver_origin, value = "")
}
RowItem(key = R.string.first_result, value = "")
last()
RowItem(key = R.string.last_approver_origin, value = "")
RowItem(key = R.string.last_result, value = "")
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.audit_phases, value = "初审受理"
)
}
//初审记录,审核通过(上报) 等待复审
vo.audit.nextAudit != null && vo.audit.nextAudit?.manager == null -> {
if (it.duty == Duty.Teacher) {
confirmDesc = R.string.recheck_btn
onConfirm = { accept("复审") }
}
first()
RowItem(key = R.string.first_approver_origin, value = vo.audit.cause)
RowItem(key = R.string.first_result, value = "通过")
last()
RowItem(key = R.string.last_approver_origin, value = "")
RowItem(key = R.string.last_result, value = "")
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.audit_phases, value = "等待复审"
)
}
//复审记录,审核结果为空 复审受理
vo.audit.nextAudit != null && vo.audit.nextAudit?.result == null -> {
first()
RowItem(key = R.string.first_approver_origin, value = vo.audit.cause)
RowItem(key = R.string.first_result, value = "通过")
last()
if (it.duty == Duty.Teacher) {
confirmDesc = R.string.allow_btn
backDesc = R.string.reject_btn
doCheck()
} else {
RowItem(key = R.string.last_approver_origin, value = "")
}
RowItem(key = R.string.last_result, value = "")
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.audit_phases, value = "复审受理"
)
}
else -> {
first()
RowItem(key = R.string.first_approver_origin, value = vo.audit.cause)
RowItem(
key = R.string.first_result, value = when (vo.audit.result) {
null -> ""
true -> "通过"
false -> "不通过"
}
)
last()
RowItem(
key = R.string.last_approver_origin,
value = vo.audit.nextAudit?.cause ?: ""
)
RowItem(
key = R.string.last_result, value = when (vo.audit.nextAudit?.result) {
null -> ""
true -> "通过"
false -> "不通过"
}
)
RowItem(
modifier = Modifier.height(BASE_HEIGHT),
key = R.string.audit_phases, value = "完成审核"
)
}
}
if (confirmDesc != null) {
BottomButton(
confirmDesc = confirmDesc,
backDesc = backDesc,
modifier = Modifier.fillMaxWidth(),
onBack = onBack
) {
onConfirm?.let { it() }
}
}
}
}

@ -2,7 +2,6 @@ package com.gyf.csams.uikit
import androidx.annotation.StringRes
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
@ -13,6 +12,7 @@ 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.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@ -93,16 +93,17 @@ fun TestTableImeSimple(
val listState = rememberLazyListState()
val insets = LocalWindowInsets.current
val imeBottom = with(LocalDensity.current) { insets.ime.bottom.toDp() }
//TODO 解决键盘遮挡问题
val isVisible = with(LocalDensity.current) { insets.ime.isVisible }
val bottom = with(LocalDensity.current) { insets.ime.bottom.toDp() }
LazyColumn(
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.nestedScroll(connection = rememberImeNestedScrollConnection()),
contentPadding = PaddingValues(vertical = 10.dp),
state = listState,
content = content
)
LaunchedEffect(imeBottom) {
listState.scrollBy(imeBottom.value)
}
}
}

@ -4,10 +4,11 @@ import android.app.Activity
import com.google.gson.reflect.TypeToken
import com.gyf.csams.account.ui.AccountActivity
import com.gyf.csams.main.ui.MainActivity
import com.gyf.csams.module.UserVo
import com.gyf.lib.uikit.AbstractInitActivity
import com.gyf.lib.util.AccountApi
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.UserVo
import java.lang.reflect.Type
class InitActivity : AbstractInitActivity<UserVo>() {

@ -3,6 +3,7 @@ package com.gyf.csams.account.model
import android.app.Activity
import android.app.Application
import android.os.Build
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
@ -10,6 +11,10 @@ import com.google.gson.reflect.TypeToken
import com.gyf.csams.R
import com.gyf.csams.account.ui.AccountActivity
import com.gyf.csams.account.ui.AccountRoute
import com.gyf.csams.module.ClientType
import com.gyf.csams.module.UserLoginVo
import com.gyf.csams.module.UserRegVo
import com.gyf.csams.module.UserVo
import com.gyf.lib.model.AbstractLoginViewModel
import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.ValidStringForm
@ -23,9 +28,9 @@ import kotlinx.coroutines.launch
* 密码弹窗信息
*
* @property message
* @property userPasswordVo
* @property password
*/
data class DialogMessage(val message: String, val userPasswordVo: UserPasswordVo?)
data class DialogMessage(val message: String, val password: String)
/**
* 注册表单
@ -60,7 +65,10 @@ class AccountViewModel(application: Application) : AbstractLoginViewModel(applic
private var checkJob: Job? = null
//姓名
val name = object : ValidStringForm(formDesc = "姓名", textLength = 4) {
val name = object : ValidStringForm(
formDesc = "姓名",
textLength = application.resources.getInteger(R.integer.name_length)
) {
override fun check() {
_statusForm.value = FormStatus.Valid
checkForm()
@ -164,18 +172,18 @@ class AccountViewModel(application: Application) : AbstractLoginViewModel(applic
val url = Api.buildUrl(AccountApi.Register)
Logger.i("开始$regBtnDesc,请求接口:$url")
HttpClient.post(
url, HttpCallback<UserPasswordVo>(
url, HttpCallback<String>(
action = regBtnDesc,
onSuccess = {
_dialogMsg.postValue(
DialogMessage(
message = it.message,
userPasswordVo = it.body
password = it.body ?: throw IllegalArgumentException("无法获取生成密码")
)
)
},
onFail = onFail,
typeToken = object : TypeToken<ApiResponse<UserPasswordVo>>() {}.type
typeToken = object : TypeToken<ApiResponse<String>>() {}.type
),
jsonParam = UserRegVo(
studentId = id.formValue.value ?: throw IllegalArgumentException("学号为空"),
@ -198,4 +206,28 @@ class AccountViewModel(application: Application) : AbstractLoginViewModel(applic
}
}
class RefreshViewModel(application: Application) : AndroidViewModel(application) {
private val _refresh = MutableLiveData<Boolean>()
val refresh: LiveData<Boolean> = _refresh
fun refresh() {
viewModelScope.launch {
HttpClient.post(
url = Api.buildUrl(AccountApi.Refresh),
callback = HttpCallback<UserVo>(action = "刷新个人信息", onSuccess = { it ->
it.body.let {
if (it != null) {
TokenManager.update(it)
}
_refresh.postValue(it != null)
}
}, typeToken = object : TypeToken<ApiResponse<UserVo>>() {}.type),
jsonParam = OnlyToken(clientType = ClientType.Foreground)
)
}
}
}

@ -240,7 +240,7 @@ class AccountActivity : ComponentActivity() {
private fun RegisterDialog(accountViewModel: AccountViewModel = viewModel()) {
val dialogMsg: DialogMessage? by accountViewModel.dialogMsg.observeAsState(null)
val message = dialogMsg?.userPasswordVo?.password
val message = dialogMsg?.password
if (message?.isNotEmpty() == true) {
PasswordDialog(message = message)
}

@ -4,14 +4,16 @@ import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.gyf.csams.module.ActivityDetailVo
import com.gyf.csams.module.ActivityPhotoVo
import com.gyf.csams.module.BBSVo
import com.gyf.csams.module.UserInfoVo
import com.gyf.csams.uikit.AbstractComment
import com.gyf.csams.uikit.ActivityDetailMenu
import com.gyf.csams.uikit.TopMenuInterface
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.*
import kotlinx.coroutines.launch
import com.gyf.lib.util.NOT_IMPL_TIP
/**
* 活动详情菜单通用状态
@ -57,16 +59,7 @@ class ActivityInfoViewModel : ViewModel() {
}
private fun loadInfo() {
viewModelScope.launch {
_activityDetailVo.value = ActivityDetailVo(
activityName = randomChinese(4),
associationName = randomChinese(4),
activityTime = randomDateTime(),
activityLocation = randomChinese(3),
activityDesc = randomChinese(500)
)
}
TODO()
}
}

@ -7,6 +7,7 @@ import android.graphics.BitmapFactory
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.baidu.mapapi.map.*
import com.baidu.mapapi.model.LatLng
import com.baidu.mapapi.model.LatLngBounds
@ -17,44 +18,25 @@ import com.baidu.mapapi.search.poi.*
import com.baidu.mapapi.search.sug.SuggestionResult
import com.baidu.mapapi.search.sug.SuggestionSearch
import com.baidu.mapapi.search.sug.SuggestionSearchOption
import com.google.gson.reflect.TypeToken
import com.gyf.csams.BuildConfig
import com.gyf.csams.MyLocationListener
import com.gyf.csams.R
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.util.ContextUtil
import com.gyf.lib.util.DATETIME_FORMAT
import com.gyf.lib.util.NOT_IMPL_TIP
import com.gyf.csams.module.*
import com.gyf.lib.uikit.*
import com.gyf.lib.util.*
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.DateTimeUtil.DATE_FORMAT
import com.gyf.lib.util.DateTimeUtil.TIME_FORMAT
import com.gyf.lib.util.DateTimeUtil.dateFormat
import com.gyf.lib.util.DateTimeUtil.timeFormat
import com.gyf.lib.util.DateTimeUtil.toDate
import com.orhanobut.logger.Logger
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.*
class SizeForm(val application: Application) :
StringForm(formDesc = application.getString(R.string.activity_size), textLength = 2) {
val _formError = MutableLiveData<String>()
val formError: LiveData<String> = _formError
val maxActivitySize = application.resources.getInteger(R.integer.activity_size)
val rangeError = application.getString(R.string.activity_size_error, 1, maxActivitySize)
override fun onChange(value: String) {
if (value.length > textLength) {
_formError.value = "${formDesc}不能超过最大长度$textLength"
_formValue.value = value.slice(IntRange(0, textLength - 1))
} else if (value.matches(Regex("\\d+")) && value.toInt() !in 1..maxActivitySize) {
Logger.i("活动人数:${value}不合法")
_formError.value = rangeError
} else {
_formValue.value = value
_formError.value = ""
}
}
}
/**
* 申请活动数据状态管理
*
@ -65,20 +47,48 @@ class SizeForm(val application: Application) :
*/
class ApplyActViewModel(application: Application) : AndroidViewModel(application) {
val activityName =
StringForm(formDesc = application.getString(R.string.activity_name), textLength = 10)
val activityTime = StringForm(
AsyncStringForm(formDesc = application.getString(R.string.activity_name), textLength = 10)
val activityDate = AsyncStringForm(
formDesc = application.getString(R.string.activity_date),
textLength = DATE_FORMAT.length
)
val activityTime = AsyncStringForm(
formDesc = application.getString(R.string.activity_time),
textLength = DATETIME_FORMAT.length
textLength = TIME_FORMAT.length
)
val activityAddress =
StringForm(formDesc = application.getString(R.string.activity_address), textLength = 30)
AsyncStringForm(
formDesc = application.getString(R.string.activity_address),
textLength = 30
)
val activityDesc =
StringForm(formDesc = application.getString(R.string.activity_desc), textLength = 50)
AsyncStringForm(formDesc = application.getString(R.string.activity_desc), textLength = 50)
val activitySize = object :
AsyncStringForm(formDesc = application.getString(R.string.activity_size), textLength = 2) {
val maxActivitySize = application.resources.getInteger(R.integer.activity_size)
override fun check() {
_formValue.value?.let {
when {
it.matches(Regex("\\d+")) && it.toInt() !in 1..maxActivitySize -> _statusForm.value =
FormStatus.FormatError
else -> _statusForm.value = FormStatus.Valid
}
}
}
}
private val _checkInfo = MutableLiveData<ApiResponse<ActivityCheckVo>>()
val checkInfo: LiveData<ApiResponse<ActivityCheckVo>> = _checkInfo
val activitySize = SizeForm(application = application)
init {
read { }
}
val city =
object : StringForm(formDesc = application.getString(R.string.city), textLength = 4) {
object : ValidStringForm(formDesc = application.getString(R.string.city), textLength = 4) {
override val formPlaceholder = ""
}
@ -102,6 +112,7 @@ class ApplyActViewModel(application: Application) : AndroidViewModel(application
})
}
//TODO 抽离到单独的activity model?
lateinit var scaffoldModel: ScaffoldModel
private val _mapView = MutableLiveData<MapView>()
@ -128,7 +139,6 @@ class ApplyActViewModel(application: Application) : AndroidViewModel(application
_selectPoi.postValue(null)
}
fun searchPoiInCity() {
if (city.formValue.value?.isEmpty() == true ||
address.formValue.value?.isEmpty() == true
@ -164,13 +174,6 @@ class ApplyActViewModel(application: Application) : AndroidViewModel(application
val mapStatusUpdate = MapStatusUpdateFactory.newLatLng(latLng)
map.setMapStatus(mapStatusUpdate)
// 清除之前的
// clearData()
// 显示当前的
// if (!showSuggestMarker(latLng)) {
// searchPoiInCity()
// }
}
}
@ -377,7 +380,7 @@ class ApplyActViewModel(application: Application) : AndroidViewModel(application
mBaiduMap.addOverlay(markerOptions)
}
fun isLatlngEqual(latLng0: LatLng, latLng1: LatLng): Boolean {
private fun isLatlngEqual(latLng0: LatLng, latLng1: LatLng): Boolean {
return (latLng0.latitude == latLng1.latitude
&& latLng0.longitude == latLng1.longitude)
}
@ -559,7 +562,8 @@ class ApplyActViewModel(application: Application) : AndroidViewModel(application
clearData()
}
private fun destroy() {
override fun onCleared() {
super.onCleared()
mPoiSearch.destroy()
mSuggestionSearch.destroy()
@ -569,11 +573,82 @@ class ApplyActViewModel(application: Application) : AndroidViewModel(application
mBitmapDescWaterDrop?.recycle()
}
private fun clean() {
activityName.clean()
activityDate.clean()
activityTime.clean()
activityDesc.clean()
activitySize.clean()
activityAddress.clean()
}
/**
* TODO 提交申请
* 提交申请
*
*/
fun apply(callback: (message: String) -> Unit) {
callback(NOT_IMPL_TIP)
val activityName = activityName.formValue.value ?: throw IllegalArgumentException("活动名称为空")
val activityDate = activityDate.formValue.value ?: throw IllegalArgumentException("活动日期为空")
val activityTime = activityTime.formValue.value ?: throw IllegalArgumentException("活动时间为空")
val activityDesc = activityDesc.formValue.value ?: throw IllegalArgumentException("活动介绍为空")
val activitySize = activitySize.formValue.value ?: throw IllegalArgumentException("活动规模为空")
val activityAddress =
activityAddress.formValue.value ?: throw IllegalArgumentException("活动地点为空")
val associationId = (TokenManager.getOwnInfo() as? UserVo)?.associationVo?.associationId
?: throw IllegalArgumentException("社团id为空")
viewModelScope.launch {
HttpClient.post(
url = Api.buildUrl(ActivityApi.Register),
callback = HttpCallback<Boolean>(action = "提交活动申请书", onSuccess = {
if (it.body == true) {
clean()
}
callback(it.message)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type),
jsonParam = ActivityApplyVo(
associationId = associationId,
activityVo = ActivityVo(
activityName = activityName,
activityTime = "$activityDate $activityTime".toDate().time,
activityAddress = activityAddress,
activityDesc = activityDesc,
activitySize = activitySize.toInt()
),
activityId = _checkInfo.value?.body?.activityId,
token = TokenManager.getToken()
)
)
}
}
/**
* 查看审核进度
*
* @param callback
*/
private fun read(callback: (message: String) -> Unit) {
viewModelScope.launch {
HttpClient.post(url = Api.buildUrl(ActivityApi.Read),
HttpCallback<ActivityCheckVo>(action = "查看活动申请书审核进度",
onSuccess = { it ->
_checkInfo.postValue(it)
it.body?.activityVo?.let {
activityName.setValue(it.activityName)
val d = Date(it.activityTime)
activityDate.setValue(d.dateFormat())
activityTime.setValue(d.timeFormat())
activityAddress.setValue(it.activityAddress)
activityDesc.setValue(it.activityDesc)
activitySize.setValue(it.activitySize.toString())
}
}, typeToken = object : TypeToken<ApiResponse<ActivityCheckVo>>() {}.type
),
jsonParam = OnlyToken(clientType = ClientType.Foreground)
)
}
}
}

@ -22,15 +22,18 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.gyf.csams.R
import com.gyf.csams.activity.model.*
import com.gyf.csams.module.ActivityPhotoVo
import com.gyf.csams.module.BBSVo
import com.gyf.csams.module.UserInfoVo
import com.gyf.csams.uikit.*
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.NavBody
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.uikit.ShowSnackbar
import com.gyf.lib.util.ActivityPhotoVo
import com.gyf.lib.util.BBSVo
import com.gyf.lib.util.UserInfoVo
import com.gyf.lib.util.format
import com.gyf.lib.util.DateTimeUtil.datetimeFormat
import java.util.*
/**
* 活动详情
@ -140,10 +143,10 @@ class ActivityDetailActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceAround
) {
RectListItem(text = it.associationName)
RectListItem(text = it.activityName)
RectListItem(text = it.activityTime.format())
RectListItem(text = it.activityLocation)
RectListItem(text = it.associationVo.name)
RectListItem(text = it.activityVo.activityName)
RectListItem(text = Date(it.activityVo.activityTime).datetimeFormat())
RectListItem(text = it.activityVo.activityAddress)
}
}
@ -184,7 +187,10 @@ class ActivityDetailActivity : ComponentActivity() {
Column(modifier = modifier) {
val activityDetailVo by model.activityDetailVo.observeAsState()
activityDetailVo?.let {
DescCard(modifier = Modifier.weight(0.5F), content = it.activityDesc)
DescCard(
modifier = Modifier.weight(0.5F),
content = it.activityVo.activityDesc
)
}
}
}
@ -440,7 +446,7 @@ class ActivityDetailActivity : ComponentActivity() {
verticalArrangement = Arrangement.SpaceEvenly
) {
Text(text = vo.name)
Text(text = vo.createTime.format())
Text(text = vo.createTime.datetimeFormat())
}
}
Spacer(modifier = Modifier.weight(0.5F))

@ -13,16 +13,14 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
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.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
@ -33,19 +31,21 @@ import com.baidu.location.LocationClientOption
import com.baidu.mapapi.map.MapStatusUpdateFactory
import com.baidu.mapapi.search.sug.SuggestionResult
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.MaterialTimePicker.INPUT_MODE_KEYBOARD
import com.google.android.material.timepicker.TimeFormat
import com.gyf.csams.MainApplication
import com.gyf.csams.MyLocationListener
import com.gyf.csams.R
import com.gyf.csams.activity.model.ApplyActViewModel
import com.gyf.csams.module.CheckStatus
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.uikit.CheckTip
import com.gyf.csams.uikit.DescCard
import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.uikit.*
import com.gyf.lib.util.BottomButton
import com.gyf.lib.util.format
import com.gyf.lib.util.DateTimeUtil.dateFormat
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
import java.util.*
@ -87,7 +87,7 @@ class ApplyActActivity : AppCompatActivity() {
model.scaffoldModel = scaffoldModel
val showMap by model.showMap.observeAsState(false)
val location by MyLocationListener.location.observeAsState()
val checkInfo by model.checkInfo.observeAsState()
when {
showMap && location != null -> {
Box {
@ -133,19 +133,35 @@ class ApplyActActivity : AppCompatActivity() {
)
}
}
}
}
}
else -> {
Title(modifier = Modifier.weight(0.2F))
checkInfo?.body?.auditCheckVo.let {
if (it == null) {
Title(modifier = Modifier.weight(0.2F))
} else {
Title()
CheckTip(it, modifier = Modifier.weight(0.15F))
}
}
Row(
Modifier
.weight(0.1F)
.fillMaxWidth(), horizontalArrangement = Arrangement.Center
) {
BaseTextField(form = model.activityName)
BaseTextField(
form = model.activityName,
readOnly = isReadOnly(model = model)
)
}
Row(
Modifier
.weight(0.1F)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
ActivityDate()
}
Row(
Modifier
@ -162,13 +178,9 @@ class ApplyActActivity : AppCompatActivity() {
)
DescCard(
modifier = Modifier.weight(0.3F),
stringForm = model.activityDesc
stringForm = model.activityDesc,
readonly = isReadOnly(model = model)
)
//TODO 活动人数不合法警告
// val error by model.activitySize.formError.observeAsState()
// if (error?.isNotEmpty() == true) {
// scaffoldModel.update(message = error)
// }
Row(
Modifier
.weight(0.1F)
@ -177,12 +189,35 @@ class ApplyActActivity : AppCompatActivity() {
BaseTextField(
form = model.activitySize,
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
readOnly = isReadOnly(model = model)
)
}
Spacer(modifier = Modifier.weight(0.05F))
BottomButton(modifier = Modifier.fillMaxWidth()) {
model.apply { scaffoldModel.update(message = it) }
val activityName = model.activityName.statusForm.observeAsState()
val activityDate = model.activityDate.statusForm.observeAsState()
val activityTime = model.activityTime.statusForm.observeAsState()
val activityDesc = model.activityDesc.statusForm.observeAsState()
val activitySize = model.activitySize.statusForm.observeAsState()
BottomButton(
modifier = Modifier.fillMaxWidth(),
enabled = check(
activityName,
activityDate,
activityTime,
activityDesc,
activitySize
),
confirmDesc = if (checkInfo?.body != null) R.string.reg_again_btn else R.string.confirm_btn,
) {
model.apply {
scaffoldModel.update(message = it, actionLabel = "返回") {
onBackPressed()
}
}
}
}
}
@ -191,6 +226,23 @@ class ApplyActActivity : AppCompatActivity() {
}
}
@Composable
private fun isReadOnly(model: ApplyActViewModel): Boolean {
val checkInfo by model.checkInfo.observeAsState()
val flag = (checkInfo?.body?.let { it.auditCheckVo.checkStatus != CheckStatus.Finish })
Logger.d("flag=${flag}")
return flag == true
}
private fun check(vararg arrayOfStates: State<FormStatus?>): Boolean {
arrayOfStates.forEach {
if (it.value != FormStatus.Valid) {
return false
}
}
return true
}
@Composable
private fun SelectIcon(modifier: Modifier = Modifier) {
IconButton(modifier = modifier, onClick = {
@ -233,16 +285,6 @@ class ApplyActActivity : AppCompatActivity() {
BaseTextField(modifier = Modifier.weight(0.5F), form = model.address)
}
}
// Row(
// modifier = Modifier.fillMaxWidth(),
// horizontalArrangement = Arrangement.Center
// ) {
// OutlinedButton(onClick = {
// model.searchPoiInCity()
// }) {
// Text(text = stringResource(id = R.string.search_btn))
// }
// }
}
@ -363,11 +405,40 @@ class ApplyActActivity : AppCompatActivity() {
Box(modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(
text = stringResource(id = R.string.activity_application),
style = MaterialTheme.typography.h4
style = MaterialTheme.typography.h6
)
}
}
@Composable
private fun ActivityDate(
modifier: Modifier = Modifier,
model: ApplyActViewModel = viewModel()
) {
val onClick: () -> Unit = {
MaterialDatePicker.Builder.datePicker().build().apply {
show(supportFragmentManager, tag)
addOnPositiveButtonClickListener {
model.activityDate.onChange(Date(it).dateFormat())
}
}
}
BaseTextField(
modifier = modifier.clickable(onClick = onClick),
form = model.activityDate,
readOnly = true
) {
if (!isReadOnly(model = model)) {
IconButton(onClick = onClick) {
Icon(
painter = painterResource(id = R.drawable.ic_date),
contentDescription = null
)
}
}
}
}
/**
* 活动时间
*
@ -380,28 +451,39 @@ class ApplyActActivity : AppCompatActivity() {
model: ApplyActViewModel = viewModel()
) {
val scope = rememberCoroutineScope()
val onClick: () -> Unit = {
scope.launch {
MaterialTimePicker.Builder()
.setInputMode(INPUT_MODE_KEYBOARD)
.setTimeFormat(TimeFormat.CLOCK_24H)
.build()
.apply {
show(supportFragmentManager, tag)
addOnPositiveButtonClickListener {
model.activityTime.onChange(
"${
String.format(
"%02d",
hour
)
}:${String.format("%02d", minute)}"
)
}
}
}
}
BaseTextField(
modifier = modifier,
form = model.activityTime, readOnly = true
modifier = modifier.clickable(onClick = onClick),
form = model.activityTime,
readOnly = true
) {
IconButton(onClick = {
scope.launch {
val picker = MaterialDatePicker
.Builder
.datePicker()
.setSelection(Date().time)
.build()
picker.show(supportFragmentManager, picker.toString())
picker.addOnPositiveButtonClickListener {
model.activityTime.onChange(Date(it).format())
}
if (!isReadOnly(model = model)) {
IconButton(onClick = onClick) {
Icon(
painter = painterResource(id = R.drawable.ic_clock),
contentDescription = null
)
}
}) {
Icon(
painter = painterResource(id = R.drawable.ic_date),
contentDescription = null
)
}
}
}
@ -471,25 +553,27 @@ class ApplyActActivity : AppCompatActivity() {
}
BaseTextField(modifier = modifier, form = model.activityAddress, readOnly = true) {
IconButton(onClick = {
when (PackageManager.PERMISSION_GRANTED) {
checkSelfPermissions() -> {
// Some works that require permission
requestLocation()
model.openMap()
Logger.i("准备打开地图")
}
else -> {
// Asking for permission
Logger.i("询问权限")
launcher.launch(location_permissions)
if (!isReadOnly(model = model)) {
IconButton(onClick = {
when (PackageManager.PERMISSION_GRANTED) {
checkSelfPermissions() -> {
// Some works that require permission
requestLocation()
model.openMap()
Logger.i("准备打开地图")
}
else -> {
// Asking for permission
Logger.i("询问权限")
launcher.launch(location_permissions)
}
}
}) {
Icon(
painter = painterResource(id = R.drawable.ic_icon_location),
contentDescription = null
)
}
}) {
Icon(
painter = painterResource(id = R.drawable.ic_icon_location),
contentDescription = null
)
}
}
}

@ -6,11 +6,14 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.R
import com.gyf.csams.module.*
import com.gyf.csams.uikit.AssociationMenu
import com.gyf.csams.uikit.TopMenuInterface
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.util.*
import com.gyf.lib.util.ApiResponse
import kotlinx.coroutines.launch
@ -35,7 +38,7 @@ class AssociationViewModel : ViewModel(), TopMenuInterface<AssociationMenu> {
HttpCallback<AssociationMainVo>(action = "获取社团信息", onSuccess = {
it.body?.let { _associationVo.postValue(it) }
}, typeToken = object : TypeToken<ApiResponse<AssociationMainVo>>() {}.type),
jsonParam = ShowAssociationVo(id = id)
jsonParam = ShowAssociationVo(id = id, token = TokenManager.getToken())
)
}
}
@ -61,7 +64,8 @@ class AssociationViewModel : ViewModel(), TopMenuInterface<AssociationMenu> {
*
*/
class MemberViewModel(application: Application) : ScrollViewModel<UserInfoVo>(application) {
val name = StringForm(formDesc = "姓名关键字", 5)
val name =
StringForm(formDesc = "姓名关键字", application.resources.getInteger(R.integer.name_length))
override val initSize: Int = 10
@ -81,7 +85,11 @@ class MemberViewModel(application: Application) : ScrollViewModel<UserInfoVo>(ap
},
typeToken = object : TypeToken<ApiResponse<MutableList<UserInfoVo>>>() {}.type
),
jsonParam = QueryAssociationMembers(id = id, name = name.formValue.value)
jsonParam = QueryAssociationMembers(
id = id,
name = name.formValue.value,
token = TokenManager.getToken()
)
)
}
}

@ -3,12 +3,12 @@ package com.gyf.csams.association.model
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.gyf.csams.module.ChoiceQuestionVo
import com.gyf.csams.module.Exam
import com.gyf.csams.module.ExamType
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.util.*
import kotlinx.coroutines.launch
import kotlin.random.Random
import com.gyf.lib.util.NOT_IMPL_TIP
/**
* 题库界面类型
@ -64,6 +64,10 @@ class ExamViewModel(application: Application) : ScrollViewModel<Exam>(applicatio
load()
}
fun createQuestion(): StringForm {
return StringForm(formDesc = "问题", textLength = QUESTION_TEXT_LENGTH)
}
/**
* 切换题型
*
@ -81,21 +85,7 @@ class ExamViewModel(application: Application) : ScrollViewModel<Exam>(applicatio
* @return
*/
private fun createExam(type: ExamType): Exam {
val question = StringForm(formDesc = "问题", textLength = QUESTION_TEXT_LENGTH)
return when (type) {
ExamType.CQ -> ChoiceQuestionVo(
answers = ('A'..'D').map {
StringForm(
formDesc = "选项",
textLength = ANSWER_TEXT_LENGTH,
value = "选项$it"
)
},
rightAnswer = 0,
question = question
)
ExamType.OQ -> OpenQuestionsVo(question = question)
}
TODO("创建题目")
}
@ -140,42 +130,7 @@ class ExamViewModel(application: Application) : ScrollViewModel<Exam>(applicatio
*
*/
fun load() {
viewModelScope.launch {
_data.value?.apply {
repeat(initSize) {
if (Random.nextBoolean()) {
add(
OpenQuestionsVo(
question = StringForm(
formDesc = "问题",
textLength = QUESTION_TEXT_LENGTH,
value = "这是一道开放题:$size"
)
)
)
} else {
add(
ChoiceQuestionVo(
question = StringForm(
formDesc = "问题",
textLength = QUESTION_TEXT_LENGTH,
value = "这是一道选择题:$size"
),
answers = ('A'..'D').map {
StringForm(
formDesc = "选项",
textLength = ANSWER_TEXT_LENGTH,
value = "选项$it"
)
},
rightAnswer = Random.nextInt(ANSWER_SIZE)
)
)
}
}
}
}
TODO("加载题目")
}
/**

@ -12,9 +12,12 @@ import coil.imageLoader
import coil.request.ImageRequest
import com.google.gson.reflect.TypeToken
import com.gyf.csams.MainApplication
import com.gyf.csams.module.AssociationCheckVo
import com.gyf.csams.module.AssociationRegVo
import com.gyf.csams.module.ClientType
import com.gyf.lib.BuildConfig
import com.gyf.lib.uikit.AsyncStringForm
import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
@ -22,19 +25,13 @@ import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
class TestStringForm(formDesc: String, textLength: Int) : ValidStringForm(formDesc, textLength) {
fun setValue(value: String) {
_formValue.postValue(value)
}
}
class RegAssociationViewModel(application: Application) : AndroidViewModel(application) {
val frameDesc = "社团注册资料"
val name = TestStringForm(formDesc = "社团名称", textLength = 5)
val desc = TestStringForm(formDesc = "社团简介", textLength = 30)
val name = AsyncStringForm(formDesc = "社团名称", textLength = 5)
val desc = AsyncStringForm(formDesc = "社团简介", textLength = 30)
private val _picture = MutableLiveData<Uri?>()
val picture: LiveData<Uri?> = _picture
@ -42,8 +39,8 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
private val _fileId = MutableLiveData<Int>()
val fileId: LiveData<Int> = _fileId
private val _checkInfo = MutableLiveData<AssociationCheckVo?>()
val checkInfo: LiveData<AssociationCheckVo?> = _checkInfo
private val _checkInfo = MutableLiveData<ApiResponse<AssociationCheckVo>>()
val checkInfo: LiveData<ApiResponse<AssociationCheckVo>> = _checkInfo
val picturePlaceHolder = "请上传图片"
@ -74,17 +71,18 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
HttpClient.post(
Api.buildUrl(AssociationApi.Read),
HttpCallback<AssociationCheckVo>(action = "加载历史注册资料", onSuccess = { it ->
_checkInfo.postValue(it)
it.body?.let { it ->
name.setValue(it.name)
desc.setValue(it.desc)
_checkInfo.postValue(it)
name.setValue(it.associationVo.name)
desc.setValue(it.associationVo.desc)
val context = getApplication<Application>()
val request = ImageRequest.Builder(context)
.data("${BuildConfig.SERVER_ADDRESS}/${it.logo}")
.data("${BuildConfig.SERVER_ADDRESS}/${it.associationVo.logo}")
.target(
onSuccess = { result ->
it.logo.split("/").apply {
it.associationVo.logo.split("/").apply {
File.createTempFile(last(), null, context.cacheDir).apply {
Logger.d("文件路径:${absolutePath}")
FileOutputStream(this).use {
@ -146,6 +144,13 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
}
}
private fun clean() {
name.clean()
desc.clean()
_picture.postValue(null)
}
/**
*
*
@ -164,18 +169,18 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
HttpCallback<Boolean>("注册社团", onSuccess = {
Logger.i(it.message)
callback(it.message)
name.clean()
desc.clean()
_picture.postValue(null)
if (it.body == true) {
clean()
}
}, onFail = {
Logger.e(it)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type),
jsonParam =
AssociationRegVo(
jsonParam = AssociationRegVo(
name = nameValue,
desc = descValue,
fileId = fileId,
id = checkInfo.value?.id
associationId = _checkInfo.value?.body?.associationVo?.associationId,
token = TokenManager.getToken()
)
)
}

@ -30,9 +30,10 @@ import com.gyf.csams.R
import com.gyf.csams.activity.ui.ActivityDetailActivity
import com.gyf.csams.activity.ui.ApplyActActivity
import com.gyf.csams.association.model.*
import com.gyf.csams.module.HistoryActVo
import com.gyf.csams.uikit.*
import com.gyf.lib.uikit.*
import com.gyf.lib.util.HistoryActVo
import com.gyf.lib.util.TokenManager
/**
@ -73,76 +74,90 @@ class AssociationActivity : ComponentActivity() {
onDismissRequest = { },
properties = PopupProperties()
) {
DropdownMenuItem(onClick = {
startActivity(
Intent(
this@AssociationActivity,
ApplyActActivity::class.java
)
)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = getString(R.string.apply_act_menu))
Icon(
painter = painterResource(id = R.drawable.ic_add_fill),
contentDescription = null
)
}
}
DropdownMenuItem(onClick = {
intent.apply {
putExtra(
ExamActivityType::name.name,
ExamActivityType.SET_EXAM
)
}
startActivity(intent)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = getString(R.string.set_exam_menu))
Icon(
painter = painterResource(id = R.drawable.ic_editor),
contentDescription = null
)
}
}
DropdownMenuItem(onClick = {
startActivity(
Intent(
this@AssociationActivity,
ReNameActivity::class.java
)
)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = getString(R.string.rename_menu))
Icon(
painter = painterResource(id = R.drawable.ic_exchange_rate),
contentDescription = null
)
}
}
DropdownMenuItem(onClick = {
intent.apply {
putExtra(
ExamActivityType::name.name,
ExamActivityType.JOIN_Association
)
when {
TokenManager.getUserInfo()?.associationVo?.associationId == associationId && TokenManager.getUserInfo()?.isHead == true -> {
DropdownMenuItem(onClick = {
startActivity(
Intent(
this@AssociationActivity,
ApplyActActivity::class.java
).apply {
putExtra(
AssociationActivity::javaClass.name,
associationId
)
}
)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = getString(R.string.apply_act_menu))
Icon(
painter = painterResource(id = R.drawable.ic_add_fill),
contentDescription = null
)
}
}
DropdownMenuItem(onClick = {
intent.apply {
putExtra(
ExamActivityType::name.name,
ExamActivityType.SET_EXAM
)
}
startActivity(intent)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = getString(R.string.set_exam_menu))
Icon(
painter = painterResource(id = R.drawable.ic_editor),
contentDescription = null
)
}
}
DropdownMenuItem(onClick = {
startActivity(
Intent(
this@AssociationActivity,
ReNameActivity::class.java
)
)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = getString(R.string.rename_menu))
Icon(
painter = painterResource(id = R.drawable.ic_exchange_rate),
contentDescription = null
)
}
}
}
startActivity(intent)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = getString(R.string.join_association))
Icon(
painter = painterResource(id = R.drawable.ic_add_account),
contentDescription = null
)
TokenManager.getUserInfo()?.associationVo == null -> DropdownMenuItem(
onClick = {
intent.apply {
putExtra(
ExamActivityType::name.name,
ExamActivityType.JOIN_Association
)
}
startActivity(intent)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = getString(R.string.join_association))
Icon(
painter = painterResource(id = R.drawable.ic_add_account),
contentDescription = null
)
}
}
}
DropdownMenuItem(onClick = {
model.close()
}) {

@ -21,6 +21,9 @@ import com.gyf.csams.R
import com.gyf.csams.association.model.ANSWER_SIZE
import com.gyf.csams.association.model.ExamActivityType
import com.gyf.csams.association.model.ExamViewModel
import com.gyf.csams.module.ChoiceQuestionVo
import com.gyf.csams.module.Exam
import com.gyf.csams.module.OpenQuestionsVo
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.BaseTextField
@ -28,9 +31,6 @@ 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.ChoiceQuestionVo
import com.gyf.lib.util.Exam
import com.gyf.lib.util.OpenQuestionsVo
/**
@ -173,9 +173,15 @@ class ExamActivity : ComponentActivity() {
* @param exam
*/
@Composable
private fun Question(modifier: Modifier = Modifier, exam: Exam) {
private fun Question(
modifier: Modifier = Modifier,
exam: Exam,
model: ExamViewModel = viewModel()
) {
/*问题**/
val s = model.createQuestion()
BaseTextField(
form = exam.question,
form = s,
modifier = modifier
.fillMaxSize()
.background(color = MaterialTheme.colors.background)
@ -202,7 +208,7 @@ class ExamActivity : ComponentActivity() {
) {
IconButton(onClick = {
if (isAdd) {
if ((newExam?.question?.formValue?.value ?: "").isNotEmpty()) {
if ((newExam?.question ?: "").isNotEmpty()) {
scaffoldModel.update(
message = model.addTip,
actionLabel = model.actionLabel
@ -321,7 +327,8 @@ class ExamActivity : ComponentActivity() {
val isRightAnswer =
choiceQuestionVo.rightAnswer == answerIndex
RadioButton(selected = isRightAnswer, onClick = click)
BaseTextField(form = it)
val c = model.createQuestion()
BaseTextField(form = c)
}
}
}

@ -1,7 +1,6 @@
package com.gyf.csams.association.ui
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
@ -24,23 +23,19 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.coil.rememberCoilPainter
import com.gyf.csams.R
import com.gyf.csams.association.model.RegAssociationViewModel
import com.gyf.csams.module.CheckStatus
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.uikit.CheckTip
import com.gyf.lib.uikit.*
import com.gyf.lib.util.BottomButton
import com.gyf.lib.util.CheckStatus
import com.gyf.lib.util.format
import com.orhanobut.logger.Logger
import java.util.*
/**
@ -51,97 +46,70 @@ class RegAssociationActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Body {
MainColumnFrame(background = {
Background(
BackgroundImage.RegAssociation,
alpha = 0.5F
)
}) {
Spacer(
modifier = Modifier
.weight(0.1F)
)
Title()
Tip()
Name()
Desc(
modifier = Modifier
.weight(0.1F)
.fillMaxWidth()
)
Spacer(modifier = Modifier.weight(0.05F))
Logo(
modifier = Modifier
.weight(0.2F)
.fillMaxWidth()
)
Spacer(modifier = Modifier.weight(0.05F))
val model: RegAssociationViewModel = viewModel()
val checkInfo by model.checkInfo.observeAsState()
checkInfo?.body.let { it ->
if (it?.auditCheckVo?.checkStatus == CheckStatus.Finish && it.auditCheckVo.result) {
setResult(RESULT_OK, Intent().apply {
putExtra(RegAssociationActivity::class.java.name, CheckStatus.Finish.name)
})
onBackPressed()
} else {
Body {
MainColumnFrame(background = {
Background(
BackgroundImage.RegAssociation,
alpha = 0.5F
)
}) {
Spacer(
modifier = Modifier
.weight(0.1F)
)
Title()
it?.auditCheckVo?.let { CheckTip(it) }
Name()
Desc(
modifier = Modifier
.weight(0.1F)
.fillMaxWidth()
)
Spacer(modifier = Modifier.weight(0.05F))
Logo(
modifier = Modifier
.weight(0.2F)
.fillMaxWidth()
)
Spacer(modifier = Modifier.weight(0.05F))
val model: RegAssociationViewModel = viewModel()
val scaffoldModel: ScaffoldModel = viewModel()
val name by model.name.statusForm.observeAsState()
val desc by model.name.statusForm.observeAsState()
val fileId by model.fileId.observeAsState()
val checkInfo by model.checkInfo.observeAsState()
BottomButton(
modifier = Modifier.fillMaxWidth(),
enabled = name == FormStatus.Valid && desc == FormStatus.Valid && fileId != null,
confirmDesc = if (checkInfo != null) R.string.reg_again_btn else R.string.reg_btn,
) {
model.register {
scaffoldModel.update(message = it, actionLabel = "返回") {
finish()
}
}
}
Spacer(modifier = Modifier.weight(0.05F))
}
}
val scaffoldModel: ScaffoldModel = viewModel()
val name by model.name.statusForm.observeAsState()
val desc by model.name.statusForm.observeAsState()
val fileId by model.fileId.observeAsState()
}
}
BottomButton(
modifier = Modifier.fillMaxWidth(),
enabled = name == FormStatus.Valid && desc == FormStatus.Valid && fileId != null,
confirmDesc = if (it != null) R.string.reg_again_btn else R.string.reg_btn,
@Composable
private fun Tip(model: RegAssociationViewModel = viewModel()) {
val checkInfo by model.checkInfo.observeAsState()
checkInfo?.let {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
when (val status = it.checkStatus) {
CheckStatus.Finish -> Text(buildAnnotatedString {
withStyle(style = MaterialTheme.typography.h5.toParagraphStyle()) {
append("您于${Date(it.applyTime).format()}提交的${model.frameDesc}审核不通过原因如下:\n")
}
withStyle(
style = MaterialTheme.typography.h6.copy(color = MaterialTheme.colors.error)
.toParagraphStyle()
) {
append("初审意见:${it.firstCause}\n")
it.lastCause?.let {
append("复审意见:${it}")
) {
model.register {
scaffoldModel.update(message = it, actionLabel = "返回") {
finish()
}
}
}
Spacer(modifier = Modifier.weight(0.05F))
}
})
else -> {
Text(
text = status.desc, style =
MaterialTheme.typography.h5.copy(color = MaterialTheme.colors.primary)
)
}
}
}
}
}
@Preview
@Composable
private fun TestPreview9() {
Text(text = "123", color = MaterialTheme.colors.error)
}
/**
@ -164,7 +132,7 @@ class RegAssociationActivity : ComponentActivity() {
val resultLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
when (it.resultCode) {
Activity.RESULT_OK -> {
RESULT_OK -> {
it.data?.data?.apply {
Logger.i("uri=$this")
@ -227,8 +195,7 @@ class RegAssociationActivity : ComponentActivity() {
Image(
painter = logo, contentDescription = null
)
val checkInfo by model.checkInfo.observeAsState()
if (checkInfo == null || checkInfo?.checkStatus == CheckStatus.Finish) {
if (!isReadOnly(model = model)) {
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceEvenly
@ -285,16 +252,23 @@ class RegAssociationActivity : ComponentActivity() {
}
}
@Composable
private fun isReadOnly(model: RegAssociationViewModel): Boolean {
val checkInfo by model.checkInfo.observeAsState()
val flag = (checkInfo?.body?.let { it.auditCheckVo.checkStatus != CheckStatus.Finish })
Logger.d("flag=${flag}")
return flag == true
}
/**
* 社团名称
* @param model
*/
@Composable
private fun Name(model: RegAssociationViewModel = viewModel()) {
val checkInfo by model.checkInfo.observeAsState()
BaseTextField(
form = model.name, singeLine = true, modifier = Modifier.fillMaxWidth(),
readOnly = checkInfo?.checkStatus != CheckStatus.Finish
readOnly = isReadOnly(model = model)
)
}
@ -304,10 +278,9 @@ class RegAssociationActivity : ComponentActivity() {
*/
@Composable
private fun Desc(model: RegAssociationViewModel = viewModel(), modifier: Modifier) {
val checkInfo by model.checkInfo.observeAsState()
BaseTextField(
form = model.desc, modifier = modifier,
readOnly = checkInfo?.checkStatus != CheckStatus.Finish
readOnly = isReadOnly(model = model)
)
}

@ -3,12 +3,14 @@ package com.gyf.csams.main.model
import android.app.Application
import androidx.lifecycle.*
import com.google.gson.reflect.TypeToken
import com.gyf.csams.module.*
import com.gyf.csams.uikit.AbstractComment
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.*
import com.gyf.lib.util.ApiResponse
import com.orhanobut.logger.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@ -42,6 +44,7 @@ class NotificationViewModel(application: Application) : AndroidViewModel(applica
jsonParam = NotificationDto(
receiverId = TokenManager.getToken().id,
receiverClient = ClientType.Foreground,
token = TokenManager.getToken()
)
)
}
@ -94,7 +97,8 @@ class MarqueeViewModel : AbstractComment() {
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type
),
jsonParam = LeaveMessageVo(
message = newContent.formValue.value ?: ""
message = newContent.formValue.value ?: "",
token = TokenManager.getToken()
)
)
}
@ -180,7 +184,8 @@ class AssociationListViewModel(application: Application) :
),
jsonParam = SearchAssociationVo(
name = name.formValue.value ?: "",
desc = desc.formValue.value ?: ""
desc = desc.formValue.value ?: "",
token = TokenManager.getToken()
)
)
}
@ -199,14 +204,10 @@ class CenterViewModel : ViewModel() {
val myCollectActivity = "我收藏的社团活动"
init {
load()
}
private fun load() {
viewModelScope.launch {
TODO()
}
}
}

@ -2,7 +2,9 @@ package com.gyf.csams.main.ui
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
@ -26,11 +28,16 @@ import androidx.navigation.compose.composable
import com.google.accompanist.coil.rememberCoilPainter
import com.gyf.csams.R
import com.gyf.csams.account.model.AccountViewModel
import com.gyf.csams.account.model.RefreshViewModel
import com.gyf.csams.activity.ui.ActivityDetailActivity
import com.gyf.csams.association.ui.AssociationActivity
import com.gyf.csams.association.ui.RegAssociationActivity
import com.gyf.csams.main.model.*
import com.gyf.csams.message.ui.MessageActivity
import com.gyf.csams.message.ui.SysMessageActivity
import com.gyf.csams.module.AssociationVo
import com.gyf.csams.module.CheckStatus
import com.gyf.csams.module.ClientType
import com.gyf.csams.module.UserVo
import com.gyf.csams.uikit.*
import com.gyf.lib.service.BaseActivity
import com.gyf.lib.uikit.*
@ -64,8 +71,13 @@ class MainActivity : BaseActivity() {
imageViewModel.cancel()
}
composable(MainMenu.Center.name) {
Center(navController = nav)
ShowSnackbar(scaffoldState = scaffoldState)
val refresh: RefreshViewModel = viewModel()
refresh.refresh()
val load by refresh.refresh.observeAsState()
load?.let {
Center(navController = nav)
ShowSnackbar(scaffoldState = scaffoldState)
}
imageViewModel.cancel()
}
}
@ -105,12 +117,21 @@ class MainActivity : BaseActivity() {
modifier = Modifier.weight(0.7F),
verticalArrangement = Arrangement.SpaceEvenly
) {
CenterMenuItem(text = model.myAssociationDesc) {
startActivity(Intent(this@MainActivity, AssociationActivity::class.java))
(TokenManager.getOwnInfo() as? UserVo)?.associationVo?.associationId?.let {
CenterMenuItem(text = model.myAssociationDesc) {
startActivity(
Intent(
this@MainActivity,
AssociationActivity::class.java
).apply {
putExtra(AssociationActivity::javaClass.name, it)
})
}
CenterMenuItem(text = model.myJoinActivity)
CenterMenuItem(text = model.myLikeActivity)
CenterMenuItem(text = model.myCollectActivity)
}
CenterMenuItem(text = model.myJoinActivity)
CenterMenuItem(text = model.myLikeActivity)
CenterMenuItem(text = model.myCollectActivity)
CenterMenuItem(text = "退出登录") {
accountViewModel.logout(this@MainActivity) {
scaffoldModel.update(message = it)
@ -191,7 +212,7 @@ class MainActivity : BaseActivity() {
val associationVo: AssociationVo? =
(TokenManager.getOwnInfo() as? UserVo)?.associationVo
if (associationVo == null) {
RegisterAssociation()
RegisterAssociation(navController = navController)
}
AssociationSearch()
AssociationListBody()
@ -203,7 +224,26 @@ class MainActivity : BaseActivity() {
*
*/
@Composable
private fun RegisterAssociation() {
private fun RegisterAssociation(
model: AssociationListViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel(),
navController: NavHostController
) {
val launch =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { it ->
when (it.resultCode) {
RESULT_OK -> {
it.data?.getStringExtra(RegAssociationActivity::class.java.name)?.let {
if (it == CheckStatus.Finish.name) {
// model.load {
// scaffoldModel.update(message = "社团审核通过",actionLabel = "关闭提示")
// }
navController.navigate(MainMenu.List.name)
}
}
}
}
}
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
@ -211,7 +251,7 @@ class MainActivity : BaseActivity() {
.padding(10.dp)
) {
IconButton(onClick = {
startActivity(Intent(this@MainActivity, RegAssociationActivity::class.java))
launch.launch(Intent(this@MainActivity, RegAssociationActivity::class.java))
}) {
Icon(
painter = painterResource(id = R.drawable.ic_add_fill),
@ -228,8 +268,7 @@ class MainActivity : BaseActivity() {
*/
@Composable
private fun AssociationListBody(
model: AssociationListViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel()
model: AssociationListViewModel = viewModel()
) {
val associationListList: MutableList<AssociationVo>? by model.data.observeAsState()
val listState = rememberLazyListState()
@ -279,7 +318,7 @@ class MainActivity : BaseActivity() {
private fun Association(associationVo: AssociationVo) {
Card(modifier = Modifier.clickable(onClick = {
val intent = Intent(this, AssociationActivity::class.java)
intent.putExtra(AssociationActivity::javaClass.name, associationVo.id)
intent.putExtra(AssociationActivity::javaClass.name, associationVo.associationId)
startActivity(intent)
})) {
val backgroundImage = rememberCoilPainter(request = R.drawable.association_list_border)
@ -362,7 +401,7 @@ class MainActivity : BaseActivity() {
.padding(10.dp)
) {
IconButton(onClick = {
startActivity(Intent(this@MainActivity, MessageActivity::class.java))
startActivity(Intent(this@MainActivity, SysMessageActivity::class.java))
}) {
Icon(
painter = painterResource(id = if (it > 0) R.drawable.ic_notice else R.drawable.ic_notification),

@ -1,8 +1,9 @@
package com.gyf.csams.message.model
import android.app.Application
import com.gyf.csams.module.ClientType
import com.gyf.lib.model.SysMessageViewModel
import com.gyf.lib.util.ClientType
class ForegroundViewModel(application: Application) : SysMessageViewModel(application) {
override fun clientType(): ClientType {

@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.message.model.ForegroundViewModel
import com.gyf.csams.message.model.MessageType
import com.gyf.csams.module.NotificationVo
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.uikit.TextTopAppBar
@ -28,8 +29,8 @@ import com.gyf.lib.model.JoinContent
import com.gyf.lib.model.RenameContent
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.util.NotificationVo
import com.gyf.lib.util.format
import com.gyf.lib.util.DateTimeUtil.datetimeFormat
import java.util.*
/**
@ -98,7 +99,7 @@ class SysMessageActivity : ComponentActivity() {
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
Text(text = Date(content.createTime).format())
Text(text = Date(content.createTime).datetimeFormat())
}
}
}

@ -23,7 +23,9 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LiveData
@ -35,8 +37,12 @@ import com.google.accompanist.coil.rememberCoilPainter
import com.gyf.csams.MainApplication
import com.gyf.csams.R
import com.gyf.csams.main.model.MarqueeViewModel
import com.gyf.csams.module.AuditCheckVo
import com.gyf.csams.module.CheckStatus
import com.gyf.lib.uikit.*
import com.gyf.lib.util.DateTimeUtil.datetimeFormat
import com.orhanobut.logger.Logger
import java.util.*
/**
@ -484,7 +490,12 @@ fun Poster(modifier: Modifier = Modifier, imageBitmap: ImageBitmap? = null) {
*
*/
@Composable
fun DescCard(modifier: Modifier, content: String? = null, stringForm: StringForm? = null) {
fun DescCard(
modifier: Modifier,
content: String? = null,
stringForm: StringForm? = null,
readonly: Boolean = false
) {
Card(
modifier = modifier,
backgroundColor = Color.Transparent
@ -504,7 +515,11 @@ fun DescCard(modifier: Modifier, content: String? = null, stringForm: StringForm
) {
Spacer(modifier = Modifier.weight(0.15F))
if (stringForm != null) {
BaseTextField(modifier = Modifier.weight(0.65F), form = stringForm)
BaseTextField(
modifier = Modifier.weight(0.65F),
form = stringForm,
readOnly = readonly
)
} else {
Text(
modifier = Modifier.weight(0.65F),
@ -520,10 +535,37 @@ fun DescCard(modifier: Modifier, content: String? = null, stringForm: StringForm
}
}
//@Preview
/**
* 资料审核提示
*
* @param it
*/
@Composable
fun AnimationTextPreview() {
AnimationText(text = "6666")
fun CheckTip(it: AuditCheckVo, modifier: Modifier = Modifier) {
Row(modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
when (val status = it.checkStatus) {
CheckStatus.Finish -> Text(buildAnnotatedString {
withStyle(style = MaterialTheme.typography.h5.toParagraphStyle()) {
append("您于${Date(it.applyTime).datetimeFormat()}提交的资料审核不通过原因如下:")
}
withStyle(
style = MaterialTheme.typography.h6.copy(color = MaterialTheme.colors.error)
.toParagraphStyle()
) {
append("初审意见:${it.firstCause}\n")
it.lastCause?.let {
append("复审意见:${it}")
}
}
})
else -> {
Text(
text = status.desc, style =
MaterialTheme.typography.h5.copy(color = MaterialTheme.colors.primary)
)
}
}
}
}
//@Preview

@ -3,13 +3,12 @@ package com.gyf.csams.util
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import com.gyf.csams.association.model.ANSWER_SIZE
import com.gyf.csams.association.model.ANSWER_TEXT_LENGTH
import com.gyf.csams.association.model.QUESTION_TEXT_LENGTH
import com.gyf.csams.module.ChoiceQuestionVo
import com.gyf.csams.module.Exam
import com.gyf.csams.module.ExamType
import com.gyf.csams.module.OpenQuestionsVo
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.util.ChoiceQuestionVo
import com.gyf.lib.util.Exam
import com.gyf.lib.util.ExamType
import com.gyf.lib.util.OpenQuestionsVo
import java.lang.reflect.Type
class OpenQuestionsVoSerializer : JsonSerializer<OpenQuestionsVo> {
@ -21,7 +20,7 @@ class OpenQuestionsVoSerializer : JsonSerializer<OpenQuestionsVo> {
val gson = Gson()
val c = JsonObject()
c.add("examType", gson.toJsonTree(vo?.examType?.name))
c.add("question", gson.toJsonTree(vo?.question?.formValue?.value))
// c.add("question", gson.toJsonTree(vo?.question?.formValue?.value))
return c
}
}
@ -37,7 +36,8 @@ class OpenQuestionsVoDeserializer : JsonDeserializer<OpenQuestionsVo> {
val question = StringForm(formDesc = "问题", textLength = QUESTION_TEXT_LENGTH)
if (value != null) {
question.onChange(value)
return OpenQuestionsVo(question = question)
TODO()
// return OpenQuestionsVo(question = question)
} else {
throw NullPointerException("问题无法解析!!!!")
}
@ -53,7 +53,7 @@ class ChoiceQuestionVoSerializer : JsonSerializer<ChoiceQuestionVo> {
val gson = Gson()
val c = JsonObject()
c.add("examType", gson.toJsonTree(vo?.examType?.name))
c.add("question", gson.toJsonTree(vo?.question?.formValue?.value))
// c.add("question", gson.toJsonTree(vo?.question?.formValue?.value))
c.add("answers", gson.toJsonTree(vo?.answers))
c.add("rightAnswer", gson.toJsonTree(vo?.rightAnswer))
return c
@ -78,17 +78,18 @@ class ChoiceQuestionVoDeserializer : JsonDeserializer<ChoiceQuestionVo> {
throw IllegalArgumentException("选项数量!=$QUESTION_TEXT_LENGTH")
}
val rightAnswer = root.get("rightAnswer").asInt
return ChoiceQuestionVo(
question = question,
answers = answers.map {
StringForm(
formDesc = "选项",
textLength = ANSWER_TEXT_LENGTH,
value = it
)
},
rightAnswer = rightAnswer
)
TODO()
// return ChoiceQuestionVo(
// question = question,
// answers = answers.map {
// StringForm(
// formDesc = "选项",
// textLength = ANSWER_TEXT_LENGTH,
// value = it
// )
// },
// rightAnswer = rightAnswer
// )
} else {
throw NullPointerException("问题无法解析!!!!")
}

@ -2,8 +2,9 @@ package com.gyf.csams.util
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.gyf.csams.module.Exam
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.Exam
import com.gyf.lib.util.HttpCallback
import java.lang.reflect.Type

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M512,149.33c200.3,0 362.67,162.37 362.67,362.67s-162.37,362.67 -362.67,362.67S149.33,712.3 149.33,512 311.7,149.33 512,149.33zM512,213.33c-164.95,0 -298.67,133.72 -298.67,298.67s133.72,298.67 298.67,298.67 298.67,-133.72 298.67,-298.67 -133.72,-298.67 -298.67,-298.67zM544,291.82v201.13h140.76v64L512,556.95a32,32 0,0 1,-32 -32L480,291.84h64z" />
</vector>

@ -50,8 +50,7 @@ class ExampleUnitTest {
@Test
fun testLib() {
println(0 in 1..2)
println(1 in 1..2)
}
}

@ -51,6 +51,8 @@ android {
}
dependencies {
api(project(":module"))
implementation("androidx.test.ext:junit-ktx:1.1.2")
//生命周期组件版本
val lifecycle_version = "2.3.1"
//https://developer.android.com/topic/libraries/architecture/workmanager/basics?hl=zh-cn

@ -2,7 +2,7 @@ package com.gyf.lib
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

@ -8,6 +8,9 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.module.ClientType
import com.gyf.csams.module.ManagerVo
import com.gyf.csams.module.UserVo
import com.gyf.lib.R
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.*
@ -66,8 +69,14 @@ abstract class AbstractLoginViewModel(application: Application) : AndroidViewMod
it.body?.let {
val db = AppDatabase.getInstance(context)
viewModelScope.launch {
TokenManager.init(it)
db?.tokenDao()?.save(token = it.token)
TokenManager.update(it)
db?.tokenDao()?.save(
clientToken = ClientToken(
id = it.token.id,
token = it.token.token,
createTime = it.token.createTime
)
)
}.invokeOnCompletion {
_finishLogin.postValue(true)
}
@ -85,8 +94,14 @@ abstract class AbstractLoginViewModel(application: Application) : AndroidViewMod
it.body?.let {
val db = AppDatabase.getInstance(context)
viewModelScope.launch {
TokenManager.init(it)
db?.tokenDao()?.save(token = it.token)
TokenManager.update(it)
db?.tokenDao()?.save(
clientToken = ClientToken(
id = it.token.id,
token = it.token.token,
createTime = it.token.createTime
)
)
}.invokeOnCompletion {
_finishLogin.postValue(true)
}

@ -6,6 +6,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.module.OwnInfoVo
import com.gyf.csams.module.Token
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
@ -50,7 +52,7 @@ class InitViewModel(application: Application) : AndroidViewModel(application) {
val db = AppDatabase.getInstance(context)
val tokenList = db?.tokenDao()?.queryAll()
if (tokenList != null && tokenList.size == 1) {
val currentToken: Token = tokenList[0]
val currentClientToken: Token = tokenList[0]
val url = Api.buildUrl(api)
val action = "校验token"
Logger.i("${action}api=$url")
@ -61,7 +63,7 @@ class InitViewModel(application: Application) : AndroidViewModel(application) {
onSuccess = { it ->
it.body?.let {
Logger.i("token校验结果:${it}")
TokenManager.init(it)
TokenManager.update(it)
onSuccess()
}
@ -70,7 +72,7 @@ class InitViewModel(application: Application) : AndroidViewModel(application) {
Logger.e(it)
onFail()
}, typeToken = typeToken),
jsonParam = currentToken
jsonParam = currentClientToken
)
} else if (tokenList != null && tokenList.size > 1) {

@ -5,6 +5,9 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.module.ClientType
import com.gyf.csams.module.NotificationDto
import com.gyf.csams.module.NotificationVo
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
@ -118,10 +121,8 @@ abstract class SysMessageViewModel(application: Application) :
),
jsonParam = NotificationDto(
receiverId = TokenManager.getToken().id,
receiverClient = clientType(), page = PageDto(
currentPage = _currentPage.value ?: 1,
pageSize = initSize
)
receiverClient = clientType(),
token = TokenManager.getToken()
)
)
}

@ -3,7 +3,8 @@ package com.gyf.lib.service
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import com.gyf.lib.util.ClientType
import com.gyf.csams.module.ClientType
abstract class BaseActivity : ComponentActivity() {

@ -6,6 +6,9 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.gyf.csams.module.ClientType
import com.gyf.csams.module.NotificationDto
import com.gyf.csams.module.NotificationVo
import com.gyf.lib.R
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
@ -32,16 +35,22 @@ class MessageService : JobIntentService() {
}
override fun onHandleWork(intent: Intent) {
HttpClient.postAsync<List<NotificationVo>>(
url = Api.buildUrl(NotificationApi.Pull),
jsonParam = NotificationDto(
receiverId = TokenManager.getToken().id,
receiverClient = clientType
),
type = object : TypeToken<ApiResponse<List<NotificationVo>>>() {}.type
)?.let { it1 ->
Logger.i("拉取最新通知")
it1.body?.forEach {
val token = try {
TokenManager.getToken()
} catch (e: Exception) {
throw IllegalArgumentException("通知服务非法调用,请先初始化token")
}
HttpClient.postAsync<List<NotificationVo>>(
url = Api.buildUrl(NotificationApi.Pull),
jsonParam = NotificationDto(
receiverId = token.id,
receiverClient = clientType,
token = token
),
type = object : TypeToken<ApiResponse<List<NotificationVo>>>() {}.type
)?.let { it1 ->
Logger.i("拉取最新通知")
it1.body?.forEach {
Logger.i("构造通知【${it.title}")
val builder =
NotificationCompat.Builder(applicationContext, NotificationUtil.CHANNEL_ID)

@ -9,10 +9,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.module.OwnInfoVo
import com.gyf.lib.BuildConfig
import com.gyf.lib.model.InitViewModel
import com.gyf.lib.util.AccountApi
import com.gyf.lib.util.OwnInfoVo
import com.orhanobut.logger.Logger
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay

@ -88,6 +88,19 @@ open class ValidStringForm(formDesc: String, textLength: Int) : StringForm(formD
}
}
/**
*
* 异步更新值
* @param formDesc
* @param textLength
*/
open class AsyncStringForm(formDesc: String, textLength: Int) :
ValidStringForm(formDesc, textLength) {
fun setValue(value: String) {
_formValue.postValue(value)
}
}
/**
* 通用文本输入框
*

@ -11,7 +11,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.google.accompanist.coil.rememberCoilPainter
import com.gyf.lib.util.*
import com.gyf.csams.module.ManagerInfoVo
import com.gyf.csams.module.ManagerVo
import com.gyf.csams.module.PersonInfoVo
import com.gyf.csams.module.UserVo
import com.gyf.lib.util.Api
import com.gyf.lib.util.TokenManager
@Composable

@ -1,5 +1,6 @@
package com.gyf.lib.util
import com.gyf.csams.module.ClientType
import com.gyf.lib.BuildConfig
import java.util.*
@ -40,7 +41,10 @@ enum class AccountApi(val path: String) : UrlPath {
BackgroundToken("${BackgroundLogin.path}/token"),
//登出
Logout("/logout");
Logout("/logout"),
//刷新用户信息
Refresh("/refresh");
override fun build(): String {
@ -107,6 +111,33 @@ enum class AssociationApi(val path: String) : UrlPath {
}
}
/**
* 活动接口
*
* @property path
*/
enum class ActivityApi(val path: String) : UrlPath {
//前台提交活动申请书
Register("/register"),
//后台查看活动申请书
Audit("/audit"),
//后台受理活动申请书
Accept("/accept"),
//后台审核活动申请书
Check("/check"),
//审核进度
Read("/read");
override fun build(): String {
return "/api/activity${this.path}"
}
}
enum class NotificationApi(val path: String) : UrlPath {
//通知计数
Count("/count"),

@ -0,0 +1,53 @@
package com.gyf.lib.util
import java.sql.Timestamp
import java.text.SimpleDateFormat
import java.util.*
object DateTimeUtil {
const val DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm"
const val DATE_FORMAT = "yyyy-MM-dd"
const val TIME_FORMAT = "HH:mm"
const val DATETIME_FORMAT = "yyyy-MM-dd HH:mm"
const val START_TIME = "2021-01-01 00:00"
fun String.toTimeStamp(pattern: String = DATE_TIME_FORMAT): Timestamp {
return Timestamp(this.toDate(pattern).time)
}
fun String.toDate(pattern: String = DATE_TIME_FORMAT): Date {
return SimpleDateFormat(pattern, Locale.CHINA).parse(this)
?: throw IllegalArgumentException("日期字符串按[$pattern]转换失败")
}
val startUnix = SimpleDateFormat(DATETIME_FORMAT, Locale.CHINA).parse(START_TIME)?.time
fun randomDateTime(): Date {
if (startUnix != null) {
return Date("${(startUnix..Date().time).random()}".toLong())
} else {
throw IllegalArgumentException("生成随机失败,无法获取起始时间")
}
}
fun Date.datetimeFormat(): String {
return SimpleDateFormat(DATETIME_FORMAT, Locale.CHINA).format(this)
}
fun Date.dateFormat(): String {
return SimpleDateFormat(DATE_FORMAT, Locale.CHINA).format(this)
}
fun Date.timeFormat(): String {
return SimpleDateFormat(TIME_FORMAT, Locale.CHINA).format(this)
}
}

@ -1,7 +1,7 @@
package com.gyf.lib.util
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.google.gson.*
import com.gyf.csams.module.Token
import com.orhanobut.logger.Logger
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
@ -9,20 +9,55 @@ import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.io.IOException
import java.lang.reflect.Field
import java.lang.reflect.Type
import java.net.SocketTimeoutException
interface TokenInterface {
fun token(): String
object SuperclassExclusionStrategy : ExclusionStrategy {
//序列化反序列化白名单
val whiteClass = listOf<Class<*>>(Token::class.java)
override fun shouldSkipClass(arg0: Class<*>?): Boolean {
return false
}
override fun shouldSkipField(fieldAttributes: FieldAttributes): Boolean {
val fieldName = fieldAttributes.name
val theClass = fieldAttributes.declaringClass
return isFieldInSuperclass(theClass, fieldName)
}
private fun isFieldInSuperclass(subclass: Class<*>, fieldName: String): Boolean {
var superclass = subclass.superclass
var field: Field?
while (superclass != null && !whiteClass.contains(subclass)) {
field = getField(superclass, fieldName)
if (field != null) return true
superclass = superclass.superclass
}
return false
}
private fun getField(theClass: Class<*>, fieldName: String): Field? {
return try {
theClass.getDeclaredField(fieldName)
} catch (e: Exception) {
null
}
}
}
val HTTP_CALLBACK_GSON_CONFIG: Gson = GsonBuilder()
.addSerializationExclusionStrategy(SuperclassExclusionStrategy)
.addDeserializationExclusionStrategy(SuperclassExclusionStrategy)
.create()
object HttpClient {
private val httpClient: OkHttpClient = OkHttpClient()
private val JSON_CONTENT_TYPE = "application/json; charset=UTF-8".toMediaType()
private val json = Gson()
/**
* 构建url查询参数
@ -80,11 +115,11 @@ object HttpClient {
*
* @param url
* @param callback
* @param jsonParam
* @param jsonParam TODO 改写成reified
*/
fun post(url: String, callback: Callback, jsonParam: Any) {
Logger.i("request url=$url")
val jsonBody = json.toJson(jsonParam)
val jsonBody = HTTP_CALLBACK_GSON_CONFIG.toJson(jsonParam)
Logger.json(jsonBody)
val request = Request.Builder()
.url(url)
@ -96,7 +131,7 @@ object HttpClient {
fun <T> postAsync(url: String, jsonParam: Any, type: Type): ApiResponse<T>? {
Logger.i("request url=$url")
val jsonBody = json.toJson(jsonParam)
val jsonBody = HTTP_CALLBACK_GSON_CONFIG.toJson(jsonParam)
Logger.json(jsonBody)
val request = Request.Builder()
.url(url)
@ -161,7 +196,7 @@ open class HttpCallback<T>(
private val onFail: (error: String) -> Unit = { Logger.e(it) },
private val typeToken: Type
) : Callback, GsonBuilderInterface {
override val gson: Gson = Gson()
override val gson: Gson = HTTP_CALLBACK_GSON_CONFIG
override fun onFailure(call: Call, e: IOException) {
when (e) {

@ -1,14 +1,11 @@
package com.gyf.lib.util
import okhttp3.internal.toHexString
import java.text.SimpleDateFormat
import java.util.*
fun randomNum(length: Int = 8): String {
return List(length) { ('0'..'9').random() }.joinToString("")
}
fun encode(char: Char) = "\\u${char.toInt().toHexString()}"
//unicode ->String
@ -34,23 +31,4 @@ fun randomChinese(length: Int = 8): String {
}.joinToString("")
}
const val DATETIME_FORMAT = "yyyy-MM-dd HH:mm"
const val START_TIME = "2021-01-01 00:00"
val FORMAT = SimpleDateFormat(DATETIME_FORMAT, Locale.US)
val startUnix = FORMAT.parse(START_TIME)?.time
fun randomDateTime(): Date {
if (startUnix != null) {
return Date("${(startUnix..Date().time).random()}".toLong())
} else {
throw IllegalArgumentException("生成随机失败,无法获取起始时间")
}
}
fun Date.format(): String {
return FORMAT.format(this)
}

@ -4,43 +4,56 @@ import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.room.*
import com.gyf.csams.module.*
import com.orhanobut.logger.Logger
/**
* 登陆令牌
*/
@Entity
data class Token(
@PrimaryKey val id: Int,
@ColumnInfo val token: String,
@ColumnInfo val createTime: Long
)
class ClientToken(
@PrimaryKey
override val id: Int,
@ColumnInfo
override val token: String,
@ColumnInfo
override val createTime: Long,
) : Token(token, createTime, id)
data class OnlyToken(
override val clientType: ClientType
) : ClientBaseVo()
) : ClientBaseVo() {
override val token: ClientToken = TokenManager.getToken()
}
@Dao
interface TokenDao {
@Query("select * from token")
suspend fun queryAll(): List<Token>
@Query("select * from ClientToken")
suspend fun queryAll(): List<ClientToken>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(token: Token)
suspend fun save(clientToken: ClientToken)
@Delete
suspend fun delete(user: Token)
suspend fun delete(user: ClientToken)
@Query("delete from token")
@Query("delete from ClientToken")
suspend fun deleteAll()
}
object TokenManager {
private var ownInfo: OwnInfoVo? = null
private lateinit var personInfo: PersonInfoVo
fun init(ownInfo: OwnInfoVo) {
private var personInfo: PersonInfoVo? = null
private var clientToken: ClientToken? = null
fun update(ownInfo: OwnInfoVo) {
this.ownInfo = ownInfo
ownInfo.token.let {
clientToken = ClientToken(id = it.id, token = it.token, createTime = it.createTime)
}
when (ownInfo) {
is ManagerVo -> this.personInfo = ManagerInfoVo(
duty = ownInfo.duty,
@ -50,8 +63,9 @@ object TokenManager {
)
is UserVo -> this.personInfo =
UserInfoVo(name = ownInfo.name, headImg = ownInfo.headImg, desc = ownInfo.desc)
else -> throw IllegalArgumentException("token失败")
else -> throw IllegalArgumentException("token初始化失败")
}
Logger.i("token刷新完成")
}
fun clear() {
@ -62,17 +76,26 @@ object TokenManager {
return ownInfo ?: throw IllegalArgumentException("token没有初始化,非法调用")
}
fun getUserInfo(): UserVo? {
return ownInfo as? UserVo
}
fun getManagerInfo(): ManagerVo? {
return ownInfo as? ManagerVo
}
fun getPersonInfo(): PersonInfoVo {
return personInfo
return personInfo ?: throw IllegalArgumentException("个人信息没有初始化,非法调用")
}
fun getToken(): Token {
return ownInfo?.token ?: throw IllegalArgumentException("token没有初始化,非法调用")
fun getToken(): ClientToken {
return clientToken ?: throw IllegalArgumentException("token没有初始化,非法调用")
}
}
@Database(entities = [Token::class], version = 1, exportSchema = false)
@Database(entities = [ClientToken::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun tokenDao(): TokenDao
private val mIsDatabaseCreated = MutableLiveData<Boolean>()

@ -1,527 +0,0 @@
package com.gyf.lib.util
import androidx.annotation.IntRange
import com.gyf.lib.uikit.StringForm
import java.util.*
/**
* 一般信息
*
*/
abstract class PersonInfoVo {
abstract val name: String
abstract val headImg: String?
abstract val desc: String
}
enum class Duty(val desc: String, val level: Int) {
Teacher("老师", 1),
PamphaBhusal("总部长", 2),
SecretaryOfTheMinister("秘书部部长", 3),
PropagandaDepartment("宣传部部长", 3),
LiaisonMinister("外联部部长", 3),
SecretaryDepartmentOfficer("秘书部干事", 4),
PublicityDepartmentOfficer("宣传部干事", 4),
LiaisonOfficer("外联部干事", 4);
/**
* 是否是部门部长
*
*/
fun isMinister(): Boolean {
return minister.contains(this)
}
fun isOfficer(): Boolean {
return officer.contains(this)
}
}
private val minister =
arrayOf(Duty.SecretaryOfTheMinister, Duty.LiaisonMinister, Duty.PropagandaDepartment)
private val officer =
arrayOf(Duty.SecretaryDepartmentOfficer, Duty.PublicityDepartmentOfficer, Duty.LiaisonOfficer)
/**
* 个人信息
*
*/
abstract class OwnInfoVo : PersonInfoVo() {
abstract val token: Token
}
data class ManagerInfoVo(
val duty: Duty,
override val name: String,
override val headImg: String?,
override val desc: String
) : PersonInfoVo()
data class UserInfoVo(
override val name: String,
override val headImg: String?,
override val desc: String
) : PersonInfoVo()
/**
* 管理员个人信息
*
* @property account 管理员账号
* @property name 姓名
* @property duty 职务
* @property headImg 头像
* @property desc 个人简介
*/
data class ManagerVo(
val account: String,
val duty: Duty,
override val token: Token,
override val name: String,
override val headImg: String?,
override val desc: String
) : OwnInfoVo()
/**
* 用户个人信息
*
* @property studentId 学号
* @property name 姓名
* @property headImg 头像
* @property desc 个人简介
*/
data class UserVo(
val studentId: String,
val manager: ManagerVo? = null,
override val token: Token,
override val name: String,
override val headImg: String?,
override val desc: String,
val associationVo: AssociationVo?,
val isHead: Boolean?
) : OwnInfoVo()
data class PageDto(val currentPage: Long, val pageSize: Int = 10)
data class NotificationDto(
val receiverId: Int,
val receiverClient: ClientType,
val page: PageDto? = null,
override val clientType: ClientType = receiverClient
) : ClientBaseVo()
data class UserRegVo(val studentId: String, val name: String)
/**
* 客户端类型
*
*/
enum class ClientType {
//前台
Foreground,
//后台
Background
}
data class NotificationVo(val title: String, val content: String, val id: Int, val createTime: Long)
/**
* 响应自动生成密码
*
* @property password
*/
data class UserPasswordVo(val password: String)
/**
* 用户登陆表单
*
* @property studentId 学号
* @property password 密码
* @property device 设备型号
*/
data class UserLoginVo(val studentId: String, val password: String, val device: String)
/**
*
* @property associationName 社团名字
* @property activityName 活动名
* @property activityTime 活动时间
* @property activityLocation 活动地点
* @property activityDesc 活动介绍
*/
data class ActivityDetailVo(
val associationName: String, val activityName: String,
val activityTime: Date, val activityLocation: String,
val activityDesc: String
)
/**
* 图片
* @property name 文件名
* @property size 文件大小
* @property url 文件路径
* @property md5 文件hash
* @property createTime 文件创建时间
* @property studentId 文件上传人
*/
data class ActivityPhotoVo(
val name: String,
val size: Long,
val url: String,
val md5: String,
val createTime: Date,
val studentId: String
)
const val MAX_SCORE = 5L
data class ActivityVo(
val activityId: Long, val activityName: String, val association: String,
@IntRange(from = 1, to = MAX_SCORE) val score: Int, val activityTime: Date, val location: String
)
data class AllOfficerVo(
val secretariat: MutableList<ManagerInfoVo>,
val propaganda: MutableList<ManagerInfoVo>,
val publicRelationsDepartment: MutableList<ManagerInfoVo>
)
data class ApplyActVo(
val activityName: String, val activityTime: String,
val location: String, val desc: String,
val size: Int
)
data class LeaveMessageVo(
val message: String,
override val clientType: ClientType = ClientType.Foreground
) : ClientBaseVo()
/**
* 社团级别
*
*/
enum class AssociationLevel {
A,
B,
C,
D
}
/**
* 所属院系
*
*/
enum class AssociationFaculty(val desc: String, val range: kotlin.ranges.IntRange) {
ForeignLanguageDept("外语系", 0..0),
CivilEngineeringDept("土木工程", 1..10),
SEM("经理管理学院", 11..20),
MechanicalEngineeringDept("机械工程", 21..30),
TransportationDept("交通运输", 31..40),
ArchitectureAndArts("建筑与艺术", 41..50),
ElectricalDept("电气", 51..60),
MaterialsDept("材料", 61..70),
MessageDept("信息", 71..80),
MathematicsDept("数理", 81..90),
GraduateStudent("研究生", 91..99)
}
abstract class BaseAssociationVo {
abstract val associationId: Int
abstract val name: String
abstract val desc: String
abstract val logo: String
abstract val faculty: AssociationFaculty
abstract val level: AssociationLevel?
}
/**
* 社团列表
*
*/
class AssociationVo(
override val associationId: Int,
override val name: String,
override val desc: String,
override val logo: String,
override val faculty: AssociationFaculty,
override val level: AssociationLevel?
) : BaseAssociationVo()
//审核状态
enum class CheckStatus(val desc: String) {
WaitFirst("等待初审"),
AcceptFirst("初审受理"),
WaitLast("等待复审"),
AcceptLast("复审受理"),
Finish("审核完成")
}
/**
* 活动照片
*
* @property path
*/
data class AssociationActPhotoVo(val path: String)
data class AssociationMainVo(
val associationVo: AssociationVo, val head: UserInfoVo,
val photos: List<AssociationActPhotoVo>? = null
)
data class AuditCheckVo(
val checkStatus: CheckStatus, val applyTime: Long,
val firstCause: String, val lastCause: String?
)
//前台社团注册资料
data class AssociationCheckVo(
override val associationId: Int,
override val name: String,
override val desc: String,
override val logo: String,
override val faculty: AssociationFaculty,
override val level: AssociationLevel?,
val fileId: Int,
val auditCheckVo: AuditCheckVo
) : BaseAssociationVo()
/**
* 搜索社团
*
* @property name
* @property desc
* @property clientType
*/
data class SearchAssociationVo(
val name: String, val desc: String,
override val clientType: ClientType = ClientType.Foreground
) : ClientBaseVo()
data class ShowAssociationVo(
val id: Int,
override val clientType: ClientType = ClientType.Foreground
) : ClientBaseVo()
data class QueryAssociationMembers(
val id: Int,
val name: String? = null,
override val clientType: ClientType = ClientType.Foreground
) : ClientBaseVo()
data class BBSVo(val studentId: String, val name: String, val createTime: Date, val content: String)
/**
* 题型
*
*/
enum class ExamType(val type: String) {
//选择题
CQ("选择题"),
//开放题
OQ("开放题")
}
abstract class Exam {
abstract val examType: ExamType
abstract val question: StringForm
}
/**
* 选择题
*
* @property examType 题型描述
* @property answers 答案
* @property rightAnswer 正确答案
* @property question 问题
*/
data class ChoiceQuestionVo(
override val examType: ExamType = ExamType.CQ,
val answers: List<StringForm>,
val rightAnswer: Int,
override val question: StringForm
) : Exam()
data class HistoryActVo(val name: String)
/**
* 开放题
*
* @property examType 题型描述
* @property question 问题
*/
data class OpenQuestionsVo(
override val examType: ExamType = ExamType.OQ, override val question: StringForm
) : Exam()
data class LeaveMessageFormatVo(val message: String, val user: UserInfoVo)
data class ManagerLoginVo(val account: String, val password: String, val device: String)
data class OngoingActVo(val name: String)
/**
* 活动质量汇报单
*
* @property applyName 申请人
* @property activityName 活动名称
* @property merit 优点
* @property defect 缺点
* @property score 星级评价
*/
data class QualityReportVo(
val applyName: String,
val activityName: String,
val merit: String,
val defect: String,
@IntRange(from = 1L, to = MAX_SCORE) val score: Int
)
/**
* 社团注册资料表单
*
* @property name
* @property desc
* @property fileId
*/
data class AssociationRegVo(
val associationId: Int?, val name: String, val desc: String, val fileId: Int,
override val clientType: ClientType = ClientType.Foreground
) : ClientBaseVo()
abstract class AuditVo {
abstract val audit: AuditLoggingVo
}
/**
* 社团注册审核记录
*
*/
data class AuditAssociationVo(
val name: String,
val desc: String,
val logo: String,
override val audit: AuditLoggingVo
) : AuditVo()
/**
* 通用审核记录
*
* @property id
* @property user
* @property applyTime
* @property manager
* @property acceptTime
* @property cause
* @property result
* @property auditTime
* @property nextAudit
*/
data class AuditLoggingVo(
val id: Int, val user: UserInfoVo, val applyTime: Long, val manager: ManagerInfoVo?,
val acceptTime: Long?, val cause: String?, val result: Boolean?,
val auditTime: Long?, val nextAudit: AuditLoggingVo?
)
/**
* 社团注册资料受理
*
* @property auditId
* @property clientType
*/
data class AcceptVo(
val auditId: Int,
override val clientType: ClientType = ClientType.Background
) : ClientBaseVo()
/**
* 社团注册资料审核
*
* @property auditId
* @property result
* @property cause
* @property clientType
*/
data class CheckVo(
val auditId: Int, val result: Boolean, val cause: String,
override val clientType: ClientType = ClientType.Background
) : ClientBaseVo()
/**
* 前台活动申请书
*
* @property activityId
* @property activityName
* @property activityTime
* @property activityAddress
* @property activityDesc
* @property activitySize
* @property clientType
*/
data class ActivityApplyVo(
val activityId: Int?, val associationId: Int,
val activityName: String, val activityTime: Long,
val activityAddress: String,
val activityDesc: String, val activitySize: Int,
override val clientType: ClientType = ClientType.Foreground
) : ClientBaseVo()
/**
* 后台活动申请书
*
* @property auditId
* @property activityName
* @property activityTime
* @property activityAddress
* @property activityDesc
* @property activitySize
*/
data class AuditActVo(
val auditId: Int, val activityName: String, val activityTime: Long,
val activityAddress: String,
val activityDesc: String, val activitySize: Int,
override val audit: AuditLoggingVo
) : AuditVo()
/**
* 换名申请表
*
* @property studentId 学号
* @property oldName 社团原名
* @property newName 社团新名
* @property reason 申请理由
*/
data class RenameVo(
val studentId: String,
val oldName: String,
val newName: String,
val reason: String
)
abstract class ClientBaseVo {
val token: Token = TokenManager.getToken()
abstract val clientType: ClientType
}

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="activity_size">20</integer>
<integer name="name_length">4</integer>
</resources>

@ -30,4 +30,5 @@
<string name="recheck_btn">复审</string>
<string name="audit_phases">审核阶段</string>
<string name="last_approver_origin">复审意见</string>
<string name="activity_date">活动日期</string>
</resources>

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="activity_size">20</integer>
<integer name="name_length">4</integer>
</resources>

@ -30,4 +30,5 @@
<string name="recheck_btn">复审</string>
<string name="audit_phases">审核阶段</string>
<string name="last_approver_origin">复审意见</string>
<string name="activity_date">活动日期</string>
</resources>

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="activity_size">20</integer>
<integer name="name_length">4</integer>
</resources>

@ -30,4 +30,5 @@
<string name="recheck_btn">复审</string>
<string name="audit_phases">审核阶段</string>
<string name="last_approver_origin">复审意见</string>
<string name="activity_date">活动日期</string>
</resources>

@ -8,9 +8,17 @@ import org.junit.Test
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
@Test
fun testClientToken() {
}
}

@ -15,3 +15,7 @@ include(":foreground")
include(":background")
//公共库
include(":lib")
include(":module")
project(":module").projectDir = File(settingsDir, "..\\..\\IdeaProjects\\CsamsServer\\module")

Loading…
Cancel
Save