simple ktor-client implementation

This commit is contained in:
2024-08-08 13:03:17 +03:00
parent c2f79f9007
commit efe4536ebf
22 changed files with 383 additions and 166 deletions
+5
View File
@@ -84,14 +84,18 @@ kotlin {
implementation(libs.coil.network.ktor) implementation(libs.coil.network.ktor)
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
implementation(libs.ktor.core) implementation(libs.ktor.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.kotlinx.serialization.json)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.koin.core) implementation(libs.koin.core)
implementation(libs.koin.compose) implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.haze) implementation(libs.haze)
implementation(libs.haze.materials) implementation(libs.haze.materials)
implementation(libs.kstore) implementation(libs.kstore)
implementation(libs.napier) implementation(libs.napier)
implementation(libs.message.bar)
} }
commonTest.dependencies { commonTest.dependencies {
@@ -124,6 +128,7 @@ kotlin {
wasmJsMain.dependencies { wasmJsMain.dependencies {
implementation(libs.kstore.storage) implementation(libs.kstore.storage)
implementation(libs.ktor.client.js)
} }
} }
} }
@@ -0,0 +1,8 @@
package dev.meloda.overseerr.network.model
import io.ktor.client.engine.*
import io.ktor.client.engine.okhttp.*
actual class HttpClientEngineFactoryProvider actual constructor() {
actual fun get(): HttpClientEngineFactory<*> = OkHttp
}
@@ -3,6 +3,7 @@ package dev.meloda.overseerr.di
import dev.meloda.overseerr.model.Platform import dev.meloda.overseerr.model.Platform
import dev.meloda.overseerr.network.di.networkModule import dev.meloda.overseerr.network.di.networkModule
import dev.meloda.overseerr.screens.login.di.loginModule import dev.meloda.overseerr.screens.login.di.loginModule
import dev.meloda.overseerr.screens.requests.di.requestsModule
import dev.meloda.overseerr.screens.url.di.urlModule import dev.meloda.overseerr.screens.url.di.urlModule
import dev.meloda.overseerr.settings.di.settingsModule import dev.meloda.overseerr.settings.di.settingsModule
import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.singleOf
@@ -15,6 +16,7 @@ val appModule = module {
settingsModule, settingsModule,
networkModule, networkModule,
loginModule, loginModule,
urlModule urlModule,
requestsModule
) )
} }
@@ -1,12 +1,19 @@
package dev.meloda.overseerr.network.di package dev.meloda.overseerr.network.di
import dev.meloda.overseerr.network.model.HttpClientEngineFactoryProvider
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module import org.koin.dsl.module
val networkModule = module { val networkModule = module {
singleOf(::HttpClientEngineFactoryProvider)
single { single {
HttpClient { HttpClient(engineFactory = get<HttpClientEngineFactoryProvider>().get()) {
install(ContentNegotiation) {
json()
}
} }
} }
} }
@@ -0,0 +1,7 @@
package dev.meloda.overseerr.network.model
import io.ktor.client.engine.*
expect class HttpClientEngineFactoryProvider() {
fun get(): HttpClientEngineFactory<*>
}
@@ -1,7 +1,9 @@
package dev.meloda.overseerr.screens.login.di package dev.meloda.overseerr.screens.login.di
import dev.meloda.overseerr.screens.login.LoginViewModelImpl
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module import org.koin.dsl.module
val loginModule = module { val loginModule = module {
viewModelOf(::LoginViewModelImpl)
} }
@@ -17,13 +17,13 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import dev.meloda.overseerr.screens.login.LoginViewModel import dev.meloda.overseerr.screens.login.LoginViewModel
import dev.meloda.overseerr.screens.login.LoginViewModelImpl import dev.meloda.overseerr.screens.login.LoginViewModelImpl
import dev.meloda.overseerr.screens.login.model.LoginScreenState import dev.meloda.overseerr.screens.login.model.LoginScreenState
import org.koin.compose.viewmodel.koinViewModel
class LoginScreen : Screen { class LoginScreen : Screen {
@@ -31,7 +31,7 @@ class LoginScreen : Screen {
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val viewModel: LoginViewModel = viewModel { LoginViewModelImpl() } val viewModel: LoginViewModel = koinViewModel<LoginViewModelImpl>()
val screenState: LoginScreenState by viewModel.screenState.collectAsState() val screenState: LoginScreenState by viewModel.screenState.collectAsState()
var loginValue by rememberSaveable { var loginValue by rememberSaveable {
@@ -11,7 +11,7 @@ import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import dev.meloda.overseerr.screens.login.presentation.LoginScreen import dev.meloda.overseerr.screens.login.presentation.LoginScreen
import dev.meloda.overseerr.screens.requests.RequestsScreen import dev.meloda.overseerr.screens.requests.presentation.RequestsScreen
import dev.meloda.overseerr.screens.url.presentation.UrlScreen import dev.meloda.overseerr.screens.url.presentation.UrlScreen
class MainScreen : Screen { class MainScreen : Screen {
@@ -1,143 +0,0 @@
package dev.meloda.overseerr.screens.requests
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator
import androidx.compose.material3.pulltorefresh.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds
class RequestsScreen : Screen {
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalHazeMaterialsApi::class
)
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
var isRefreshing by remember {
mutableStateOf(false)
}
val hazeState = remember { HazeState() }
val hazeStyle = HazeMaterials.ultraThin()
val refreshState = rememberPullToRefreshState()
var isNeedToRefresh by remember {
mutableStateOf(false)
}
LaunchedEffect(isNeedToRefresh) {
if (isNeedToRefresh) {
isRefreshing = true
delay(2.seconds)
isRefreshing = false
isNeedToRefresh = false
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = { Text(text = "Requests") },
navigationIcon = {
IconButton(onClick = navigator::pop) {
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = null
)
}
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
modifier = Modifier
.hazeChild(
state = hazeState,
style = hazeStyle
).fillMaxWidth()
)
}
) { padding ->
val bottomPadding = padding.calculateBottomPadding()
Box(
modifier = Modifier
.fillMaxSize()
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr))
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.haze(
state = hazeState,
style = hazeStyle
)
.pullToRefresh(
isRefreshing = isRefreshing,
state = refreshState,
onRefresh = { isNeedToRefresh = true }
)
) {
item {
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
}
items(count = 1000) { index ->
Text(
text = "Text #${index + 1}",
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.background(Color.Red)
)
Spacer(modifier = Modifier.height(64.dp))
}
item {
Spacer(modifier = Modifier.height(bottomPadding))
}
}
if (bottomPadding.value > 0) {
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.hazeChild(
state = hazeState,
style = hazeStyle
)
.background(Color.Transparent)
.height(bottomPadding)
.fillMaxWidth()
)
}
Indicator(
state = refreshState,
isRefreshing = isRefreshing,
modifier = Modifier.align(Alignment.TopCenter)
.padding(top = padding.calculateTopPadding())
)
}
}
}
}
@@ -0,0 +1,78 @@
package dev.meloda.overseerr.screens.requests
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dev.meloda.overseerr.ext.setValue
import dev.meloda.overseerr.screens.requests.model.RequestsScreenState
import dev.meloda.overseerr.settings.SettingsController
import io.github.aakira.napier.Napier
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlin.time.Duration.Companion.seconds
interface RequestsViewModel {
val screenState: StateFlow<RequestsScreenState>
fun onRefresh()
fun onSuccessMessageShown()
fun onErrorMessageShown()
}
class RequestsViewModelImpl(
private val httpClient: HttpClient,
private val settingsController: SettingsController
) : ViewModel(), RequestsViewModel {
override val screenState = MutableStateFlow(RequestsScreenState.EMPTY)
override fun onRefresh() {
viewModelScope.launch {
screenState.setValue { old -> old.copy(isLoading = true) }
delay(1.seconds)
loadInfo()
screenState.setValue { old -> old.copy(isLoading = false) }
}
}
override fun onSuccessMessageShown() {
screenState.setValue { old -> old.copy(apiInfo = null) }
}
override fun onErrorMessageShown() {
screenState.setValue { old -> old.copy(apiErrorText = null) }
}
private fun loadInfo() {
viewModelScope.launch {
kotlin.runCatching {
httpClient.get("${settingsController.settings.value.url}/api/v1") {
headers {
append("X-Api-Key", settingsController.settings.value.plexToken)
}
}.body() as ApiInfo
}.fold(
onSuccess = { response ->
Napier.d { "Response: $response" }
screenState.setValue { old -> old.copy(apiInfo = response) }
},
onFailure = { error ->
Napier.e(error) { "Error occurred" }
screenState.setValue { old -> old.copy(apiErrorText = error.message.toString()) }
}
)
}
}
}
@Serializable
data class ApiInfo(
val api: String,
val version: String
)
@@ -0,0 +1,9 @@
package dev.meloda.overseerr.screens.requests.di
import dev.meloda.overseerr.screens.requests.RequestsViewModelImpl
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module
val requestsModule = module {
viewModelOf(::RequestsViewModelImpl)
}
@@ -0,0 +1,19 @@
package dev.meloda.overseerr.screens.requests.model
import dev.meloda.overseerr.screens.requests.ApiInfo
data class RequestsScreenState(
val dummyItems: List<Int>,
val isLoading: Boolean,
val apiInfo: ApiInfo?,
val apiErrorText: String?
) {
companion object {
val EMPTY: RequestsScreenState = RequestsScreenState(
dummyItems = List(50) { it },
isLoading = false,
apiInfo = null,
apiErrorText = null
)
}
}
@@ -0,0 +1,163 @@
package dev.meloda.overseerr.screens.requests.presentation
import ContentWithMessageBar
import MessageBarPosition
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator
import androidx.compose.material3.pulltorefresh.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import dev.meloda.overseerr.screens.requests.RequestsViewModel
import dev.meloda.overseerr.screens.requests.RequestsViewModelImpl
import dev.meloda.overseerr.screens.requests.model.RequestsScreenState
import org.koin.compose.viewmodel.koinViewModel
import rememberMessageBarState
class RequestsScreen : Screen {
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalHazeMaterialsApi::class
)
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val viewModel: RequestsViewModel = koinViewModel<RequestsViewModelImpl>()
val screenState: RequestsScreenState by viewModel.screenState.collectAsState()
val hazeState = remember { HazeState() }
val hazeStyle = HazeMaterials.ultraThin()
val refreshState = rememberPullToRefreshState()
val messageBarState = rememberMessageBarState()
LaunchedEffect(screenState) {
if (screenState.apiErrorText != null) {
messageBarState.addError(Exception(screenState.apiErrorText))
viewModel.onErrorMessageShown()
}
if (screenState.apiInfo != null) {
messageBarState.addSuccess(screenState.apiInfo.toString())
viewModel.onSuccessMessageShown()
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = { Text(text = "Requests") },
navigationIcon = {
IconButton(onClick = navigator::pop) {
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = null
)
}
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
modifier = Modifier
.hazeChild(
state = hazeState,
style = hazeStyle
).fillMaxWidth(),
actions = {
IconButton(
onClick = viewModel::onRefresh
) {
Icon(
imageVector = Icons.Rounded.Refresh,
contentDescription = null
)
}
}
)
}
) { padding ->
val bottomPadding = padding.calculateBottomPadding()
ContentWithMessageBar(
messageBarState = messageBarState,
position = MessageBarPosition.BOTTOM
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr))
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.haze(
state = hazeState,
style = hazeStyle
)
.pullToRefresh(
isRefreshing = screenState.isLoading,
state = refreshState,
onRefresh = viewModel::onRefresh
)
) {
item {
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
}
items(items = screenState.dummyItems) { index ->
Text(
text = "Text #${index + 1}",
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.background(Color.Red)
)
Spacer(modifier = Modifier.height(64.dp))
}
item {
Spacer(modifier = Modifier.height(bottomPadding))
}
}
Indicator(
state = refreshState,
isRefreshing = screenState.isLoading,
modifier = Modifier.align(Alignment.TopCenter)
.padding(top = padding.calculateTopPadding())
)
if (bottomPadding.value > 0) {
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.hazeChild(
state = hazeState,
style = hazeStyle
)
.background(Color.Transparent)
.height(bottomPadding)
.fillMaxWidth()
)
}
}
}
}
}
}
@@ -16,6 +16,7 @@ interface UrlViewModel {
val screenState: StateFlow<UrlScreenState> val screenState: StateFlow<UrlScreenState>
fun onUrlInputChanged(newText: String) fun onUrlInputChanged(newText: String)
fun onPlexTokenInputChanged(newToken: String)
fun onLoadButtonClicked() fun onLoadButtonClicked()
fun onSaveButtonClicked() fun onSaveButtonClicked()
fun onTestButtonClicked() fun onTestButtonClicked()
@@ -29,7 +30,14 @@ class UrlViewModelImpl(
init { init {
settingsController.settings settingsController.settings
.onEach { settings -> screenState.setValue { old -> old.copy(url = settings.url) } } .onEach { settings ->
screenState.setValue { old ->
old.copy(
url = settings.url,
plexToken = settings.plexToken
)
}
}
.launchIn(viewModelScope) .launchIn(viewModelScope)
} }
@@ -37,18 +45,30 @@ class UrlViewModelImpl(
screenState.setValue { old -> old.copy(url = newText) } screenState.setValue { old -> old.copy(url = newText) }
} }
override fun onPlexTokenInputChanged(newToken: String) {
screenState.setValue { old -> old.copy(plexToken = newToken) }
}
override fun onLoadButtonClicked() { override fun onLoadButtonClicked() {
viewModelScope.launch { viewModelScope.launch {
val settings = settingsController.loadAppSettings() val settings = settingsController.loadAppSettings()
screenState.setValue { old -> old.copy(url = settings.url) } screenState.setValue { old ->
old.copy(
url = settings.url,
plexToken = settings.plexToken
)
}
} }
} }
override fun onSaveButtonClicked() { override fun onSaveButtonClicked() {
viewModelScope.launch { viewModelScope.launch {
settingsController.updateAppSettings { settings -> settingsController.updateAppSettings { settings ->
settings.copy(url = screenState.value.url) settings.copy(
url = screenState.value.url,
plexToken = screenState.value.plexToken
)
} }
} }
} }
@@ -1,7 +1,9 @@
package dev.meloda.overseerr.screens.url.di package dev.meloda.overseerr.screens.url.di
import dev.meloda.overseerr.screens.url.UrlViewModelImpl
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module import org.koin.dsl.module
val urlModule = module { val urlModule = module {
viewModelOf(::UrlViewModelImpl)
} }
@@ -2,11 +2,13 @@ package dev.meloda.overseerr.screens.url.model
data class UrlScreenState( data class UrlScreenState(
val url: String, val url: String,
val plexToken: String,
val isWrongUrlError: Boolean val isWrongUrlError: Boolean
) { ) {
companion object { companion object {
val EMPTY: UrlScreenState = UrlScreenState( val EMPTY: UrlScreenState = UrlScreenState(
url = "", url = "",
plexToken = "",
isWrongUrlError = false isWrongUrlError = false
) )
} }
@@ -13,14 +13,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import dev.meloda.overseerr.screens.url.UrlViewModel import dev.meloda.overseerr.screens.url.UrlViewModel
import dev.meloda.overseerr.screens.url.UrlViewModelImpl import dev.meloda.overseerr.screens.url.UrlViewModelImpl
import dev.meloda.overseerr.settings.SettingsController import org.koin.compose.viewmodel.koinViewModel
import org.koin.compose.koinInject
class UrlScreen : Screen { class UrlScreen : Screen {
@@ -28,10 +26,7 @@ class UrlScreen : Screen {
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val viewModel: UrlViewModel = koinViewModel<UrlViewModelImpl>()
val settingsController: SettingsController = koinInject()
val viewModel: UrlViewModel = viewModel { UrlViewModelImpl(settingsController) }
val screenState by viewModel.screenState.collectAsState() val screenState by viewModel.screenState.collectAsState()
Scaffold( Scaffold(
@@ -72,6 +67,19 @@ class UrlScreen : Screen {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
TextField(
modifier = Modifier.fillMaxWidth(),
value = screenState.plexToken,
onValueChange = viewModel::onPlexTokenInputChanged,
placeholder = { Text(text = "Token") },
singleLine = true,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Go
)
)
Spacer(modifier = Modifier.height(16.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp) horizontalArrangement = Arrangement.spacedBy(16.dp)
@@ -4,11 +4,10 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class AppSettings( data class AppSettings(
val url: String val url: String = "",
val plexToken: String = ""
) { ) {
companion object { companion object {
val EMPTY: AppSettings = AppSettings( val EMPTY: AppSettings = AppSettings()
url = ""
)
} }
} }
@@ -0,0 +1,8 @@
package dev.meloda.overseerr.network.model
import io.ktor.client.engine.*
import io.ktor.client.engine.darwin.*
actual class HttpClientEngineFactoryProvider actual constructor() {
actual fun get(): HttpClientEngineFactory<*> = Darwin
}
@@ -0,0 +1,8 @@
package dev.meloda.overseerr.network.model
import io.ktor.client.engine.*
import io.ktor.client.engine.okhttp.*
actual class HttpClientEngineFactoryProvider actual constructor() {
actual fun get(): HttpClientEngineFactory<*> = OkHttp
}
@@ -0,0 +1,8 @@
package dev.meloda.overseerr.network.model
import io.ktor.client.engine.*
import io.ktor.client.engine.js.*
actual class HttpClientEngineFactoryProvider actual constructor() {
actual fun get(): HttpClientEngineFactory<*> = Js
}
+6 -1
View File
@@ -16,6 +16,7 @@ haze = "0.7.3"
kstore = "0.8.0" kstore = "0.8.0"
appdirs = "1.2.2" appdirs = "1.2.2"
napier = "2.7.1" napier = "2.7.1"
message-bar = "1.0.5"
[libraries] [libraries]
@@ -32,12 +33,15 @@ kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutine
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-kotlinx-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" } haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
haze-materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" } haze-materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" }
kstore = { module = "io.github.xxfast:kstore", version.ref = "kstore" } kstore = { module = "io.github.xxfast:kstore", version.ref = "kstore" }
@@ -45,6 +49,7 @@ kstore-file = { module = "io.github.xxfast:kstore-file", version.ref = "kstore"
kstore-storage = { module = "io.github.xxfast:kstore-storage", version.ref = "kstore" } kstore-storage = { module = "io.github.xxfast:kstore-storage", version.ref = "kstore" }
appdirs = { module = "net.harawata:appdirs", version.ref = "appdirs" } appdirs = { module = "net.harawata:appdirs", version.ref = "appdirs" }
napier = { module = "io.github.aakira:napier", version.ref = "napier" } napier = { module = "io.github.aakira:napier", version.ref = "napier" }
message-bar = { module = "com.stevdza-san:messagebarkmp", version.ref = "message-bar" }
[plugins] [plugins]