diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d1786fc..96cd765 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -9,6 +9,7 @@ plugins {
alias(libs.plugins.hilt)
alias(libs.plugins.protobuf)
alias(libs.plugins.kotlinxSerialization)
+ alias(libs.plugins.secrets)
}
android {
@@ -58,6 +59,7 @@ android {
}
buildFeatures {
compose = true
+ buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.12"
@@ -113,10 +115,15 @@ afterEvaluate {
tasks.findByName("kspReleaseKotlin")?.dependsOn(tasks.findByName("generateReleaseProto"))
}
+secrets {
+ defaultPropertiesFileName = "secrets.defaults.properties"
+}
+
dependencies {
coreLibraryDesugaring(libs.desugar.jdk.libs)
testImplementation(libs.junit)
+ testImplementation(libs.paging.common)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
@@ -156,4 +163,9 @@ dependencies {
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.hilt.navigation.compose)
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)
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dfc821f..094cc34 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
+
>
+
+ suspend fun detail(
+ username: String,
+ toolId: String,
+ ver: String = "latest",
+ platform: ToolBaseVo.Platform = ToolBaseVo.Platform.ANDROID
+ ): Flow>
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/data/tool/ToolDataSource.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/data/tool/ToolDataSource.kt
index b42835b..7766eb1 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/data/tool/ToolDataSource.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/data/tool/ToolDataSource.kt
@@ -1,36 +1,7 @@
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 kotlin.random.Random
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("")
- }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/di/DataModule.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/di/DataModule.kt
index b1caa1a..621594a 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/di/DataModule.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/di/DataModule.kt
@@ -9,10 +9,10 @@ import top.fatweb.oxygen.toolbox.monitor.NetworkMonitor
import top.fatweb.oxygen.toolbox.monitor.TimeZoneBroadcastMonitor
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.lib.impl.LocalDepRepository
+import top.fatweb.oxygen.toolbox.repository.tool.impl.NetworkToolRepository
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
@Module
@@ -28,7 +28,7 @@ abstract class DataModule {
internal abstract fun bindsUserDataRepository(userDataRepository: LocalUserDataRepository): UserDataRepository
@Binds
- internal abstract fun bindsToolRepository(toolRepository: LocalToolRepository): ToolRepository
+ internal abstract fun bindsToolRepository(toolRepository: NetworkToolRepository): ToolRepository
@Binds
internal abstract fun bindsDepRepository(depRepository: LocalDepRepository): DepRepository
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/di/NetworkModule.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/di/NetworkModule.kt
new file mode 100644
index 0000000..16ca519
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/di/NetworkModule.kt
@@ -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
+ ): OxygenNetworkDataSource = RetrofitOxygenNetwork(networkJson, okhttpCallFactory)
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Page.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Page.kt
new file mode 100644
index 0000000..2f1fa98
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Page.kt
@@ -0,0 +1,13 @@
+package top.fatweb.oxygen.toolbox.model
+
+data class Page(
+ val total: Long,
+
+ val pages: Long,
+
+ val size: Long,
+
+ val current: Long,
+
+ val records: List
+)
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Result.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Result.kt
new file mode 100644
index 0000000..52c2048
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Result.kt
@@ -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 {
+ data class Success(val data: T) : Result
+ data class Fail(val message: String): Result
+ data class Error(val exception: Throwable) : Result
+ data object Loading : Result
+}
+
+fun Flow>.asResult(): Flow> = map, Result> {
+ if (it.success) {
+ Result.Success(it.data!!)
+ } else {
+ Result.Fail(it.msg)
+ }
+}
+ .onStart { emit(Result.Loading) }
+ .catch { emit(Result.Error(it)) }
+
+fun Result.asExternalModel(block: (T) -> R): Result =
+ 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
+ }
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/tool/Tool.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/tool/Tool.kt
index d081007..86318e8 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/tool/Tool.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/tool/Tool.kt
@@ -1,12 +1,53 @@
package top.fatweb.oxygen.toolbox.model.tool
-import androidx.compose.ui.graphics.vector.ImageVector
-import top.fatweb.oxygen.toolbox.icon.OxygenIcons
+import kotlinx.datetime.LocalDateTime
data class Tool(
- val id: String,
+ val id: Long,
- val icon: ImageVector = OxygenIcons.Tool,
+ val name: String,
- val name: String
-)
\ No newline at end of file
+ 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,
+
+ val categories: List,
+
+ 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
+ )
+}
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/PageVo.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/PageVo.kt
new file mode 100644
index 0000000..9ae9a78
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/PageVo.kt
@@ -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(
+ val total: Long,
+
+ val pages: Long,
+
+ val size: Long,
+
+ val current: Long,
+
+ val records: List
+)
+
+fun PageVo.asExternalModel(block: (T) -> R): Page = Page(
+ total = total,
+ pages = pages,
+ size = size,
+ current = current,
+ records = records.map(block)
+)
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ResponseResult.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ResponseResult.kt
new file mode 100644
index 0000000..72c9326
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ResponseResult.kt
@@ -0,0 +1,14 @@
+package top.fatweb.oxygen.toolbox.network.model
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponseResult(
+ val code: Long,
+
+ val success: Boolean,
+
+ val msg: String,
+
+ val data: T? = null
+)
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolBaseVo.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolBaseVo.kt
new file mode 100644
index 0000000..6796cfb
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolBaseVo.kt
@@ -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)
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolCategoryVo.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolCategoryVo.kt
new file mode 100644
index 0000000..df054e8
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolCategoryVo.kt
@@ -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
+)
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolDataVo.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolDataVo.kt
new file mode 100644
index 0000000..1508780
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolDataVo.kt
@@ -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
+)
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolVo.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolVo.kt
new file mode 100644
index 0000000..2271770
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/ToolVo.kt
@@ -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,
+
+ val categories: List,
+
+ 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
+)
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/UserWithInfoVo.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/UserWithInfoVo.kt
new file mode 100644
index 0000000..c2368e3
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/model/UserWithInfoVo.kt
@@ -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
+)
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/paging/ToolStorePagingSource.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/paging/ToolStorePagingSource.kt
new file mode 100644
index 0000000..20dbec8
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/paging/ToolStorePagingSource.kt
@@ -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() {
+ override fun getRefreshKey(state: PagingState): Int? = null
+
+ override suspend fun load(params: LoadParams): LoadResult {
+ 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)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/retrofit/RetrofitOxygenNetwork.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/retrofit/RetrofitOxygenNetwork.kt
new file mode 100644
index 0000000..cf4a27b
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/retrofit/RetrofitOxygenNetwork.kt
@@ -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>
+
+ @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
+}
+
+private const val API_BASE_URL = BuildConfig.API_URL
+
+internal class RetrofitOxygenNetwork @Inject constructor(
+ networkJson: Json,
+ okhttpCallFactory: dagger.Lazy
+) : 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> =
+ networkApi.getStore(searchValue = searchValue, currentPage = currentPage)
+
+ override suspend fun detail(
+ username: String,
+ toolId: String,
+ ver: String,
+ platform: ToolBaseVo.Platform
+ ): Flow> = flow {
+ emit(
+ networkApi.detail(
+ username = username,
+ toolId = toolId,
+ ver = ver,
+ platform = platform.name
+ )
+ )
+ }.asResult()
+}
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/serializer/LocalDateTimeSerializer.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/serializer/LocalDateTimeSerializer.kt
new file mode 100644
index 0000000..7f7de88
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/network/serializer/LocalDateTimeSerializer.kt
@@ -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 {
+
+ 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'))
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/lib/LocalDepRepository.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/lib/impl/LocalDepRepository.kt
similarity index 89%
rename from app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/lib/LocalDepRepository.kt
rename to app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/lib/impl/LocalDepRepository.kt
index ad8b795..a2dd943 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/lib/LocalDepRepository.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/lib/impl/LocalDepRepository.kt
@@ -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.flow.Flow
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import top.fatweb.oxygen.toolbox.data.lib.DepDataSource
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
+import top.fatweb.oxygen.toolbox.repository.lib.DepRepository
import javax.inject.Inject
class LocalDepRepository @Inject constructor(
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/LocalToolRepository.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/LocalToolRepository.kt
deleted file mode 100644
index a023841..0000000
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/LocalToolRepository.kt
+++ /dev/null
@@ -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> =
- toolDataSource.tool
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/ToolRepository.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/ToolRepository.kt
index 1f95461..1d32b4a 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/ToolRepository.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/ToolRepository.kt
@@ -1,8 +1,17 @@
package top.fatweb.oxygen.toolbox.repository.tool
+import androidx.paging.PagingData
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 {
- val toolGroups: Flow>
+ suspend fun getStore(searchValue: String, currentPage: Int): Flow>
+
+ suspend fun detail(
+ username: String,
+ toolId: String,
+ ver: String = "latest",
+ platform: Tool.Platform = Tool.Platform.ANDROID
+ ): Flow>
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/impl/NetworkToolRepository.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/impl/NetworkToolRepository.kt
new file mode 100644
index 0000000..dbfd6a1
--- /dev/null
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/tool/impl/NetworkToolRepository.kt
@@ -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> =
+ Pager(
+ config = PagingConfig(PAGE_SIZE),
+ pagingSourceFactory = { ToolStorePagingSource(oxygenNetworkDataSource, searchValue) }
+ ).flow
+
+ override suspend fun detail(
+ username: String,
+ toolId: String,
+ ver: String,
+ platform: Tool.Platform
+ ): Flow> =
+ oxygenNetworkDataSource.detail(
+ username,
+ toolId,
+ ver,
+ ToolBaseVo.Platform.valueOf(platform.name)
+ ).map {
+ it.asExternalModel(ToolVo::asExternalModel)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/userdata/LocalUserDataRepository.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/userdata/impl/LocalUserDataRepository.kt
similarity index 91%
rename from app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/userdata/LocalUserDataRepository.kt
rename to app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/userdata/impl/LocalUserDataRepository.kt
index 25726c2..8de15d0 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/userdata/LocalUserDataRepository.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/repository/userdata/impl/LocalUserDataRepository.kt
@@ -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 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.ThemeBrandConfig
import top.fatweb.oxygen.toolbox.model.userdata.UserData
+import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
import javax.inject.Inject
internal class LocalUserDataRepository @Inject constructor(
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ToolGroupCard.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ToolGroupCard.kt
index b636f8d..b20e288 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ToolGroupCard.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/component/ToolGroupCard.kt
@@ -1,3 +1,4 @@
+/*
package top.fatweb.oxygen.toolbox.ui.component
import androidx.compose.animation.AnimatedVisibility
@@ -202,4 +203,4 @@ fun ToolGroupContentPreview() {
ToolDataSource().tool.first().map { it.tools }.flatten()
})
}
-}
\ No newline at end of file
+}*/
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsPanel.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsPanel.kt
index 0fe9596..6b57aa9 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsPanel.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsPanel.kt
@@ -2,21 +2,17 @@ package top.fatweb.oxygen.toolbox.ui.tool
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
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(
- toolsScreenUiState: ToolsScreenUiState
+ toolStorePagingItems: LazyPagingItems
) {
- when (toolsScreenUiState) {
- ToolsScreenUiState.Loading -> Unit
-
- is ToolsScreenUiState.Success -> {
- items(
- items = toolsScreenUiState.toolGroups,
- key = { it.id },
- ) {
- ToolGroupCard(toolGroup = it)
- }
- }
+ items(
+ items = toolStorePagingItems.itemSnapshotList,
+ key = { it!!.id },
+ ) {
+ Text(text = it!!.name)
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreen.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreen.kt
index f86e173..86ea983 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreen.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreen.kt
@@ -1,5 +1,6 @@
package top.fatweb.oxygen.toolbox.ui.tool
+import android.util.Log
import androidx.activity.compose.ReportDrawnWhen
import androidx.compose.foundation.gestures.Orientation
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.StaggeredGridItemSpan
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.runBlocking
-import top.fatweb.oxygen.toolbox.R
-import top.fatweb.oxygen.toolbox.data.tool.ToolDataSource
+import androidx.paging.LoadState
+import androidx.paging.compose.LazyPagingItems
+import androidx.paging.compose.collectAsLazyPagingItems
+import top.fatweb.oxygen.toolbox.model.tool.Tool
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.rememberDraggableScroller
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
internal fun ToolsRoute(
modifier: Modifier = Modifier,
viewModel: ToolsScreenViewModel = hiltViewModel()
) {
- val toolsScreenUiState by viewModel.toolsScreenUiState.collectAsStateWithLifecycle()
+ val toolStorePagingItems = viewModel.getStoreData().collectAsLazyPagingItems()
ToolsScreen(
modifier = modifier,
- toolsScreenUiState = toolsScreenUiState
+ toolStorePagingItems = toolStorePagingItems
)
}
@Composable
internal fun ToolsScreen(
modifier: Modifier = Modifier,
- toolsScreenUiState: ToolsScreenUiState
+ toolStorePagingItems: LazyPagingItems
) {
- val isToolLoading = toolsScreenUiState is ToolsScreenUiState.Loading
+ val isToolLoading = toolStorePagingItems.loadState.refresh is LoadState.Loading
+ Log.d("TAG", "ToolsScreen: ${toolStorePagingItems.loadState}")
+
ReportDrawnWhen { !isToolLoading }
- val itemsAvailable = howManyItems(toolsScreenUiState)
+ val itemsAvailable = toolStorePagingItems.itemCount
val state = rememberLazyStaggeredGridState()
val scrollbarState = state.scrollbarState(itemsAvailable = itemsAvailable)
@@ -68,49 +65,35 @@ internal fun ToolsScreen(
Box(
modifier.fillMaxSize()
) {
- when (toolsScreenUiState) {
- ToolsScreenUiState.Loading -> {
- Text(text = stringResource(R.string.feature_settings_loading))
- }
+ LazyVerticalStaggeredGrid(
+ columns = StaggeredGridCells.Adaptive(300.dp),
+ contentPadding = PaddingValues(16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalItemSpacing = 24.dp,
+ state = state
+ ) {
- is ToolsScreenUiState.Success -> {
- LazyVerticalStaggeredGrid(
- columns = StaggeredGridCells.Adaptive(300.dp),
- contentPadding = PaddingValues(16.dp),
- horizontalArrangement = Arrangement.spacedBy(16.dp),
- verticalItemSpacing = 24.dp,
- state = state
- ) {
+ toolsPanel(toolStorePagingItems = toolStorePagingItems)
- toolsPanel(toolsScreenUiState = toolsScreenUiState)
-
- 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)
- )
+ 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)
+ )
}
}
-fun howManyItems(toolScreenUiState: ToolsScreenUiState) =
- when (toolScreenUiState) {
- ToolsScreenUiState.Loading -> 0
-
- is ToolsScreenUiState.Success -> toolScreenUiState.toolGroups.size
- }
-
+/*
@OxygenPreviews
@Composable
fun ToolsScreenLoadingPreview() {
@@ -130,4 +113,4 @@ fun ToolsScreenPreview() {
})
)
}
-}
\ No newline at end of file
+}*/
diff --git a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreenViewModel.kt b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreenViewModel.kt
index 75a8f1a..1faf51a 100644
--- a/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreenViewModel.kt
+++ b/app/src/main/kotlin/top/fatweb/oxygen/toolbox/ui/tool/ToolsScreenViewModel.kt
@@ -1,34 +1,44 @@
package top.fatweb.oxygen.toolbox.ui.tool
+import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import androidx.paging.PagingData
+import androidx.paging.cachedIn
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.tool.ToolGroup
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import top.fatweb.oxygen.toolbox.model.Page
+import top.fatweb.oxygen.toolbox.model.tool.Tool
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
import javax.inject.Inject
-import kotlin.time.Duration.Companion.seconds
@HiltViewModel
class ToolsScreenViewModel @Inject constructor(
- toolRepository: ToolRepository
+ private val toolRepository: ToolRepository,
+ savedStateHandle: SavedStateHandle
) : ViewModel() {
- val toolsScreenUiState: StateFlow =
- toolRepository.toolGroups
- .map {
- ToolsScreenUiState.Success(it)
- }
- .stateIn(
- scope = viewModelScope,
- initialValue = ToolsScreenUiState.Loading,
- started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
- )
+ private val searchValue = savedStateHandle.getStateFlow(SEARCH_VALUE, "")
+ private val currentPage = savedStateHandle.getStateFlow(CURRENT_PAGE, 1)
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ fun getStoreData(): Flow> {
+ return combine(
+ searchValue,
+ currentPage,
+ ::Pair
+ ).flatMapLatest { (searchValue, currentPage) ->
+ toolRepository.getStore(searchValue, currentPage).cachedIn(viewModelScope)
+ }
+ }
}
sealed interface ToolsScreenUiState {
data object Loading : ToolsScreenUiState
- data class Success(val toolGroups: List) : ToolsScreenUiState
-}
\ No newline at end of file
+ data class Success(val tools: Page) : ToolsScreenUiState
+}
+
+private const val SEARCH_VALUE = "searchValue"
+private const val CURRENT_PAGE = "currentPage"
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 0ad3fa5..eec1b59 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,4 +7,5 @@ plugins {
alias(libs.plugins.hilt) apply false
alias(libs.plugins.protobuf) apply false
alias(libs.plugins.kotlinxSerialization) apply false
+ alias(libs.plugins.secrets) apply false
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6947c74..00d6322 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -5,6 +5,8 @@ ksp = "1.9.23-1.0.20"
aboutlibraries = "11.1.0"
protobufPlugin = "0.9.4"
kotlinxSerialization = "1.9.23"
+secrets = "2.0.1"
+paging = "3.2.1"
desugarJdkLibs = "2.0.4"
composeBom = "2024.05.00"
@@ -24,20 +26,25 @@ protobuf = "3.25.2"
androidxNavigation = "2.7.7"
androidxHiltNavigationCompose = "1.2.0"
kotlinxSerializationJson = "1.6.3"
+retrofit = "2.9.0"
+retrofitKotlinxSerializationJson = "1.0.0"
+okhttp = "4.12.0"
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
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" }
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinxSerialization" }
+secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" }
[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" }
+paging-common = { group = "androidx.paging", name = "paging-common", version.ref = "paging" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
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-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core"}
-material-icons-extended = {group = "androidx.compose.material", name = "material-icons-extended"}
-material3-window-size = {group = "androidx.compose.material3", name = "material3-window-size-class"}
+material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
+material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
+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-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
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-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-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" }
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-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
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-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "androidxNavigation" }
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" }
diff --git a/secrets.defaults.properties b/secrets.defaults.properties
new file mode 100644
index 0000000..86aa308
--- /dev/null
+++ b/secrets.defaults.properties
@@ -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"
\ No newline at end of file