增加accompanist compose ui工具库

社团注册资料审核,增加防输入法遮挡
完善个人名片逻辑
社团接口更新
底部按钮组件更新
Http工具类响应回调逻辑完善
token管理逻辑完善
增加Vo统一管理
社团注册逻辑完善
社团列表逻辑完善
master
pan 4 years ago
parent 61671b3f1b
commit a3e4308037
  1. 18
      background/src/main/AndroidManifest.xml
  2. 8
      background/src/main/java/com/gyf/csams/InitActivity.kt
  3. 5
      background/src/main/java/com/gyf/csams/account/model/LoginViewModel.kt
  4. 70
      background/src/main/java/com/gyf/csams/main/model/AssociationManagementViewModel.kt
  5. 81
      background/src/main/java/com/gyf/csams/main/model/AuditAssociationViewModel.kt
  6. 11
      background/src/main/java/com/gyf/csams/main/model/BackgroundViewModel.kt
  7. 7
      background/src/main/java/com/gyf/csams/main/model/CheckActViewModel.kt
  8. 20
      background/src/main/java/com/gyf/csams/main/model/CheckQualityReportViewModel.kt
  9. 42
      background/src/main/java/com/gyf/csams/main/model/MainViewModel.kt
  10. 96
      background/src/main/java/com/gyf/csams/main/model/ManagementOfficerModel.kt
  11. 7
      background/src/main/java/com/gyf/csams/main/model/ManagerActViewModel.kt
  12. 3
      background/src/main/java/com/gyf/csams/main/model/MenuViewModel.kt
  13. 20
      background/src/main/java/com/gyf/csams/main/model/RenameViewModel.kt
  14. 12
      background/src/main/java/com/gyf/csams/main/ui/AssociationManagementActivity.kt
  15. 310
      background/src/main/java/com/gyf/csams/main/ui/AuditAssociationActivity.kt
  16. 13
      background/src/main/java/com/gyf/csams/main/ui/CheckActActivity.kt
  17. 15
      background/src/main/java/com/gyf/csams/main/ui/CheckQualityReportActivity.kt
  18. 24
      background/src/main/java/com/gyf/csams/main/ui/MainActivity.kt
  19. 18
      background/src/main/java/com/gyf/csams/main/ui/ManagementOfficerActivity.kt
  20. 6
      background/src/main/java/com/gyf/csams/main/ui/ManagerActActivity.kt
  21. 80
      background/src/main/java/com/gyf/csams/main/ui/NotificationActivity.kt
  22. 11
      background/src/main/java/com/gyf/csams/main/ui/RenameActivity.kt
  23. 59
      background/src/main/java/com/gyf/csams/uikit/Table.kt
  24. 5
      background/src/main/res/values-en/strings.xml
  25. 5
      background/src/main/res/values-zh/strings.xml
  26. 5
      background/src/main/res/values/strings.xml
  27. 13
      background/src/test/java/com/gyf/csams/ExampleUnitTest.kt
  28. 1
      foreground/src/main/AndroidManifest.xml
  29. 8
      foreground/src/main/java/com/gyf/csams/InitActivity.kt
  30. 44
      foreground/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt
  31. 4
      foreground/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt
  32. 49
      foreground/src/main/java/com/gyf/csams/activity/model/ActivityDetailViewModel.kt
  33. 7
      foreground/src/main/java/com/gyf/csams/activity/ui/ActivityDetailActivity.kt
  34. 10
      foreground/src/main/java/com/gyf/csams/association/model/AssociationViewModel.kt
  35. 45
      foreground/src/main/java/com/gyf/csams/association/model/ExamViewModel.kt
  36. 101
      foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt
  37. 4
      foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt
  38. 7
      foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt
  39. 71
      foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt
  40. 143
      foreground/src/main/java/com/gyf/csams/main/model/MainViewModel.kt
  41. 29
      foreground/src/main/java/com/gyf/csams/main/ui/MainActivity.kt
  42. 11
      foreground/src/main/java/com/gyf/csams/message/model/ForegroundViewModel.kt
  43. 9
      foreground/src/main/java/com/gyf/csams/message/ui/SysMessageActivity.kt
  44. 2
      foreground/src/main/java/com/gyf/csams/uikit/BaseView.kt
  45. 10
      foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt
  46. 8
      foreground/src/main/java/com/gyf/csams/util/GsonUtil.kt
  47. 6
      foreground/src/main/java/com/gyf/csams/util/HttpCallback.kt
  48. 1
      foreground/src/main/res/values-en/strings.xml
  49. 1
      foreground/src/main/res/values-zh/strings.xml
  50. 1
      foreground/src/main/res/values/strings.xml
  51. 8
      foreground/src/test/java/com/gyf/csams/ExampleUnitTest.kt
  52. 9
      lib/build.gradle.kts
  53. 53
      lib/src/main/java/com/gyf/lib/model/AbstractLoginViewModel.kt
  54. 7
      lib/src/main/java/com/gyf/lib/model/ApplyViewModel.kt
  55. 20
      lib/src/main/java/com/gyf/lib/model/InitViewModel.kt
  56. 7
      lib/src/main/java/com/gyf/lib/model/ScrollViewModel.kt
  57. 25
      lib/src/main/java/com/gyf/lib/model/SysMessageViewModel.kt
  58. 7
      lib/src/main/java/com/gyf/lib/service/MessageService.kt
  59. 10
      lib/src/main/java/com/gyf/lib/uikit/AbstractInitActivity.kt
  60. 20
      lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt
  61. 22
      lib/src/main/java/com/gyf/lib/uikit/Profile.kt
  62. 9
      lib/src/main/java/com/gyf/lib/util/Api.kt
  63. 10
      lib/src/main/java/com/gyf/lib/util/BottomButton.kt
  64. 25
      lib/src/main/java/com/gyf/lib/util/HttpUtil.kt
  65. 40
      lib/src/main/java/com/gyf/lib/util/TokenUtil.kt
  66. 477
      lib/src/main/java/com/gyf/lib/util/Vo.kt
  67. 27
      lib/src/main/java/com/gyf/lib/util/vo.kt
  68. 12
      lib/src/main/res/values-en/strings.xml
  69. 12
      lib/src/main/res/values-zh/strings.xml
  70. 12
      lib/src/main/res/values/strings.xml

@ -4,6 +4,8 @@
<!--访问网络-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
@ -26,15 +28,31 @@
</intent-filter>
</activity>
<activity android:name=".account.ui.LoginActivity" />
<!--个人中心-->
<activity android:name=".main.ui.MainActivity" />
<!--部门管理-->
<activity android:name=".main.ui.DepartmentActivity" />
<!--干事管理-->
<activity android:name=".main.ui.ManagementOfficerActivity" />
<!--社团管理-->
<activity android:name=".main.ui.AssociationManagementActivity" />
<!--菜单-->
<activity android:name=".main.ui.MenuActivity" />
<!--社团换名-->
<activity android:name=".main.ui.RenameActivity" />
<!--活动管理-->
<activity android:name=".main.ui.ManagerActActivity" />
<!--审核社团注册-->
<activity
android:name=".main.ui.AuditAssociationActivity"
android:windowSoftInputMode="adjustResize" />
<!--活动申请书-->
<activity android:name=".main.ui.CheckActActivity" />
<!--活动质量汇报单-->
<activity android:name=".main.ui.CheckQualityReportActivity" />
<!--通知界面-->
<activity android:name=".main.ui.NotificationActivity" />
<!--通知服务-->
<service android:name="com.gyf.lib.service.MessageService" />
</application>

