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,39 +157,55 @@ internal fun ToolStoreScreen(
|
||||
.clipToBounds()
|
||||
.nestedScroll(pullToRefreshState.nestedScrollConnection)
|
||||
) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
columns = StaggeredGridCells.Adaptive(160.dp),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalItemSpacing = 24.dp,
|
||||
state = state
|
||||
) {
|
||||
toolsPanel(
|
||||
toolStorePagingItems = toolStorePagingItems,
|
||||
onAction = { tool, installType ->
|
||||
installTool = tool
|
||||
onChangeInstallStatus(ToolStoreUiState.InstallInfo.Status.Pending)
|
||||
onChangeInstallType(installType)
|
||||
},
|
||||
onClick = {
|
||||
onNavigateToToolView(it.authorUsername, it.toolId, it.upgrade != null)
|
||||
if (itemsAvailable > 0 || (toolStorePagingItems.loadState.refresh == LoadState.Loading && itemsAvailable == 0)) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
columns = StaggeredGridCells.Adaptive(160.dp),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalItemSpacing = 24.dp,
|
||||
state = state
|
||||
) {
|
||||
if (itemsAvailable > 0) {
|
||||
toolsPanel(
|
||||
toolStorePagingItems = toolStorePagingItems,
|
||||
onAction = { tool, installType ->
|
||||
installTool = tool
|
||||
onChangeInstallStatus(ToolStoreUiState.InstallInfo.Status.Pending)
|
||||
onChangeInstallType(installType)
|
||||
},
|
||||
onClick = {
|
||||
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))
|
||||
}
|
||||
)
|
||||
|
||||
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(
|
||||
modifier = Modifier
|
||||
@@ -183,15 +214,30 @@ internal fun ToolStoreScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
if (searchValue.isEmpty()) R.string.core_nothing
|
||||
else R.string.core_nothing_found
|
||||
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
|
||||
else R.string.core_nothing_found
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullToRefreshContainer(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter),
|
||||
state = pullToRefreshState,
|
||||
)
|
||||
|
||||
state.DraggableScrollbar(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user