Refactor(ToolStore): Optimize user experience
Added prompts for errors when loading more and errors when reloading. Add autoload next page.
This commit is contained in:
@@ -48,7 +48,7 @@ internal class ToolStorePagingSource(
|
||||
}
|
||||
} ?: toolEntity
|
||||
},
|
||||
prevKey = if (currentPage == 0) null else currentPage - 1,
|
||||
prevKey = if (currentPage == 1) null else currentPage - 1,
|
||||
nextKey = if (currentPage < pages) currentPage + 1 else null
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.component
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
|
||||
|
||||
@Composable
|
||||
fun ClickableText(
|
||||
@StringRes text: Int,
|
||||
@StringRes replaceText: Int,
|
||||
onClick: (Int) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val primaryColor = MaterialTheme.colorScheme.primary
|
||||
|
||||
val annotatedString = buildAnnotatedString {
|
||||
val clickablePart = ResourcesUtils.getString(
|
||||
context = context,
|
||||
resId = replaceText
|
||||
)
|
||||
val mainText = ResourcesUtils.getString(
|
||||
context = context,
|
||||
resId = text,
|
||||
clickablePart
|
||||
)
|
||||
append(mainText.substringBefore(clickablePart))
|
||||
pushStringAnnotation(tag = "Click", annotation = clickablePart)
|
||||
withStyle(style = SpanStyle(color = primaryColor)) {
|
||||
append(clickablePart)
|
||||
}
|
||||
pop()
|
||||
append(mainText.substringAfter(clickablePart))
|
||||
}
|
||||
|
||||
ClickableText(text = annotatedString, onClick = onClick)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.store
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.ReportDrawnWhen
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -23,7 +24,6 @@ import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -46,6 +46,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
@@ -55,10 +56,14 @@ import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
import top.fatweb.oxygen.toolbox.ui.component.ClickableText
|
||||
import top.fatweb.oxygen.toolbox.ui.component.DEFAULT_TOOL_CARD_SKELETON_COUNT
|
||||
import top.fatweb.oxygen.toolbox.ui.component.ToolCard
|
||||
import top.fatweb.oxygen.toolbox.ui.component.ToolCardSkeleton
|
||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar
|
||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.rememberDraggableScroller
|
||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.scrollbarState
|
||||
import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
|
||||
|
||||
@Composable
|
||||
internal fun ToolStoreRoute(
|
||||
@@ -101,9 +106,11 @@ internal fun ToolStoreScreen(
|
||||
onChangeInstallType: (type: ToolStoreUiState.InstallInfo.Type) -> Unit,
|
||||
onInstallTool: (installTool: ToolEntity) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val isToolLoading =
|
||||
toolStorePagingItems.loadState.refresh is LoadState.Loading
|
||||
|| toolStorePagingItems.loadState.append is LoadState.Loading
|
||||
toolStorePagingItems.loadState.refresh == LoadState.Loading
|
||||
|| toolStorePagingItems.loadState.append == LoadState.Loading
|
||||
|
||||
ReportDrawnWhen { !isToolLoading }
|
||||
|
||||
@@ -114,11 +121,19 @@ internal fun ToolStoreScreen(
|
||||
}
|
||||
}
|
||||
LaunchedEffect(toolStorePagingItems.loadState.refresh) {
|
||||
if (toolStorePagingItems.loadState.refresh is LoadState.Loading) {
|
||||
pullToRefreshState.startRefresh()
|
||||
} else {
|
||||
if (toolStorePagingItems.loadState.refresh != LoadState.Loading) {
|
||||
pullToRefreshState.endRefresh()
|
||||
}
|
||||
if (toolStorePagingItems.loadState.refresh is LoadState.Error) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
ResourcesUtils.getString(
|
||||
context = context,
|
||||
resId = R.string.feature_store_reload_error
|
||||
),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
val itemsAvailable = toolStorePagingItems.itemCount
|
||||
@@ -142,6 +157,7 @@ internal fun ToolStoreScreen(
|
||||
.clipToBounds()
|
||||
.nestedScroll(pullToRefreshState.nestedScrollConnection)
|
||||
) {
|
||||
if (itemsAvailable > 0 || (toolStorePagingItems.loadState.refresh == LoadState.Loading && itemsAvailable == 0)) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
@@ -151,6 +167,7 @@ internal fun ToolStoreScreen(
|
||||
verticalItemSpacing = 24.dp,
|
||||
state = state
|
||||
) {
|
||||
if (itemsAvailable > 0) {
|
||||
toolsPanel(
|
||||
toolStorePagingItems = toolStorePagingItems,
|
||||
onAction = { tool, installType ->
|
||||
@@ -162,18 +179,32 @@ internal fun ToolStoreScreen(
|
||||
onNavigateToToolView(it.authorUsername, it.toolId, it.upgrade != null)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
if (itemsAvailable == 0 || toolStorePagingItems.loadState.append == LoadState.Loading) {
|
||||
items(count = DEFAULT_TOOL_CARD_SKELETON_COUNT) {
|
||||
ToolCardSkeleton()
|
||||
}
|
||||
}
|
||||
if (toolStorePagingItems.loadState.append is LoadState.Error) {
|
||||
item(span = StaggeredGridItemSpan.FullLine) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
ClickableText(
|
||||
text = R.string.feature_store_load_more_error,
|
||||
replaceText = R.string.feature_store_retry
|
||||
) {
|
||||
toolStorePagingItems.retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item(span = StaggeredGridItemSpan.FullLine) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
|
||||
}
|
||||
}
|
||||
|
||||
PullToRefreshContainer(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter),
|
||||
state = pullToRefreshState,
|
||||
)
|
||||
}
|
||||
|
||||
if (itemsAvailable == 0 && !isToolLoading) {
|
||||
Column(
|
||||
@@ -183,6 +214,14 @@ internal fun ToolStoreScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
if (toolStorePagingItems.loadState.refresh is LoadState.Error) {
|
||||
ClickableText(
|
||||
text = R.string.feature_store_load_error,
|
||||
replaceText = R.string.feature_store_retry
|
||||
) {
|
||||
toolStorePagingItems.refresh()
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(
|
||||
if (searchValue.isEmpty()) R.string.core_nothing
|
||||
@@ -191,6 +230,13 @@ internal fun ToolStoreScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullToRefreshContainer(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter),
|
||||
state = pullToRefreshState,
|
||||
)
|
||||
|
||||
state.DraggableScrollbar(
|
||||
modifier = Modifier
|
||||
@@ -218,21 +264,21 @@ private fun LazyStaggeredGridScope.toolsPanel(
|
||||
onClick: (ToolEntity) -> Unit
|
||||
) {
|
||||
items(
|
||||
items = toolStorePagingItems.itemSnapshotList,
|
||||
key = { it!!.id },
|
||||
count = toolStorePagingItems.itemCount
|
||||
) {
|
||||
val item = toolStorePagingItems[it]!!
|
||||
ToolCard(
|
||||
tool = it!!,
|
||||
specifyVer = it.upgrade,
|
||||
actionIcon = if (it.upgrade != null) OxygenIcons.Upgrade else if (!it.isInstalled) OxygenIcons.Download else null,
|
||||
tool = item,
|
||||
specifyVer = item.upgrade,
|
||||
actionIcon = if (item.upgrade != null) OxygenIcons.Upgrade else if (!item.isInstalled) OxygenIcons.Download else null,
|
||||
actionIconContentDescription = stringResource(R.string.core_install),
|
||||
onAction = {
|
||||
onAction(
|
||||
it,
|
||||
if (it.upgrade != null) ToolStoreUiState.InstallInfo.Type.Upgrade else ToolStoreUiState.InstallInfo.Type.Install
|
||||
item,
|
||||
if (item.upgrade != null) ToolStoreUiState.InstallInfo.Type.Upgrade else ToolStoreUiState.InstallInfo.Type.Install
|
||||
)
|
||||
},
|
||||
onClick = { onClick(it) }
|
||||
onClick = { onClick(item) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,6 @@ object ResourcesUtils {
|
||||
-1
|
||||
}
|
||||
|
||||
fun getString(context: Context, @StringRes resId: Int): String =
|
||||
context.resources.getString(resId)
|
||||
fun getString(context: Context, @StringRes resId: Int, vararg formatArgs: Any): String =
|
||||
context.resources.getString(resId, *formatArgs)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
<string name="core_nothing_found">未找到相关内容</string>
|
||||
|
||||
<string name="feature_store_title">商店</string>
|
||||
<string name="feature_store_retry">重试</string>
|
||||
<string name="feature_store_load_error">⚠️ 无法加载商店内容,请稍后%1$s……</string>
|
||||
<string name="feature_store_reload_error">⚠️ 重载失败,请稍后重试……</string>
|
||||
<string name="feature_store_load_more_error">⚠️ 无法载入更多内容,请稍后%1$s……</string>
|
||||
<string name="feature_store_install_tool">安装工具</string>
|
||||
<string name="feature_store_ask_install">确定安装由用户 %1$s 提供的工具 %2$s 吗?</string>
|
||||
<string name="feature_store_install_success">安装成功</string>
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
<string name="core_nothing_found">Nothing found</string>
|
||||
|
||||
<string name="feature_store_title">Store</string>
|
||||
<string name="feature_store_retry">try again</string>
|
||||
<string name="feature_store_load_error">⚠️ Unable to load store content, please %1$s later…</string>
|
||||
<string name="feature_store_reload_error">⚠️ Reload failed, please try again later…</string>
|
||||
<string name="feature_store_load_more_error">⚠️ Unable to load more content, please %1$s later…</string>
|
||||
<string name="feature_store_install_tool">Install Tool</string>
|
||||
<string name="feature_store_ask_install">Are you sure you want to install tool %1$s provided by user %2$s?</string>
|
||||
<string name="feature_store_install_success">Install Success</string>
|
||||
@@ -40,7 +44,7 @@
|
||||
|
||||
<string name="feature_tools_title">Tools</string>
|
||||
<string name="feature_tools_description">Desc</string>
|
||||
<string name="feature_tools_can_not_open">⚠️ Can not open the tool</string>
|
||||
<string name="feature_tools_can_not_open">⚠️ Unable to open the tool</string>
|
||||
<string name="feature_tools_no_tools_installed">No tools installed yet</string>
|
||||
<string name="feature_tools_go_to_store">Go to store…</string>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user