Add DisplayText and FeedBackIconButton

This commit is contained in:
Ash 2022-04-03 21:07:28 +08:00
parent 5f11616c6a
commit 43bbb87280
9 changed files with 262 additions and 144 deletions

View File

@ -0,0 +1,51 @@
package me.ash.reader.ui.component
import androidx.compose.animation.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@Composable
fun DisplayText(
modifier: Modifier = Modifier,
text: String,
desc: String,
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(
start = 24.dp,
top = 48.dp,
end = 24.dp,
bottom = 24.dp,
)
) {
Text(
text = text,
style = MaterialTheme.typography.displaySmall,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
AnimatedVisibility(
visible = desc.isNotEmpty(),
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
Text(
text = desc,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}

View File

@ -0,0 +1,40 @@
package me.ash.reader.ui.component
import android.view.HapticFeedbackConstants
import android.view.SoundEffectConstants
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView
@Composable
fun FeedbackIconButton(
modifier: Modifier = Modifier,
imageVector: ImageVector,
contentDescription: String?,
tint: Color = LocalContentColor.current,
isHaptic: Boolean? = true,
isSound: Boolean? = true,
onClick: () -> Unit = {},
) {
val view = LocalView.current
IconButton(
onClick = {
if (isHaptic == true) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
if (isSound == true) view.playSoundEffect(SoundEffectConstants.CLICK)
onClick()
},
) {
Icon(
modifier = modifier,
imageVector = imageVector,
contentDescription = contentDescription,
tint = tint,
)
}
}

View File

@ -1,11 +1,21 @@
package me.ash.reader.ui.ext
import androidx.compose.foundation.clickable
import android.annotation.SuppressLint
import android.view.HapticFeedbackConstants
import android.view.SoundEffectConstants
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
@ -48,3 +58,70 @@ fun Modifier.roundClick(onClick: () -> Unit = {}) = this
fun Modifier.paddingFixedHorizontal(top: Dp = 0.dp, bottom: Dp = 0.dp) = this
.padding(horizontal = 10.dp)
.padding(top = top, bottom = bottom)
@OptIn(
ExperimentalFoundationApi::class,
androidx.compose.ui.ExperimentalComposeUiApi::class
)
@Composable
@SuppressLint("ComposableModifierFactory")
fun Modifier.combinedFeedbackClickable(
isHaptic: Boolean? = false,
isSound: Boolean? = false,
onPressDown: (() -> Unit)? = null,
onPressUp: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
onClick: (() -> Unit)? = null,
): Modifier {
val view = LocalView.current
val interactionSource = remember { MutableInteractionSource() }
return if (onPressDown != null || onPressUp != null) {
indication(interactionSource, LocalIndication.current)
.pointerInput(Unit) {
detectTapGestures(
onPress = { offset ->
onPressDown?.let {
if (isHaptic == true) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
val press = PressInteraction.Press(offset)
interactionSource.emit(press)
tryAwaitRelease()
interactionSource.emit(PressInteraction.Release(press))
it()
}
},
onTap = {
onPressUp?.let {
if (isHaptic == true) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
if (isSound == true) view.playSoundEffect(SoundEffectConstants.CLICK)
it()
}
}
)
}
} else {
combinedClickable(
onClick = {
onClick?.let {
if (isHaptic == true) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
if (isSound == true) view.playSoundEffect(SoundEffectConstants.CLICK)
it()
}
},
onLongClick = {
onLongClick?.let {
if (isHaptic == true) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
if (isSound == true) view.playSoundEffect(SoundEffectConstants.CLICK)
it()
}
},
onDoubleClick = {
onDoubleClick?.let {
if (isHaptic == true) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
if (isSound == true) view.playSoundEffect(SoundEffectConstants.CLICK)
it()
}
},
)
}
}

View File

@ -1,9 +1,11 @@
package me.ash.reader.ui.page.home
import android.view.SoundEffectConstants
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.google.accompanist.pager.ExperimentalPagerApi
@ -17,6 +19,8 @@ fun FilterBar(
filter: Filter,
filterOnClick: (Filter) -> Unit = {},
) {
val view = LocalView.current
Box(
modifier = Modifier.height(60.dp)
) {
@ -45,7 +49,11 @@ fun FilterBar(
)
},
selected = filter == item,
onClick = { filterOnClick(item) },
onClick = {
// view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
view.playSoundEffect(SoundEffectConstants.CLICK)
filterOnClick(item)
},
// colors = NavigationBarItemDefaults.colors(
// selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer,
// unselectedIconColor = MaterialTheme.colorScheme.outline,

View File

@ -2,7 +2,6 @@ package me.ash.reader.ui.page.home.feeds
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
@ -12,8 +11,8 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.KeyboardArrowRight
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material.icons.rounded.Tune
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
@ -22,7 +21,6 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.LiveData
@ -31,6 +29,8 @@ import androidx.work.WorkInfo
import me.ash.reader.R
import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing
import me.ash.reader.ui.component.Banner
import me.ash.reader.ui.component.DisplayText
import me.ash.reader.ui.component.FeedbackIconButton
import me.ash.reader.ui.component.Subtitle
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.getDesc
@ -104,39 +104,35 @@ fun FeedsPage(
SmallTopAppBar(
title = {},
navigationIcon = {
IconButton(onClick = {
FeedbackIconButton(
isHaptic = false,
imageVector = Icons.Rounded.Tune,
contentDescription = stringResource(R.string.settings),
tint = MaterialTheme.colorScheme.onSurface,
) {
onScrollToPage(0)
}) {
Icon(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
)
}
},
actions = {
IconButton(onClick = {
if (!isSyncing) {
onSyncClick()
}
}) {
Icon(
FeedbackIconButton(
isHaptic = false,
modifier = Modifier.rotate(if (isSyncing) angle else 0f),
imageVector = Icons.Rounded.Refresh,
contentDescription = stringResource(R.string.refresh),
tint = MaterialTheme.colorScheme.onSurface,
)
) {
if (!isSyncing) {
onSyncClick()
}
IconButton(onClick = {
subscribeViewModel.dispatch(SubscribeViewAction.Show)
}) {
Icon(
}
FeedbackIconButton(
isHaptic = false,
imageVector = Icons.Rounded.Add,
contentDescription = stringResource(R.string.subscribe),
tint = MaterialTheme.colorScheme.onSurface,
)
) {
subscribeViewModel.dispatch(SubscribeViewAction.Show)
}
}
)
},
@ -144,15 +140,8 @@ fun FeedsPage(
SubscribeDialog()
LazyColumn {
item {
Text(
modifier = Modifier
.padding(
start = 24.dp,
top = 48.dp,
end = 24.dp,
// bottom = 24.dp
)
.pointerInput(Unit) {
DisplayText(
modifier = Modifier.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
launcher.launch("ReadYou.opml")
@ -160,32 +149,9 @@ fun FeedsPage(
)
},
text = viewState.account?.name ?: stringResource(R.string.unknown),
style = MaterialTheme.typography.displaySmall,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
desc = if (isSyncing) stringResource(R.string.syncing) else "",
)
}
item {
AnimatedVisibility(
visible = isSyncing,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
Text(
modifier = Modifier.padding(
start = 24.dp,
top = 0.dp,
end = 24.dp,
bottom = 0.dp
),
text = stringResource(R.string.syncing),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
)
}
Spacer(modifier = Modifier.height(24.dp))
}
item {
Banner(
title = filterState.filter.getName(),

View File

@ -10,14 +10,16 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.DoneAll
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallTopAppBar
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.LiveData
@ -29,6 +31,8 @@ import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing
import me.ash.reader.ui.component.DisplayText
import me.ash.reader.ui.component.FeedbackIconButton
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.getName
import me.ash.reader.ui.page.home.FilterBar
@ -87,12 +91,13 @@ fun FlowPage(
SmallTopAppBar(
title = {},
navigationIcon = {
IconButton(onClick = { onScrollToPage(0) }) {
Icon(
FeedbackIconButton(
isHaptic = false,
imageVector = Icons.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
)
) {
onScrollToPage(0)
}
},
actions = {
@ -101,13 +106,8 @@ fun FlowPage(
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
IconButton(onClick = {
scope.launch {
viewState.listState.scrollToItem(0)
markAsRead = !markAsRead
}
}) {
Icon(
FeedbackIconButton(
isHaptic = false,
imageVector = Icons.Rounded.DoneAll,
contentDescription = stringResource(R.string.mark_all_as_read),
tint = if (markAsRead) {
@ -115,15 +115,19 @@ fun FlowPage(
} else {
MaterialTheme.colorScheme.onSurface
},
)
) {
scope.launch {
viewState.listState.scrollToItem(0)
markAsRead = !markAsRead
}
}
IconButton(onClick = {}) {
Icon(
}
FeedbackIconButton(
isHaptic = false,
imageVector = Icons.Rounded.Search,
contentDescription = stringResource(R.string.search),
tint = MaterialTheme.colorScheme.onSurface,
)
) {
}
}
)
@ -142,46 +146,16 @@ fun FlowPage(
state = viewState.listState,
) {
item {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
start = if (true) 54.dp else 24.dp,
top = 48.dp,
end = 24.dp,
// bottom = 24.dp
),
DisplayText(
modifier = Modifier.padding(start = 30.dp),
text = when {
filterState.group != null -> filterState.group.name
filterState.feed != null -> filterState.feed.name
else -> filterState.filter.getName()
},
style = MaterialTheme.typography.displaySmall,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
desc = if (isSyncing) stringResource(R.string.syncing) else "",
)
}
item {
AnimatedVisibility(
visible = isSyncing,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
Text(
modifier = Modifier.padding(
start = if (true) 54.dp else 24.dp,
top = 0.dp,
end = 24.dp,
bottom = 0.dp
),
text = stringResource(R.string.syncing),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
)
}
Spacer(modifier = Modifier.height(24.dp))
}
item {
AnimatedVisibility(
visible = markAsRead,

View File

@ -22,6 +22,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import me.ash.reader.R
import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.ui.component.FeedbackIconButton
import me.ash.reader.ui.component.WebView
import me.ash.reader.ui.ext.collectAsStateValue
@ -143,34 +144,33 @@ private fun TopBar(
),
title = {},
navigationIcon = {
IconButton(onClick = {
onScrollToPage(1) {
onClearArticle()
}
}) {
Icon(
FeedbackIconButton(
isHaptic = false,
imageVector = Icons.Rounded.Close,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
)
) {
onScrollToPage(1) {
onClearArticle()
}
}
},
actions = {
if (isShowActions) {
IconButton(onClick = {}) {
Icon(
FeedbackIconButton(
isHaptic = false,
modifier = Modifier.size(22.dp),
imageVector = Icons.Outlined.Headphones,
contentDescription = stringResource(R.string.mark_all_as_read),
tint = MaterialTheme.colorScheme.onSurface,
)
) {
}
IconButton(onClick = {}) {
Icon(
FeedbackIconButton(
isHaptic = false,
imageVector = Icons.Outlined.MoreVert,
contentDescription = stringResource(R.string.search),
tint = MaterialTheme.colorScheme.onSurface,
)
) {
}
}
}

View File

@ -16,6 +16,7 @@
<string name="unknown">未知</string>
<string name="back">返回</string>
<string name="go_to">转到</string>
<string name="settings">设置</string>
<string name="refresh">刷新</string>
<string name="search">搜索</string>
<string name="searching">搜索中…</string>

View File

@ -16,6 +16,7 @@
<string name="unknown">Unknown</string>
<string name="back">Back</string>
<string name="go_to">Goto</string>
<string name="settings">Settings</string>
<string name="refresh">Refresh</string>
<string name="search">Search</string>
<string name="searching">Searching…</string>