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 { return values() } } /** * 顶部菜单 * */ interface TopMenuInterface { /** * 当前菜单 */ val _currentMenu: MutableLiveData val currentMenu: LiveData /** * 切换顶部菜单 * * @param menu */ fun clickMenu(menu: T) { _currentMenu.value = menu } } interface TopBarMenu : MenuEnum { val menuName: String } interface StartMenu { val startMenu: T } /** * 社团菜单 * */ enum class AssociationMenu(override val menuName: String) : TopBarMenu { Member("社团成员"), Main("社团主页"), ActivityList("活动列表"); companion object Menu : StartMenu { override val startMenu: AssociationMenu = Main } } enum class ActivityDetailMenu(override val menuName: String) : TopBarMenu { Info("活动信息"), Photo("相册"), //TODO 活动成员 // Member("活动成员"), BBS("交流区"); companion object Menu : StartMenu { override val startMenu: ActivityDetailMenu = Info } } /** * 评论数据状态 * */ abstract class AbstractComment : ViewModel() { /** * 弹窗状态 */ protected val _openDialog: MutableLiveData = MutableLiveData() val openDialog: LiveData = _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 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, 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) { 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, 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 = 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") } } }