Feat(ToolScreen): Support get tool online
Support get tool online in page
This commit is contained in:
@@ -9,6 +9,7 @@ plugins {
|
|||||||
alias(libs.plugins.hilt)
|
alias(libs.plugins.hilt)
|
||||||
alias(libs.plugins.protobuf)
|
alias(libs.plugins.protobuf)
|
||||||
alias(libs.plugins.kotlinxSerialization)
|
alias(libs.plugins.kotlinxSerialization)
|
||||||
|
alias(libs.plugins.secrets)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -58,6 +59,7 @@ android {
|
|||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
}
|
}
|
||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.5.12"
|
kotlinCompilerExtensionVersion = "1.5.12"
|
||||||
@@ -113,10 +115,15 @@ afterEvaluate {
|
|||||||
tasks.findByName("kspReleaseKotlin")?.dependsOn(tasks.findByName("generateReleaseProto"))
|
tasks.findByName("kspReleaseKotlin")?.dependsOn(tasks.findByName("generateReleaseProto"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secrets {
|
||||||
|
defaultPropertiesFileName = "secrets.defaults.properties"
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
|
testImplementation(libs.paging.common)
|
||||||
|
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
@@ -156,4 +163,9 @@ dependencies {
|
|||||||
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)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
|
implementation(libs.retrofit.core)
|
||||||
|
implementation(libs.retrofit.kotlin.serialization)
|
||||||
|
implementation(libs.okhttp.logging)
|
||||||
|
implementation(libs.paging.runtime)
|
||||||
|
implementation(libs.paging.compose)
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".OxygenApplication"
|
android:name=".OxygenApplication"
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.data.network
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import top.fatweb.oxygen.toolbox.model.Result
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.PageVo
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ResponseResult
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ToolBaseVo
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ToolVo
|
||||||
|
|
||||||
|
interface OxygenNetworkDataSource {
|
||||||
|
suspend fun getStore(
|
||||||
|
searchValue: String = "",
|
||||||
|
currentPage: Int = 1
|
||||||
|
): ResponseResult<PageVo<ToolVo>>
|
||||||
|
|
||||||
|
suspend fun detail(
|
||||||
|
username: String,
|
||||||
|
toolId: String,
|
||||||
|
ver: String = "latest",
|
||||||
|
platform: ToolBaseVo.Platform = ToolBaseVo.Platform.ANDROID
|
||||||
|
): Flow<Result<ToolVo>>
|
||||||
|
}
|
||||||
@@ -1,36 +1,7 @@
|
|||||||
package top.fatweb.oxygen.toolbox.data.tool
|
package top.fatweb.oxygen.toolbox.data.tool
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
|
||||||
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
|
||||||
import top.fatweb.oxygen.toolbox.model.tool.ToolGroup
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
class ToolDataSource @Inject constructor() {
|
class ToolDataSource @Inject constructor() {
|
||||||
val tool = flowOf(
|
|
||||||
(0..100).map { index ->
|
|
||||||
ToolGroup(
|
|
||||||
id = "local-base-$index",
|
|
||||||
title = "${generateRandomString()}-$index",
|
|
||||||
icon = OxygenIcons.Tool,
|
|
||||||
tools = (0..20).map {
|
|
||||||
Tool(
|
|
||||||
id = "local-base-$index-time-screen-$it",
|
|
||||||
icon = OxygenIcons.Time,
|
|
||||||
name = "${generateRandomString()}-$index-$it"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun generateRandomString(length: Int = (1..10).random()): String {
|
|
||||||
val words = ('a'..'z') + ('A'..'Z')
|
|
||||||
|
|
||||||
return (1..length)
|
|
||||||
.map { Random.nextInt(0, words.size) }
|
|
||||||
.map(words::get)
|
|
||||||
.joinToString("")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -9,10 +9,10 @@ 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.DepRepository
|
||||||
import top.fatweb.oxygen.toolbox.repository.lib.LocalDepRepository
|
import top.fatweb.oxygen.toolbox.repository.lib.impl.LocalDepRepository
|
||||||
import top.fatweb.oxygen.toolbox.repository.tool.LocalToolRepository
|
import top.fatweb.oxygen.toolbox.repository.tool.impl.NetworkToolRepository
|
||||||
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.impl.LocalUserDataRepository
|
||||||
import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
|
import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -28,7 +28,7 @@ abstract class DataModule {
|
|||||||
internal abstract fun bindsUserDataRepository(userDataRepository: LocalUserDataRepository): UserDataRepository
|
internal abstract fun bindsUserDataRepository(userDataRepository: LocalUserDataRepository): UserDataRepository
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
internal abstract fun bindsToolRepository(toolRepository: LocalToolRepository): ToolRepository
|
internal abstract fun bindsToolRepository(toolRepository: NetworkToolRepository): ToolRepository
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
internal abstract fun bindsDepRepository(depRepository: LocalDepRepository): DepRepository
|
internal abstract fun bindsDepRepository(depRepository: LocalDepRepository): DepRepository
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.di
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import top.fatweb.oxygen.toolbox.BuildConfig
|
||||||
|
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
|
||||||
|
import top.fatweb.oxygen.toolbox.network.retrofit.RetrofitOxygenNetwork
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object NetworkModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesNetworkJson(): Json = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun okHttpCallFactory(): Call.Factory =
|
||||||
|
OkHttpClient.Builder()
|
||||||
|
.addInterceptor(
|
||||||
|
HttpLoggingInterceptor()
|
||||||
|
.apply {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesOxygenNetworkDataSource(
|
||||||
|
networkJson: Json,
|
||||||
|
okhttpCallFactory: dagger.Lazy<Call.Factory>
|
||||||
|
): OxygenNetworkDataSource = RetrofitOxygenNetwork(networkJson, okhttpCallFactory)
|
||||||
|
}
|
||||||
13
app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Page.kt
Normal file
13
app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Page.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model
|
||||||
|
|
||||||
|
data class Page<T>(
|
||||||
|
val total: Long,
|
||||||
|
|
||||||
|
val pages: Long,
|
||||||
|
|
||||||
|
val size: Long,
|
||||||
|
|
||||||
|
val current: Long,
|
||||||
|
|
||||||
|
val records: List<T>
|
||||||
|
)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.model
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ResponseResult
|
||||||
|
|
||||||
|
sealed interface Result<out T> {
|
||||||
|
data class Success<T>(val data: T) : Result<T>
|
||||||
|
data class Fail(val message: String): Result<Nothing>
|
||||||
|
data class Error(val exception: Throwable) : Result<Nothing>
|
||||||
|
data object Loading : Result<Nothing>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Flow<ResponseResult<T>>.asResult(): Flow<Result<T>> = map<ResponseResult<T>, Result<T>> {
|
||||||
|
if (it.success) {
|
||||||
|
Result.Success(it.data!!)
|
||||||
|
} else {
|
||||||
|
Result.Fail(it.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onStart { emit(Result.Loading) }
|
||||||
|
.catch { emit(Result.Error(it)) }
|
||||||
|
|
||||||
|
fun <T, R> Result<T>.asExternalModel(block: (T) -> R): Result<R> =
|
||||||
|
when (this) {
|
||||||
|
is Result.Success -> {
|
||||||
|
Result.Success(block(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
is Result.Fail -> {
|
||||||
|
Result.Fail(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Result.Error -> {
|
||||||
|
Result.Error(exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Loading -> Result.Loading
|
||||||
|
}
|
||||||
@@ -1,12 +1,53 @@
|
|||||||
package top.fatweb.oxygen.toolbox.model.tool
|
package top.fatweb.oxygen.toolbox.model.tool
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import kotlinx.datetime.LocalDateTime
|
||||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
|
||||||
|
|
||||||
data class Tool(
|
data class Tool(
|
||||||
val id: String,
|
val id: Long,
|
||||||
|
|
||||||
val icon: ImageVector = OxygenIcons.Tool,
|
val name: String,
|
||||||
|
|
||||||
val name: String
|
val toolId: String,
|
||||||
)
|
|
||||||
|
val icon: String,
|
||||||
|
|
||||||
|
val platform: Platform,
|
||||||
|
|
||||||
|
val description: String,
|
||||||
|
|
||||||
|
val base: String? = null,
|
||||||
|
|
||||||
|
val author: Author,
|
||||||
|
|
||||||
|
val ver: String,
|
||||||
|
|
||||||
|
val keywords: List<String>,
|
||||||
|
|
||||||
|
val categories: List<String>,
|
||||||
|
|
||||||
|
val source: String? = null,
|
||||||
|
|
||||||
|
val dist: String? = null,
|
||||||
|
|
||||||
|
val entryPoint: String,
|
||||||
|
|
||||||
|
val createTime: LocalDateTime,
|
||||||
|
|
||||||
|
val updateTime: LocalDateTime
|
||||||
|
) {
|
||||||
|
enum class Platform {
|
||||||
|
WEB,
|
||||||
|
|
||||||
|
DESKTOP,
|
||||||
|
|
||||||
|
ANDROID
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Author(
|
||||||
|
val username: String,
|
||||||
|
|
||||||
|
val nickname: String,
|
||||||
|
|
||||||
|
val avatar: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import top.fatweb.oxygen.toolbox.model.Page
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PageVo<T>(
|
||||||
|
val total: Long,
|
||||||
|
|
||||||
|
val pages: Long,
|
||||||
|
|
||||||
|
val size: Long,
|
||||||
|
|
||||||
|
val current: Long,
|
||||||
|
|
||||||
|
val records: List<T>
|
||||||
|
)
|
||||||
|
|
||||||
|
fun <T, R> PageVo<T>.asExternalModel(block: (T) -> R): Page<R> = Page(
|
||||||
|
total = total,
|
||||||
|
pages = pages,
|
||||||
|
size = size,
|
||||||
|
current = current,
|
||||||
|
records = records.map(block)
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ResponseResult<T>(
|
||||||
|
val code: Long,
|
||||||
|
|
||||||
|
val success: Boolean,
|
||||||
|
|
||||||
|
val msg: String,
|
||||||
|
|
||||||
|
val data: T? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.model
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||||
|
import top.fatweb.oxygen.toolbox.network.serializer.LocalDateTimeSerializer
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ToolBaseVo(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
val source: ToolDataVo? = null,
|
||||||
|
|
||||||
|
val dist: ToolDataVo,
|
||||||
|
|
||||||
|
val platform: Platform? = null,
|
||||||
|
|
||||||
|
val compiled: Boolean? = null,
|
||||||
|
|
||||||
|
@Serializable(LocalDateTimeSerializer::class)
|
||||||
|
val createTime: LocalDateTime? = null,
|
||||||
|
|
||||||
|
@Serializable(LocalDateTimeSerializer::class)
|
||||||
|
val updateTime: LocalDateTime? = null
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
enum class Platform {
|
||||||
|
@SerialName("WEB")
|
||||||
|
WEB,
|
||||||
|
|
||||||
|
@SerialName("DESKTOP")
|
||||||
|
DESKTOP,
|
||||||
|
|
||||||
|
@SerialName("ANDROID")
|
||||||
|
ANDROID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ToolBaseVo.Platform.asExternalModel() = Tool.Platform.valueOf(this.name)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.model
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import top.fatweb.oxygen.toolbox.network.serializer.LocalDateTimeSerializer
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ToolCategoryVo(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
val enable: Boolean,
|
||||||
|
|
||||||
|
@Serializable(LocalDateTimeSerializer::class)
|
||||||
|
val createTime: LocalDateTime,
|
||||||
|
|
||||||
|
@Serializable(LocalDateTimeSerializer::class)
|
||||||
|
val updateTime: LocalDateTime
|
||||||
|
)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.model
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import top.fatweb.oxygen.toolbox.network.serializer.LocalDateTimeSerializer
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ToolDataVo(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
val data: String,
|
||||||
|
|
||||||
|
@Serializable(LocalDateTimeSerializer::class)
|
||||||
|
val createTime: LocalDateTime,
|
||||||
|
|
||||||
|
@Serializable(LocalDateTimeSerializer::class)
|
||||||
|
val updateTime: LocalDateTime
|
||||||
|
)
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.model
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||||
|
import top.fatweb.oxygen.toolbox.network.serializer.LocalDateTimeSerializer
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ToolVo(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
val toolId: String,
|
||||||
|
|
||||||
|
val icon: String,
|
||||||
|
|
||||||
|
val platform: ToolBaseVo.Platform,
|
||||||
|
|
||||||
|
val description: String,
|
||||||
|
|
||||||
|
val base: ToolBaseVo? = null,
|
||||||
|
|
||||||
|
val author: UserWithInfoVo,
|
||||||
|
|
||||||
|
val ver: String,
|
||||||
|
|
||||||
|
val keywords: List<String>,
|
||||||
|
|
||||||
|
val categories: List<ToolCategoryVo>,
|
||||||
|
|
||||||
|
val source: ToolDataVo? = null,
|
||||||
|
|
||||||
|
val dist: ToolDataVo? = null,
|
||||||
|
|
||||||
|
val entryPoint: String,
|
||||||
|
|
||||||
|
val publish: Long,
|
||||||
|
|
||||||
|
val review: ReviewType,
|
||||||
|
|
||||||
|
@Serializable(LocalDateTimeSerializer::class)
|
||||||
|
val createTime: LocalDateTime,
|
||||||
|
|
||||||
|
@Serializable(LocalDateTimeSerializer::class)
|
||||||
|
val updateTime: LocalDateTime
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
enum class ReviewType {
|
||||||
|
@SerialName("NONE")
|
||||||
|
NONE,
|
||||||
|
|
||||||
|
@SerialName("PROCESSING")
|
||||||
|
PROCESSING,
|
||||||
|
|
||||||
|
@SerialName("PASS")
|
||||||
|
PASS,
|
||||||
|
|
||||||
|
@SerialName("REJECT")
|
||||||
|
REJECT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ToolVo.asExternalModel() = Tool(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
toolId = toolId,
|
||||||
|
icon = icon,
|
||||||
|
platform = platform.asExternalModel(),
|
||||||
|
description = description,
|
||||||
|
base = base?.dist?.data,
|
||||||
|
author = author.asExternalModel(),
|
||||||
|
ver = ver,
|
||||||
|
keywords = keywords,
|
||||||
|
categories = categories.map { it.name },
|
||||||
|
source = source?.data,
|
||||||
|
dist = dist?.data,
|
||||||
|
entryPoint = entryPoint,
|
||||||
|
createTime = createTime,
|
||||||
|
updateTime = updateTime
|
||||||
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserWithInfoVo(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
val username: String,
|
||||||
|
|
||||||
|
val userInfo: UserInfoVo
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class UserInfoVo(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
val nickname: String,
|
||||||
|
|
||||||
|
val avatar: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun UserWithInfoVo.asExternalModel() = Tool.Author(
|
||||||
|
username = username,
|
||||||
|
nickname = userInfo.nickname,
|
||||||
|
avatar = userInfo.avatar
|
||||||
|
)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.paging
|
||||||
|
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
|
||||||
|
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ToolVo
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.asExternalModel
|
||||||
|
|
||||||
|
internal class ToolStorePagingSource(
|
||||||
|
private val oxygenNetworkDataSource: OxygenNetworkDataSource,
|
||||||
|
private val searchValue: String
|
||||||
|
) : PagingSource<Int, Tool>() {
|
||||||
|
override fun getRefreshKey(state: PagingState<Int, Tool>): Int? = null
|
||||||
|
|
||||||
|
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Tool> {
|
||||||
|
return try {
|
||||||
|
val currentPage = params.key ?: 1
|
||||||
|
val (_, success, msg, data) = oxygenNetworkDataSource.getStore(searchValue, currentPage)
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return LoadResult.Error(RuntimeException(msg))
|
||||||
|
}
|
||||||
|
val (_, pages, _, _, records) = data!!
|
||||||
|
|
||||||
|
val nextPage = if (currentPage < pages) currentPage + 1 else null
|
||||||
|
LoadResult.Page(
|
||||||
|
data = records.map(ToolVo::asExternalModel),
|
||||||
|
prevKey = null,
|
||||||
|
nextKey = nextPage
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
LoadResult.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.retrofit
|
||||||
|
|
||||||
|
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Query
|
||||||
|
import top.fatweb.oxygen.toolbox.BuildConfig
|
||||||
|
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
|
||||||
|
import top.fatweb.oxygen.toolbox.model.Result
|
||||||
|
import top.fatweb.oxygen.toolbox.model.asResult
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.PageVo
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ResponseResult
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ToolBaseVo
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ToolVo
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private interface RetrofitOxygenNetworkApi {
|
||||||
|
@GET(value = "/tool/store")
|
||||||
|
suspend fun getStore(
|
||||||
|
@Query("currentPage") currentPage: Int,
|
||||||
|
@Query("searchValue") searchValue: String,
|
||||||
|
@Query("platform") platform: ToolBaseVo.Platform? = ToolBaseVo.Platform.ANDROID
|
||||||
|
): ResponseResult<PageVo<ToolVo>>
|
||||||
|
|
||||||
|
@GET(value = "/tool/detail/{username}/{toolId}/{ver}")
|
||||||
|
suspend fun detail(
|
||||||
|
@Path("username") username: String,
|
||||||
|
@Path("toolId") toolId: String,
|
||||||
|
@Path("ver") ver: String,
|
||||||
|
@Query("platform") platform: String
|
||||||
|
): ResponseResult<ToolVo>
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val API_BASE_URL = BuildConfig.API_URL
|
||||||
|
|
||||||
|
internal class RetrofitOxygenNetwork @Inject constructor(
|
||||||
|
networkJson: Json,
|
||||||
|
okhttpCallFactory: dagger.Lazy<Call.Factory>
|
||||||
|
) : OxygenNetworkDataSource {
|
||||||
|
private val networkApi = Retrofit.Builder()
|
||||||
|
.baseUrl(API_BASE_URL)
|
||||||
|
.callFactory { okhttpCallFactory.get().newCall(it) }
|
||||||
|
.addConverterFactory(
|
||||||
|
networkJson.asConverterFactory("application/json".toMediaType())
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
.create(RetrofitOxygenNetworkApi::class.java)
|
||||||
|
|
||||||
|
override suspend fun getStore(
|
||||||
|
searchValue: String,
|
||||||
|
currentPage: Int
|
||||||
|
): ResponseResult<PageVo<ToolVo>> =
|
||||||
|
networkApi.getStore(searchValue = searchValue, currentPage = currentPage)
|
||||||
|
|
||||||
|
override suspend fun detail(
|
||||||
|
username: String,
|
||||||
|
toolId: String,
|
||||||
|
ver: String,
|
||||||
|
platform: ToolBaseVo.Platform
|
||||||
|
): Flow<Result<ToolVo>> = flow {
|
||||||
|
emit(
|
||||||
|
networkApi.detail(
|
||||||
|
username = username,
|
||||||
|
toolId = toolId,
|
||||||
|
ver = ver,
|
||||||
|
platform = platform.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.asResult()
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.network.serializer
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor =
|
||||||
|
PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): LocalDateTime =
|
||||||
|
LocalDateTime.parse(decoder.decodeString().removeSuffix("Z"))
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
||||||
|
encoder.encodeString(value.toString().padEnd(24, 'Z'))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package top.fatweb.oxygen.toolbox.repository.lib
|
package top.fatweb.oxygen.toolbox.repository.lib.impl
|
||||||
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.flatMapLatest
|
|||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import top.fatweb.oxygen.toolbox.data.lib.DepDataSource
|
import top.fatweb.oxygen.toolbox.data.lib.DepDataSource
|
||||||
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
||||||
|
import top.fatweb.oxygen.toolbox.repository.lib.DepRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LocalDepRepository @Inject constructor(
|
class LocalDepRepository @Inject constructor(
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package top.fatweb.oxygen.toolbox.repository.tool
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import top.fatweb.oxygen.toolbox.data.tool.ToolDataSource
|
|
||||||
import top.fatweb.oxygen.toolbox.model.tool.ToolGroup
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class LocalToolRepository @Inject constructor(
|
|
||||||
toolDataSource: ToolDataSource
|
|
||||||
) : ToolRepository {
|
|
||||||
override val toolGroups: Flow<List<ToolGroup>> =
|
|
||||||
toolDataSource.tool
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
package top.fatweb.oxygen.toolbox.repository.tool
|
package top.fatweb.oxygen.toolbox.repository.tool
|
||||||
|
|
||||||
|
import androidx.paging.PagingData
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import top.fatweb.oxygen.toolbox.model.tool.ToolGroup
|
import top.fatweb.oxygen.toolbox.model.Result
|
||||||
|
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||||
|
|
||||||
interface ToolRepository {
|
interface ToolRepository {
|
||||||
val toolGroups: Flow<List<ToolGroup>>
|
suspend fun getStore(searchValue: String, currentPage: Int): Flow<PagingData<Tool>>
|
||||||
|
|
||||||
|
suspend fun detail(
|
||||||
|
username: String,
|
||||||
|
toolId: String,
|
||||||
|
ver: String = "latest",
|
||||||
|
platform: Tool.Platform = Tool.Platform.ANDROID
|
||||||
|
): Flow<Result<Tool>>
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package top.fatweb.oxygen.toolbox.repository.tool.impl
|
||||||
|
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
|
||||||
|
import top.fatweb.oxygen.toolbox.model.Result
|
||||||
|
import top.fatweb.oxygen.toolbox.model.asExternalModel
|
||||||
|
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ToolBaseVo
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.ToolVo
|
||||||
|
import top.fatweb.oxygen.toolbox.network.model.asExternalModel
|
||||||
|
import top.fatweb.oxygen.toolbox.network.paging.ToolStorePagingSource
|
||||||
|
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val PAGE_SIZE = 20
|
||||||
|
|
||||||
|
internal class NetworkToolRepository @Inject constructor(
|
||||||
|
private val oxygenNetworkDataSource: OxygenNetworkDataSource
|
||||||
|
) : ToolRepository {
|
||||||
|
|
||||||
|
override suspend fun getStore(searchValue: String, currentPage: Int): Flow<PagingData<Tool>> =
|
||||||
|
Pager(
|
||||||
|
config = PagingConfig(PAGE_SIZE),
|
||||||
|
pagingSourceFactory = { ToolStorePagingSource(oxygenNetworkDataSource, searchValue) }
|
||||||
|
).flow
|
||||||
|
|
||||||
|
override suspend fun detail(
|
||||||
|
username: String,
|
||||||
|
toolId: String,
|
||||||
|
ver: String,
|
||||||
|
platform: Tool.Platform
|
||||||
|
): Flow<Result<Tool>> =
|
||||||
|
oxygenNetworkDataSource.detail(
|
||||||
|
username,
|
||||||
|
toolId,
|
||||||
|
ver,
|
||||||
|
ToolBaseVo.Platform.valueOf(platform.name)
|
||||||
|
).map {
|
||||||
|
it.asExternalModel(ToolVo::asExternalModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package top.fatweb.oxygen.toolbox.repository.userdata
|
package top.fatweb.oxygen.toolbox.repository.userdata.impl
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import top.fatweb.oxygen.toolbox.data.userdata.OxygenPreferencesDataSource
|
import top.fatweb.oxygen.toolbox.data.userdata.OxygenPreferencesDataSource
|
||||||
@@ -7,6 +7,7 @@ import top.fatweb.oxygen.toolbox.model.userdata.LanguageConfig
|
|||||||
import top.fatweb.oxygen.toolbox.model.userdata.LaunchPageConfig
|
import top.fatweb.oxygen.toolbox.model.userdata.LaunchPageConfig
|
||||||
import top.fatweb.oxygen.toolbox.model.userdata.ThemeBrandConfig
|
import top.fatweb.oxygen.toolbox.model.userdata.ThemeBrandConfig
|
||||||
import top.fatweb.oxygen.toolbox.model.userdata.UserData
|
import top.fatweb.oxygen.toolbox.model.userdata.UserData
|
||||||
|
import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class LocalUserDataRepository @Inject constructor(
|
internal class LocalUserDataRepository @Inject constructor(
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/*
|
||||||
package top.fatweb.oxygen.toolbox.ui.component
|
package top.fatweb.oxygen.toolbox.ui.component
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
@@ -202,4 +203,4 @@ fun ToolGroupContentPreview() {
|
|||||||
ToolDataSource().tool.first().map { it.tools }.flatten()
|
ToolDataSource().tool.first().map { it.tools }.flatten()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|||||||
@@ -2,21 +2,17 @@ package top.fatweb.oxygen.toolbox.ui.tool
|
|||||||
|
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
|
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||||
import top.fatweb.oxygen.toolbox.ui.component.ToolGroupCard
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.paging.compose.LazyPagingItems
|
||||||
|
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||||
|
|
||||||
fun LazyStaggeredGridScope.toolsPanel(
|
fun LazyStaggeredGridScope.toolsPanel(
|
||||||
toolsScreenUiState: ToolsScreenUiState
|
toolStorePagingItems: LazyPagingItems<Tool>
|
||||||
) {
|
) {
|
||||||
when (toolsScreenUiState) {
|
|
||||||
ToolsScreenUiState.Loading -> Unit
|
|
||||||
|
|
||||||
is ToolsScreenUiState.Success -> {
|
|
||||||
items(
|
items(
|
||||||
items = toolsScreenUiState.toolGroups,
|
items = toolStorePagingItems.itemSnapshotList,
|
||||||
key = { it.id },
|
key = { it!!.id },
|
||||||
) {
|
) {
|
||||||
ToolGroupCard(toolGroup = it)
|
Text(text = it!!.name)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package top.fatweb.oxygen.toolbox.ui.tool
|
package top.fatweb.oxygen.toolbox.ui.tool
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.compose.ReportDrawnWhen
|
import androidx.activity.compose.ReportDrawnWhen
|
||||||
import androidx.compose.foundation.gestures.Orientation
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -19,48 +20,44 @@ import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
|||||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
|
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.paging.LoadState
|
||||||
import kotlinx.coroutines.flow.first
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import kotlinx.coroutines.runBlocking
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
import top.fatweb.oxygen.toolbox.R
|
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||||
import top.fatweb.oxygen.toolbox.data.tool.ToolDataSource
|
|
||||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar
|
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.rememberDraggableScroller
|
||||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.scrollbarState
|
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 ToolsRoute(
|
internal fun ToolsRoute(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: ToolsScreenViewModel = hiltViewModel()
|
viewModel: ToolsScreenViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
val toolsScreenUiState by viewModel.toolsScreenUiState.collectAsStateWithLifecycle()
|
val toolStorePagingItems = viewModel.getStoreData().collectAsLazyPagingItems()
|
||||||
|
|
||||||
ToolsScreen(
|
ToolsScreen(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
toolsScreenUiState = toolsScreenUiState
|
toolStorePagingItems = toolStorePagingItems
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ToolsScreen(
|
internal fun ToolsScreen(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
toolsScreenUiState: ToolsScreenUiState
|
toolStorePagingItems: LazyPagingItems<Tool>
|
||||||
) {
|
) {
|
||||||
val isToolLoading = toolsScreenUiState is ToolsScreenUiState.Loading
|
val isToolLoading = toolStorePagingItems.loadState.refresh is LoadState.Loading
|
||||||
|
|
||||||
|
Log.d("TAG", "ToolsScreen: ${toolStorePagingItems.loadState}")
|
||||||
|
|
||||||
ReportDrawnWhen { !isToolLoading }
|
ReportDrawnWhen { !isToolLoading }
|
||||||
|
|
||||||
val itemsAvailable = howManyItems(toolsScreenUiState)
|
val itemsAvailable = toolStorePagingItems.itemCount
|
||||||
|
|
||||||
val state = rememberLazyStaggeredGridState()
|
val state = rememberLazyStaggeredGridState()
|
||||||
val scrollbarState = state.scrollbarState(itemsAvailable = itemsAvailable)
|
val scrollbarState = state.scrollbarState(itemsAvailable = itemsAvailable)
|
||||||
@@ -68,12 +65,6 @@ internal fun ToolsScreen(
|
|||||||
Box(
|
Box(
|
||||||
modifier.fillMaxSize()
|
modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
when (toolsScreenUiState) {
|
|
||||||
ToolsScreenUiState.Loading -> {
|
|
||||||
Text(text = stringResource(R.string.feature_settings_loading))
|
|
||||||
}
|
|
||||||
|
|
||||||
is ToolsScreenUiState.Success -> {
|
|
||||||
LazyVerticalStaggeredGrid(
|
LazyVerticalStaggeredGrid(
|
||||||
columns = StaggeredGridCells.Adaptive(300.dp),
|
columns = StaggeredGridCells.Adaptive(300.dp),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
@@ -82,7 +73,7 @@ internal fun ToolsScreen(
|
|||||||
state = state
|
state = state
|
||||||
) {
|
) {
|
||||||
|
|
||||||
toolsPanel(toolsScreenUiState = toolsScreenUiState)
|
toolsPanel(toolStorePagingItems = toolStorePagingItems)
|
||||||
|
|
||||||
item(span = StaggeredGridItemSpan.FullLine) {
|
item(span = StaggeredGridItemSpan.FullLine) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
@@ -100,17 +91,9 @@ internal fun ToolsScreen(
|
|||||||
onThumbMoved = state.rememberDraggableScroller(itemsAvailable = itemsAvailable)
|
onThumbMoved = state.rememberDraggableScroller(itemsAvailable = itemsAvailable)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun howManyItems(toolScreenUiState: ToolsScreenUiState) =
|
/*
|
||||||
when (toolScreenUiState) {
|
|
||||||
ToolsScreenUiState.Loading -> 0
|
|
||||||
|
|
||||||
is ToolsScreenUiState.Success -> toolScreenUiState.toolGroups.size
|
|
||||||
}
|
|
||||||
|
|
||||||
@OxygenPreviews
|
@OxygenPreviews
|
||||||
@Composable
|
@Composable
|
||||||
fun ToolsScreenLoadingPreview() {
|
fun ToolsScreenLoadingPreview() {
|
||||||
@@ -130,4 +113,4 @@ fun ToolsScreenPreview() {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|||||||
@@ -1,34 +1,44 @@
|
|||||||
package top.fatweb.oxygen.toolbox.ui.tool
|
package top.fatweb.oxygen.toolbox.ui.tool
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.cachedIn
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import top.fatweb.oxygen.toolbox.model.tool.ToolGroup
|
import top.fatweb.oxygen.toolbox.model.Page
|
||||||
|
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||||
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
|
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ToolsScreenViewModel @Inject constructor(
|
class ToolsScreenViewModel @Inject constructor(
|
||||||
toolRepository: ToolRepository
|
private val toolRepository: ToolRepository,
|
||||||
|
savedStateHandle: SavedStateHandle
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val toolsScreenUiState: StateFlow<ToolsScreenUiState> =
|
private val searchValue = savedStateHandle.getStateFlow(SEARCH_VALUE, "")
|
||||||
toolRepository.toolGroups
|
private val currentPage = savedStateHandle.getStateFlow(CURRENT_PAGE, 1)
|
||||||
.map {
|
|
||||||
ToolsScreenUiState.Success(it)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
fun getStoreData(): Flow<PagingData<Tool>> {
|
||||||
|
return combine(
|
||||||
|
searchValue,
|
||||||
|
currentPage,
|
||||||
|
::Pair
|
||||||
|
).flatMapLatest { (searchValue, currentPage) ->
|
||||||
|
toolRepository.getStore(searchValue, currentPage).cachedIn(viewModelScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
initialValue = ToolsScreenUiState.Loading,
|
|
||||||
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface ToolsScreenUiState {
|
sealed interface ToolsScreenUiState {
|
||||||
data object Loading : ToolsScreenUiState
|
data object Loading : ToolsScreenUiState
|
||||||
data class Success(val toolGroups: List<ToolGroup>) : ToolsScreenUiState
|
data class Success(val tools: Page<Tool>) : ToolsScreenUiState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val SEARCH_VALUE = "searchValue"
|
||||||
|
private const val CURRENT_PAGE = "currentPage"
|
||||||
@@ -7,4 +7,5 @@ plugins {
|
|||||||
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
|
alias(libs.plugins.kotlinxSerialization) apply false
|
||||||
|
alias(libs.plugins.secrets) apply false
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,8 @@ 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"
|
kotlinxSerialization = "1.9.23"
|
||||||
|
secrets = "2.0.1"
|
||||||
|
paging = "3.2.1"
|
||||||
|
|
||||||
desugarJdkLibs = "2.0.4"
|
desugarJdkLibs = "2.0.4"
|
||||||
composeBom = "2024.05.00"
|
composeBom = "2024.05.00"
|
||||||
@@ -24,20 +26,25 @@ protobuf = "3.25.2"
|
|||||||
androidxNavigation = "2.7.7"
|
androidxNavigation = "2.7.7"
|
||||||
androidxHiltNavigationCompose = "1.2.0"
|
androidxHiltNavigationCompose = "1.2.0"
|
||||||
kotlinxSerializationJson = "1.6.3"
|
kotlinxSerializationJson = "1.6.3"
|
||||||
|
retrofit = "2.9.0"
|
||||||
|
retrofitKotlinxSerializationJson = "1.0.0"
|
||||||
|
okhttp = "4.12.0"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
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" }
|
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinxSerialization" }
|
||||||
|
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" }
|
||||||
|
|
||||||
[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" }
|
||||||
|
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
paging-common = { group = "androidx.paging", name = "paging-common", version.ref = "paging" }
|
||||||
|
|
||||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
@@ -51,19 +58,19 @@ androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
|||||||
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
material-icons-core = { group = "androidx.compose.material", name = "material-icons-core"}
|
material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
|
||||||
material-icons-extended = {group = "androidx.compose.material", name = "material-icons-extended"}
|
material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
|
||||||
material3-window-size = {group = "androidx.compose.material3", name = "material3-window-size-class"}
|
material3-window-size = { group = "androidx.compose.material3", name = "material3-window-size-class" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
|
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
|
||||||
lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
|
lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
|
||||||
lifecycle-runtime-testing = { group = "androidx.lifecycle", name = "lifecycle-runtime-testing", version.ref = "androidxLifecycle" }
|
lifecycle-runtime-testing = { group = "androidx.lifecycle", name = "lifecycle-runtime-testing", version.ref = "androidxLifecycle" }
|
||||||
lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle"}
|
lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
|
||||||
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSplashscreen" }
|
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSplashscreen" }
|
||||||
dagger-compiler = { group = "com.google.dagger", name = "dagger-compiler", version.ref = "hilt" }
|
dagger-compiler = { group = "com.google.dagger", name = "dagger-compiler", version.ref = "hilt" }
|
||||||
hilt-android = {group = "com.google.dagger", name = "hilt-android", version.ref = "hilt"}
|
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
|
||||||
hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
|
hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
|
||||||
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
|
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
|
||||||
coil-kt = { group = "io.coil-kt", name = "coil", version.ref = "coil" }
|
coil-kt = { group = "io.coil-kt", name = "coil", version.ref = "coil" }
|
||||||
@@ -76,4 +83,9 @@ 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"}
|
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
||||||
|
retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
||||||
|
retrofit-kotlin-serialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerializationJson" }
|
||||||
|
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
|
||||||
|
paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" }
|
||||||
|
paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" }
|
||||||
|
|||||||
4
secrets.defaults.properties
Normal file
4
secrets.defaults.properties
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## This file provides default values to modules using the secrets-gradle-plugin. It is necessary
|
||||||
|
# because the secrets properties file is not under source control so CI builds will fail without
|
||||||
|
# default values.
|
||||||
|
API_URL="https://example.com"
|
||||||
Reference in New Issue
Block a user