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.

537 lines
15 KiB

package com.gyf.csams.uikit
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.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.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
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.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.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.CarouselViewModel
import com.gyf.csams.main.model.MarqueeViewModel
import com.gyf.csams.uikit.theme.CSAMSTheme
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, 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) {
//图标宽度平等分
val weight = 1 / (MainMenu.values().size * 1.0f)
Row(Modifier.fillMaxWidth()) {
MenuIconButton(_menu = menu, menu = MainMenu.Main, Modifier.weight(weight),
onClick = { nav.navigate(MainMenu.Main.name) })
MenuIconButton(_menu = menu, menu = MainMenu.List, Modifier.weight(weight),
onClick = { nav.navigate(MainMenu.List.name) })
MenuIconButton(_menu = menu, menu = MainMenu.Center, Modifier.weight(weight),
onClick = { nav.navigate(MainMenu.Center.name) })
}
}
}
/**
* 社团菜单
*
*/
enum class AssociationMenu(val menuName: String) {
member("社团成员"),
main("社团主页"),
list("活动列表")
}
/**
* 社团顶部菜单
*
*/
@Composable
fun AssociationAppBar(
menu: AssociationMenu,
nav: NavHostController,
back: () -> Unit,
dropMenu: () -> Unit
) {
TopAppBar(backgroundColor = MaterialTheme.colors.secondary) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = back, 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.Center,
verticalAlignment = Alignment.CenterVertically
) {
val menus = AssociationMenu.values()
menus.forEach {
Row(
modifier = Modifier
.weight(1F / menus.size)
.clickable(onClick = { nav.navigate(it.name) }),
horizontalArrangement = Arrangement.Center
) {
Text(
text = it.menuName,
color = if (menu == it) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground
)
}
}
}
IconButton(onClick = dropMenu, modifier = Modifier.weight(0.1F)) {
Icon(
painter = painterResource(id = R.drawable.ic_configuration),
contentDescription = null
)
}
}
}
}
/**
* 跑马灯
*
* @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(
model: CarouselViewModel = viewModel(),
durationMillis: Int = 2000,
content: @Composable (id: Int) -> Unit
) {
val index: Int by model.index.observeAsState(0)
Crossfade(targetState = index, animationSpec = tween(durationMillis = durationMillis)) {
content(id = model.imageList[it])
}
}
/**
* 通用文本输入框
*
* @param T
* @param modifier
* @param form
* @param singeLine
*/
@Composable
fun <T : StringForm> BaseTextField(
modifier: Modifier = Modifier,
form: T,
singeLine: Boolean = false
) {
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.Default.copy(imeAction = ImeAction.Done),
trailingIcon = { Text(text = "${name.length}/${form.textLength}") })
}
/**
* 底部提示
*
* @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),
//社团列表
list(R.drawable.mb_bg_fb_07),
//个人中心
center(R.drawable.mb_bg_fb_28),
//注册社团
reg_association(R.drawable.mb_bg_fb_06),
//社团主界面
association_main(R.drawable.mb_bg_fb_25_180),
//社团重命名
rename(R.drawable.mb_bg_fb_27),
//社团题库管理
exam(R.drawable.mb_bg_fb_02)
}
/**
* 界面背景图
*
* @param image
* @param alpha
*/
@Composable
fun Background(image: BackgroundImage, alpha: Float = DefaultAlpha) {
val app = LocalContext.current.applicationContext as APP
BoxWithConstraints {
Image(
bitmap = app.getImage(
image = image,
reqHeight = maxHeight.value.toInt() / 2,
reqWidth = maxWidth.value.toInt() / 2
),
contentDescription = null,
contentScale = ContentScale.FillHeight,
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, @DrawableRes id: Int) {
Card(
modifier = modifier,
backgroundColor = Color.Transparent
) {
Box(contentAlignment = Alignment.Center) {
Image(
painter = painterResource(id = R.drawable.hot_activity_background),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
Image(
painter = painterResource(id = id),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
}
}
/**
* 介绍卡片
*
*/
@Composable
fun DescCard(modifier: Modifier) {
Card(
modifier = modifier,
backgroundColor = Color.Transparent
) {
Image(
painter = painterResource(id = R.drawable.hot_activity_desc_background),
contentDescription = null
)
Row(modifier = Modifier.fillMaxWidth()) {
Spacer(modifier = Modifier.weight(0.2F))
Column(
modifier = Modifier
.weight(0.5F)
) {
Spacer(modifier = Modifier.weight(0.1F))
Text(
text = "文字对任何界面都属于核心内容,而利用 Jetpack Compose 可以更轻松地显示或写入文字。Compose 可以充分利用其构建块的组合,这意味着您无需覆盖各种属性和方法,也无需扩展大型类,即可拥有特定的可组合项设计以及按您期望的方式运行的逻辑。"
.repeat(10), overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(0.8F)
)
Spacer(modifier = Modifier.weight(0.1F))
}
Spacer(modifier = Modifier.weight(0.2F))
}
}
}
@Preview
@Composable
fun AnimationTextPreview() {
AnimationText(text = "6666")
}
//@Preview
@Composable
fun MyBottomAppBarPreview() {
CSAMSTheme {
}
}