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

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")
}
}
}