diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/paging/ToolStorePagingSource.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/paging/ToolStorePagingSource.kt index 32aef6b..ef56e1a 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/paging/ToolStorePagingSource.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/paging/ToolStorePagingSource.kt @@ -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) { diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ClickableText.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ClickableText.kt new file mode 100644 index 0000000..ea0ba91 --- /dev/null +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ClickableText.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/store/ToolStoreScreen.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/store/ToolStoreScreen.kt index 553455f..2e5bf28 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/store/ToolStoreScreen.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/store/ToolStoreScreen.kt @@ -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) } ) } } diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/util/ResourcesUtils.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/util/ResourcesUtils.kt index fe3ae36..550bee8 100644 --- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/util/ResourcesUtils.kt +++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/util/ResourcesUtils.kt @@ -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) } diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 9505b89..27179d5 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -26,6 +26,10 @@ 未找到相关内容 商店 + 重试 + ⚠️ 无法加载商店内容,请稍后%1$s…… + ⚠️ 重载失败,请稍后重试…… + ⚠️ 无法载入更多内容,请稍后%1$s…… 安装工具 确定安装由用户 %1$s 提供的工具 %2$s 吗? 安装成功 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 00e941b..f80eca7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,6 +25,10 @@ Nothing found Store + try again + ⚠️ Unable to load store content, please %1$s later… + ⚠️ Reload failed, please try again later… + ⚠️ Unable to load more content, please %1$s later… Install Tool Are you sure you want to install tool %1$s provided by user %2$s? Install Success @@ -40,7 +44,7 @@ Tools Desc - ⚠️ Can not open the tool + ⚠️ Unable to open the tool No tools installed yet Go to store…