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
3 years ago
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.*
3 years ago
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
3 years ago
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
3 years ago
import androidx.compose.ui.res.painterResource
3 years ago
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
3 years ago
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
3 years ago
import com.gyf.csams.R
import com.gyf.csams.main.model.MarqueeViewModel
3 years ago
import com.gyf.csams.module.AuditCheckVo
import com.gyf.csams.module.CheckStatus
import com.gyf.lib.uikit.*
3 years ago
import com.gyf.lib.util.DateTimeUtil.datetimeFormat
import com.orhanobut.logger.Logger
3 years ago
import java.util.*
3 years ago
/**
* 主菜单
*
*/
enum class MainMenu(
@DrawableRes override val selectedIcon: Int,
@DrawableRes override val unSelectedIcon: Int
) : BottomBarMenu {
3 years ago
//主页
Main(R.drawable.ic_home_fill, R.drawable.ic_home),
3 years ago
//社团列表
List(R.drawable.ic_all_fill, R.drawable.ic_all),
3 years ago
//个人中心
Center(R.drawable.ic_account_fill, R.drawable.ic_account);
3 years ago
override fun allMenu(): Array<out BottomBarMenu> {
return values()
3 years ago
}
}
/**
* 顶部菜单
*
*/
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),
3 years ago
//社团重命名
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()
)
}
3 years ago
@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
3 years ago
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) {
3 years ago
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))
}
}
}
}
3 years ago
/**
* 资料审核提示
*
* @param it
*/
@Composable
3 years ago
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)
)
}
}
}
3 years ago
}
//@Preview
3 years ago
@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")
}
}
}