diff --git a/app/src/main/java/me/ash/reader/ui/component/DisplayText.kt b/app/src/main/java/me/ash/reader/ui/component/DisplayText.kt
new file mode 100644
index 0000000..748ffcd
--- /dev/null
+++ b/app/src/main/java/me/ash/reader/ui/component/DisplayText.kt
@@ -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,
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/ash/reader/ui/component/FeedbackIconButton.kt b/app/src/main/java/me/ash/reader/ui/component/FeedbackIconButton.kt
new file mode 100644
index 0000000..0e00ea6
--- /dev/null
+++ b/app/src/main/java/me/ash/reader/ui/component/FeedbackIconButton.kt
@@ -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,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/ash/reader/ui/ext/ModifierExt.kt b/app/src/main/java/me/ash/reader/ui/ext/ModifierExt.kt
index 511f1cd..6d86d30 100644
--- a/app/src/main/java/me/ash/reader/ui/ext/ModifierExt.kt
+++ b/app/src/main/java/me/ash/reader/ui/ext/ModifierExt.kt
@@ -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()
+ }
+ },
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt
index ffe62a4..86a070d 100644
--- a/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt
+++ b/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt
@@ -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,
diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt
index bcb236a..cd9591b 100644
--- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt
+++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt
@@ -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 = {
+ 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()
}
- }) {
- Icon(
- modifier = Modifier.rotate(if (isSyncing) angle else 0f),
- imageVector = Icons.Rounded.Refresh,
- contentDescription = stringResource(R.string.refresh),
- tint = MaterialTheme.colorScheme.onSurface,
- )
}
- IconButton(onClick = {
+ FeedbackIconButton(
+ isHaptic = false,
+ imageVector = Icons.Rounded.Add,
+ contentDescription = stringResource(R.string.subscribe),
+ tint = MaterialTheme.colorScheme.onSurface,
+ ) {
subscribeViewModel.dispatch(SubscribeViewAction.Show)
- }) {
- Icon(
- imageVector = Icons.Rounded.Add,
- contentDescription = stringResource(R.string.subscribe),
- tint = MaterialTheme.colorScheme.onSurface,
- )
}
-
}
)
},
@@ -144,48 +140,18 @@ fun FeedsPage(
SubscribeDialog()
LazyColumn {
item {
- Text(
- modifier = Modifier
- .padding(
- start = 24.dp,
- top = 48.dp,
- end = 24.dp,
-// bottom = 24.dp
+ DisplayText(
+ modifier = Modifier.pointerInput(Unit) {
+ detectTapGestures(
+ onLongPress = {
+ launcher.launch("ReadYou.opml")
+ }
)
- .pointerInput(Unit) {
- detectTapGestures(
- onLongPress = {
- launcher.launch("ReadYou.opml")
- }
- )
- },
+ },
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(),
diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt
index 2030d63..a397894 100644
--- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt
+++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt
@@ -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(
- imageVector = Icons.Rounded.ArrowBack,
- contentDescription = stringResource(R.string.back),
- tint = MaterialTheme.colorScheme.onSurface
- )
+ FeedbackIconButton(
+ isHaptic = false,
+ imageVector = Icons.Rounded.ArrowBack,
+ contentDescription = stringResource(R.string.back),
+ tint = MaterialTheme.colorScheme.onSurface
+ ) {
+ onScrollToPage(0)
}
},
actions = {
@@ -101,29 +106,28 @@ fun FlowPage(
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
- IconButton(onClick = {
+ FeedbackIconButton(
+ isHaptic = false,
+ imageVector = Icons.Rounded.DoneAll,
+ contentDescription = stringResource(R.string.mark_all_as_read),
+ tint = if (markAsRead) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ MaterialTheme.colorScheme.onSurface
+ },
+ ) {
scope.launch {
viewState.listState.scrollToItem(0)
markAsRead = !markAsRead
}
- }) {
- Icon(
- imageVector = Icons.Rounded.DoneAll,
- contentDescription = stringResource(R.string.mark_all_as_read),
- tint = if (markAsRead) {
- MaterialTheme.colorScheme.primary
- } else {
- MaterialTheme.colorScheme.onSurface
- },
- )
}
}
- IconButton(onClick = {}) {
- Icon(
- imageVector = Icons.Rounded.Search,
- contentDescription = stringResource(R.string.search),
- tint = MaterialTheme.colorScheme.onSurface,
- )
+ 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,
diff --git a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt
index a75c175..590acd3 100644
--- a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt
+++ b/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt
@@ -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 = {
+ FeedbackIconButton(
+ isHaptic = false,
+ imageVector = Icons.Rounded.Close,
+ contentDescription = stringResource(R.string.back),
+ tint = MaterialTheme.colorScheme.onSurface
+ ) {
onScrollToPage(1) {
onClearArticle()
}
- }) {
- Icon(
- imageVector = Icons.Rounded.Close,
- contentDescription = stringResource(R.string.back),
- tint = MaterialTheme.colorScheme.onSurface
- )
}
},
actions = {
if (isShowActions) {
- IconButton(onClick = {}) {
- Icon(
- modifier = Modifier.size(22.dp),
- imageVector = Icons.Outlined.Headphones,
- contentDescription = stringResource(R.string.mark_all_as_read),
- tint = MaterialTheme.colorScheme.onSurface,
- )
+ 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(
- imageVector = Icons.Outlined.MoreVert,
- contentDescription = stringResource(R.string.search),
- tint = MaterialTheme.colorScheme.onSurface,
- )
+ FeedbackIconButton(
+ isHaptic = false,
+ imageVector = Icons.Outlined.MoreVert,
+ contentDescription = stringResource(R.string.search),
+ tint = MaterialTheme.colorScheme.onSurface,
+ ) {
}
}
}
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 5c3b961..5090934 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -16,6 +16,7 @@
未知
返回
转到
+ 设置
刷新
搜索
搜索中…
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 66e0a7a..5f8d0d1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -16,6 +16,7 @@
Unknown
Back
Goto
+ Settings
Refresh
Search
Searching…