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

View File

@ -75,14 +75,13 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.4'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "me.onebone:toolbar-compose:2.3.1"
implementation "com.google.accompanist:accompanist-insets:0.24.1-alpha"
implementation "com.google.accompanist:accompanist-systemuicontroller:0.24.1-alpha"
implementation 'com.google.accompanist:accompanist-pager:0.24.1-alpha'
implementation 'androidx.core:core-ktx:1.7.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha01"
implementation "androidx.compose.material:material:1.2.0-alpha03"
implementation "androidx.compose.material:material:1.2.0-alpha04"
implementation "androidx.compose.material3:material3:1.0.0-alpha06"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"

View File

@ -10,7 +10,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.Reader">
<activity

View File

@ -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()
}

View File

@ -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
}

View File

@ -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)
},
)
}
}
}
}
}

View File

@ -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(

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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 = {
}
)
}
}
}

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB