simple ktor-client implementation
This commit is contained in:
@@ -3,6 +3,7 @@ package dev.meloda.overseerr.di
|
||||
import dev.meloda.overseerr.model.Platform
|
||||
import dev.meloda.overseerr.network.di.networkModule
|
||||
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.settings.di.settingsModule
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
@@ -15,6 +16,7 @@ val appModule = module {
|
||||
settingsModule,
|
||||
networkModule,
|
||||
loginModule,
|
||||
urlModule
|
||||
urlModule,
|
||||
requestsModule
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
package dev.meloda.overseerr.network.di
|
||||
|
||||
import dev.meloda.overseerr.network.model.HttpClientEngineFactoryProvider
|
||||
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
|
||||
|
||||
val networkModule = module {
|
||||
singleOf(::HttpClientEngineFactoryProvider)
|
||||
single {
|
||||
HttpClient {
|
||||
|
||||
HttpClient(engineFactory = get<HttpClientEngineFactoryProvider>().get()) {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package dev.meloda.overseerr.network.model
|
||||
|
||||
import io.ktor.client.engine.*
|
||||
|
||||
expect class HttpClientEngineFactoryProvider() {
|
||||
fun get(): HttpClientEngineFactory<*>
|
||||
}
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
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
|
||||
|
||||
val loginModule = module {
|
||||
|
||||
viewModelOf(::LoginViewModelImpl)
|
||||
}
|
||||
|
||||
+2
-2
@@ -17,13 +17,13 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.meloda.overseerr.screens.login.LoginViewModel
|
||||
import dev.meloda.overseerr.screens.login.LoginViewModelImpl
|
||||
import dev.meloda.overseerr.screens.login.model.LoginScreenState
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
class LoginScreen : Screen {
|
||||
|
||||
@@ -31,7 +31,7 @@ class LoginScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel: LoginViewModel = viewModel { LoginViewModelImpl() }
|
||||
val viewModel: LoginViewModel = koinViewModel<LoginViewModelImpl>()
|
||||
val screenState: LoginScreenState by viewModel.screenState.collectAsState()
|
||||
|
||||
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.currentOrThrow
|
||||
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
|
||||
|
||||
class MainScreen : Screen {
|
||||
|
||||
-143
@@ -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())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -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
|
||||
)
|
||||
+9
@@ -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)
|
||||
}
|
||||
+19
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
+163
@@ -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>
|
||||
|
||||
fun onUrlInputChanged(newText: String)
|
||||
fun onPlexTokenInputChanged(newToken: String)
|
||||
fun onLoadButtonClicked()
|
||||
fun onSaveButtonClicked()
|
||||
fun onTestButtonClicked()
|
||||
@@ -29,7 +30,14 @@ class UrlViewModelImpl(
|
||||
|
||||
init {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -37,18 +45,30 @@ class UrlViewModelImpl(
|
||||
screenState.setValue { old -> old.copy(url = newText) }
|
||||
}
|
||||
|
||||
override fun onPlexTokenInputChanged(newToken: String) {
|
||||
screenState.setValue { old -> old.copy(plexToken = newToken) }
|
||||
}
|
||||
|
||||
override fun onLoadButtonClicked() {
|
||||
viewModelScope.launch {
|
||||
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() {
|
||||
viewModelScope.launch {
|
||||
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
|
||||
|
||||
import dev.meloda.overseerr.screens.url.UrlViewModelImpl
|
||||
import org.koin.core.module.dsl.viewModelOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val urlModule = module {
|
||||
|
||||
viewModelOf(::UrlViewModelImpl)
|
||||
}
|
||||
|
||||
+2
@@ -2,11 +2,13 @@ package dev.meloda.overseerr.screens.url.model
|
||||
|
||||
data class UrlScreenState(
|
||||
val url: String,
|
||||
val plexToken: String,
|
||||
val isWrongUrlError: Boolean
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY: UrlScreenState = UrlScreenState(
|
||||
url = "",
|
||||
plexToken = "",
|
||||
isWrongUrlError = false
|
||||
)
|
||||
}
|
||||
|
||||
+15
-7
@@ -13,14 +13,12 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.meloda.overseerr.screens.url.UrlViewModel
|
||||
import dev.meloda.overseerr.screens.url.UrlViewModelImpl
|
||||
import dev.meloda.overseerr.settings.SettingsController
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
class UrlScreen : Screen {
|
||||
|
||||
@@ -28,10 +26,7 @@ class UrlScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val settingsController: SettingsController = koinInject()
|
||||
val viewModel: UrlViewModel = viewModel { UrlViewModelImpl(settingsController) }
|
||||
|
||||
val viewModel: UrlViewModel = koinViewModel<UrlViewModelImpl>()
|
||||
val screenState by viewModel.screenState.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
@@ -72,6 +67,19 @@ class UrlScreen : Screen {
|
||||
|
||||
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(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
|
||||
@@ -4,11 +4,10 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AppSettings(
|
||||
val url: String
|
||||
val url: String = "",
|
||||
val plexToken: String = ""
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY: AppSettings = AppSettings(
|
||||
url = ""
|
||||
)
|
||||
val EMPTY: AppSettings = AppSettings()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user