Feat(ToolScreen): Finish tool store list
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package top.fatweb.oxygen.toolbox.icon
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun VectorPreview() {
|
||||
Image(OxygenIcons.Loading, null)
|
||||
}
|
||||
|
||||
private var loading: ImageVector? = null
|
||||
|
||||
val OxygenIcons.Loading: ImageVector
|
||||
get() {
|
||||
if (loading != null) {
|
||||
return loading!!
|
||||
}
|
||||
loading = ImageVector.Builder(
|
||||
name = "Loading",
|
||||
defaultWidth = 1024.dp,
|
||||
defaultHeight = 1024.dp,
|
||||
viewportWidth = 1024f,
|
||||
viewportHeight = 1024f
|
||||
).apply {
|
||||
path(
|
||||
fill = SolidColor(Color.Black),
|
||||
fillAlpha = 1.0f,
|
||||
stroke = null,
|
||||
strokeAlpha = 1.0f,
|
||||
strokeLineWidth = 1.0f,
|
||||
strokeLineCap = StrokeCap.Butt,
|
||||
strokeLineJoin = StrokeJoin.Miter,
|
||||
strokeLineMiter = 1.0f,
|
||||
pathFillType = PathFillType.NonZero
|
||||
) {
|
||||
moveTo(988f, 548f)
|
||||
curveToRelative(-19.9f, 0f, -36f, -16.1f, -36f, -36f)
|
||||
curveToRelative(0f, -59.4f, -11.6f, -117f, -34.6f, -171.3f)
|
||||
arcToRelative(
|
||||
440.45f,
|
||||
440.45f,
|
||||
0f,
|
||||
isMoreThanHalf = false,
|
||||
isPositiveArc = false,
|
||||
-94.3f,
|
||||
-139.9f
|
||||
)
|
||||
arcToRelative(
|
||||
437.71f,
|
||||
437.71f,
|
||||
0f,
|
||||
isMoreThanHalf = false,
|
||||
isPositiveArc = false,
|
||||
-139.9f,
|
||||
-94.3f
|
||||
)
|
||||
curveTo(629f, 83.6f, 571.4f, 72f, 512f, 72f)
|
||||
curveToRelative(-19.9f, 0f, -36f, -16.1f, -36f, -36f)
|
||||
reflectiveCurveToRelative(16.1f, -36f, 36f, -36f)
|
||||
curveToRelative(69.1f, 0f, 136.2f, 13.5f, 199.3f, 40.3f)
|
||||
curveTo(772.3f, 66f, 827f, 103f, 874f, 150f)
|
||||
curveToRelative(47f, 47f, 83.9f, 101.8f, 109.7f, 162.7f)
|
||||
curveToRelative(26.7f, 63.1f, 40.2f, 130.2f, 40.2f, 199.3f)
|
||||
curveToRelative(0.1f, 19.9f, -16f, 36f, -35.9f, 36f)
|
||||
close()
|
||||
}
|
||||
}.build()
|
||||
return loading!!
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package top.fatweb.oxygen.toolbox.icon
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.PictureDrawable
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccessTime
|
||||
import androidx.compose.material.icons.filled.Build
|
||||
@@ -16,6 +18,12 @@ import androidx.compose.material.icons.rounded.Home
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.caverock.androidsvg.SVG
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
object OxygenIcons {
|
||||
val ArrowDown = Icons.Rounded.KeyboardArrowDown
|
||||
@@ -33,4 +41,23 @@ object OxygenIcons {
|
||||
val StarBorder = Icons.Outlined.StarBorder
|
||||
val Time = Icons.Default.AccessTime
|
||||
val Tool = Icons.Default.Build
|
||||
}
|
||||
|
||||
fun fromSvgBase64(base64String: String): ImageBitmap {
|
||||
val svg = SVG.getFromString(base64DecodeToString(base64String))
|
||||
val drawable = PictureDrawable(svg.renderToPicture())
|
||||
return drawable.toBitmap().asImageBitmap()
|
||||
}
|
||||
|
||||
fun fromPngBase64(base64String: String): ImageBitmap {
|
||||
val byteArray = base64DecodeToByteArray(base64String)
|
||||
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size).asImageBitmap()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
private fun base64DecodeToString(base64String: String): String =
|
||||
Base64.decode(base64String).decodeToString()
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
private fun base64DecodeToByteArray(base64String: String): ByteArray =
|
||||
Base64.decode(base64String)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ fun OxygenNavHost(
|
||||
modifier: Modifier = Modifier,
|
||||
appState: OxygenAppState,
|
||||
onShowSnackbar: suspend (String, String?) -> Boolean,
|
||||
startDestination: String
|
||||
startDestination: String,
|
||||
handleOnCanScrollChange: (Boolean) -> Unit
|
||||
) {
|
||||
val navController = appState.navController
|
||||
NavHost(
|
||||
@@ -29,7 +30,8 @@ fun OxygenNavHost(
|
||||
onBackClick = navController::popBackStack
|
||||
)
|
||||
toolsScreen(
|
||||
|
||||
onShowSnackbar = onShowSnackbar,
|
||||
handleOnCanScrollChange = handleOnCanScrollChange
|
||||
)
|
||||
starScreen(
|
||||
|
||||
|
||||
@@ -10,10 +10,16 @@ const val TOOLS_ROUTE = "tools_route"
|
||||
|
||||
fun NavController.navigateToTools(navOptions: NavOptions) = navigate(TOOLS_ROUTE, navOptions)
|
||||
|
||||
fun NavGraphBuilder.toolsScreen() {
|
||||
fun NavGraphBuilder.toolsScreen(
|
||||
onShowSnackbar: suspend (String, String?) -> Boolean,
|
||||
handleOnCanScrollChange: (Boolean) -> Unit
|
||||
) {
|
||||
composable(
|
||||
route = TOOLS_ROUTE
|
||||
) {
|
||||
ToolsRoute()
|
||||
ToolsRoute(
|
||||
onShowSnackbar = onShowSnackbar,
|
||||
handleOnCanScrollChange = handleOnCanScrollChange
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,12 @@ fun OxygenApp(appState: OxygenAppState) {
|
||||
|
||||
val noConnectMessage = stringResource(R.string.core_no_connect)
|
||||
|
||||
val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||
var canScroll by remember { mutableStateOf(false) }
|
||||
val handleOnCanScrollChange = { value: Boolean ->
|
||||
canScroll = value
|
||||
}
|
||||
val topAppBarScrollBehavior =
|
||||
TopAppBarDefaults.enterAlwaysScrollBehavior(canScroll = { canScroll })
|
||||
val bottomAppBarScrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
|
||||
|
||||
LaunchedEffect(isOffline) {
|
||||
@@ -175,7 +180,8 @@ fun OxygenApp(appState: OxygenAppState) {
|
||||
startDestination = when (appState.launchPageConfig) {
|
||||
LaunchPageConfig.TOOLS -> TOOLS_ROUTE
|
||||
LaunchPageConfig.STAR -> STAR_ROUTE
|
||||
}
|
||||
},
|
||||
handleOnCanScrollChange = handleOnCanScrollChange
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@ package top.fatweb.oxygen.toolbox.ui.about
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.ReportDrawnWhen
|
||||
import androidx.compose.animation.core.Ease
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -21,6 +26,7 @@ import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||
@@ -33,6 +39,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
@@ -46,6 +53,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -54,6 +62,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.Loading
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar
|
||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar
|
||||
@@ -105,6 +114,8 @@ internal fun LibrariesScreen(
|
||||
|
||||
val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(canScroll = { state.canScrollForward })
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "infiniteTransition")
|
||||
|
||||
var activeSearch by remember { mutableStateOf(false) }
|
||||
var searchValue by remember { mutableStateOf("") }
|
||||
|
||||
@@ -156,8 +167,26 @@ internal fun LibrariesScreen(
|
||||
)
|
||||
) {
|
||||
when (librariesScreenUiState) {
|
||||
LibrariesScreenUiState.Loading -> {
|
||||
Text(text = stringResource(R.string.feature_settings_loading))
|
||||
LibrariesScreenUiState.Loading -> {Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
val angle by infiniteTransition.animateFloat(
|
||||
initialValue = 0F,
|
||||
targetValue = 360F,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(800, easing = Ease),
|
||||
), label = "angle"
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.graphicsLayer { rotationZ = angle },
|
||||
imageVector = OxygenIcons.Loading,
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LibrariesScreenUiState.Nothing -> {
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.component
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||
|
||||
@Composable
|
||||
fun ToolCard(
|
||||
modifier: Modifier = Modifier,
|
||||
tool: Tool,
|
||||
onClickToolCard: () -> Unit
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||
onClick = onClickToolCard
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
ToolVer(ver = tool.ver)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
ToolIcon(icon = tool.icon)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
ToolInfo(
|
||||
toolName = tool.name,
|
||||
toolId = tool.toolId,
|
||||
toolDesc = tool.description
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
AuthorInfo(
|
||||
avatar = tool.author.avatar,
|
||||
nickname = tool.author.nickname
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToolVer(
|
||||
modifier: Modifier = Modifier,
|
||||
ver: String
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
colors = CardDefaults.cardColors(contentColor = MaterialTheme.colorScheme.onSecondaryContainer)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.surfaceContainer)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = ver
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToolIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: String
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.size(80.dp),
|
||||
bitmap = OxygenIcons.fromSvgBase64(icon),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToolInfo(
|
||||
modifier: Modifier = Modifier,
|
||||
toolName: String,
|
||||
toolId: String,
|
||||
toolDesc: String
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
text = toolName
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "ID: $toolId"
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
text = "${stringResource(R.string.feature_tools_description)}: $toolDesc"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AuthorInfo(
|
||||
modifier: Modifier = Modifier,
|
||||
avatar: String,
|
||||
nickname: String
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(24.dp),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer),
|
||||
bitmap = OxygenIcons.fromPngBase64(avatar), contentDescription = "Avatar"
|
||||
)
|
||||
}
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = nickname
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,22 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.settings
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.Ease
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -26,6 +34,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -34,6 +43,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.Loading
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.DarkThemeConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LanguageConfig
|
||||
@@ -82,6 +92,7 @@ fun SettingsDialog(
|
||||
onNavigateToAbout: () -> Unit
|
||||
) {
|
||||
val configuration = LocalConfiguration.current
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "infiniteTransition")
|
||||
|
||||
AlertDialog(
|
||||
modifier = modifier
|
||||
@@ -101,10 +112,26 @@ fun SettingsDialog(
|
||||
) {
|
||||
when (settingsUiState) {
|
||||
SettingsUiState.Loading -> {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 16.dp),
|
||||
text = stringResource(R.string.feature_settings_loading)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
val angle by infiniteTransition.animateFloat(
|
||||
initialValue = 0F,
|
||||
targetValue = 360F,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(800, easing = Ease),
|
||||
), label = "angle"
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.graphicsLayer { rotationZ = angle },
|
||||
imageVector = OxygenIcons.Loading,
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is SettingsUiState.Success -> {
|
||||
|
||||
@@ -2,17 +2,21 @@ package top.fatweb.oxygen.toolbox.ui.tool
|
||||
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||
import top.fatweb.oxygen.toolbox.ui.component.ToolCard
|
||||
|
||||
fun LazyStaggeredGridScope.toolsPanel(
|
||||
toolStorePagingItems: LazyPagingItems<Tool>
|
||||
toolStorePagingItems: LazyPagingItems<Tool>,
|
||||
onClickToolCard: (username: String, toolId: String) -> Unit
|
||||
) {
|
||||
items(
|
||||
items = toolStorePagingItems.itemSnapshotList,
|
||||
key = { it!!.id },
|
||||
) {
|
||||
Text(text = it!!.name)
|
||||
ToolCard(
|
||||
tool = it!!,
|
||||
onClickToolCard = {onClickToolCard(it.author.username, it.toolId)}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.tool
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.ReportDrawnWhen
|
||||
import androidx.compose.animation.core.Ease
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
@@ -20,14 +28,21 @@ 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.rememberLazyStaggeredGridState
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import top.fatweb.oxygen.toolbox.icon.Loading
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar
|
||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.rememberDraggableScroller
|
||||
@@ -36,12 +51,16 @@ import top.fatweb.oxygen.toolbox.ui.component.scrollbar.scrollbarState
|
||||
@Composable
|
||||
internal fun ToolsRoute(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: ToolsScreenViewModel = hiltViewModel()
|
||||
viewModel: ToolsScreenViewModel = hiltViewModel(),
|
||||
onShowSnackbar: suspend (String, String?) -> Boolean,
|
||||
handleOnCanScrollChange: (Boolean) -> Unit
|
||||
) {
|
||||
val toolStorePagingItems = viewModel.getStoreData().collectAsLazyPagingItems()
|
||||
val toolStorePagingItems = viewModel.storeData.collectAsLazyPagingItems()
|
||||
|
||||
ToolsScreen(
|
||||
modifier = modifier,
|
||||
onShowSnackbar = onShowSnackbar,
|
||||
handleOnCanScrollChange = handleOnCanScrollChange,
|
||||
toolStorePagingItems = toolStorePagingItems
|
||||
)
|
||||
}
|
||||
@@ -49,12 +68,15 @@ internal fun ToolsRoute(
|
||||
@Composable
|
||||
internal fun ToolsScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
onShowSnackbar: suspend (String, String?) -> Boolean,
|
||||
handleOnCanScrollChange: (Boolean) -> Unit,
|
||||
toolStorePagingItems: LazyPagingItems<Tool>
|
||||
) {
|
||||
val isToolLoading = toolStorePagingItems.loadState.refresh is LoadState.Loading
|
||||
val context = LocalContext.current
|
||||
val isToolLoading =
|
||||
toolStorePagingItems.loadState.refresh is LoadState.Loading
|
||||
|| toolStorePagingItems.loadState.append is LoadState.Loading
|
||||
|
||||
Log.d("TAG", "ToolsScreen: ${toolStorePagingItems.loadState}")
|
||||
|
||||
ReportDrawnWhen { !isToolLoading }
|
||||
|
||||
val itemsAvailable = toolStorePagingItems.itemCount
|
||||
@@ -62,18 +84,30 @@ internal fun ToolsScreen(
|
||||
val state = rememberLazyStaggeredGridState()
|
||||
val scrollbarState = state.scrollbarState(itemsAvailable = itemsAvailable)
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "infiniteTransition")
|
||||
|
||||
val handleOnClickToolCard = { username: String, toolId: String ->
|
||||
Toast.makeText(context, "$username:$toolId", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
LaunchedEffect(state.canScrollForward) {
|
||||
handleOnCanScrollChange(state.canScrollForward)
|
||||
}
|
||||
Box(
|
||||
modifier.fillMaxSize()
|
||||
) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
columns = StaggeredGridCells.Adaptive(300.dp),
|
||||
columns = StaggeredGridCells.Adaptive(160.dp),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalItemSpacing = 24.dp,
|
||||
state = state
|
||||
) {
|
||||
|
||||
toolsPanel(toolStorePagingItems = toolStorePagingItems)
|
||||
toolsPanel(
|
||||
toolStorePagingItems = toolStorePagingItems,
|
||||
onClickToolCard = handleOnClickToolCard
|
||||
)
|
||||
|
||||
item(span = StaggeredGridItemSpan.FullLine) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
@@ -81,6 +115,29 @@ internal fun ToolsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (toolStorePagingItems.loadState.refresh is LoadState.Loading || toolStorePagingItems.loadState.append is LoadState.Loading) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
val angle by infiniteTransition.animateFloat(
|
||||
initialValue = 0F,
|
||||
targetValue = 360F,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(800, easing = Ease),
|
||||
), label = "angle"
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.graphicsLayer { rotationZ = angle },
|
||||
imageVector = OxygenIcons.Loading,
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
state.DraggableScrollbar(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
@@ -91,26 +148,4 @@ internal fun ToolsScreen(
|
||||
onThumbMoved = state.rememberDraggableScroller(itemsAvailable = itemsAvailable)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun ToolsScreenLoadingPreview() {
|
||||
OxygenTheme {
|
||||
ToolsScreen(toolsScreenUiState = ToolsScreenUiState.Loading)
|
||||
}
|
||||
}
|
||||
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun ToolsScreenPreview() {
|
||||
OxygenTheme {
|
||||
ToolsScreen(
|
||||
toolsScreenUiState = ToolsScreenUiState.Success(
|
||||
runBlocking {
|
||||
ToolDataSource().tool.first()
|
||||
})
|
||||
)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -24,14 +24,12 @@ class ToolsScreenViewModel @Inject constructor(
|
||||
private val currentPage = savedStateHandle.getStateFlow(CURRENT_PAGE, 1)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun getStoreData(): Flow<PagingData<Tool>> {
|
||||
return combine(
|
||||
searchValue,
|
||||
currentPage,
|
||||
::Pair
|
||||
).flatMapLatest { (searchValue, currentPage) ->
|
||||
toolRepository.getStore(searchValue, currentPage).cachedIn(viewModelScope)
|
||||
}
|
||||
val storeData: Flow<PagingData<Tool>> = combine(
|
||||
searchValue,
|
||||
currentPage,
|
||||
::Pair
|
||||
).flatMapLatest { (searchValue, currentPage) ->
|
||||
toolRepository.getStore(searchValue, currentPage).cachedIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user