@ -1,14 +1,20 @@
package com.gyf.csams
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.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() {
class InitActivity : AbstractInitActivity<ManagerVo>() {
override val main: Class<out Activity> = MainActivity::class.java
override val login: Class<out Activity> = LoginActivity::class.java
override val api: AccountApi = AccountApi.BackgroundToken
override val typeToken: Type = object : TypeToken<ApiResponse<ManagerVo>>() {}.type
}

@ -9,8 +9,7 @@ import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.AccountApi
import com.gyf.lib.util.ClientType
data class ManagerVo(val account: String, val password: String, val device: String)
import com.gyf.lib.util.ManagerLoginVo
class LoginViewModel(application: Application) : AbstractLoginViewModel(application) {
override val id = ValidStringForm(formDesc = "管理帐号", textLength = 8)
@ -25,7 +24,7 @@ class LoginViewModel(application: Application) : AbstractLoginViewModel(applicat
override fun loginParam(): Any {
val account = "${id.formValue.value}"
val password = "${password.formValue.value}"
return ManagerVo(
return ManagerLoginVo(
account = account,
password = password,
device = "${Build.MANUFACTURER} ${Build.MODEL}"

@ -1,49 +1,12 @@
package com.gyf.csams.main.model
import android.app.Application
import androidx.lifecycle.viewModelScope
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.util.randomChinese
import kotlinx.coroutines.launch
import com.gyf.lib.util.AssociationLevel
import com.gyf.lib.util.AssociationVo
/**
* 社团级别
*
*/
enum class AssociationLevel {
A,
B,
C,
D
}
/**
* 所属院系
*
*/
enum class AssociationFaculty(val desc: String, val range: 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)
}
/**
* 社团
*
* @property name 社团名称
*/
data class AssociationVo(
val associationId: Long, val name: String, val commander: String,
val faculty: AssociationFaculty, val level: AssociationLevel, val desc: String
)
/**
* 数据状态管理
@ -59,22 +22,7 @@ class AssociationManagementViewModel(application: Application) : ScrollViewModel
}
override fun load() {
viewModelScope.launch {
_data.value?.apply {
repeat(initSize) {
add(
AssociationVo(
associationId = (0..65535L).random(),
name = randomChinese(5),
commander = randomChinese(3),
faculty = AssociationFaculty.values().random(),
level = AssociationLevel.values().random(),
desc = randomChinese(20)
)
)
}
}
}
TODO()
}
override fun loadMore(callback: (message: String) -> Unit) {
@ -82,18 +30,10 @@ class AssociationManagementViewModel(application: Application) : ScrollViewModel
}
/**
* TODO 更新社团级别
*
* @param level
*/
fun update(associationVo: AssociationVo, level: AssociationLevel) {
_data.value?.apply {
val i = indexOf(associationVo)
set(i, associationVo.copy(level = level))
val new = mutableListOf<AssociationVo>()
new.addAll(this)
clear()
_data.postValue(new)
}
fun update(associationDto: AssociationVo, level: AssociationLevel) {
TODO("更新社团级别")
}
}

@ -0,0 +1,81 @@
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
class AuditAssociationViewModel(application: Application) : ScrollViewModel<DisposeRegInfoVo>(
application
) {
override val initSize: Int = 10
init {
load { }
}
override 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)
)
}
}
override fun loadMore(callback: (message: String) -> Unit) {
TODO("Not yet implemented")
}
/**
* 受理注册资料
*
* @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
)
)
}
}
}

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

@ -3,17 +3,12 @@ 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
data class ApplyActVo(
val activityName: String, val activityTime: String,
val location: String, val desc: String,
val size: Int
)
/**
* 活动数据管理

@ -1,31 +1,13 @@
package com.gyf.csams.main.model
import android.app.Application
import androidx.annotation.IntRange
import androidx.lifecycle.viewModelScope
import com.gyf.lib.model.ApplyViewModel
import com.gyf.lib.util.QualityReportVo
import com.gyf.lib.util.randomChinese
import kotlinx.coroutines.launch
const val MAX_SCORE = 5L
/**
* 活动质量汇报单
*
* @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
)
/**
* 活动质量汇报单评价

@ -1,42 +0,0 @@
package com.gyf.csams.main.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gyf.lib.uikit.PersonInfoVo
import com.gyf.lib.util.randomChinese
/**
* 部长
*
* @property name 姓名
* @property duty 职务
* @property headImg 头像
* @property desc 个人简介
*/
data class MinisterVo(
override val name: String,
override val duty: String,
override val headImg: String,
override val desc: String
) : PersonInfoVo()
class MainViewModel : ViewModel() {
private val _person = MutableLiveData<MinisterVo>()
val person: LiveData<MinisterVo> = _person
init {
loadInfo()
}
private fun loadInfo() {
_person.postValue(
MinisterVo(
name = randomChinese(3),
duty = "总部长",
headImg = "",
desc = randomChinese(8)
)
)
}
}

@ -3,49 +3,14 @@ package com.gyf.csams.main.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.gyf.lib.util.randomChinese
import com.gyf.lib.util.randomNum
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
import com.gyf.lib.util.AllOfficerVo
import com.gyf.lib.util.ManagerInfoVo
enum class ColumnType {
Text,
DropMenu
}
/**
* 职务
*
* @property desc
*/
enum class Duty(val desc: String) {
Minister("部长"),
Manager("干事")
}
/**
* 人员信息
*
* @property name 名字
* @property studentId 学号
* @property mobile 手机号
* @property duty 职务
* @property counselor 导员
*/
data class OfficerVo(
val name: String,
val studentId: String,
val mobile: String,
val duty: Duty,
val counselor: String
)
data class AllOfficerVo(
val secretariat: MutableList<OfficerVo>,
val propaganda: MutableList<OfficerVo>,
val publicRelationsDepartment: MutableList<OfficerVo>
)
/**
* 部门干事数据状态管理
@ -55,69 +20,20 @@ class ManagementOfficerModel : ViewModel() {
private val _data = MutableLiveData<AllOfficerVo>()
val data: LiveData<AllOfficerVo> = _data
init {
load()
}
private fun replace(
list: MutableList<OfficerVo>,
index: Int,
callback: (s: MutableList<OfficerVo>) -> Unit
) {
val s = mutableListOf<OfficerVo>()
list[index] = list[index].copy(duty = Duty.Minister)
s.add(list[index])
s.addAll(list.filter { officerVo -> officerVo != list[index] }
.map { officerVo -> officerVo.copy(duty = Duty.Manager) })
callback(s)
}
fun updateDuty(list: MutableList<OfficerVo>, index: Int) {
_data.value?.apply {
Logger.i("$secretariat")
when (list) {
secretariat -> replace(list = list, index = index) {
_data.postValue(copy(secretariat = it))
}
propaganda -> replace(list = list, index = index) {
_data.postValue(copy(propaganda = it))
}
publicRelationsDepartment -> replace(list = list, index = index) {
_data.postValue(copy(publicRelationsDepartment = it))
}
}
}
fun updateDuty(list: MutableList<ManagerInfoVo>, index: Int) {
TODO("更新职务")
}
/**
* TODO 加载部门成员
*
*/
private fun load() {
viewModelScope.launch {
val officerVoList = mutableListOf<OfficerVo>()
val baseSize = 3
val peopleSize = 5
repeat(peopleSize * baseSize) {
officerVoList.add(
OfficerVo(
name = randomChinese(3), studentId = randomNum(8), mobile = randomNum(11),
if (it % peopleSize == 0) Duty.Minister else Duty.Manager, counselor = ""
)
)
}
val all = officerVoList.chunked(peopleSize)
_data.postValue(
AllOfficerVo(
secretariat = all[0].toMutableList(),
propaganda = all[1].toMutableList(),
publicRelationsDepartment = all[2].toMutableList()
)
)
}
TODO("加载部门成员")
}
}

@ -1,17 +1,12 @@
package com.gyf.csams.main.model
import android.app.Application
import androidx.annotation.IntRange
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
import java.util.*
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
)
/**
* 活动信息数据管理

@ -9,7 +9,8 @@ enum class MenuType(val desc: String, val clazz: Map<String, Class<out Activity>
"社团管理",
mapOf(
"社团信息管理" to AssociationManagementActivity::class.java,
"审核换名申请表" to RenameActivity::class.java
"审核社团换名" to RenameActivity::class.java,
"审核社团注册" to AuditAssociationActivity::class.java
),
),
Act(

@ -5,29 +5,19 @@ import androidx.lifecycle.viewModelScope
import com.gyf.csams.R
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
/**
* 换名申请表
*
* @property studentId 学号
* @property oldName 社团原名
* @property newName 社团新名
* @property reason 申请理由
*/
data class RenameVo(
val studentId: String,
val oldName: String,
val newName: String,
val reason: String
)
class RenameViewModel(application: Application) : ScrollViewModel<RenameVo>(application) {
val approverOrigin =
StringForm(formDesc = application.getString(R.string.approver_origin), textLength = 30)
StringForm(
formDesc = application.getString(R.string.first_approver_origin),
textLength = 30
)
override val initSize: Int = 10

@ -16,10 +16,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.main.model.AssociationLevel
import com.gyf.csams.main.model.AssociationManagementViewModel
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 = "$associationId",
text = "$id",
style = MaterialTheme.typography.h6
)
Text(
@ -68,7 +68,9 @@ class AssociationManagementActivity : ComponentActivity() {
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = commander,
text = ""
/**TODO 用户**/
,
style = MaterialTheme.typography.h5
)
Text(
@ -80,7 +82,7 @@ class AssociationManagementActivity : ComponentActivity() {
expanded = true
}) {
Text(
text = level.name,
text = level?.name ?: "暂无评级",
style = MaterialTheme.typography.h5
)
DropdownMenu(
@ -89,7 +91,7 @@ class AssociationManagementActivity : ComponentActivity() {
AssociationLevel.values().forEach {
DropdownMenuItem(onClick = {
model.update(
associationVo = this@apply,
associationDto = this@apply,
level = it
)
expanded = false

@ -0,0 +1,310 @@
package com.gyf.csams.main.ui
import android.os.Bundle
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.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
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.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.uikit.RowItem
import com.gyf.csams.uikit.TestTableImeSimple
import com.gyf.lib.uikit.*
import com.gyf.lib.util.*
class AuditAssociationActivity : ComponentActivity() {
@ExperimentalMaterialApi
@ExperimentalAnimatedInsets
@ExperimentalComposeApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ImeBody {
val model: AuditAssociationViewModel = viewModel()
val data by model.data.observeAsState()
TestTableImeSimple(
title = R.string.association_reg_title
) {
data?.forEach {
item {
RegisterForm(vo = it)
Spacer(modifier = Modifier.height(10.dp))
}
}
}
}
}
}
@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
) {
Column(
modifier = modifier.border(
width = 1.dp,
color = MaterialTheme.colors.onBackground
)
) {
val baseHeight = 50.dp
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.petitioner,
value = vo.log.user.name
)
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.association_name,
value = vo.name
)
RowItem(
modifier = Modifier.height(baseHeight),
key = R.string.association_desc,
value = vo.desc
)
RowItem(
modifier = Modifier.height(baseHeight * 3),
key = R.string.association_logo
) {
//TODO 图片全屏显示
val painter =
rememberCoilPainter("${com.gyf.lib.BuildConfig.SERVER_ADDRESS}/${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 ?: ""
)
})
}
}
}

@ -13,13 +13,14 @@ 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.ApplyActVo
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
/**
@ -27,6 +28,7 @@ import com.gyf.lib.util.BottomButton
*
*/
class CheckActActivity : ComponentActivity() {
@ExperimentalAnimatedInsets
@ExperimentalComposeApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -69,11 +71,14 @@ class CheckActActivity : ComponentActivity() {
value = "${vo.size}"
)
RowItem(
modifier = Modifier.height(baseHeight), key = R.string.approver, value = ""
modifier = Modifier.height(baseHeight), key = R.string.first_approver, value = ""
/**TODO 获取审批人**/
)
RowItem(modifier = Modifier.height(baseHeight * 3), key = R.string.approver_origin) {
BaseTextField(modifier = Modifier.fillMaxSize(), form = model.approverOrigin)
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(

@ -12,21 +12,23 @@ import androidx.compose.ui.res.painterResource
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.CheckQualityReportViewModel
import com.gyf.csams.main.model.MAX_SCORE
import com.gyf.csams.main.model.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
/**
* 审批质量报告单
*
*/
class CheckQualityReportActivity : ComponentActivity() {
@ExperimentalAnimatedInsets
@ExperimentalComposeApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -86,11 +88,14 @@ class CheckQualityReportActivity : ComponentActivity() {
}
}
RowItem(
modifier = Modifier.height(baseHeight), key = R.string.approver, value = ""
modifier = Modifier.height(baseHeight), key = R.string.first_approver, value = ""
/**TODO 获取审批人**/
)
RowItem(modifier = Modifier.height(baseHeight * 3), key = R.string.approver_origin) {
BaseTextField(modifier = Modifier.fillMaxSize(), form = model.approverOrigin)
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(

@ -9,19 +9,18 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
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.gyf.csams.R
import com.gyf.csams.account.model.LoginViewModel
import com.gyf.csams.main.model.MainViewModel
import com.gyf.csams.main.model.MenuType
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() {
@ -32,22 +31,33 @@ class MainActivity : BaseActivity() {
setContent {
Body {
val model: MainViewModel = viewModel()
val loginViewModel: LoginViewModel = viewModel()
val scaffoldModel: ScaffoldModel = viewModel()
val person by model.person.observeAsState()
MainColumnFrame(background = { /*TODO*/ }) {
person?.let {
(TokenManager.getPersonInfo() as? ManagerInfoVo)?.let {
Profile(
modifier = Modifier
.weight(0.3F)
.padding(10.dp), personInfoVo = it
.padding(10.dp),
personInfoVo = it
)
}
Column(
modifier = Modifier.weight(0.7F),
verticalArrangement = Arrangement.SpaceEvenly
) {
OutlinedButton(onClick = {
startActivity(
Intent(
this@MainActivity,
NotificationActivity::class.java
)
)
}, modifier = Modifier.fillMaxWidth()) {
Text(text = "我的通知")
}
OutlinedButton(onClick = {
startActivity(Intent(this@MainActivity, DepartmentActivity::class.java))
}, modifier = Modifier.fillMaxWidth()) {

@ -21,14 +21,12 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.R
import com.gyf.csams.main.model.Duty
import com.gyf.csams.main.model.ManagementOfficerModel
import com.gyf.csams.main.model.MinisterVo
import com.gyf.csams.main.model.OfficerVo
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
/**
@ -99,7 +97,7 @@ class ManagementOfficerActivity : ComponentActivity() {
model: ManagementOfficerModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel(),
@StringRes id: Int,
officerVoList: MutableList<OfficerVo>,
officerVoList: MutableList<ManagerInfoVo>,
) {
Column(
modifier = modifier
@ -124,19 +122,13 @@ class ManagementOfficerActivity : ComponentActivity() {
.width(200.dp)
.fillMaxHeight()
.border(width = 1.dp, color = MaterialTheme.colors.onBackground),
personInfoVo =
MinisterVo(
name = it.value.name,
duty = it.value.duty.desc,
headImg = "",
desc = it.value.mobile
)
personInfoVo = it.value
) {
Logger.i("expanded=$expanded")
Text(
text = it.value.duty.desc,
modifier = Modifier.clickable(onClick = {
if (it.value.duty.desc != Duty.Minister.desc) expanded =
if (it.value.duty.isOfficer()) expanded =
true else scaffoldModel
.update(message = context.getString(R.string.update_duty_error))
})
@ -149,7 +141,7 @@ class ManagementOfficerActivity : ComponentActivity() {
model.updateDuty(list = officerVoList, index = it.index)
expanded = false
}) {
Text(text = Duty.Minister.desc)
Text(text = it.value.duty.desc)
}
}
}

@ -13,12 +13,13 @@ import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.google.accompanist.insets.ExperimentalAnimatedInsets
import com.gyf.csams.R
import com.gyf.csams.main.model.ActivityVo
import com.gyf.csams.main.model.MAX_SCORE
import com.gyf.csams.main.model.ManagerActViewModel
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
/**
@ -26,6 +27,7 @@ import com.gyf.lib.util.format
*
*/
class ManagerActActivity : ComponentActivity() {
@ExperimentalAnimatedInsets
@ExperimentalComposeApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

@ -0,0 +1,80 @@
package com.gyf.csams.main.ui
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
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.gyf.csams.main.model.BackgroundViewModel
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 java.util.*
/**
* 通知
*
*/
class NotificationActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Body {
val model: BackgroundViewModel = viewModel()
val data by model.data.observeAsState()
MainColumnFrame(background = { /*TODO*/ }) {
if (data?.size == 0) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(text = "目前没有收到任何通知")
}
}
LazyColumn {
data?.forEach {
item {
MessageItem(content = it)
}
}
}
}
}
}
}
@Composable
private fun MessageItem(modifier: Modifier = Modifier, content: NotificationVo) {
Card(modifier = modifier, backgroundColor = MaterialTheme.colors.background) {
Column(modifier = Modifier.padding(10.dp)) {
Text(text = content.title, style = MaterialTheme.typography.h5)
Spacer(modifier = Modifier.height(5.dp))
Card(
backgroundColor = MaterialTheme.colors.background,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Text(text = content.content)
}
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
Text(text = Date(content.createTime).format())
}
}
}
}
}

@ -13,17 +13,19 @@ 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.RenameViewModel
import com.gyf.csams.main.model.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() {
@ExperimentalAnimatedInsets
@ExperimentalComposeApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -63,10 +65,13 @@ class RenameActivity : ComponentActivity() {
value = renameVo.reason
)
RowItem(
modifier = Modifier.height(baseHeight), key = R.string.approver, value = ""
modifier = Modifier.height(baseHeight), key = R.string.first_approver, value = ""
/**TODO 获取审批人**/
)
RowItem(modifier = Modifier.height(baseHeight * 3), key = R.string.approver_origin) {
RowItem(
modifier = Modifier.height(baseHeight * 3),
key = R.string.first_approver_origin
) {
BaseTextField(modifier = Modifier.fillMaxSize(), form = model.approverOrigin)
}
val message = stringResource(id = R.string.not_impl_error)

@ -2,25 +2,27 @@ 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
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.getValue
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.insets.*
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
/**
* 表格
*
@ -65,6 +67,53 @@ fun <A> TestTable(
}
}
@ExperimentalAnimatedInsets
@ExperimentalMaterialApi
@Composable
fun TestTableImeSimple(
modifier: Modifier = Modifier,
@StringRes title: Int? = null,
content: LazyListScope.() -> Unit
) {
Column(modifier = modifier) {
title?.let {
Spacer(modifier = Modifier.height(20.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.border(width = 1.dp, color = MaterialTheme.colors.onBackground),
horizontalArrangement = Arrangement.Center,
) {
Text(
text = stringResource(id = title),
style = MaterialTheme.typography.h4
)
}
}
val listState = rememberLazyListState()
val insets = LocalWindowInsets.current
val imeBottom = with(LocalDensity.current) { insets.ime.bottom.toDp() }
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
content = content
)
LaunchedEffect(imeBottom) {
listState.scrollBy(imeBottom.value)
}
}
}
/**
* 表格行
*
* @param modifier
* @param key
* @param value
* @param content
*/
/**
* 表格行
*

@ -26,4 +26,9 @@
<string name="activity_id">活动编号</string>
<string name="activity_association">活动社团</string>
<string name="activity_location">活动地点</string>
<string name="accept_btn">受理</string>
<string name="allow_btn">通过</string>
<string name="last_approver">复审负责人</string>
<string name="first_result">初审结果</string>
<string name="last_result">复审结果</string>
</resources>

@ -26,4 +26,9 @@
<string name="activity_id">活动编号</string>
<string name="activity_association">活动社团</string>
<string name="activity_location">活动地点</string>
<string name="accept_btn">受理</string>
<string name="allow_btn">通过</string>
<string name="last_approver">复审负责人</string>
<string name="first_result">初审结果</string>
<string name="last_result">复审结果</string>
</resources>

@ -26,4 +26,9 @@
<string name="activity_id">活动编号</string>
<string name="activity_association">活动社团</string>
<string name="activity_location">活动地点</string>
<string name="accept_btn">受理</string>
<string name="allow_btn">通过</string>
<string name="last_approver">复审负责人</string>
<string name="first_result">初审结果</string>
<string name="last_result">复审结果</string>
</resources>

@ -1,5 +1,8 @@
package com.gyf.csams
import com.google.gson.reflect.TypeToken
import com.gyf.lib.util.ApiResponse
import com.orhanobut.logger.Logger
import org.junit.Assert.assertEquals
import org.junit.Test
@ -13,4 +16,14 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
@Test
fun typeToken() {
initToken<Boolean>()
}
inline fun <reified E> initToken() {
val typeToken = object : TypeToken<ApiResponse<E>>() {}.type
Logger.i("typeToken=$typeToken")
}
}

@ -3,6 +3,7 @@
package="com.gyf.csams">
<!--访问网络-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- 这个权限用于进行网络定位-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 这个权限用于访问GPS定位-->

@ -1,14 +1,20 @@
package com.gyf.csams
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.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() {
class InitActivity : AbstractInitActivity<UserVo>() {
override val main: Class<out Activity> = MainActivity::class.java
override val login: Class<out Activity> = AccountActivity::class.java
override val api: AccountApi = AccountApi.ForegroundToken
override val typeToken: Type = object : TypeToken<ApiResponse<UserVo>>() {}.type
}

@ -10,7 +10,6 @@ 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.util.SimpleCallback
import com.gyf.lib.model.AbstractLoginViewModel
import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.ValidStringForm
@ -19,37 +18,14 @@ import com.orhanobut.logger.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
/**
* 响应自动生成密码
*
* @property password
*/
data class UserResDto(val password: String)
/**
* 构造登录注册信息实体表单
*
* @property studentId 学号
* @property name 姓名
*/
data class UserVo(val studentId: String, val name: String)
/**
* 用户登陆表单
*
* @property studentId 学号
* @property password 密码
* @property device 设备型号
*/
data class UserLoginVo(val studentId: String, val password: String, val device: String)
/**
* 密码弹窗信息
*
* @property message
* @property userResDto
* @property userPasswordVo
*/
data class DialogMessage(val message: String, val userResDto: UserResDto?)
data class DialogMessage(val message: String, val userPasswordVo: UserPasswordVo?)
/**
* 注册表单
@ -139,7 +115,7 @@ class AccountViewModel(application: Application) : AbstractLoginViewModel(applic
val url = Api.buildUrl(AccountApi.CheckId)
Logger.i("检测${id.formDesc},请求接口$url")
HttpClient.get(
url, SimpleCallback<Boolean>(
url, HttpCallback<Boolean>(
action = "${id.formDesc}重复检测",
onSuccess = {
if (it.body == true) {
@ -150,7 +126,7 @@ class AccountViewModel(application: Application) : AbstractLoginViewModel(applic
checkForm()
},
onFail = { Logger.e(it) },
type = object : TypeToken<ApiResponse<Boolean>>() {}.type
typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type
), mapOf("studentId" to "${id.formValue.value}")
)
}
@ -188,22 +164,22 @@ class AccountViewModel(application: Application) : AbstractLoginViewModel(applic
val url = Api.buildUrl(AccountApi.Register)
Logger.i("开始$regBtnDesc,请求接口:$url")
HttpClient.post(
url, SimpleCallback<UserResDto>(
url, HttpCallback<UserPasswordVo>(
action = regBtnDesc,
onSuccess = {
_dialogMsg.postValue(
DialogMessage(
message = it.message,
userResDto = it.body
userPasswordVo = it.body
)
)
},
onFail = onFail,
type = object : TypeToken<ApiResponse<UserResDto>>() {}.type
typeToken = object : TypeToken<ApiResponse<UserPasswordVo>>() {}.type
),
jsonParam = UserVo(
studentId = "${id.formValue.value}",
name = "${name.formValue.value}"
jsonParam = UserRegVo(
studentId = id.formValue.value ?: throw IllegalArgumentException("学号为空"),
name = name.formValue.value ?: throw IllegalArgumentException("姓名为空")
)
)
resetForm()

@ -41,7 +41,7 @@ class AccountActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
TestBody { nav, scaffoldState ->
NavBody { nav, scaffoldState ->
NavHost(navController = nav, startDestination = AccountRoute.Login.name) {
composable(AccountRoute.Login.name) {
Account(
@ -241,7 +241,7 @@ class AccountActivity : ComponentActivity() {
private fun RegisterDialog(accountViewModel: AccountViewModel = viewModel()) {
val dialogMsg: DialogMessage? by accountViewModel.dialogMsg.observeAsState(null)
val message = dialogMsg?.userResDto?.password
val message = dialogMsg?.userPasswordVo?.password
if (message?.isNotEmpty() == true) {
PasswordDialog(message = message)
}

@ -10,13 +10,9 @@ 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.NOT_IMPL_TIP
import com.gyf.lib.util.randomChinese
import com.gyf.lib.util.randomDateTime
import com.gyf.lib.util.randomNum
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
import java.util.*
/**
* 活动详情菜单通用状态
@ -27,21 +23,6 @@ class ActivityDetailViewModel : ViewModel(), TopMenuInterface<ActivityDetailMenu
override val currentMenu: LiveData<ActivityDetailMenu> = _currentMenu
}
/**
* TODO
*
* @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
)
/**
* 活动信息
@ -91,23 +72,6 @@ class ActivityInfoViewModel : ViewModel() {
}
/**
* 图片
* @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
)
/**
* 活动相册数据状态管理
@ -179,12 +143,6 @@ class ActivityPhotoViewModel(application: Application) :
}
}
data class ActivityMemberVo(val studentId: String, val name: String)
data class ActivityMembersVo(
val organizer: ActivityMemberVo,
val participant: MutableList<ActivityMemberVo>?
)
class ActivityMemberViewModel(application: Application) :
ScrollViewModel<ActivityMemberVo>(application) {
@ -245,11 +203,6 @@ class ActivityMemberViewModel(application: Application) :
}
}
data class BBSVo(val studentId: String, val name: String, val createTime: Date, val content: String)
class T
class BBSCommentModel : AbstractComment() {
override val newContent = ValidStringForm(formDesc = "评论内容", textLength = 80)

@ -24,9 +24,12 @@ import com.gyf.csams.R
import com.gyf.csams.activity.model.*
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.uikit.TestBody
import com.gyf.lib.util.ActivityMemberVo
import com.gyf.lib.util.ActivityPhotoVo
import com.gyf.lib.util.BBSVo
import com.gyf.lib.util.format
/**
@ -41,7 +44,7 @@ class ActivityDetailActivity : ComponentActivity() {
setContent {
TestBody { nav, scaffoldState ->
NavBody { nav, scaffoldState ->
val model: ActivityDetailViewModel = viewModel()
val currentMenuName by model.currentMenu.observeAsState(ActivityDetailMenu.startMenu)
Column {

@ -9,7 +9,10 @@ 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.HistoryActVo
import com.gyf.lib.util.MemberVo
import com.gyf.lib.util.NOT_IMPL_TIP
import com.gyf.lib.util.OngoingActVo
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
@ -42,8 +45,6 @@ class AssociationViewModel : ViewModel(), TopMenuInterface<AssociationMenu> {
}
data class MemberVo(val name: String)
/**
* 社团会员
*
@ -111,16 +112,11 @@ class MemberViewModel(application: Application) : ScrollViewModel<MemberVo>(appl
}
}
data class OngoingActVo(val name: String)
class OngoingActViewModel : ViewModel() {
private val _act = MutableLiveData<OngoingActVo>()
val act: LiveData<OngoingActVo> = _act
}
data class HistoryActVo(val name: String)
/**
* 历史活动

@ -6,7 +6,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.util.NOT_IMPL_TIP
import com.gyf.lib.util.*
import kotlinx.coroutines.launch
import kotlin.random.Random
@ -22,49 +22,6 @@ enum class ExamActivityType(val menuName: String) {
JOIN_Association("入团申请表")
}
/**
* 题型
*
*/
enum class ExamType(val type: String) {
//选择题
CQ("选择题"),
//开放题
OQ("开放题")
}
sealed class Exam {
abstract val examType: ExamType
abstract val question: StringForm
}
/**
* 开放题
*
* @property examType 题型描述
* @property question 问题
*/
data class OpenQuestionsVo(
override val examType: ExamType = ExamType.OQ, override val question: StringForm
) : Exam()
/**
* 选择题
*
* @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()
/**

@ -1,31 +1,40 @@
package com.gyf.csams.association.model
import android.app.Application
import android.graphics.Bitmap
import android.net.Uri
import androidx.core.graphics.drawable.toBitmap
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import coil.imageLoader
import coil.request.ImageRequest
import com.google.gson.reflect.TypeToken
import com.gyf.csams.MainApplication
import com.gyf.csams.util.SimpleCallback
import com.gyf.lib.BuildConfig
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
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
data class RegAssociationVo(val name: String, val desc: String, val fileId: Int, val token: Token)
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 = ValidStringForm(formDesc = "社团名称", textLength = 5)
val desc = ValidStringForm(formDesc = "社团简介", textLength = 30)
val name = TestStringForm(formDesc = "社团名称", textLength = 5)
val desc = TestStringForm(formDesc = "社团简介", textLength = 30)
private val _picture = MutableLiveData<Uri?>()
val picture: LiveData<Uri?> = _picture
@ -33,10 +42,17 @@ 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
val picturePlaceHolder = "请上传图片"
val errorPicture = "图片加载失败,请联系管理员"
init {
read()
}
fun setPicture(uri: Uri) {
_picture.value = uri
}
@ -49,12 +65,59 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
throw IllegalArgumentException(UNKNOW_ERROR)
}
/**
* 加载历史提交的注册资料可能为空
*
*/
private fun read() {
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(AssociationApi.Read),
HttpCallback<AssociationCheckVo>(action = "加载历史注册资料", onSuccess = { it ->
it.body?.let { it ->
name.setValue(it.name)
desc.setValue(it.desc)
_checkInfo.postValue(it)
val context = getApplication<Application>()
val request = ImageRequest.Builder(context)
.data("${BuildConfig.SERVER_ADDRESS}/${it.logo}")
.target(
onSuccess = { result ->
it.logo.split("/").apply {
File.createTempFile(last(), null, context.cacheDir).apply {
Logger.d("文件路径:${absolutePath}")
FileOutputStream(this).use {
result.toBitmap().compress(
Bitmap.CompressFormat.JPEG,
100,
it
)
}
}
.apply {
Logger.i("读取缓存图片")
setPicture(Uri.fromFile(this))
_fileId.postValue(it.fileId)
}
}
}
)
.build()
context.imageLoader.enqueue(request)
}
},
typeToken = object : TypeToken<ApiResponse<AssociationCheckVo>>() {}.type
),
jsonParam = OnlyToken(clientType = ClientType.Foreground)
)
}
}
fun uploadPhoto(callback: (value: String) -> Unit) {
getInputSteam()?.readBytes()?.apply {
val token = TokenManager.token
if (token != null) {
viewModelScope.launch {
val context = getApplication<MainApplication>()
@ -64,7 +127,7 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
HttpClient.uploadFile(
Api.buildUrl(AssociationApi.Logo),
SimpleCallback<List<Int>>("上传图片", onSuccess = {
HttpCallback<List<Int>>("上传图片", onSuccess = {
Logger.i(it.message)
callback(it.message)
it.body?.let {
@ -74,15 +137,12 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
}, onFail = {
Logger.e(it)
callback("图片上传失败")
}, type = object : TypeToken<ApiResponse<List<Int>>>() {}.type),
token = token,
}, typeToken = object : TypeToken<ApiResponse<List<Int>>>() {}.type),
fileList = arrayOf(cacheFile)
)
}
}
} else {
callback(UNKNOW_ERROR)
}
}
}
@ -94,15 +154,14 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
fun register(callback: (value: String) -> Unit) {
val nameValue = name.formValue.value
val descValue = desc.formValue.value
val token = TokenManager.token
val fileId = _fileId.value
if (token != null && nameValue != null && descValue != null && fileId != null &&
if (nameValue != null && descValue != null && fileId != null &&
name.statusForm.value == FormStatus.Valid && desc.statusForm.value == FormStatus.Valid
) {
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(AssociationApi.Register),
SimpleCallback<Boolean>("注册社团", onSuccess = {
HttpCallback<Boolean>("注册社团", onSuccess = {
Logger.i(it.message)
callback(it.message)
name.clean()
@ -110,13 +169,13 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
_picture.postValue(null)
}, onFail = {
Logger.e(it)
}, object : TypeToken<ApiResponse<Boolean>>() {}.type),
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type),
jsonParam =
RegAssociationVo(
AssociationRegVo(
name = nameValue,
desc = descValue,
token = token,
fileId = fileId
fileId = fileId,
id = checkInfo.value?.id
)
)
}

@ -31,6 +31,8 @@ import com.gyf.csams.activity.ui.ApplyActActivity
import com.gyf.csams.association.model.*
import com.gyf.csams.uikit.*
import com.gyf.lib.uikit.*
import com.gyf.lib.util.HistoryActVo
import com.gyf.lib.util.MemberVo
import com.gyf.lib.util.randomChinese
import com.orhanobut.logger.Logger
@ -44,7 +46,7 @@ class AssociationActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
TestBody { nav, scaffoldState ->
NavBody { nav, scaffoldState ->
val context = LocalContext.current as AssociationActivity
val model: AssociationViewModel = viewModel()
val currentMenuName: AssociationMenu by model.currentMenu.observeAsState(

@ -19,7 +19,9 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.R
import com.gyf.csams.association.model.*
import com.gyf.csams.association.model.ANSWER_SIZE
import com.gyf.csams.association.model.ExamActivityType
import com.gyf.csams.association.model.ExamViewModel
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.BaseTextField
@ -27,6 +29,9 @@ 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
/**

@ -24,6 +24,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
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
@ -33,8 +36,11 @@ import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.*
import com.gyf.lib.util.BottomButton
import com.gyf.lib.util.CheckStatus
import com.gyf.lib.util.ImageUtil
import com.gyf.lib.util.format
import com.orhanobut.logger.Logger
import java.util.*
/**
@ -57,6 +63,7 @@ class RegAssociationActivity : ComponentActivity() {
.weight(0.1F)
)
Title()
Tip()
Name()
Desc(
modifier = Modifier
@ -76,10 +83,12 @@ class RegAssociationActivity : ComponentActivity() {
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 = R.string.reg_btn
confirmDesc = if (checkInfo != null) R.string.reg_again_btn else R.string.reg_btn,
) {
model.register {
scaffoldModel.update(message = it, actionLabel = "返回") {
@ -94,6 +103,46 @@ class RegAssociationActivity : ComponentActivity() {
}
}
@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}")
}
}
})
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)
}
/**
* 社团Logo
@ -187,6 +236,8 @@ class RegAssociationActivity : ComponentActivity() {
bitmap = it, contentDescription = null
)
}
val checkInfo by model.checkInfo.observeAsState()
if (checkInfo == null || checkInfo?.checkStatus == CheckStatus.Finish) {
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceEvenly
@ -203,7 +254,10 @@ class RegAssociationActivity : ComponentActivity() {
scaffoldModel.update("确认上传此图片?", actionLabel = "确定") {
Logger.i("开始上传")
model.uploadPhoto {
scaffoldModel.update(message = it)
scaffoldModel.update(
message = it,
actionLabel = "关闭提示"
)
}
}
}) {
@ -214,6 +268,7 @@ class RegAssociationActivity : ComponentActivity() {
}
}
}
}
} else {
Text(text = model.errorPicture)
}
@ -245,7 +300,11 @@ class RegAssociationActivity : ComponentActivity() {
*/
@Composable
private fun Name(model: RegAssociationViewModel = viewModel()) {
BaseTextField(form = model.name, singeLine = true, modifier = Modifier.fillMaxWidth())
val checkInfo by model.checkInfo.observeAsState()
BaseTextField(
form = model.name, singeLine = true, modifier = Modifier.fillMaxWidth(),
readOnly = checkInfo?.checkStatus != CheckStatus.Finish
)
}
/**
@ -254,7 +313,11 @@ class RegAssociationActivity : ComponentActivity() {
*/
@Composable
private fun Desc(model: RegAssociationViewModel = viewModel(), modifier: Modifier) {
BaseTextField(form = model.desc, modifier = modifier)
val checkInfo by model.checkInfo.observeAsState()
BaseTextField(
form = model.desc, modifier = modifier,
readOnly = checkInfo?.checkStatus != CheckStatus.Finish
)
}
}

@ -3,12 +3,9 @@ package com.gyf.csams.main.model
import android.app.Application
import androidx.lifecycle.*
import com.google.gson.reflect.TypeToken
import com.gyf.csams.account.model.UserVo
import com.gyf.csams.uikit.AbstractComment
import com.gyf.csams.util.SimpleCallback
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.PersonInfoVo
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.*
@ -17,9 +14,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
data class LeaveMessageFormatVo(val message: String, val user: UserVo)
class NotificationViewModel(application: Application) : AndroidViewModel(application) {
private val _count = MutableLiveData<Long>()
@ -35,27 +29,24 @@ class NotificationViewModel(application: Application) : AndroidViewModel(applica
*/
private fun count() {
viewModelScope.launch {
TokenManager.token?.let {
HttpClient.post(
url = Api.buildUrl(NotificationApi.Count),
SimpleCallback<Long>(action = "未读通知计数", onSuccess = {
HttpCallback<Long>(action = "未读通知计数", onSuccess = {
it.body?.let {
_count.postValue(it)
}
Logger.i(it.message)
}, onFail = {
Logger.e(it)
}, type = object : TypeToken<ApiResponse<Long>>() {}.type),
}, typeToken = object : TypeToken<ApiResponse<Long>>() {}.type),
jsonParam = NotificationDto(
receiverId = it.id,
receiverId = TokenManager.getToken().id,
receiverClient = ClientType.Foreground,
token = it
)
)
}
}
}
}
/**
* 跑马灯
@ -87,11 +78,9 @@ class MarqueeViewModel : AbstractComment() {
* @param callback
*/
override fun send(callback: (message: String) -> Unit) {
TokenManager.token.let {
if (it != null) {
viewModelScope.launch {
HttpClient.post(url = Api.buildUrl(MainApi.LeaveMessage),
SimpleCallback<Boolean>(
HttpCallback<Boolean>(
action = "发送留言", onSuccess = {
if (it.body == true) {
callback("留言发送成功")
@ -102,18 +91,13 @@ class MarqueeViewModel : AbstractComment() {
}
}, onFail = {
callback("留言发送失败")
}, type = object : TypeToken<ApiResponse<Boolean>>() {}.type
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type
),
jsonParam = LeaveMessageVo(
message = newContent.formValue.value ?: "",
token = it
message = newContent.formValue.value ?: ""
)
)
}
} else {
callback(UNKNOW_ERROR)
}
}
}
@ -121,16 +105,22 @@ class MarqueeViewModel : AbstractComment() {
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(MainApi.GetMessage),
SimpleCallback<List<LeaveMessageFormatVo>>(action = "获取留言", onSuccess = {
HttpCallback<List<LeaveMessageFormatVo>>(
action = "获取留言",
onSuccess = {
// callback(it.message)
Logger.i(it.message)
it.body?.let {
_marqueeTexts.postValue(it)
}
}, onFail = {
},
onFail = {
Logger.e(it)
callback("留言获取失败")
}, type = object : TypeToken<ApiResponse<List<LeaveMessageFormatVo>>>() {}.type),
},
typeToken = object :
TypeToken<ApiResponse<List<LeaveMessageFormatVo>>>() {}.type
),
jsonParam = OnlyToken(clientType = ClientType.Foreground)
)
}
@ -153,67 +143,47 @@ class MarqueeViewModel : AbstractComment() {
}
/**
* 社团
*
* @property name 社团名称
*/
data class AssociationListVo(val name: String)
data class LeaveMessageVo(val message: String, val token: Token)
/**
* 社团列表
*
*/
class ListViewModel(application: Application) : ScrollViewModel<AssociationListVo>(application) {
class ListViewModel(application: Application) : ScrollViewModel<AssociationVo>(application) {
val name = StringForm(formDesc = "社团名称", textLength = 5)
val desc = StringForm(formDesc = "社团简介", textLength = 10)
//社团列表加载数量
val associationListSize = 10
//社团列表
private val _associationList = MutableLiveData<MutableList<AssociationListVo>>(mutableListOf())
val associationListVo: LiveData<MutableList<AssociationListVo>> = _associationList
val searchDesc = "搜索"
/**
* TODO 社团检索
*
* @param callback
*/
fun search(callback: (value: String) -> Unit) {
Logger.i("搜索条件[社团名称:${name.formValue.value},社团简介:${desc.formValue.value}]")
callback(NOT_IMPL_TIP)
}
override val initSize: Int = 10
init {
load()
load {}
}
/**
* 加载社团列表
*
*/
override fun load() {
override fun load(callback: (message: String) -> Unit) {
viewModelScope.launch {
_associationList.value?.apply {
repeat(
10
) {
add(AssociationListVo(name = "社团${_associationList.value?.size}"))
}
HttpClient.post(
Api.buildUrl(AssociationApi.List),
HttpCallback<MutableList<AssociationVo>>(
action = "加载社团列表",
onSuccess = {
it.body?.let {
_data.postValue(it)
}
Logger.i("初始化社团列表size=${_associationList.value?.size}")
callback(it.message)
},
typeToken = object :
TypeToken<ApiResponse<MutableList<AssociationVo>>>() {}.type
),
jsonParam = SearchAssociationVo(
name = name.formValue.value ?: "",
desc = desc.formValue.value ?: ""
)
)
}
}
@ -222,41 +192,10 @@ class ListViewModel(application: Application) : ScrollViewModel<AssociationListV
*
*/
override fun loadMore(callback: (message: String) -> Unit) {
viewModelScope.launch {
_associationList.value?.apply {
val list = mutableListOf<AssociationListVo>()
list.addAll(this)
list.apply {
repeat(10) {
add(AssociationListVo(name = "社团${size}"))
}
}
Logger.i("t.size=${size}")
_associationList.postValue(list)
Logger.i("加载更多社团size=${_associationList.value?.size}")
callback("成功加载更多社团")
}
}
TODO(NOT_IMPL_TIP)
}
}
/**
* 我的信息
*
* @property studentId 学号
* @property name 姓名
* @property duty 职务
* @property headImg 头像
* @property desc 个人简介
*/
data class InfoVo(
val studentId: String,
override val name: String,
override val duty: String,
override val headImg: String,
override val desc: String
) : PersonInfoVo()
/**
* 个人中心
@ -268,8 +207,6 @@ class CenterViewModel : ViewModel() {
val myLikeActivity = "我点赞的社团活动"
val myCollectActivity = "我收藏的社团活动"
private val _info = MutableLiveData<InfoVo>()
val info: LiveData<InfoVo> = _info
init {
load()
@ -277,13 +214,7 @@ class CenterViewModel : ViewModel() {
private fun load() {
viewModelScope.launch {
_info.value = InfoVo(
studentId = randomNum(),
name = randomChinese(3),
duty = randomChinese(3),
headImg = "",
desc = randomChinese(10)
)
TODO()
}
}

@ -33,8 +33,7 @@ import com.gyf.csams.message.ui.MessageActivity
import com.gyf.csams.uikit.*
import com.gyf.lib.service.BaseActivity
import com.gyf.lib.uikit.*
import com.gyf.lib.util.ClientType
import com.gyf.lib.util.randomChinese
import com.gyf.lib.util.*
/**
@ -51,7 +50,7 @@ class MainActivity : BaseActivity() {
setContent {
val imageViewModel: ImageViewModel = viewModel()
TestBody { nav, scaffoldState ->
NavBody { nav, scaffoldState ->
NavHost(navController = nav, startDestination = MainMenu.Main.name) {
composable(MainMenu.Main.name) {
Main(navController = nav)
@ -94,16 +93,11 @@ class MainActivity : BaseActivity() {
mainMenu = MainMenu.Center,
nav = navController
) {
val info by model.info.observeAsState()
info?.let {
Profile(
modifier = Modifier
.weight(0.3F)
.padding(10.dp),
personInfoVo = it
.padding(10.dp)
)
}
Column(
@ -193,7 +187,11 @@ class MainActivity : BaseActivity() {
mainMenu = MainMenu.List,
nav = navController
) {
val memberVo: AssociationMemberVo? =
(TokenManager.getOwnInfo() as? UserVo)?.associationMemberVo
if (memberVo == null) {
RegisterAssociation()
}
AssociationSearch()
AssociationListBody()
}
@ -233,7 +231,7 @@ class MainActivity : BaseActivity() {
model: ListViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel()
) {
val associationListList: MutableList<AssociationListVo>? by model.associationListVo.observeAsState()
val associationListList: MutableList<AssociationVo>? by model.data.observeAsState()
val listState = rememberLazyListState()
LazyColumn(state = listState) {
@ -267,13 +265,13 @@ class MainActivity : BaseActivity() {
}
}
if (listState.layoutInfo.totalItemsCount - listState.firstVisibleItemIndex == model.associationListSize / 2 - 1) {
if (listState.layoutInfo.totalItemsCount - listState.firstVisibleItemIndex == model.initSize / 2 - 1) {
model.loadMore { scaffoldModel.update(message = it) }
}
}
@Composable
private fun Association(associationListVo: AssociationListVo) {
private fun Association(associationListVo: AssociationVo) {
val context = LocalContext.current
Card(modifier = Modifier.clickable(onClick = {
context.startActivity(
@ -307,7 +305,6 @@ class MainActivity : BaseActivity() {
model: ListViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel()
) {
Card(modifier = Modifier.padding(horizontal = 50.dp, vertical = 10.dp)) {
Column {
@ -329,7 +326,11 @@ class MainActivity : BaseActivity() {
modifier = Modifier.fillMaxWidth()
) {
OutlinedButton(
onClick = { model.search { scaffoldModel.update(message = it) } },
onClick = {
model.load {
scaffoldModel.update(message = it)
}
},
modifier = Modifier.width(100.dp)
) {
Text(text = model.searchDesc)

@ -0,0 +1,11 @@
package com.gyf.csams.message.model
import android.app.Application
import com.gyf.lib.model.SysMessageViewModel
import com.gyf.lib.util.ClientType
class ForegroundViewModel(application: Application) : SysMessageViewModel(application) {
override fun clientType(): ClientType {
return ClientType.Foreground
}
}

@ -18,12 +18,17 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.message.model.*
import com.gyf.csams.message.model.ForegroundViewModel
import com.gyf.csams.message.model.MessageType
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.uikit.TextTopAppBar
import com.gyf.lib.model.ActCheckContent
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 java.util.*
@ -54,7 +59,7 @@ class SysMessageActivity : ComponentActivity() {
@Composable
private fun MessageList(
modifier: Modifier = Modifier,
model: SysMessageViewModel = viewModel()
model: ForegroundViewModel = viewModel()
) {
val listState = rememberLazyListState()
val list by model.data.observeAsState()

@ -23,7 +23,6 @@ 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.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -591,7 +590,6 @@ fun MainContent() {
.padding(32.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
var textState by remember { mutableStateOf(TextFieldValue()) }
val localFocusManager = LocalFocusManager.current
val focusRequester = FocusRequester()

@ -8,11 +8,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.R
import com.gyf.csams.util.SimpleCallback
import com.gyf.lib.util.Api
import com.gyf.lib.util.HttpClient
import com.gyf.lib.util.ImageUtil
import com.gyf.lib.util.MainApi
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@ -57,7 +53,7 @@ class ImageViewModel(application: Application) : AndroidViewModel(application) {
HttpClient.get(
url = urlPath,
SimpleCallback<List<String>>("获取轮播图", onSuccess = {
HttpCallback<List<String>>("获取轮播图", onSuccess = {
_imageUrls.postValue(it.body)
var index = 0
_imageUrls.value?.apply {
@ -81,7 +77,7 @@ class ImageViewModel(application: Application) : AndroidViewModel(application) {
Logger.e("无法从接口地址:${urlPath}获取图片url列表")
_error.postValue(it)
defaultLoad()
}, type = object : TypeToken<List<String>>() {}.type)
}, typeToken = object : TypeToken<ApiResponse<List<String>>>() {}.type)
)
}.apply {
start()

@ -2,8 +2,14 @@ package com.gyf.csams.util
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import com.gyf.csams.association.model.*
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.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> {

@ -2,8 +2,8 @@ package com.gyf.csams.util
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.gyf.csams.association.model.Exam
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.Exam
import com.gyf.lib.util.HttpCallback
import java.lang.reflect.Type
@ -14,9 +14,9 @@ import java.lang.reflect.Type
* @property action
* @property onSuccess
* @property onFail
* @property type
*/
class SimpleCallback<T>(
class ExamCallback<T>(
private val action: String,
private val onSuccess: (res: ApiResponse<T>) -> Unit,
private val onFail: (error: String) -> Unit,

@ -15,4 +15,5 @@
<string name="student_id_format">入学年份(四位)+班级代码(两位)+学生代码(两位)</string>
<string name="welcome_start">同学您好\n</string>
<string name="welcome_end">欢迎使用</string>
<string name="reg_again_btn">再次申请</string>
</resources>

@ -15,4 +15,5 @@
<string name="student_id_format">入学年份(四位)+班级代码(两位)+学生代码(两位)</string>
<string name="welcome_start">同学您好\n</string>
<string name="welcome_end">欢迎使用</string>
<string name="reg_again_btn">再次申请</string>
</resources>

@ -15,4 +15,5 @@
<string name="student_id_format">入学年份(四位)+班级代码(两位)+学生代码(两位)</string>
<string name="welcome_start">同学您好\n</string>
<string name="welcome_end">欢迎使用</string>
<string name="reg_again_btn">再次申请</string>
</resources>

@ -2,6 +2,7 @@ package com.gyf.csams
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.randomChinese
import org.junit.Assert.assertEquals
@ -39,9 +40,12 @@ class ExampleUnitTest {
repeat(100) {
println(randomChinese())
}
}
// println(java.time.format.DateTimeFormatter.ISO_INSTANT
// .format(java.time.Instant.ofEpochMilli(1532358895000)))
@Test
fun testFind() {
val s = StringForm(formDesc = "", textLength = 3)
println("${s.formValue.value}")
}
@Test

@ -54,12 +54,13 @@ dependencies {
//生命周期组件版本
val lifecycle_version = "2.3.1"
//https://developer.android.com/topic/libraries/architecture/workmanager/basics?hl=zh-cn
val work_version = "2.5.0"
val room_version = "2.3.0"
// Kotlin + coroutines
api("androidx.work:work-runtime-ktx:$work_version")
val accompanist_version = "0.10.0"
//https://github.com/google/accompanist
api("com.google.accompanist:accompanist-insets:$accompanist_version")
api("com.google.accompanist:accompanist-systemuicontroller:$accompanist_version")
api("com.google.accompanist:accompanist-coil:$accompanist_version")
/**
* 针对最新的平台功能和 API 调整应用,同时还支持旧设备。
* https://developer.android.com/jetpack/androidx/releases/core

@ -30,7 +30,7 @@ abstract class AbstractLoginViewModel(application: Application) : AndroidViewMod
/**
* 完成登录状态
*/
private val _finishLogin = MutableLiveData<Boolean>()
protected val _finishLogin = MutableLiveData<Boolean>()
val finishLogin: LiveData<Boolean> = _finishLogin
abstract fun checkForm(): Boolean
@ -55,9 +55,9 @@ abstract class AbstractLoginViewModel(application: Application) : AndroidViewMod
val id = "${id.formValue.value}"
val password = "${password.formValue.value}"
Logger.i("使用账号:$id,密码:$password 进行登录")
HttpClient.post(
url,
HttpCallback<Token>(
val call = when (clientType) {
ClientType.Background -> HttpCallback<ManagerVo>(
action = loginDesc,
onSuccess = {
Logger.i(it.message)
@ -66,16 +66,40 @@ abstract class AbstractLoginViewModel(application: Application) : AndroidViewMod
it.body?.let {
val db = AppDatabase.getInstance(context)
viewModelScope.launch {
TokenManager.token = it
db?.tokenDao()?.save(token = it)
TokenManager.init(it)
db?.tokenDao()?.save(token = it.token)
}.invokeOnCompletion {
_finishLogin.postValue(true)
}
}
},
onFail = { callback(it) },
type = object : TypeToken<ApiResponse<Token>>() {}.type
),
typeToken = object : TypeToken<ApiResponse<ManagerVo>>() {}.type
)
ClientType.Foreground -> HttpCallback<UserVo>(
action = loginDesc,
onSuccess = {
Logger.i(it.message)
callback(it.message)
val context = getApplication<Application>().applicationContext
it.body?.let {
val db = AppDatabase.getInstance(context)
viewModelScope.launch {
TokenManager.init(it)
db?.tokenDao()?.save(token = it.token)
}.invokeOnCompletion {
_finishLogin.postValue(true)
}
}
},
onFail = { callback(it) },
typeToken = object : TypeToken<ApiResponse<UserVo>>() {}.type
)
}
HttpClient.post(
url,
call,
jsonParam = loginParam()
)
} else {
@ -91,8 +115,7 @@ abstract class AbstractLoginViewModel(application: Application) : AndroidViewMod
* @param callback
*/
fun logout(context: Activity, callback: (message: String) -> Unit) {
TokenManager.token?.let {
Logger.i("帐号${it.id}将要退出登录")
viewModelScope.launch {
HttpClient.post(
Api.buildUrl(AccountApi.Logout),
@ -112,16 +135,16 @@ abstract class AbstractLoginViewModel(application: Application) : AndroidViewMod
)
)
}
TokenManager.token = null
TokenManager.clear()
}
}
callback(it.message)
}, onFail = { callback("退出登陆失败") },
type = object : TypeToken<ApiResponse<Boolean>>() {}.type
},
onFail = { callback("退出登陆失败") },
typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type
),
jsonParam = OnlyToken(token = it, clientType = clientType)
jsonParam = OnlyToken(clientType = clientType)
)
}
}
}
}

@ -5,6 +5,9 @@ import com.gyf.lib.R
import com.gyf.lib.uikit.StringForm
abstract class ApplyViewModel<T>(application: Application) : ScrollViewModel<T>(application) {
val approverOrigin =
StringForm(formDesc = application.getString(R.string.approver_origin), textLength = 30)
val approveOrigin =
StringForm(
formDesc = application.getString(R.string.first_approver_origin),
textLength = 30
)
}

@ -9,6 +9,7 @@ import com.google.gson.reflect.TypeToken
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
import java.lang.reflect.Type
class InitViewModel(application: Application) : AndroidViewModel(application) {
/**
@ -17,7 +18,6 @@ class InitViewModel(application: Application) : AndroidViewModel(application) {
private val _isNetWorkWorking = MutableLiveData<Boolean>()
val isNetWorkWorking: LiveData<Boolean> = _isNetWorkWorking
init {
checkServer()
}
@ -32,7 +32,7 @@ class InitViewModel(application: Application) : AndroidViewModel(application) {
}, onFail = {
Logger.e(it)
_isNetWorkWorking.postValue(false)
}, type = object : TypeToken<ApiResponse<Boolean>>() {}.type)
}, typeToken = object : TypeToken<ApiResponse<Boolean>>() {}.type)
)
}
@ -41,7 +41,10 @@ class InitViewModel(application: Application) : AndroidViewModel(application) {
/**
* 查询本地是否有且只有一个用户token如果有则自动登录
*/
fun hasOnlyUserToken(onSuccess: () -> Unit, onFail: () -> Unit, api: AccountApi) {
fun <T : OwnInfoVo> hasOnlyUserToken(
onSuccess: () -> Unit, onFail: () -> Unit, api: AccountApi,
typeToken: Type
) {
viewModelScope.launch {
val context = getApplication<Application>()
val db = AppDatabase.getInstance(context)
@ -53,14 +56,12 @@ class InitViewModel(application: Application) : AndroidViewModel(application) {
Logger.i("${action}api=$url")
HttpClient.post(
url,
HttpCallback<Boolean>(
HttpCallback<T>(
action = action,
onSuccess = { it ->
it.body?.let {
Logger.i("token校验结果:${it}")
if (it) {
TokenManager.token = currentToken
}
TokenManager.init(it)
onSuccess()
}
@ -68,11 +69,10 @@ class InitViewModel(application: Application) : AndroidViewModel(application) {
onFail = {
Logger.e(it)
onFail()
},
type = object : TypeToken<ApiResponse<Boolean>>() {}.type
),
}, typeToken = typeToken),
jsonParam = currentToken
)
} else if (tokenList != null && tokenList.size > 1) {
//TODO 实现切换历史登录帐号

@ -11,8 +11,13 @@ abstract class ScrollViewModel<T>(application: Application) : AndroidViewModel(a
abstract val initSize: Int
//加载列表
abstract fun load()
@Deprecated("")
open fun load() {
}
open fun load(callback: (message: String) -> Unit) {}
//加载更多数据
abstract fun loadMore(callback: (message: String) -> Unit)

@ -1,12 +1,10 @@
package com.gyf.csams.message.model
package com.gyf.lib.model
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.util.SimpleCallback
import com.gyf.lib.model.ScrollViewModel
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
@ -19,8 +17,6 @@ enum class SystemType(val desc: String) {
}
data class NotificationVo(val title: String, val content: String, val id: Int, val createTime: Long)
/**
* 通知内容
*
@ -82,7 +78,8 @@ data class RenameContent(
* 系统通知数据状态管理
*
*/
class SysMessageViewModel(application: Application) : ScrollViewModel<NotificationVo>(application) {
abstract class SysMessageViewModel(application: Application) :
ScrollViewModel<NotificationVo>(application) {
val title = "系统通知"
override val initSize: Int = 10
@ -90,20 +87,22 @@ class SysMessageViewModel(application: Application) : ScrollViewModel<Notificati
private val _currentPage = MutableLiveData<Long>()
val currentPage: LiveData<Long> = _currentPage
abstract fun clientType(): ClientType
init {
load()
}
/**
*加载通知列表
*
*/
override fun load() {
final override fun load() {
viewModelScope.launch {
TokenManager.token?.let { it ->
HttpClient.post(
Api.buildUrl(NotificationApi.List),
SimpleCallback<MutableList<NotificationVo>>(
HttpCallback<MutableList<NotificationVo>>(
action = "获取通知列表",
onSuccess = {
it.body?.let {
@ -114,13 +113,12 @@ class SysMessageViewModel(application: Application) : ScrollViewModel<Notificati
onFail = {
Logger.e(it)
},
type = object :
typeToken = object :
TypeToken<ApiResponse<MutableList<NotificationVo>>>() {}.type
),
jsonParam = NotificationDto(
receiverId = it.id,
receiverClient = ClientType.Foreground,
token = it, page = PageDto(
receiverId = TokenManager.getToken().id,
receiverClient = clientType(), page = PageDto(
currentPage = _currentPage.value ?: 1,
pageSize = initSize
)
@ -128,7 +126,6 @@ class SysMessageViewModel(application: Application) : ScrollViewModel<Notificati
)
}
}
}
/**
*TODO

@ -32,13 +32,11 @@ class MessageService : JobIntentService() {
}
override fun onHandleWork(intent: Intent) {
TokenManager.token?.let { it ->
HttpClient.postAsync<List<NotificationVo>>(
url = Api.buildUrl(NotificationApi.Pull),
jsonParam = NotificationDto(
receiverId = it.id,
receiverClient = clientType,
token = it
receiverId = TokenManager.getToken().id,
receiverClient = clientType
),
type = object : TypeToken<ApiResponse<List<NotificationVo>>>() {}.type
)?.let { it1 ->
@ -60,4 +58,3 @@ class MessageService : JobIntentService() {
}
}
}
}

@ -12,12 +12,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel
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
import kotlinx.coroutines.launch
import java.lang.reflect.Type
abstract class AbstractInitActivity : ComponentActivity() {
abstract class AbstractInitActivity<T : OwnInfoVo> : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -49,13 +51,15 @@ abstract class AbstractInitActivity : ComponentActivity() {
abstract val api: AccountApi
abstract val typeToken: Type
private fun init(initViewModel: InitViewModel) {
//后台检查token
initViewModel.hasOnlyUserToken(onSuccess = {
initViewModel.hasOnlyUserToken<T>(onSuccess = {
startActivity(Intent(this, main))
}, onFail = {
startActivity(Intent(this, login))
}, api = api)
}, api = api, typeToken = typeToken)
GlobalScope.launch {
delay(1000)
finish()

@ -8,6 +8,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.insets.ExperimentalAnimatedInsets
import com.google.accompanist.insets.ProvideWindowInsets
import com.gyf.lib.uikit.theme.CSAMSTheme
@ -157,9 +159,24 @@ fun Body(content: @Composable () -> Unit) {
}
}
@ExperimentalAnimatedInsets
@Composable
fun ImeBody(content: @Composable () -> Unit) {
CSAMSTheme {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Surface(color = MaterialTheme.colors.background) {
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState) {
content()
ShowSnackbar(scaffoldState = scaffoldState)
}
}
}
}
}
@Composable
fun TestBody(content: @Composable (nav: NavHostController, scaffoldState: ScaffoldState) -> Unit) {
fun NavBody(content: @Composable (nav: NavHostController, scaffoldState: ScaffoldState) -> Unit) {
CSAMSTheme {
Surface(color = MaterialTheme.colors.background) {
val navController = rememberNavController()
@ -171,3 +188,4 @@ fun TestBody(content: @Composable (nav: NavHostController, scaffoldState: Scaffo
}
}
}

@ -12,21 +12,23 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.gyf.lib.util.ImageUtil
import com.gyf.lib.util.*
abstract class PersonInfoVo {
abstract val name: String
abstract val duty: String
abstract val headImg: String
abstract val desc: String
}
@Composable
fun Profile(
modifier: Modifier,
personInfoVo: PersonInfoVo,
duty: @Composable () -> Unit = { Text(text = personInfoVo.duty) }
personInfoVo: PersonInfoVo = TokenManager.getOwnInfo(),
duty: @Composable () -> Unit = {
Text(
text = when (personInfoVo) {
is UserVo -> personInfoVo.manager?.duty?.desc ?: "----"
is ManagerVo -> personInfoVo.duty.desc
is ManagerInfoVo -> personInfoVo.duty.desc
else -> throw IllegalArgumentException("个人信息类型错误:${personInfoVo}")
}
)
}
) {
var headImg: ImageBitmap? by remember {
mutableStateOf(null)

@ -33,7 +33,6 @@ enum class AccountApi(val path: String) : UrlPath {
//后台登陆
BackgroundLogin("/login/${ClientType.Background.name.toLowerCase(Locale.ROOT)}"),
//前台令牌校验
ForegroundToken("${ForegroundLogin.path}/token"),
@ -74,8 +73,12 @@ enum class MainApi(val path: String) : UrlPath {
*/
enum class AssociationApi(val path: String) : UrlPath {
Logo("/uploadLogo"),
Register("/register");
Register("/register"),
Accept("/accept"),
List("/list"),
Check("/check"),
Audit("/audit"),
Read("/read");
override fun build(): String {
return "/api/association${this.path}"

@ -22,7 +22,8 @@ fun BottomButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
@StringRes confirmDesc: Int = R.string.confirm_btn,
@StringRes backDesc: Int = R.string.back_btn,
@StringRes backDesc: Int? = R.string.back_btn,
onBack: (() -> Unit)? = null,
onConfirm: () -> Unit
) {
val context = LocalContext.current as Activity
@ -34,11 +35,16 @@ fun BottomButton(
) {
Text(text = stringResource(id = confirmDesc))
}
if (backDesc != null) {
Spacer(modifier = Modifier.width(10.dp))
OutlinedButton(onClick = {
context.onBackPressed()
if (onBack == null) context.onBackPressed() else onBack()
}, modifier = Modifier.background(color = MaterialTheme.colors.secondary)) {
Text(text = stringResource(id = backDesc))
}
}
}
}

@ -1,6 +1,7 @@
package com.gyf.lib.util
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.orhanobut.logger.Logger
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
@ -116,8 +117,9 @@ object HttpClient {
* @param url
* @param callback
*/
fun uploadFile(url: String, callback: Callback, token: Token, vararg fileList: File) {
fun uploadFile(url: String, callback: Callback, vararg fileList: File) {
Logger.i("request url=$url")
val token = TokenManager.getToken()
val body = MultipartBody.Builder()
.addFormDataPart("id", token.id.toString())
.addFormDataPart("token", token.token)
@ -155,9 +157,9 @@ interface GsonBuilderInterface {
open class HttpCallback<T>(
private val action: String,
private val onSuccess: (res: ApiResponse<T>) -> Unit,
private val onFail: (error: String) -> Unit,
private val type: Type
private val onSuccess: (res: ApiResponse<T>) -> Unit = { Logger.i(it.message) },
private val onFail: (error: String) -> Unit = { Logger.e(it) },
private val typeToken: Type
) : Callback, GsonBuilderInterface {
override val gson: Gson = Gson()
@ -178,11 +180,18 @@ open class HttpCallback<T>(
val body = response.body
if (body != null && body.contentType()?.subtype == "json") {
val jsonRes = body.string()
Logger.json(jsonRes)
val res: ApiResponse<T> = gson.fromJson(jsonRes, type)
Logger.i("json解析成功:$res")
Logger.i(jsonRes)
try {
val res: ApiResponse<T> = gson.fromJson(jsonRes, typeToken)
onSuccess(res)
} catch (e: JsonSyntaxException) {
Logger.e(e, "json反序列化成${typeToken}失败")
onFail("json反序列化成${typeToken}失败")
} catch (e: Exception) {
Logger.e(e, "发生未知错误")
onFail("发生未知错误")
}
} else {
onFail("${action}失败,请联系管理员")
Logger.e("无法解析${action}请求响应数据:,响应码:${response.code},${response.body}")

@ -17,16 +17,11 @@ data class Token(
)
abstract class ClientBaseVo {
abstract val token: Token
val token: Token = TokenManager.getToken()
abstract val clientType: ClientType
}
abstract class BaseToken {
abstract val token: Token
}
data class OnlyToken(
override val token: Token = TokenManager.token ?: throw IllegalArgumentException("无法获取token"),
override val clientType: ClientType
) : ClientBaseVo()
@ -46,8 +41,39 @@ interface TokenDao {
}
object TokenManager {
var token: Token? = null
private var ownInfo: OwnInfoVo? = null
private lateinit var personInfo: PersonInfoVo
fun init(ownInfo: OwnInfoVo) {
this.ownInfo = ownInfo
when (ownInfo) {
is ManagerVo -> this.personInfo = ManagerInfoVo(
duty = ownInfo.duty,
name = ownInfo.name,
headImg = ownInfo.headImg,
desc = ownInfo.desc
)
is UserVo -> this.personInfo =
UserInfoVo(name = ownInfo.name, headImg = ownInfo.headImg, desc = ownInfo.desc)
else -> throw IllegalArgumentException("token失败")
}
}
fun clear() {
ownInfo = null
}
fun getOwnInfo(): OwnInfoVo {
return ownInfo ?: throw IllegalArgumentException("token没有初始化,非法调用")
}
fun getPersonInfo(): PersonInfoVo {
return personInfo
}
fun getToken(): Token {
return ownInfo?.token ?: throw IllegalArgumentException("token没有初始化,非法调用")
}
}

@ -0,0 +1,477 @@
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 associationMemberVo: AssociationMemberVo?
) : 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
)
data class ActivityMemberVo(val studentId: String, val name: String)
/**
* 活动成员
*
* @property organizer
* @property participant
*/
data class ActivityMembersVo(
val organizer: ActivityMemberVo,
val participant: MutableList<ActivityMemberVo>?
)
/**
* 图片
* @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 id: 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 id: 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("审核完成")
}
data class AssociationMemberVo(
val association: AssociationVo,
val isHead: Boolean
)
//用户社团
data class AssociationCheckVo(
override val id: Int,
override val name: String,
override val desc: String,
override val logo: String,
override val faculty: AssociationFaculty,
override val level: AssociationLevel?,
val checkStatus: CheckStatus,
val applyTime: Long,
val firstCause: String,
val lastCause: String?,
val fileId: Int
) : 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 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 MemberVo(val name: 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 id: Int?, val name: String, val desc: String, val fileId: Int,
override val clientType: ClientType = ClientType.Foreground
) : ClientBaseVo()
/**
* 社团注册审核记录
*
*/
data class DisposeRegInfoVo(
val name: String,
val desc: String,
val logo: String,
val log: AuditLoggingVo
)
/**
* 通用审核记录
*
* @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 regId
* @property clientType
*/
data class AcceptRegAssociation(
val regId: Int,
val isFirstAccept: Boolean,
override val clientType: ClientType = ClientType.Background
) : ClientBaseVo()
/**
* 社团注册资料审核
*
* @property regId
* @property result
* @property cause
* @property clientType
*/
data class CheckRegVo(
val regId: Int, val result: Boolean, val cause: String,
override val clientType: ClientType = ClientType.Background
) : ClientBaseVo()
/**
* 换名申请表
*
* @property studentId 学号
* @property oldName 社团原名
* @property newName 社团新名
* @property reason 申请理由
*/
data class RenameVo(
val studentId: String,
val oldName: String,
val newName: String,
val reason: String
)

@ -1,27 +0,0 @@
package com.gyf.lib.util
data class NotificationVo(val title: String, val content: String, val id: Int)
data class PageDto(val currentPage: Long, val pageSize: Int = 10)
data class NotificationDto(
val receiverId: Int,
val receiverClient: ClientType,
override val token: Token,
val page: PageDto? = null,
override val clientType: ClientType = receiverClient
) : ClientBaseVo()
/**
* 客户端类型
*
*/
enum class ClientType {
//前台
Foreground,
//后台
Background
}

@ -17,9 +17,17 @@
<string name="oldname">社团原名</string>
<string name="newname">社团新名</string>
<string name="reason_for_application">申请理由</string>
<string name="approver">审批</string>
<string name="approver_origin">审核理由</string>
<string name="first_approver">初审负责</string>
<string name="first_approver_origin">初审意见</string>
<string name="not_impl_error">抱歉此功能尚未开放</string>
<string name="quality_report_title">活动质量汇报单</string>
<string name="login_btn">登录</string>
<string name="association_reg_title">社团注册资料</string>
<string name="association_desc">社团简介</string>
<string name="association_logo">社团logo</string>
<string name="association_name">社团名称</string>
<string name="approver_result">审核结果</string>
<string name="recheck_btn">复审</string>
<string name="audit_phases">审核阶段</string>
<string name="last_approver_origin">复审意见</string>
</resources>

@ -17,9 +17,17 @@
<string name="oldname">社团原名</string>
<string name="newname">社团新名</string>
<string name="reason_for_application">申请理由</string>
<string name="approver">审批</string>
<string name="approver_origin">审核理由</string>
<string name="first_approver">初审负责</string>
<string name="first_approver_origin">初审意见</string>
<string name="not_impl_error">抱歉此功能尚未开放</string>
<string name="quality_report_title">活动质量汇报单</string>
<string name="login_btn">登录</string>
<string name="association_reg_title">社团注册资料</string>
<string name="association_desc">社团简介</string>
<string name="association_logo">社团logo</string>
<string name="association_name">社团名称</string>
<string name="approver_result">审核结果</string>
<string name="recheck_btn">复审</string>
<string name="audit_phases">审核阶段</string>
<string name="last_approver_origin">复审意见</string>
</resources>

@ -17,9 +17,17 @@
<string name="oldname">社团原名</string>
<string name="newname">社团新名</string>
<string name="reason_for_application">申请理由</string>
<string name="approver">审批</string>
<string name="approver_origin">审核理由</string>
<string name="first_approver">初审负责</string>
<string name="first_approver_origin">初审意见</string>
<string name="not_impl_error">抱歉此功能尚未开放</string>
<string name="quality_report_title">活动质量汇报单</string>
<string name="login_btn">登录</string>
<string name="association_reg_title">社团注册资料</string>
<string name="association_desc">社团简介</string>
<string name="association_logo">社团logo</string>
<string name="association_name">社团名称</string>
<string name="approver_result">审核结果</string>
<string name="recheck_btn">复审</string>
<string name="audit_phases">审核阶段</string>
<string name="last_approver_origin">复审意见</string>
</resources>
Loading…
Cancel
Save