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 { /** * 当前菜单 */ val _currentMenu: MutableLiveData val currentMenu: LiveData /** * 切换顶部菜单 * * @param menu */ fun clickMenu(menu: T) { _currentMenu.value = menu } } interface TopBarMenu { val menuName: String val name: 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("相册"), Member("活动成员"), BBS("交流区"); companion object Menu : StartMenu { override val startMenu: ActivityDetailMenu = Info } } interface SendInterface { /** * 弹窗状态 */ val _openDialog: MutableLiveData val openDialog: LiveData /** * 编辑内容 */ val newContent: StringForm /** * 打开弹窗 * */ fun openDialog() /** * 关闭弹窗 * */ fun closeDialog() /** * 发送评论 * * @param callback */ fun send(callback: (message: String) -> Unit) } /** * 发送评论 * * @param T * @param model * @param scaffoldModel */ @Composable fun 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, 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 ) -> 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) { 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, 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 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 = 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() { }