manager: add basic module repo support 2
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.sukisu.ultra.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -23,9 +24,12 @@ import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -38,6 +42,7 @@ import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationSty
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.NavGraphs
|
||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
@@ -166,8 +171,8 @@ fun MainScreen(navController: DestinationsNavigator) {
|
||||
val pagerState = rememberPagerState(initialPage = 0, pageCount = { 4 })
|
||||
val hazeState = remember { HazeState() }
|
||||
val hazeStyle = HazeStyle(
|
||||
backgroundColor = MiuixTheme.colorScheme.background,
|
||||
tint = HazeTint(MiuixTheme.colorScheme.background.copy(0.8f))
|
||||
backgroundColor = MiuixTheme.colorScheme.surface,
|
||||
tint = HazeTint(MiuixTheme.colorScheme.surface.copy(0.8f))
|
||||
)
|
||||
val handlePageChange: (Int) -> Unit = remember(pagerState, coroutineScope) {
|
||||
{ page ->
|
||||
|
||||
@@ -37,7 +37,7 @@ fun BottomBar(
|
||||
|
||||
if (!fullFeatured) return
|
||||
|
||||
val item = BottomBarDestination.entries.mapIndexed { index, destination ->
|
||||
val item = BottomBarDestination.entries.map { destination ->
|
||||
NavigationItem(
|
||||
label = stringResource(destination.label),
|
||||
icon = destination.icon,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
@@ -16,6 +17,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -23,15 +25,15 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Code
|
||||
import androidx.compose.material.icons.rounded.Download
|
||||
import androidx.compose.material.icons.rounded.Link
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -58,13 +60,17 @@ import dev.chrisbanes.haze.HazeTint
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.sukisu.ultra.ui.component.MarkdownContent
|
||||
import com.sukisu.ultra.ui.component.SearchBox
|
||||
import com.sukisu.ultra.ui.component.SearchPager
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.theme.isInDarkTheme
|
||||
import com.sukisu.ultra.ui.util.DownloadListener
|
||||
import com.sukisu.ultra.ui.util.download
|
||||
import com.sukisu.ultra.ui.viewmodel.ModuleRepoViewModel
|
||||
@@ -76,12 +82,16 @@ import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.IconButton
|
||||
import top.yukonga.miuix.kmp.basic.InfiniteProgressIndicator
|
||||
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
||||
import top.yukonga.miuix.kmp.basic.PullToRefresh
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
import top.yukonga.miuix.kmp.basic.SmallTitle
|
||||
import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||
import top.yukonga.miuix.kmp.basic.rememberPullToRefreshState
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.icons.useful.Back
|
||||
import top.yukonga.miuix.kmp.icon.icons.useful.Redo
|
||||
import top.yukonga.miuix.kmp.icon.icons.useful.Save
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
|
||||
import top.yukonga.miuix.kmp.utils.PressFeedbackType
|
||||
import top.yukonga.miuix.kmp.utils.getWindowSize
|
||||
@@ -129,8 +139,11 @@ fun ModuleRepoScreen(
|
||||
navigator: DestinationsNavigator
|
||||
) {
|
||||
val viewModel = viewModel<ModuleRepoViewModel>()
|
||||
val searchStatus by viewModel.searchStatus
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val isDark = isInDarkTheme(prefs.getInt("color_mode", 0))
|
||||
val actionIconTint = colorScheme.onSurface.copy(alpha = if (isDark) 0.7f else 0.9f)
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -140,6 +153,9 @@ fun ModuleRepoScreen(
|
||||
}
|
||||
|
||||
val scrollBehavior = MiuixScrollBehavior()
|
||||
val dynamicTopPadding by remember {
|
||||
derivedStateOf { 12.dp * (1f - scrollBehavior.state.collapsedFraction) }
|
||||
}
|
||||
val hazeState = remember { HazeState() }
|
||||
val hazeStyle = HazeStyle(
|
||||
backgroundColor = colorScheme.surface,
|
||||
@@ -158,21 +174,15 @@ fun ModuleRepoScreen(
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
searchStatus.TopAppBarAnim(hazeState = hazeState, hazeStyle = hazeStyle) {
|
||||
TopAppBar(
|
||||
modifier = Modifier.hazeEffect(hazeState) {
|
||||
style = hazeStyle
|
||||
blurRadius = 30.dp
|
||||
noiseFactor = 0f
|
||||
},
|
||||
color = Color.Transparent,
|
||||
title = stringResource(R.string.module_repo),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
onClick = {
|
||||
navigator.popBackStack()
|
||||
}
|
||||
onClick = { navigator.popBackStack() }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.Useful.Back,
|
||||
@@ -183,6 +193,84 @@ fun ModuleRepoScreen(
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
popupHost = {
|
||||
searchStatus.SearchPager(defaultResult = {}) {
|
||||
item {
|
||||
Spacer(Modifier.height(6.dp))
|
||||
}
|
||||
items(viewModel.searchResults.value, key = { it.moduleId }) { module ->
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp)
|
||||
.padding(bottom = 12.dp),
|
||||
insideMargin = PaddingValues(16.dp),
|
||||
showIndication = true,
|
||||
pressFeedbackType = PressFeedbackType.Sink,
|
||||
onClick = {
|
||||
val args = RepoModuleArg(
|
||||
moduleId = module.moduleId,
|
||||
moduleName = module.moduleName,
|
||||
authors = module.authors,
|
||||
authorsList = module.authorList.map { AuthorArg(it.name, it.link) },
|
||||
homepageUrl = module.homepageUrl,
|
||||
sourceUrl = module.sourceUrl,
|
||||
latestRelease = module.latestRelease,
|
||||
latestReleaseTime = module.latestReleaseTime,
|
||||
releases = module.releases.map { r ->
|
||||
ReleaseArg(
|
||||
tagName = r.tagName,
|
||||
name = r.name,
|
||||
publishedAt = r.publishedAt,
|
||||
assets = r.assets.map { a ->
|
||||
ReleaseAssetArg(name = a.name, downloadUrl = a.downloadUrl, size = a.size)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
navigator.navigate(ModuleRepoDetailScreenDestination(args)) { launchSingleTop = true }
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
if (module.moduleName.isNotBlank()) {
|
||||
Text(
|
||||
text = module.moduleName,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight(550),
|
||||
color = colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
if (module.moduleId.isNotBlank()) {
|
||||
Text(
|
||||
text = "ID: ${module.moduleId}",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight(550),
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "${stringResource(id = R.string.module_author)}: ${module.authors}",
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(bottom = 1.dp),
|
||||
fontWeight = FontWeight(550),
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
if (module.summary.isNotBlank()) {
|
||||
Text(
|
||||
text = module.summary,
|
||||
fontSize = 14.sp,
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 4,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) { innerPadding ->
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val isLoading = viewModel.modules.value.isEmpty()
|
||||
@@ -190,13 +278,49 @@ fun ModuleRepoScreen(
|
||||
if (isLoading) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(getWindowSize().height.dp)
|
||||
.hazeSource(state = hazeState),
|
||||
.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
InfiniteProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
LaunchedEffect(searchStatus.searchText) { viewModel.updateSearchText(searchStatus.searchText) }
|
||||
searchStatus.SearchBox(
|
||||
searchBarTopPadding = dynamicTopPadding,
|
||||
contentPadding = PaddingValues(
|
||||
top = innerPadding.calculateTopPadding(),
|
||||
start = innerPadding.calculateStartPadding(layoutDirection),
|
||||
end = innerPadding.calculateEndPadding(layoutDirection)
|
||||
),
|
||||
hazeState = hazeState,
|
||||
hazeStyle = hazeStyle
|
||||
) { boxHeight ->
|
||||
var isRefreshing by rememberSaveable { mutableStateOf(false) }
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
LaunchedEffect(isRefreshing) {
|
||||
if (isRefreshing) {
|
||||
delay(350)
|
||||
viewModel.refresh()
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
val refreshTexts = listOf(
|
||||
stringResource(R.string.refresh_pulling),
|
||||
stringResource(R.string.refresh_release),
|
||||
stringResource(R.string.refresh_refresh),
|
||||
stringResource(R.string.refresh_complete),
|
||||
)
|
||||
PullToRefresh(
|
||||
isRefreshing = isRefreshing,
|
||||
pullToRefreshState = pullToRefreshState,
|
||||
onRefresh = { if (!isRefreshing) isRefreshing = true },
|
||||
refreshTexts = refreshTexts,
|
||||
contentPadding = PaddingValues(
|
||||
top = innerPadding.calculateTopPadding() + boxHeight.value + 6.dp,
|
||||
start = innerPadding.calculateStartPadding(layoutDirection),
|
||||
end = innerPadding.calculateEndPadding(layoutDirection)
|
||||
),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.height(getWindowSize().height.dp)
|
||||
@@ -205,36 +329,30 @@ fun ModuleRepoScreen(
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.hazeSource(state = hazeState),
|
||||
contentPadding = PaddingValues(
|
||||
top = innerPadding.calculateTopPadding(),
|
||||
top = innerPadding.calculateTopPadding() + boxHeight.value + 6.dp,
|
||||
start = innerPadding.calculateStartPadding(layoutDirection),
|
||||
end = innerPadding.calculateEndPadding(layoutDirection)
|
||||
),
|
||||
overscrollEffect = null,
|
||||
) {
|
||||
items(viewModel.modules.value, key = { it.moduleId }) { module ->
|
||||
val moduleName = remember(module.moduleName) { module.moduleName }
|
||||
val moduleId = remember(module.moduleId) { module.moduleId }
|
||||
val authors = remember(module.authors) { module.authors }
|
||||
val summary = remember(module.summary) { module.summary }
|
||||
val latestReleaseTime = remember(module.latestReleaseTime) { module.latestReleaseTime }
|
||||
val latestTag = remember(module.latestRelease) { module.latestRelease }
|
||||
items(
|
||||
items = viewModel.modules.value,
|
||||
key = { it.moduleId },
|
||||
contentType = { "module" }
|
||||
) { module ->
|
||||
val latestTag = module.latestRelease
|
||||
val latestRel = remember(module.releases, latestTag) {
|
||||
module.releases.find { it.tagName == latestTag } ?: module.releases.firstOrNull()
|
||||
}
|
||||
val latestAsset = remember(latestRel) { latestRel?.assets?.firstOrNull() }
|
||||
|
||||
val moduleVersion = stringResource(id = R.string.module_version)
|
||||
val moduleAuthor = stringResource(id = R.string.module_author)
|
||||
|
||||
val secondaryContainer = colorScheme.secondaryContainer.copy(alpha = 0.8f)
|
||||
val actionIconTint = colorScheme.onSurface.copy(alpha = 0.9f)
|
||||
val installBg = colorScheme.tertiaryContainer.copy(alpha = 0.6f)
|
||||
val installTint = colorScheme.onTertiaryContainer.copy(alpha = 0.8f)
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp)
|
||||
.padding(vertical = 8.dp),
|
||||
.padding(bottom = 12.dp),
|
||||
insideMargin = PaddingValues(16.dp),
|
||||
showIndication = true,
|
||||
pressFeedbackType = PressFeedbackType.Sink,
|
||||
@@ -242,7 +360,7 @@ fun ModuleRepoScreen(
|
||||
val args = RepoModuleArg(
|
||||
moduleId = module.moduleId,
|
||||
moduleName = module.moduleName,
|
||||
authors = authors,
|
||||
authors = module.authors,
|
||||
authorsList = module.authorList.map { AuthorArg(it.name, it.link) },
|
||||
homepageUrl = module.homepageUrl,
|
||||
sourceUrl = module.sourceUrl,
|
||||
@@ -265,39 +383,32 @@ fun ModuleRepoScreen(
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
if (moduleName.isNotBlank()) {
|
||||
if (module.moduleName.isNotBlank()) {
|
||||
Text(
|
||||
text = moduleName,
|
||||
text = module.moduleName,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight(550),
|
||||
color = colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
if (moduleId.isNotBlank()) {
|
||||
if (module.moduleId.isNotBlank()) {
|
||||
Text(
|
||||
text = moduleId,
|
||||
fontSize = 14.sp,
|
||||
text = "ID: ${module.moduleId}",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight(550),
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "$moduleVersion: $latestTag",
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
fontWeight = FontWeight(550),
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
Text(
|
||||
text = "$moduleAuthor: $authors",
|
||||
text = "$moduleAuthor: ${module.authors}",
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(bottom = 1.dp),
|
||||
fontWeight = FontWeight(550),
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
if (summary.isNotBlank()) {
|
||||
if (module.summary.isNotBlank()) {
|
||||
Text(
|
||||
text = summary,
|
||||
text = module.summary,
|
||||
fontSize = 14.sp,
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
@@ -305,52 +416,29 @@ fun ModuleRepoScreen(
|
||||
maxLines = 4,
|
||||
)
|
||||
}
|
||||
if (latestReleaseTime.isNotBlank()) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = latestReleaseTime,
|
||||
fontSize = 12.sp,
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
textAlign = TextAlign.End
|
||||
)
|
||||
}
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
thickness = 0.5.dp,
|
||||
color = colorScheme.outline.copy(alpha = 0.5f)
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (module.homepageUrl.isNotBlank()) {
|
||||
IconButton(
|
||||
backgroundColor = secondaryContainer,
|
||||
minHeight = 35.dp,
|
||||
minWidth = 35.dp,
|
||||
onClick = { uriHandler.openUri(module.homepageUrl) },
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = Icons.Rounded.Link,
|
||||
tint = actionIconTint,
|
||||
contentDescription = null
|
||||
Column {
|
||||
Text(
|
||||
text = latestTag,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
fontWeight = FontWeight(550),
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (module.sourceUrl.isNotBlank()) {
|
||||
IconButton(
|
||||
backgroundColor = secondaryContainer,
|
||||
minHeight = 35.dp,
|
||||
minWidth = 35.dp,
|
||||
onClick = { uriHandler.openUri(module.sourceUrl) },
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = Icons.Rounded.Code,
|
||||
tint = actionIconTint,
|
||||
contentDescription = null
|
||||
if (module.latestReleaseTime.isNotBlank()) {
|
||||
Text(
|
||||
text = module.latestReleaseTime,
|
||||
fontSize = 12.sp,
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
textAlign = TextAlign.End
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -359,7 +447,7 @@ fun ModuleRepoScreen(
|
||||
val fileName = latestAsset.name
|
||||
val downloadingText = stringResource(R.string.module_downloading)
|
||||
IconButton(
|
||||
backgroundColor = installBg,
|
||||
backgroundColor = colorScheme.secondaryContainer.copy(alpha = 0.8f),
|
||||
minHeight = 35.dp,
|
||||
minWidth = 35.dp,
|
||||
onClick = {
|
||||
@@ -373,7 +461,8 @@ fun ModuleRepoScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
val confirmContent = context.getString(R.string.module_install_prompt_with_name, fileName)
|
||||
val confirmContent =
|
||||
context.getString(R.string.module_install_prompt_with_name, fileName)
|
||||
confirmDialog.showConfirm(
|
||||
title = confirmTitle,
|
||||
content = confirmContent
|
||||
@@ -386,14 +475,14 @@ fun ModuleRepoScreen(
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = Icons.Rounded.Download,
|
||||
tint = installTint,
|
||||
imageVector = MiuixIcons.Useful.Save,
|
||||
tint = actionIconTint,
|
||||
contentDescription = stringResource(R.string.install)
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 4.dp, end = 3.dp),
|
||||
modifier = Modifier.padding(start = 4.dp, end = 2.dp),
|
||||
text = stringResource(R.string.install),
|
||||
color = installTint,
|
||||
color = actionIconTint,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 15.sp
|
||||
)
|
||||
@@ -407,9 +496,11 @@ fun ModuleRepoScreen(
|
||||
item { Spacer(Modifier.height(12.dp)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
DownloadListener(context, onInstallModule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid", "DefaultLocale")
|
||||
@Composable
|
||||
@@ -419,6 +510,11 @@ fun ModuleRepoDetailScreen(
|
||||
module: RepoModuleArg
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val isDark = isInDarkTheme(prefs.getInt("color_mode", 0))
|
||||
val actionIconTint = colorScheme.onSurface.copy(alpha = if (isDark) 0.7f else 0.9f)
|
||||
val secondaryContainer = colorScheme.secondaryContainer.copy(alpha = 0.8f)
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val confirmTitle = stringResource(R.string.module)
|
||||
var pendingDownload by remember { mutableStateOf<(() -> Unit)?>(null) }
|
||||
@@ -459,6 +555,20 @@ fun ModuleRepoDetailScreen(
|
||||
tint = colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (module.homepageUrl.isNotBlank()) {
|
||||
IconButton(
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
onClick = { uriHandler.openUri(module.homepageUrl) }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.Useful.Redo,
|
||||
contentDescription = null,
|
||||
tint = colorScheme.onBackground
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -497,8 +607,13 @@ fun ModuleRepoDetailScreen(
|
||||
bottom = innerPadding.calculateBottomPadding(),
|
||||
),
|
||||
) {
|
||||
if (readmeLoaded && readmeText != null) {
|
||||
item {
|
||||
AnimatedVisibility(
|
||||
visible = readmeLoaded && readmeText != null,
|
||||
enter = expandVertically() + fadeIn(),
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
Column {
|
||||
SmallTitle(text = "README")
|
||||
Card(
|
||||
modifier = Modifier
|
||||
@@ -511,6 +626,7 @@ fun ModuleRepoDetailScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (module.authorsList.isNotEmpty()) {
|
||||
item {
|
||||
SmallTitle(
|
||||
@@ -519,9 +635,6 @@ fun ModuleRepoDetailScreen(
|
||||
)
|
||||
}
|
||||
item {
|
||||
val secondaryContainer = colorScheme.secondaryContainer.copy(alpha = 0.8f)
|
||||
val actionIconTint = colorScheme.onSurface.copy(alpha = 0.9f)
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp),
|
||||
@@ -580,10 +693,12 @@ fun ModuleRepoDetailScreen(
|
||||
modifier = Modifier.padding(top = 10.dp)
|
||||
)
|
||||
}
|
||||
items(module.releases, key = { it.tagName }) { rel ->
|
||||
items(
|
||||
items = module.releases,
|
||||
key = { it.tagName },
|
||||
contentType = { "release" }
|
||||
) { rel ->
|
||||
val title = remember(rel.name, rel.tagName) { rel.name.ifBlank { rel.tagName } }
|
||||
val installBg = colorScheme.tertiaryContainer.copy(alpha = 0.6f)
|
||||
val installTint = colorScheme.onTertiaryContainer.copy(alpha = 0.8f)
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
@@ -633,7 +748,6 @@ fun ModuleRepoDetailScreen(
|
||||
|
||||
rel.assets.forEachIndexed { index, asset ->
|
||||
val fileName = asset.name
|
||||
stringResource(R.string.module_start_downloading)
|
||||
val downloadingText = stringResource(R.string.module_downloading)
|
||||
val sizeText = remember(asset.size) {
|
||||
val s = asset.size
|
||||
@@ -683,7 +797,7 @@ fun ModuleRepoDetailScreen(
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
backgroundColor = installBg,
|
||||
backgroundColor = secondaryContainer,
|
||||
minHeight = 35.dp,
|
||||
minWidth = 35.dp,
|
||||
onClick = onClickDownload,
|
||||
@@ -694,14 +808,14 @@ fun ModuleRepoDetailScreen(
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = Icons.Rounded.Download,
|
||||
tint = installTint,
|
||||
imageVector = MiuixIcons.Useful.Save,
|
||||
tint = actionIconTint,
|
||||
contentDescription = stringResource(R.string.install)
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 4.dp, end = 3.dp),
|
||||
modifier = Modifier.padding(start = 4.dp, end = 2.dp),
|
||||
text = stringResource(R.string.install),
|
||||
color = installTint,
|
||||
color = actionIconTint,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 15.sp
|
||||
)
|
||||
|
||||
@@ -12,6 +12,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.sukisu.ultra.ui.component.SearchStatus
|
||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||
import okhttp3.Request
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
@@ -64,6 +66,12 @@ class ModuleRepoViewModel : ViewModel() {
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
private val _searchStatus = mutableStateOf(SearchStatus(""))
|
||||
val searchStatus: State<SearchStatus> = _searchStatus
|
||||
|
||||
private val _searchResults = mutableStateOf<List<RepoModule>>(emptyList())
|
||||
val searchResults: State<List<RepoModule>> = _searchResults
|
||||
|
||||
fun refresh() {
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.Main) { isRefreshing = true }
|
||||
@@ -75,6 +83,34 @@ class ModuleRepoViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateSearchText(text: String) {
|
||||
_searchStatus.value.searchText = text
|
||||
|
||||
if (text.isEmpty()) {
|
||||
_searchStatus.value.resultStatus = SearchStatus.ResultStatus.DEFAULT
|
||||
_searchResults.value = emptyList()
|
||||
return
|
||||
}
|
||||
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
_searchStatus.value.resultStatus = SearchStatus.ResultStatus.LOAD
|
||||
_modules.value.filter {
|
||||
it.moduleId.contains(text, true)
|
||||
|| it.moduleName.contains(text, true)
|
||||
|| it.authors.contains(text, true)
|
||||
|| it.summary.contains(text, true)
|
||||
|| HanziToPinyin.getInstance().toPinyinString(it.moduleName).contains(text, true)
|
||||
}
|
||||
}
|
||||
|
||||
_searchResults.value = result
|
||||
_searchStatus.value.resultStatus = if (result.isEmpty()) {
|
||||
SearchStatus.ResultStatus.EMPTY
|
||||
} else {
|
||||
SearchStatus.ResultStatus.SHOW
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchModulesInternal(): List<RepoModule> {
|
||||
return runCatching {
|
||||
val request = Request.Builder().url(MODULES_URL).build()
|
||||
|
||||
Reference in New Issue
Block a user