Refactor button bar component

This commit is contained in:
Ash
2022-03-15 02:25:39 +08:00
parent 53a4d866bb
commit a5b81f7f23
28 changed files with 331 additions and 259 deletions
@@ -0,0 +1,231 @@
package me.ash.reader.ui.page.home.feed
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.Image
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.ash.reader.R
import me.ash.reader.ui.widget.AnimatedText
import me.ash.reader.ui.widget.Menu
@Composable
fun ButtonBar(
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
buttonBarType: ButtonBarType,
) {
var expanded by remember { mutableStateOf(false) }
var offset by remember { mutableStateOf(DpOffset.Zero) }
val interactionSource = remember { MutableInteractionSource() }
val view = LocalView.current
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.fillMaxWidth()
.height(50.dp)
.indication(interactionSource, LocalIndication.current)
.padding(horizontal = 20.dp)
.clip(RoundedCornerShape(8.dp))
.pointerInput(Unit) {
detectTapGestures(
onPress = { offset ->
val press = PressInteraction.Press(offset)
interactionSource.emit(press)
tryAwaitRelease()
interactionSource.emit(PressInteraction.Release(press))
},
onTap = {
onClick()
},
onLongPress = {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
offset = DpOffset(it.x.toDp(), it.y.toDp())
expanded = true
},
)
},
) {
when (buttonBarType) {
is ButtonBarType.FilterBar -> FilterBar(buttonBarType)
is ButtonBarType.AllBar -> AllBar(buttonBarType)
is ButtonBarType.GroupBar -> GroupBar(buttonBarType)
is ButtonBarType.FeedBar -> FeedBar(buttonBarType)
}
}
Menu(
offset = offset,
expanded = expanded,
dismissFunction = { expanded = false },
)
}
@Composable
fun FilterBar(
buttonBarType: ButtonBarType.FilterBar,
) {
AnimatedText(
text = buttonBarType.title,
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
)
AnimatedText(
text = buttonBarType.important.toString(),
fontSize = 18.sp,
color = MaterialTheme.colorScheme.outline,
)
}
@Composable
fun AllBar(
buttonBarType: ButtonBarType.AllBar,
) {
AnimatedText(
text = buttonBarType.title,
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
)
Icon(
imageVector = buttonBarType.icon,
contentDescription = "Expand More",
tint = MaterialTheme.colorScheme.outline,
)
}
@Composable
fun GroupBar(
buttonBarType: ButtonBarType.GroupBar,
) {
Row {
Icon(
imageVector = buttonBarType.icon,
contentDescription = "icon",
modifier = Modifier
.padding(end = 4.dp)
.clip(CircleShape)
.clickable(onClick = buttonBarType.iconOnClick),
tint = MaterialTheme.colorScheme.onPrimaryContainer,
)
Text(
modifier = Modifier
.weight(1f)
.padding(end = 20.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = buttonBarType.title,
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.onPrimaryContainer,
)
}
AnimatedText(
text = buttonBarType.important.toString(),
fontSize = 18.sp,
color = MaterialTheme.colorScheme.outline,
)
}
@Composable
fun FeedBar(
buttonBarType: ButtonBarType.FeedBar,
) {
Row {
Surface(
modifier = Modifier
.padding(start = 28.dp, end = 4.dp)
.size(24.dp),
//.background(MaterialTheme.colorScheme.inversePrimary),
color = if (buttonBarType.icon == null) {
MaterialTheme.colorScheme.inversePrimary
} else {
Color.Unspecified
}
) {
if (buttonBarType.icon == null) {
Icon(
painter = painterResource(id = R.drawable.default_folder),
contentDescription = "icon",
modifier = Modifier.fillMaxSize(),
tint = MaterialTheme.colorScheme.onPrimaryContainer,
)
} else {
Image(
painter = buttonBarType.icon,
contentDescription = "icon",
modifier = Modifier.fillMaxSize(),
)
}
}
Text(
modifier = Modifier
.padding(end = 20.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = buttonBarType.title,
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.onPrimaryContainer,
)
}
AnimatedText(
text = buttonBarType.important.toString(),
fontSize = 18.sp,
color = MaterialTheme.colorScheme.outline,
)
}
sealed class ButtonBarType {
data class FilterBar(
val modifier: Modifier = Modifier,
val title: String = "",
val important: Int = 0,
) : ButtonBarType()
data class AllBar(
val title: String = "",
val icon: ImageVector,
) : ButtonBarType()
data class GroupBar(
val title: String = "",
val important: Int = 0,
val icon: ImageVector,
val iconOnClick: () -> Unit = {},
) : ButtonBarType()
data class FeedBar(
val title: String = "",
val important: Int = 0,
val icon: Painter? = null,
) : ButtonBarType()
}
@@ -1,199 +0,0 @@
package me.ash.reader.ui.page.home.feed
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.ash.reader.R
import me.ash.reader.ui.extension.paddingFixedHorizontal
import me.ash.reader.ui.extension.roundClick
import me.ash.reader.ui.widget.AnimatedText
@Composable
fun FeedBar(
barButtonType: BarButtonType,
iconOnClickListener: () -> Unit = {},
onClickListener: () -> Unit = {},
) {
Box(
modifier = Modifier
.paddingFixedHorizontal()
.roundClick(onClick = onClickListener),
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.padding(
start = 10.dp,
end = if (barButtonType is FirstExpandType) 2.dp else 10.dp
)
) {
when (barButtonType) {
is SecondExpandType -> {
Icon(
imageVector = barButtonType.img,
contentDescription = "icon",
modifier = Modifier
.padding(end = 4.dp)
.clip(CircleShape)
.clickable(onClick = iconOnClickListener),
tint = MaterialTheme.colorScheme.onPrimaryContainer,
)
}
is ItemType -> {
val modifier = Modifier
Row(
modifier = modifier
.padding(start = 28.dp, end = 4.dp)
.size(24.dp)
.background(
if (barButtonType.icon != null) Color.Unspecified
else MaterialTheme.colorScheme.inversePrimary
),
// .background(MaterialTheme.colorScheme.inversePrimary),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
if (barButtonType.icon == null) {
Icon(
painter = painterResource(id = R.drawable.default_folder),
contentDescription = "icon",
modifier = modifier.fillMaxSize(),
tint = MaterialTheme.colorScheme.onPrimaryContainer,
)
} else {
Image(
painter = barButtonType.icon,
contentDescription = "icon",
modifier = modifier.fillMaxSize(),
)
}
}
}
}
when (barButtonType) {
is ButtonType -> {
AnimatedText(
modifier = Modifier.weight(1f),
text = barButtonType.text,
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
)
}
else -> {
Text(
modifier = Modifier
.weight(1f)
.padding(end = 20.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = barButtonType.text,
fontSize = if (barButtonType is FirstExpandType) 22.sp else 18.sp,
fontWeight = if (barButtonType is FirstExpandType) FontWeight.Bold else FontWeight.SemiBold,
color = if (barButtonType is FirstExpandType)
MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onPrimaryContainer,
)
}
}
when (barButtonType) {
is ButtonType, is ItemType, is SecondExpandType -> {
AnimatedText(
text = barButtonType.additional.toString(),
fontSize = 18.sp,
color = MaterialTheme.colorScheme.outline,
)
}
is FirstExpandType -> {
Icon(
imageVector = barButtonType.additional as ImageVector,
contentDescription = "Expand More",
tint = MaterialTheme.colorScheme.outline,
)
}
}
}
}
}
interface BarButtonType {
val img: Any?
val text: String
val additional: Any?
}
class ButtonType(
private val content: String,
private val important: Int,
) : BarButtonType {
override val img: ImageVector?
get() = null
override val text: String
get() = content
override val additional: Any
get() = important
}
class FirstExpandType(
private val content: String,
private val icon: ImageVector,
) : BarButtonType {
override val img: ImageVector?
get() = null
override val text: String
get() = content
override val additional: Any
get() = icon
}
class SecondExpandType(
private val icon: ImageVector,
private val content: String,
private val important: Int,
) : BarButtonType {
override val img: ImageVector
get() = icon
override val text: String
get() = content
override val additional: Any
get() = important
}
class ItemType(
// private val icon: String,
val icon: BitmapPainter?,
private val content: String,
private val important: Int,
) : BarButtonType {
// override val img: String
override val img: Painter
get() = icon ?: BitmapPainter(
BitmapFactory.decodeByteArray(byteArrayOf(), 0, 0).asImageBitmap()
)
override val text: String
get() = content
override val additional: Any
get() = important
}
@@ -23,9 +23,10 @@ fun ColumnScope.FeedList(
) {
Column(modifier = Modifier.animateContentSize()) {
feeds.forEach { feed ->
FeedBar(
barButtonType = ItemType(
// icon = feed.icon ?: "",
ButtonBar(
buttonBarType = ButtonBarType.FeedBar(
title = feed.name,
important = feed.important ?: 0,
icon = if (feed.icon == null) {
null
} else {
@@ -37,13 +38,12 @@ fun ColumnScope.FeedList(
).asImageBitmap()
)
},
content = feed.name,
important = feed.important ?: 0
)
) {
onClick(feed)
}
),
onClick = {
onClick(feed)
},
)
}
}
}
}
}
@@ -99,25 +99,27 @@ fun FeedPage(
) {
item {
Spacer(modifier = Modifier.height(114.dp))
FeedBar(
barButtonType = ButtonType(
content = filter.title,
important = viewState.filterImportant
)
) {
groupAndFeedOnClick(null, null)
}
ButtonBar(
buttonBarType = ButtonBarType.FilterBar(
title = filter.title,
important = viewState.filterImportant,
),
onClick = {
groupAndFeedOnClick(null, null)
}
)
}
item {
Spacer(modifier = Modifier.height(10.dp))
FeedBar(
barButtonType = FirstExpandType(
content = "Feeds",
ButtonBar(
buttonBarType = ButtonBarType.AllBar(
title = "Feeds",
icon = Icons.Rounded.ExpandMore
)
) {
viewModel.dispatch(FeedViewAction.ChangeGroupVisible(!viewState.groupsVisible))
}
),
onClick = {
viewModel.dispatch(FeedViewAction.ChangeGroupVisible)
}
)
}
itemsIndexed(viewState.groupWithFeedList) { index, groupWithFeed ->
GroupList(
@@ -1,7 +1,6 @@
package me.ash.reader.ui.page.home.feed
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.rounded.Add
@@ -11,9 +10,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallTopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import me.ash.reader.ui.page.common.RouteName
@@ -33,7 +30,7 @@ fun FeedPageTopBar(
navController.navigate(route = RouteName.SETTINGS)
}) {
Icon(
modifier = Modifier.size(22.dp),
// modifier = Modifier.size(22.dp),
imageVector = Icons.Outlined.Settings,
contentDescription = "Back",
tint = MaterialTheme.colorScheme.primary
@@ -47,7 +44,7 @@ fun FeedPageTopBar(
syncOnClick()
}) {
Icon(
modifier = Modifier.size(26.dp),
// modifier = Modifier.size(26.dp),
imageVector = Icons.Rounded.Refresh,
contentDescription = "Sync",
tint = MaterialTheme.colorScheme.primary,
@@ -58,7 +55,7 @@ fun FeedPageTopBar(
subscribeOnClick()
}) {
Icon(
modifier = Modifier.size(26.dp),
// modifier = Modifier.size(26.dp),
imageVector = Icons.Rounded.Add,
contentDescription = "Subscribe",
tint = MaterialTheme.colorScheme.primary,
@@ -31,7 +31,7 @@ class FeedViewModel @Inject constructor(
is FeedViewAction.FetchData -> fetchData(action.isStarred, action.isUnread)
is FeedViewAction.AddFromFile -> addFromFile(action.inputStream)
is FeedViewAction.ChangeFeedVisible -> changeFeedVisible(action.index)
is FeedViewAction.ChangeGroupVisible -> changeGroupVisible(action.visible)
is FeedViewAction.ChangeGroupVisible -> changeGroupVisible()
is FeedViewAction.ScrollToItem -> scrollToItem(action.index)
}
}
@@ -121,10 +121,10 @@ class FeedViewModel @Inject constructor(
}
}
private fun changeGroupVisible(visible: Boolean) {
private fun changeGroupVisible() {
_viewState.update {
it.copy(
groupsVisible = visible
groupsVisible = !_viewState.value.groupsVisible
)
}
}
@@ -163,9 +163,7 @@ sealed class FeedViewAction {
val index: Int
) : FeedViewAction()
data class ChangeGroupVisible(
val visible: Boolean
) : FeedViewAction()
object ChangeGroupVisible : FeedViewAction()
data class ScrollToItem(
val index: Int
@@ -18,7 +18,7 @@ fun ColumnScope.GroupList(
feedVisible: Boolean,
groupWithFeed: GroupWithFeed,
groupAndFeedOnClick: (currentGroup: Group?, currentFeed: Feed?) -> Unit = { _, _ -> },
expandOnClick: () -> Unit
expandOnClick: () -> Unit,
) {
AnimatedVisibility(
visible = groupVisible,
@@ -26,16 +26,17 @@ fun ColumnScope.GroupList(
exit = fadeOut() + shrinkVertically(),
) {
Column(modifier = modifier) {
FeedBar(
barButtonType = SecondExpandType(
content = groupWithFeed.group.name,
ButtonBar(
buttonBarType = ButtonBarType.GroupBar(
title = groupWithFeed.group.name,
icon = Icons.Rounded.ExpandMore,
important = groupWithFeed.group.important ?: 0,
iconOnClick = expandOnClick,
),
iconOnClickListener = expandOnClick
) {
groupAndFeedOnClick(groupWithFeed.group, null)
}
onClick = {
groupAndFeedOnClick(groupWithFeed.group, null)
}
)
FeedList(
visible = feedVisible,
feeds = groupWithFeed.feeds,
@@ -40,7 +40,7 @@ fun ReadPageTopBar(
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}) {
Icon(
modifier = Modifier.size(20.dp),
// modifier = Modifier.size(20.dp),
imageVector = Icons.Rounded.Share,
contentDescription = "Share",
tint = MaterialTheme.colorScheme.primary,
@@ -50,7 +50,7 @@ fun ReadPageTopBar(
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}) {
Icon(
modifier = Modifier.size(28.dp),
// modifier = Modifier.size(28.dp),
imageVector = Icons.Rounded.MoreHoriz,
contentDescription = "More",
tint = MaterialTheme.colorScheme.primary,
@@ -0,0 +1,53 @@
package me.ash.reader.ui.widget
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.DpOffset
@Composable
fun Menu(
offset: DpOffset,
expanded: Boolean,
dismissFunction: () -> Unit = {},
) {
Box {
DropdownMenu(
// modifier = Modifier.offset(offset.x.dp, offset.y.dp),
offset = offset,
expanded = expanded,
onDismissRequest = dismissFunction,
) {
DropdownMenuItem(
text = {
Text(text = "打开")
},
onClick = {
}
)
DropdownMenuItem(
text = {
Text(text = "取消订阅")
},
onClick = {
}
)
DropdownMenuItem(
text = {
Text(text = "编辑")
},
onClick = {
}
)
DropdownMenuItem(
text = {
Text(text = "默认全文解析")
},
onClick = {
}
)
}
}
}