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