Feat(LibrariesScreen): Finish LibrariesScreen
Implement open source license display in LibrariesScreen
This commit is contained in:
@@ -8,6 +8,7 @@ plugins {
|
|||||||
alias(libs.plugins.aboutlibraries)
|
alias(libs.plugins.aboutlibraries)
|
||||||
alias(libs.plugins.hilt)
|
alias(libs.plugins.hilt)
|
||||||
alias(libs.plugins.protobuf)
|
alias(libs.plugins.protobuf)
|
||||||
|
alias(libs.plugins.kotlinxSerialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -98,11 +99,7 @@ aboutLibraries {
|
|||||||
registerAndroidTasks = false
|
registerAndroidTasks = false
|
||||||
configPath = "libConfig"
|
configPath = "libConfig"
|
||||||
outputFileName = "dependencies.json"
|
outputFileName = "dependencies.json"
|
||||||
exclusionPatterns = listOf(
|
exclusionPatterns = listOf<Regex>().map { it.toPattern() }
|
||||||
Regex("androidx.*"),
|
|
||||||
Regex("org.jetbrains.*"),
|
|
||||||
Regex("com.google.guava:listenablefuture")
|
|
||||||
).map { it.toPattern() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task("exportLibrariesToJson", AboutLibrariesTask::class) {
|
task("exportLibrariesToJson", AboutLibrariesTask::class) {
|
||||||
@@ -158,4 +155,5 @@ dependencies {
|
|||||||
implementation(libs.protobuf.kotlin.lite)
|
implementation(libs.protobuf.kotlin.lite)
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation(libs.androidx.hilt.navigation.compose)
|
implementation(libs.androidx.hilt.navigation.compose)
|
||||||
|
implementation(libs.kotlinx.serialization.json)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.data.lib
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import top.fatweb.oxygen.toolbox.R
|
||||||
|
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
||||||
|
import top.fatweb.oxygen.toolbox.network.Dispatcher
|
||||||
|
import top.fatweb.oxygen.toolbox.network.OxygenDispatchers
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DepDataSource @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
@Dispatcher(OxygenDispatchers.IO) private val ioDispatcher: CoroutineDispatcher
|
||||||
|
) {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
val dependencies = flow {
|
||||||
|
val inputStream = context.resources.openRawResource(R.raw.dependencies)
|
||||||
|
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
||||||
|
val dependencies = json.decodeFromString<Dependencies>(jsonString)
|
||||||
|
emit(dependencies)
|
||||||
|
}.flowOn(ioDispatcher)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.di
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object AppModule {
|
||||||
|
@Provides
|
||||||
|
fun provideContext(app: Application): Context = app.applicationContext
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ import top.fatweb.oxygen.toolbox.monitor.ConnectivityManagerNetworkMonitor
|
|||||||
import top.fatweb.oxygen.toolbox.monitor.NetworkMonitor
|
import top.fatweb.oxygen.toolbox.monitor.NetworkMonitor
|
||||||
import top.fatweb.oxygen.toolbox.monitor.TimeZoneBroadcastMonitor
|
import top.fatweb.oxygen.toolbox.monitor.TimeZoneBroadcastMonitor
|
||||||
import top.fatweb.oxygen.toolbox.monitor.TimeZoneMonitor
|
import top.fatweb.oxygen.toolbox.monitor.TimeZoneMonitor
|
||||||
|
import top.fatweb.oxygen.toolbox.repository.lib.DepRepository
|
||||||
|
import top.fatweb.oxygen.toolbox.repository.lib.LocalDepRepository
|
||||||
import top.fatweb.oxygen.toolbox.repository.tool.LocalToolRepository
|
import top.fatweb.oxygen.toolbox.repository.tool.LocalToolRepository
|
||||||
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
|
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
|
||||||
import top.fatweb.oxygen.toolbox.repository.userdata.LocalUserDataRepository
|
import top.fatweb.oxygen.toolbox.repository.userdata.LocalUserDataRepository
|
||||||
@@ -27,4 +29,7 @@ abstract class DataModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
internal abstract fun bindsToolRepository(toolRepository: LocalToolRepository): ToolRepository
|
internal abstract fun bindsToolRepository(toolRepository: LocalToolRepository): ToolRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
internal abstract fun bindsDepRepository(depRepository: LocalDepRepository): DepRepository
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model.lib
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Dependencies(
|
||||||
|
val metadata: Metadata,
|
||||||
|
|
||||||
|
val libraries: List<Library>,
|
||||||
|
|
||||||
|
val licenses: Map<String, License>
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model.lib
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Developer(
|
||||||
|
val name: String? = null,
|
||||||
|
|
||||||
|
val organisationUrl: String? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model.lib
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Funding(
|
||||||
|
val platform: String,
|
||||||
|
|
||||||
|
val url: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model.lib
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Library(
|
||||||
|
val uniqueId: String,
|
||||||
|
|
||||||
|
val artifactVersion: String? = null,
|
||||||
|
|
||||||
|
val name: String? = null,
|
||||||
|
|
||||||
|
val description: String? = null,
|
||||||
|
|
||||||
|
val website: String? = null,
|
||||||
|
|
||||||
|
val developers: List<Developer>,
|
||||||
|
|
||||||
|
val organization: Organization? = null,
|
||||||
|
|
||||||
|
val scm: Scm? = null,
|
||||||
|
|
||||||
|
val licenses: List<String>,
|
||||||
|
|
||||||
|
val funding: List<Funding>,
|
||||||
|
|
||||||
|
val tag: String? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model.lib
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class License(
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
val url: String? = null,
|
||||||
|
|
||||||
|
val year: String? = null,
|
||||||
|
|
||||||
|
val content: String? = null,
|
||||||
|
|
||||||
|
val internalHash: String? = null,
|
||||||
|
|
||||||
|
val hash: String,
|
||||||
|
|
||||||
|
val spdxId: String? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model.lib
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Metadata(
|
||||||
|
val generated: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model.lib
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Organization(
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
val url: String? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model.lib
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Scm(
|
||||||
|
val connection: String? = null,
|
||||||
|
|
||||||
|
val developerConnection: String? = null,
|
||||||
|
|
||||||
|
val url: String? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.repository.lib
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
||||||
|
|
||||||
|
interface DepRepository {
|
||||||
|
val dependencies: Flow<Dependencies>
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.repository.lib
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import top.fatweb.oxygen.toolbox.data.lib.DepDataSource
|
||||||
|
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class LocalDepRepository @Inject constructor(
|
||||||
|
depDataSource: DepDataSource
|
||||||
|
) : DepRepository {
|
||||||
|
override val dependencies: Flow<Dependencies> =
|
||||||
|
depDataSource.dependencies
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
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
|
||||||
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -32,7 +33,7 @@ internal fun AboutRoute(
|
|||||||
modifier: Modifier = Modifier, onBackClick: () -> Unit, onNavigateToLibraries: () -> Unit
|
modifier: Modifier = Modifier, onBackClick: () -> Unit, onNavigateToLibraries: () -> Unit
|
||||||
) {
|
) {
|
||||||
AboutScreen(
|
AboutScreen(
|
||||||
modifier = modifier,
|
modifier = modifier.safeDrawingPadding(),
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
onNavigateToLibraries = onNavigateToLibraries
|
onNavigateToLibraries = onNavigateToLibraries
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.ui.about
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
|
||||||
|
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||||
|
import top.fatweb.oxygen.toolbox.ui.component.LibraryCard
|
||||||
|
|
||||||
|
fun LazyStaggeredGridScope.librariesPanel(
|
||||||
|
librariesScreenUiState: LibrariesScreenUiState,
|
||||||
|
onClickLicense: (key: String) -> Unit
|
||||||
|
) {
|
||||||
|
when (librariesScreenUiState) {
|
||||||
|
LibrariesScreenUiState.Loading -> Unit
|
||||||
|
|
||||||
|
is LibrariesScreenUiState.Success -> {
|
||||||
|
items(
|
||||||
|
items = librariesScreenUiState.dependencies.libraries,
|
||||||
|
key = { it.uniqueId }
|
||||||
|
) {
|
||||||
|
LibraryCard(
|
||||||
|
library = it,
|
||||||
|
licenses = librariesScreenUiState.dependencies.licenses.filter { entry ->
|
||||||
|
it.licenses.contains(entry.key)
|
||||||
|
},
|
||||||
|
onClickLicense = onClickLicense
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,71 @@
|
|||||||
package top.fatweb.oxygen.toolbox.ui.about
|
package top.fatweb.oxygen.toolbox.ui.about
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.activity.compose.ReportDrawnWhen
|
||||||
|
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.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
|
import androidx.compose.foundation.layout.systemBars
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
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.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
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.R
|
||||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||||
|
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.theme.OxygenPreviews
|
||||||
|
import top.fatweb.oxygen.toolbox.ui.theme.OxygenTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun LibrariesRoute(
|
internal fun LibrariesRoute(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: LibrariesScreenViewModel = hiltViewModel(),
|
||||||
onBackClick: () -> Unit
|
onBackClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val librariesScreenUiState by viewModel.librariesScreenUiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
LibrariesScreen(
|
LibrariesScreen(
|
||||||
modifier = modifier,
|
modifier = modifier.safeDrawingPadding(),
|
||||||
|
librariesScreenUiState = librariesScreenUiState,
|
||||||
onBackClick = onBackClick
|
onBackClick = onBackClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -29,15 +73,122 @@ internal fun LibrariesRoute(
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun LibrariesScreen(
|
internal fun LibrariesScreen(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
librariesScreenUiState: LibrariesScreenUiState,
|
||||||
onBackClick: () -> Unit
|
onBackClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val configuration = LocalConfiguration.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val isLibrariesLoading = librariesScreenUiState is LibrariesScreenUiState.Loading
|
||||||
|
|
||||||
|
ReportDrawnWhen { !isLibrariesLoading }
|
||||||
|
|
||||||
|
val itemsAvailable = howManyItems(librariesScreenUiState)
|
||||||
|
|
||||||
|
val state = rememberLazyStaggeredGridState()
|
||||||
|
val scrollbarState = state.scrollbarState(itemsAvailable = itemsAvailable)
|
||||||
|
|
||||||
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
var dialogTitle by remember { mutableStateOf("") }
|
||||||
|
var dialogContent by remember { mutableStateOf("") }
|
||||||
|
var dialogUrl by remember { mutableStateOf("") }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
LibrariesToolBar(
|
LibrariesToolBar(
|
||||||
onBackClick = onBackClick
|
onBackClick = onBackClick
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Box(
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
when (librariesScreenUiState) {
|
||||||
|
LibrariesScreenUiState.Loading -> {
|
||||||
|
Text(text = stringResource(R.string.feature_settings_loading))
|
||||||
|
}
|
||||||
|
|
||||||
|
is LibrariesScreenUiState.Success -> {
|
||||||
|
val handleOnClickLicense = { key: String ->
|
||||||
|
val license = librariesScreenUiState.dependencies.licenses[key]
|
||||||
|
if (license != null) {
|
||||||
|
showDialog = true
|
||||||
|
dialogTitle = license.name
|
||||||
|
dialogContent = license.content ?: ""
|
||||||
|
dialogUrl = license.url ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyVerticalStaggeredGrid(
|
||||||
|
columns = StaggeredGridCells.Adaptive(300.dp),
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalItemSpacing = 24.dp,
|
||||||
|
state = state
|
||||||
|
) {
|
||||||
|
librariesPanel(
|
||||||
|
librariesScreenUiState = librariesScreenUiState,
|
||||||
|
onClickLicense = handleOnClickLicense
|
||||||
|
)
|
||||||
|
|
||||||
|
item(span = StaggeredGridItemSpan.FullLine) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.DraggableScrollbar(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.windowInsetsPadding(WindowInsets.systemBars)
|
||||||
|
.padding(horizontal = 2.dp)
|
||||||
|
.align(Alignment.CenterEnd),
|
||||||
|
state = scrollbarState, orientation = Orientation.Vertical,
|
||||||
|
onThumbMoved = state.rememberDraggableScroller(itemsAvailable = itemsAvailable)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
modifier = Modifier
|
||||||
|
.widthIn(max = configuration.screenWidthDp.dp - 80.dp)
|
||||||
|
.heightIn(max = configuration.screenHeightDp.dp - 40.dp),
|
||||||
|
onDismissRequest = { showDialog = false },
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = dialogTitle,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier,
|
||||||
|
text = dialogContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
TextButton(onClick = {
|
||||||
|
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(dialogUrl)))
|
||||||
|
}) {
|
||||||
|
Text(text = stringResource(R.string.core_website))
|
||||||
|
}
|
||||||
|
TextButton(onClick = { showDialog = false }) {
|
||||||
|
Text(text = stringResource(R.string.core_close))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,4 +209,19 @@ private fun LibrariesToolBar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun howManyItems(librariesScreenUiState: LibrariesScreenUiState) =
|
||||||
|
when (librariesScreenUiState) {
|
||||||
|
LibrariesScreenUiState.Loading -> 0
|
||||||
|
|
||||||
|
is LibrariesScreenUiState.Success -> librariesScreenUiState.dependencies.libraries.size
|
||||||
|
}
|
||||||
|
|
||||||
|
@OxygenPreviews
|
||||||
|
@Composable
|
||||||
|
private fun LibrariesScreenLoadingPreview() {
|
||||||
|
OxygenTheme {
|
||||||
|
LibrariesScreen(librariesScreenUiState = LibrariesScreenUiState.Loading, onBackClick = {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.ui.about
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
||||||
|
import top.fatweb.oxygen.toolbox.repository.lib.DepRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class LibrariesScreenViewModel @Inject constructor(
|
||||||
|
depRepository: DepRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
val librariesScreenUiState: StateFlow<LibrariesScreenUiState> =
|
||||||
|
depRepository.dependencies
|
||||||
|
.map {
|
||||||
|
LibrariesScreenUiState.Success(it)
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
initialValue = LibrariesScreenUiState.Loading,
|
||||||
|
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface LibrariesScreenUiState {
|
||||||
|
data object Loading: LibrariesScreenUiState
|
||||||
|
|
||||||
|
data class Success(val dependencies: Dependencies) : LibrariesScreenUiState
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,9 @@
|
|||||||
<string name="app_description">All in One</string>
|
<string name="app_description">All in One</string>
|
||||||
<string name="core_ok">完成</string>
|
<string name="core_ok">完成</string>
|
||||||
<string name="core_back">返回</string>
|
<string name="core_back">返回</string>
|
||||||
|
<string name="core_close">关闭</string>
|
||||||
|
<string name="core_unknown">未知</string>
|
||||||
|
<string name="core_website">网站</string>
|
||||||
<string name="core_no_connect">⚠️ 无法连接至互联网</string>
|
<string name="core_no_connect">⚠️ 无法连接至互联网</string>
|
||||||
<string name="feature_tools_title">工具</string>
|
<string name="feature_tools_title">工具</string>
|
||||||
<string name="feature_star_title">收藏</string>
|
<string name="feature_star_title">收藏</string>
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
<string name="app_description">All in One</string>
|
<string name="app_description">All in One</string>
|
||||||
<string name="core_ok">OK</string>
|
<string name="core_ok">OK</string>
|
||||||
<string name="core_back">Back</string>
|
<string name="core_back">Back</string>
|
||||||
|
<string name="core_close">Close</string>
|
||||||
|
<string name="core_unknown">Unknown</string>
|
||||||
|
<string name="core_website">Website</string>
|
||||||
<string name="core_no_connect">⚠️ Unable to connect to the internet</string>
|
<string name="core_no_connect">⚠️ Unable to connect to the internet</string>
|
||||||
<string name="feature_tools_title">Tools</string>
|
<string name="feature_tools_title">Tools</string>
|
||||||
<string name="feature_star_title">Star</string>
|
<string name="feature_star_title">Star</string>
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ plugins {
|
|||||||
alias(libs.plugins.aboutlibraries) apply false
|
alias(libs.plugins.aboutlibraries) apply false
|
||||||
alias(libs.plugins.hilt) apply false
|
alias(libs.plugins.hilt) apply false
|
||||||
alias(libs.plugins.protobuf) apply false
|
alias(libs.plugins.protobuf) apply false
|
||||||
|
alias(libs.plugins.kotlinxSerialization) apply false
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ kotlin = "1.9.23"
|
|||||||
ksp = "1.9.23-1.0.20"
|
ksp = "1.9.23-1.0.20"
|
||||||
aboutlibraries = "11.1.0"
|
aboutlibraries = "11.1.0"
|
||||||
protobufPlugin = "0.9.4"
|
protobufPlugin = "0.9.4"
|
||||||
|
kotlinxSerialization = "1.9.23"
|
||||||
|
|
||||||
desugarJdkLibs = "2.0.4"
|
desugarJdkLibs = "2.0.4"
|
||||||
composeBom = "2024.04.01"
|
composeBom = "2024.04.01"
|
||||||
@@ -22,6 +23,7 @@ androidxDataStore = "1.1.0"
|
|||||||
protobuf = "3.25.2"
|
protobuf = "3.25.2"
|
||||||
androidxNavigation = "2.7.7"
|
androidxNavigation = "2.7.7"
|
||||||
androidxHiltNavigationCompose = "1.2.0"
|
androidxHiltNavigationCompose = "1.2.0"
|
||||||
|
kotlinxSerializationJson = "1.6.3"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
@@ -30,6 +32,7 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
|||||||
aboutlibraries = {id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries"}
|
aboutlibraries = {id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries"}
|
||||||
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
||||||
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
|
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
|
||||||
|
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinxSerialization" }
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
desugar-jdk-libs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugarJdkLibs"}
|
desugar-jdk-libs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugarJdkLibs"}
|
||||||
@@ -73,3 +76,4 @@ protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref
|
|||||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" }
|
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" }
|
||||||
androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "androidxNavigation" }
|
androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "androidxNavigation" }
|
||||||
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
|
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
|
||||||
|
kotlinx-serialization-json = {group="org.jetbrains.kotlinx", name="kotlinx-serialization-json", version.ref = "kotlinxSerializationJson"}
|
||||||
|
|||||||
Reference in New Issue
Block a user