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.

781 lines
21 KiB

package com.gyf.csams.uikit
import android.app.Activity
import androidx.annotation.DrawableRes
import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
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.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.input.ImeAction
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.navigate
import androidx.navigation.compose.rememberNavController
import com.gyf.csams.APP
import com.gyf.csams.R
import com.gyf.csams.main.model.MarqueeViewModel
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
/**
* 淡入淡出并且颜色变化文本
*
* @param text
*/
@Composable
fun AnimationText(text: String) {
val infiniteTransition = rememberInfiniteTransition()
val color by infiniteTransition.animateColor(
initialValue = MaterialTheme.colors.primary,
targetValue = MaterialTheme.colors.onPrimary,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Text(
text = text,
color = color,
style = MaterialTheme.typography.body1
)
}
/**
* 主菜单
*
*/
enum class MainMenu(
@DrawableRes val selectedIcon: Int,
@DrawableRes val unSelectedIcon: Int
) {
//主页
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)
}
/**
* 底部菜单按钮
*
* @param _menu
* @param menu
* @param modifier
* @param onClick
*/
@Composable
fun MenuIconButton(
_menu: MainMenu,
menu: MainMenu,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
Row(
modifier = modifier, horizontalArrangement = Arrangement.Center
) {
IconButton(onClick = onClick) {
Icon(
painter = painterResource(id = if (_menu == menu) menu.selectedIcon else menu.unSelectedIcon),
contentDescription = null
)
}
}
}
/**
* 主界面底部菜单
*
* @param menu
* @param nav
* @param modifier
*/
@Composable
fun MainBottomAppBar(menu: MainMenu, nav: NavHostController, modifier: Modifier = Modifier) {
BottomAppBar(backgroundColor = MaterialTheme.colors.background, modifier = modifier) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
MenuIconButton(_menu = menu, menu = MainMenu.Main,
onClick = { nav.navigate(MainMenu.Main.name) })
MenuIconButton(_menu = menu, menu = MainMenu.List,
onClick = { nav.navigate(MainMenu.List.name) })
MenuIconButton(_menu = menu, menu = MainMenu.Center,
onClick = { nav.navigate(MainMenu.Center.name) })
}
}
}
/**
* 顶部菜单
*
*/
interface TopMenuInterface<T : TopBarMenu> {
/**
* 当前菜单
*/
val _currentMenu: MutableLiveData<T>
val currentMenu: LiveData<T>
/**
* 切换顶部菜单
*
* @param menu
*/
fun clickMenu(menu: T) {
_currentMenu.value = menu
}
}
interface TopBarMenu {
val menuName: String
val name: 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("相册"),
Member("活动成员"),
BBS("交流区");
companion object Menu : StartMenu<ActivityDetailMenu> {
override val startMenu: ActivityDetailMenu = Info
}
}
interface SendInterface {
/**
* 弹窗状态
*/
val _openDialog: MutableLiveData<Boolean>
val openDialog: LiveData<Boolean>
/**
* 编辑内容
*/
val newContent: StringForm
/**
* 打开弹窗
*
*/
fun openDialog()
/**
* 关闭弹窗
*
*/
fun closeDialog()
/**
* 发送评论
*
* @param callback
*/
fun send(callback: (message: String) -> Unit)
}
/**
* 发送评论
*
* @param T
* @param model
* @param scaffoldModel
*/
@Composable
fun <T : SendInterface> SendComment(model: T, scaffoldModel: ScaffoldModel = viewModel()) {
val openDialog by model.openDialog.observeAsState()
if (openDialog == true) {
AlertDialog(onDismissRequest = { /*TODO*/ },
buttons = {
Row(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
OutlinedButton(onClick = {
model.send { scaffoldModel.update(message = it) }
}) {
Text(text = "发送")
}
OutlinedButton(onClick = {
model.closeDialog()
}) {
Text(text = "关闭")
}
}
}, text = {
Column(modifier = Modifier.padding(10.dp)) {
Card(
backgroundColor = MaterialTheme.colors.background,
modifier = Modifier.padding(10.dp)
) {
BaseTextField(
form = model.newContent, modifier = Modifier
.fillMaxWidth()
.height(200.dp)
)
}
}
})
}
}
/**
* 只包含返回按钮的顶部菜单
*
* @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
* @param content
*/
@Composable
fun Marquee(
model: MarqueeViewModel = viewModel(), content: @Composable BoxScope.(
model: MarqueeViewModel,
value: State<Float>
) -> Unit
) {
val delayMillis = 2000
Column {
BoxWithConstraints {
val transition = rememberInfiniteTransition()
val maxWidth = maxWidth + 30.dp
val offset = transition.animateFloat(
initialValue = 0F,
targetValue = maxWidth.value,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 3000, delayMillis = delayMillis),
repeatMode = RepeatMode.Restart
)
)
Box(modifier = Modifier.fillMaxWidth()) {
content(model = model, value = 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)
// Text(text = "$poetryIndex")
// Text(text = "offset.value=${offset.value}")
Text(
text = model.marqueeTexts[poetryIndex % model.marqueeTexts.size],
modifier = Modifier.offset(x = offset.value.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
/**
* 导航界面框架
*
* @param background 背景
* @param mainMenu 菜单
* @param nav 导航
* @param body 内容
*/
@Composable
fun MainFrame(
background: @Composable () -> Unit,
mainMenu: MainMenu,
nav: NavHostController,
body: @Composable ColumnScope.() -> Unit
) {
Box(modifier = Modifier.fillMaxSize()) {
background()
Column {
Column(modifier = Modifier.weight(0.9F), content = body)
MainBottomAppBar(
menu = mainMenu,
nav = nav
)
}
}
}
/**
* 界面框架
*
* @param background
* @param body
*/
@Composable
fun MainFrame(background: @Composable () -> Unit, body: @Composable ColumnScope.() -> Unit) {
Box(modifier = Modifier.fillMaxSize()) {
background()
Column(content = body)
}
}
/**
* 图片轮播
*
*/
@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)
}
}
/**
* 通用文本输入框
*
* @param T
* @param modifier
* @param form
* @param singeLine
*/
@Composable
fun <T : StringForm> BaseTextField(
modifier: Modifier = Modifier,
form: T,
singeLine: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None
) {
val name: String by form.formValue.observeAsState("")
val focusManager = LocalFocusManager.current
OutlinedTextField(
modifier = modifier,
value = name,
onValueChange = { form.onChange(it) },
label = { Text(text = form.formDesc) },
placeholder = { Text(text = form.formPlaceholder) },
singleLine = singeLine,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
keyboardOptions = keyboardOptions,
trailingIcon = { Text(text = "${name.length}/${form.textLength}") },
isError = isError,
visualTransformation = visualTransformation
)
}
/**
* 底部提示
*
* @param model
* @param scaffoldState
*/
@Composable
fun ShowSnackbar(model: ScaffoldModel = viewModel(), scaffoldState: ScaffoldState) {
val snackBar: SnackBar? by model.data.observeAsState()
snackBar?.apply {
if (message != null) {
LaunchedEffect(scaffoldState) {
launch {
if (actionLabel != null) {
val result = scaffoldState.snackbarHostState.showSnackbar(
message = message, actionLabel = actionLabel,
duration = duration
)
when (result) {
SnackbarResult.ActionPerformed -> {
Logger.i("点击操作按钮")
callback()
}
SnackbarResult.Dismissed -> {
Logger.d("窗口消失")
}
}
} else {
scaffoldState.snackbarHostState.showSnackbar(message = message)
}
model.update()
}
}
}
}
}
/**
* 界面背景
*
* @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)
}
/**
* 界面背景图
*
* @param image
* @param alpha
*/
@Composable
fun Background(image: BackgroundImage, alpha: Float = DefaultAlpha) {
val app = LocalContext.current.applicationContext as APP
var i: ImageBitmap? by remember {
mutableStateOf(null)
}
LaunchedEffect(image) {
i = app.getImage(image = image)
}
i?.let {
Image(
bitmap = it,
contentDescription = null,
contentScale = ContentScale.FillBounds,
alpha = alpha,
modifier = Modifier.fillMaxSize()
)
}
}
/**
*
*
* @param content
*/
@Composable
fun Body(content: @Composable (scaffoldState: ScaffoldState) -> Unit) {
Surface(color = MaterialTheme.colors.background) {
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState) {
content(scaffoldState = scaffoldState)
}
}
}
/**
* 带导航的主体
*
* @param content
*/
@Composable
fun Body(content: @Composable (nav: NavHostController, scaffoldState: ScaffoldState) -> Unit) {
Surface(color = MaterialTheme.colors.background) {
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState) {
content(nav = navController, scaffoldState = scaffoldState)
}
}
}
/**
* 活动海报
*
*/
@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) {
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)
) {
Spacer(modifier = Modifier.weight(0.15F))
Text(
modifier = Modifier.weight(0.65F),
text = content,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.weight(0.15F))
}
}
}
}
//@Preview
@Composable
fun AnimationTextPreview() {
AnimationText(text = "6666")
}
//@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.background)
) {
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)
}
}
}
}
}
}
fun TestPreview() {
}