You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
675 lines
18 KiB
675 lines
18 KiB
package com.gyf.csams.uikit
|
|
|
|
import android.app.Activity
|
|
import androidx.annotation.DrawableRes
|
|
import androidx.compose.animation.Crossfade
|
|
import androidx.compose.animation.core.*
|
|
import androidx.compose.foundation.Image
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.border
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.layout.*
|
|
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.focus.FocusRequester
|
|
import androidx.compose.ui.focus.focusRequester
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.DefaultAlpha
|
|
import androidx.compose.ui.graphics.ImageBitmap
|
|
import androidx.compose.ui.layout.ContentScale
|
|
import androidx.compose.ui.platform.LocalContext
|
|
import androidx.compose.ui.platform.LocalFocusManager
|
|
import androidx.compose.ui.res.painterResource
|
|
import androidx.compose.ui.text.buildAnnotatedString
|
|
import androidx.compose.ui.text.style.TextOverflow
|
|
import androidx.compose.ui.text.withStyle
|
|
import androidx.compose.ui.tooling.preview.Preview
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.lifecycle.LiveData
|
|
import androidx.lifecycle.MutableLiveData
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
import androidx.navigation.NavHostController
|
|
import com.google.accompanist.coil.rememberCoilPainter
|
|
import com.gyf.csams.MainApplication
|
|
import com.gyf.csams.R
|
|
import com.gyf.csams.main.model.MarqueeViewModel
|
|
import com.gyf.csams.module.AuditCheckVo
|
|
import com.gyf.csams.module.CheckStatus
|
|
import com.gyf.lib.uikit.*
|
|
import com.gyf.lib.util.DateTimeUtil.datetimeFormat
|
|
import com.orhanobut.logger.Logger
|
|
import java.util.*
|
|
|
|
|
|
/**
|
|
* 主菜单
|
|
*
|
|
*/
|
|
enum class MainMenu(
|
|
@DrawableRes override val selectedIcon: Int,
|
|
@DrawableRes override val unSelectedIcon: Int
|
|
) : BottomBarMenu {
|
|
//主页
|
|
Main(R.drawable.ic_home_fill, R.drawable.ic_home),
|
|
|
|
//社团列表
|
|
List(R.drawable.ic_all_fill, R.drawable.ic_all),
|
|
|
|
//个人中心
|
|
Center(R.drawable.ic_account_fill, R.drawable.ic_account);
|
|
|
|
|
|
override fun allMenu(): Array<out BottomBarMenu> {
|
|
return values()
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 顶部菜单
|
|
*
|
|
*/
|
|
interface TopMenuInterface<T : TopBarMenu> {
|
|
/**
|
|
* 当前菜单
|
|
*/
|
|
val _currentMenu: MutableLiveData<T>
|
|
val currentMenu: LiveData<T>
|
|
|
|
/**
|
|
* 切换顶部菜单
|
|
*
|
|
* @param menu
|
|
*/
|
|
fun clickMenu(menu: T) {
|
|
_currentMenu.value = menu
|
|
}
|
|
}
|
|
|
|
|
|
interface TopBarMenu : MenuEnum {
|
|
val menuName: String
|
|
}
|
|
|
|
|
|
interface StartMenu<T> {
|
|
val startMenu: T
|
|
}
|
|
|
|
/**
|
|
* 社团菜单
|
|
*
|
|
*/
|
|
enum class AssociationMenu(override val menuName: String) : TopBarMenu {
|
|
Member("社团成员"),
|
|
Main("社团主页"),
|
|
ActivityList("活动列表");
|
|
|
|
companion object Menu : StartMenu<AssociationMenu> {
|
|
override val startMenu: AssociationMenu = Main
|
|
}
|
|
}
|
|
|
|
enum class ActivityDetailMenu(override val menuName: String) : TopBarMenu {
|
|
Info("活动信息"),
|
|
Photo("相册"),
|
|
|
|
//TODO 活动成员
|
|
// Member("活动成员"),
|
|
BBS("交流区");
|
|
|
|
companion object Menu : StartMenu<ActivityDetailMenu> {
|
|
override val startMenu: ActivityDetailMenu = Info
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 评论数据状态
|
|
*
|
|
*/
|
|
abstract class AbstractComment : ViewModel() {
|
|
/**
|
|
* 弹窗状态
|
|
*/
|
|
protected val _openDialog: MutableLiveData<Boolean> = MutableLiveData()
|
|
val openDialog: LiveData<Boolean> = _openDialog
|
|
|
|
/**
|
|
* 编辑内容
|
|
*/
|
|
abstract val newContent: ValidStringForm
|
|
|
|
|
|
/**
|
|
* 打开弹窗
|
|
*
|
|
*/
|
|
fun openDialog() {
|
|
_openDialog.postValue(true)
|
|
}
|
|
|
|
/**
|
|
* 关闭弹窗
|
|
*
|
|
*/
|
|
fun closeDialog() {
|
|
_openDialog.postValue(false)
|
|
}
|
|
|
|
/**
|
|
* 发送评论
|
|
*
|
|
* @param callback
|
|
*/
|
|
abstract fun send(callback: (message: String) -> Unit)
|
|
}
|
|
|
|
/**
|
|
* 发送评论
|
|
*
|
|
* @param T
|
|
* @param model
|
|
* @param scaffoldModel
|
|
*/
|
|
|
|
@Composable
|
|
fun <T : AbstractComment> SendComment(
|
|
model: T,
|
|
scaffoldModel: ScaffoldModel = viewModel(),
|
|
callback: () -> Unit = {}
|
|
) {
|
|
val openDialog by model.openDialog.observeAsState()
|
|
|
|
val status by model.newContent.statusForm.observeAsState()
|
|
if (openDialog == true) {
|
|
|
|
AlertDialog(
|
|
onDismissRequest = { /*TODO*/ },
|
|
buttons = {
|
|
Row(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
horizontalArrangement = Arrangement.SpaceEvenly
|
|
) {
|
|
OutlinedButton(onClick = {
|
|
model.send {
|
|
model.closeDialog()
|
|
scaffoldModel.update(message = it, actionLabel = "知道了") {
|
|
|
|
}
|
|
callback()
|
|
}
|
|
}, enabled = status == FormStatus.Valid) {
|
|
Text(text = "发送")
|
|
}
|
|
OutlinedButton(onClick = {
|
|
model.closeDialog()
|
|
}) {
|
|
Text(text = "关闭")
|
|
}
|
|
}
|
|
}, text = {
|
|
Column(modifier = Modifier.padding(10.dp)) {
|
|
Card(
|
|
backgroundColor = MaterialTheme.colors.background
|
|
) {
|
|
BaseTextField(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
form = model.newContent,
|
|
isError = status !== FormStatus.Valid
|
|
)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 只包含返回按钮的顶部菜单
|
|
*
|
|
* @param title
|
|
*/
|
|
@Composable
|
|
fun TextTopAppBar(modifier: Modifier = Modifier, title: String) {
|
|
Row(
|
|
modifier = modifier.fillMaxWidth(),
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
val context = LocalContext.current as Activity
|
|
Row(modifier = Modifier.weight(1F / 3)) {
|
|
IconButton(onClick = {
|
|
context.finish()
|
|
}) {
|
|
Icon(
|
|
painter = painterResource(id = R.drawable.ic_arrow_left),
|
|
contentDescription = null
|
|
)
|
|
}
|
|
}
|
|
Row(modifier = Modifier.weight(1F / 3), horizontalArrangement = Arrangement.Center) {
|
|
Text(text = title, style = MaterialTheme.typography.h5)
|
|
}
|
|
Spacer(modifier = Modifier.weight(1F / 3))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 顶部菜单
|
|
*
|
|
*/
|
|
@Composable
|
|
fun TextTopAppBar(
|
|
nav: NavHostController,
|
|
currentMenuName: String,
|
|
menuNames: Array<out TopBarMenu>,
|
|
iconMenu: (() -> Unit)? = null,
|
|
@DrawableRes icon: Int = R.drawable.ic_configuration,
|
|
dropMenuContent: @Composable () -> Unit = {},
|
|
) {
|
|
TopAppBar(backgroundColor = MaterialTheme.colors.secondary) {
|
|
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
|
val context = LocalContext.current as Activity
|
|
IconButton(onClick = { context.finish() }, modifier = Modifier.weight(0.1F)) {
|
|
Icon(
|
|
painter = painterResource(id = R.drawable.ic_arrow_left),
|
|
contentDescription = null
|
|
)
|
|
}
|
|
Row(
|
|
modifier = Modifier
|
|
.weight(0.8F)
|
|
.fillMaxHeight(),
|
|
horizontalArrangement = Arrangement.SpaceAround,
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
menuNames.forEach {
|
|
Row(
|
|
modifier = Modifier
|
|
.weight(1F / menuNames.size)
|
|
.clickable(onClick = { nav.navigate(it.name) }),
|
|
horizontalArrangement = Arrangement.Center
|
|
) {
|
|
Text(
|
|
text = it.menuName,
|
|
color = if (currentMenuName == it.menuName) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground
|
|
)
|
|
}
|
|
}
|
|
}
|
|
if (iconMenu != null) {
|
|
IconButton(onClick = iconMenu, modifier = Modifier.weight(0.1F)) {
|
|
Icon(
|
|
painter = painterResource(id = icon),
|
|
contentDescription = null
|
|
)
|
|
}
|
|
} else {
|
|
Spacer(modifier = Modifier.weight(0.1F))
|
|
}
|
|
}
|
|
if (iconMenu != null) dropMenuContent()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 跑马灯
|
|
*
|
|
* @param model
|
|
*/
|
|
@Composable
|
|
fun Marquee(
|
|
model: MarqueeViewModel = viewModel()
|
|
) {
|
|
val poetryIndex: Int by model.marqueeIndex.observeAsState(0)
|
|
val marqueeTexts by model.marqueeTexts.observeAsState(mutableListOf())
|
|
val m =
|
|
if (marqueeTexts.isEmpty()) "欢迎大家踊跃留言" else marqueeTexts[poetryIndex % marqueeTexts.size].message
|
|
val delayMillis = 2000
|
|
val durationMillis = m.length * 300
|
|
Logger.d("durationMillis=${durationMillis}")
|
|
Column {
|
|
BoxWithConstraints {
|
|
val transition = rememberInfiniteTransition()
|
|
val maxWidth = maxWidth + 30.dp
|
|
val offset = transition.animateFloat(
|
|
initialValue = 0F,
|
|
targetValue = maxWidth.value,
|
|
animationSpec = infiniteRepeatable(
|
|
animation = tween(durationMillis = durationMillis, delayMillis = delayMillis),
|
|
repeatMode = RepeatMode.Restart
|
|
)
|
|
)
|
|
Box(modifier = Modifier.fillMaxWidth()) {
|
|
MarqueeText(
|
|
model = model,
|
|
offset = offset
|
|
)
|
|
}
|
|
|
|
if (offset.value.toInt() == maxWidth.value.toInt()) {
|
|
model.addAsync(delayMillis = delayMillis)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 跑马灯布局
|
|
*
|
|
* @param model
|
|
* @param offset
|
|
*/
|
|
@Composable
|
|
fun MarqueeText(model: MarqueeViewModel = viewModel(), offset: State<Float>) {
|
|
val poetryIndex: Int by model.marqueeIndex.observeAsState(0)
|
|
val marqueeTexts by model.marqueeTexts.observeAsState(mutableListOf())
|
|
Text(
|
|
text = if (marqueeTexts.isEmpty()) "欢迎大家踊跃留言" else marqueeTexts[poetryIndex % marqueeTexts.size].message,
|
|
modifier = Modifier.offset(x = offset.value.dp),
|
|
maxLines = 1,
|
|
overflow = TextOverflow.Ellipsis
|
|
)
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* 图片轮播
|
|
*
|
|
*/
|
|
@Composable
|
|
fun Carousel(
|
|
imageBitmap: LiveData<ImageBitmap>,
|
|
durationMillis: Int = 2000,
|
|
content: @Composable (imageBitmap: ImageBitmap?) -> Unit
|
|
) {
|
|
val data by imageBitmap.observeAsState()
|
|
|
|
Crossfade(targetState = data, animationSpec = tween(durationMillis = durationMillis)) {
|
|
content(imageBitmap = it)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 界面背景
|
|
*
|
|
* @property id 资源id
|
|
*/
|
|
enum class BackgroundImage(@DrawableRes val id: Int) {
|
|
//主页
|
|
Main(R.drawable.mb_bg_fb_08),
|
|
|
|
//社团列表
|
|
AssociationList(R.drawable.mb_bg_fb_07),
|
|
|
|
//个人中心
|
|
Center(R.drawable.mb_bg_fb_28),
|
|
|
|
//注册社团
|
|
RegAssociation(R.drawable.mb_bg_fb_06),
|
|
|
|
//社团主界面
|
|
AssociationMain(R.drawable.mb_bg_fb_25_180),
|
|
|
|
//社团重命名
|
|
Rename(R.drawable.mb_bg_fb_27),
|
|
|
|
//社团题库管理
|
|
Exam(R.drawable.mb_bg_fb_09),
|
|
|
|
//活动信息
|
|
ActivityInfo(R.drawable.mb_bg_fb_01),
|
|
|
|
//活动相册
|
|
ActivityPhoto(R.drawable.mb_bg_fb_02),
|
|
|
|
//活动成员
|
|
ActivityMember(R.drawable.mb_bg_fb_03),
|
|
|
|
//交流区
|
|
ActivityBBS(R.drawable.mb_bg_fb_04),
|
|
|
|
//系统通知
|
|
ActivityMessage(R.drawable.mb_bg_fb_26),
|
|
|
|
//申请活动
|
|
ApplyActivity(R.drawable.mb_bg_fb_07)
|
|
}
|
|
|
|
/**
|
|
* 界面背景图
|
|
*
|
|
* @param image
|
|
* @param alpha
|
|
*/
|
|
@Composable
|
|
fun Background(image: BackgroundImage, alpha: Float = DefaultAlpha) {
|
|
LocalContext.current.applicationContext as MainApplication
|
|
|
|
val backgroundImage = rememberCoilPainter(request = image.id)
|
|
|
|
Image(
|
|
painter = backgroundImage,
|
|
contentDescription = null,
|
|
contentScale = ContentScale.FillBounds,
|
|
alpha = alpha,
|
|
modifier = Modifier.fillMaxSize()
|
|
)
|
|
|
|
}
|
|
|
|
@Composable
|
|
fun Poster(modifier: Modifier = Modifier, image: @Composable () -> Unit) {
|
|
Card(
|
|
modifier = modifier,
|
|
backgroundColor = Color.Transparent
|
|
) {
|
|
Box(contentAlignment = Alignment.Center) {
|
|
Image(
|
|
painter = painterResource(id = R.drawable.hot_activity_background),
|
|
contentDescription = null,
|
|
modifier = Modifier.fillMaxSize()
|
|
)
|
|
Box(modifier = Modifier.padding(vertical = 10.dp)) {
|
|
image()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 活动海报
|
|
*
|
|
*/
|
|
@Composable
|
|
fun Poster(modifier: Modifier = Modifier, imageBitmap: ImageBitmap? = null) {
|
|
Card(
|
|
modifier = modifier,
|
|
backgroundColor = Color.Transparent
|
|
) {
|
|
Box(contentAlignment = Alignment.Center) {
|
|
Image(
|
|
painter = painterResource(id = R.drawable.hot_activity_background),
|
|
contentDescription = null,
|
|
modifier = Modifier.fillMaxSize()
|
|
)
|
|
if (imageBitmap != null) {
|
|
Image(
|
|
bitmap = imageBitmap,
|
|
contentDescription = null,
|
|
modifier = Modifier.fillMaxSize()
|
|
)
|
|
} else {
|
|
Image(
|
|
painter = painterResource(id = R.drawable.ic_launcher_foreground),
|
|
contentDescription = null
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 介绍卡片
|
|
*
|
|
*/
|
|
@Composable
|
|
fun DescCard(
|
|
modifier: Modifier,
|
|
content: String? = null,
|
|
stringForm: StringForm? = null,
|
|
readonly: Boolean = false
|
|
) {
|
|
Card(
|
|
modifier = modifier,
|
|
backgroundColor = Color.Transparent
|
|
) {
|
|
Box {
|
|
Image(
|
|
painter = painterResource(id = R.drawable.hot_activity_desc_background),
|
|
contentDescription = null,
|
|
contentScale = ContentScale.FillBounds,
|
|
modifier = Modifier.fillMaxSize()
|
|
)
|
|
Column(
|
|
modifier = Modifier
|
|
.fillMaxSize()
|
|
.padding(horizontal = 10.dp),
|
|
horizontalAlignment = Alignment.CenterHorizontally
|
|
) {
|
|
Spacer(modifier = Modifier.weight(0.15F))
|
|
if (stringForm != null) {
|
|
BaseTextField(
|
|
modifier = Modifier.weight(0.65F),
|
|
form = stringForm,
|
|
readOnly = readonly
|
|
)
|
|
} else {
|
|
Text(
|
|
modifier = Modifier.weight(0.65F),
|
|
text = content ?: "",
|
|
overflow = TextOverflow.Ellipsis
|
|
)
|
|
}
|
|
|
|
Spacer(modifier = Modifier.weight(0.15F))
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 资料审核提示
|
|
*
|
|
* @param it
|
|
*/
|
|
@Composable
|
|
fun CheckTip(it: AuditCheckVo, modifier: Modifier = Modifier) {
|
|
Row(modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
|
when (val status = it.checkStatus) {
|
|
CheckStatus.Finish -> Text(buildAnnotatedString {
|
|
withStyle(style = MaterialTheme.typography.h5.toParagraphStyle()) {
|
|
append("您于${Date(it.applyTime).datetimeFormat()}提交的资料审核不通过原因如下:")
|
|
}
|
|
withStyle(
|
|
style = MaterialTheme.typography.h6.copy(color = MaterialTheme.colors.error)
|
|
.toParagraphStyle()
|
|
) {
|
|
append("初审意见:${it.firstCause}\n")
|
|
it.lastCause?.let {
|
|
append("复审意见:${it}")
|
|
}
|
|
}
|
|
})
|
|
else -> {
|
|
Text(
|
|
text = status.desc, style =
|
|
MaterialTheme.typography.h5.copy(color = MaterialTheme.colors.primary)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//@Preview
|
|
@Composable
|
|
fun MyBottomAppBarPreview() {
|
|
val arr: List<Arrangement.Horizontal> = listOf(
|
|
Arrangement.Start,
|
|
Arrangement.End,
|
|
Arrangement.Center,
|
|
Arrangement.SpaceBetween,
|
|
Arrangement.SpaceAround,
|
|
Arrangement.SpaceEvenly
|
|
)
|
|
var i by remember {
|
|
mutableStateOf(3)
|
|
}
|
|
Column(modifier = Modifier.fillMaxSize()) {
|
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
|
OutlinedButton(onClick = { i += 1 }) {
|
|
Text(text = "添加元素")
|
|
}
|
|
if (i > 1) {
|
|
OutlinedButton(onClick = { i -= 1 }) {
|
|
Text(text = "删除元素")
|
|
}
|
|
}
|
|
}
|
|
arr.forEach {
|
|
Column(
|
|
modifier = Modifier
|
|
.weight(1F / arr.size)
|
|
.border(width = 1.dp, color = MaterialTheme.colors.onBackground)
|
|
) {
|
|
Row(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
horizontalArrangement = Arrangement.Center
|
|
) {
|
|
Text(text = "$it", style = MaterialTheme.typography.h5)
|
|
}
|
|
Row(
|
|
modifier = Modifier.fillMaxSize(),
|
|
horizontalArrangement = it,
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
repeat(i) {
|
|
Text(text = "$it", style = MaterialTheme.typography.h3)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Preview
|
|
@Composable
|
|
fun MainContent() {
|
|
Column(
|
|
Modifier
|
|
.background(Color(0xFFEDEAE0))
|
|
.fillMaxSize()
|
|
.padding(32.dp),
|
|
verticalArrangement = Arrangement.spacedBy(24.dp)
|
|
) {
|
|
val localFocusManager = LocalFocusManager.current
|
|
val focusRequester = FocusRequester()
|
|
|
|
BaseTextField(
|
|
modifier = Modifier
|
|
.focusRequester(focusRequester)
|
|
.fillMaxWidth(), form = StringForm(formDesc = "", textLength = 5)
|
|
)
|
|
|
|
|
|
Button(onClick = {
|
|
localFocusManager.clearFocus()
|
|
}) {
|
|
Text(text = "Clear Focus")
|
|
}
|
|
}
|
|
} |