mirror of https://github.com/readrops/Readrops.git
Add initial FreshRSS login with new kotlin repository
This commit is contained in:
parent
a7c0749641
commit
f14ed7f331
|
@ -3,8 +3,13 @@ package com.readrops.api
|
||||||
import com.readrops.api.localfeed.LocalRSSDataSource
|
import com.readrops.api.localfeed.LocalRSSDataSource
|
||||||
import com.readrops.api.services.Credentials
|
import com.readrops.api.services.Credentials
|
||||||
import com.readrops.api.services.freshrss.FreshRSSDataSource
|
import com.readrops.api.services.freshrss.FreshRSSDataSource
|
||||||
import com.readrops.api.services.freshrss.FreshRSSService
|
import com.readrops.api.services.freshrss.NewFreshRSSDataSource
|
||||||
import com.readrops.api.services.freshrss.adapters.*
|
import com.readrops.api.services.freshrss.NewFreshRSSService
|
||||||
|
import com.readrops.api.services.freshrss.adapters.FreshRSSFeedsAdapter
|
||||||
|
import com.readrops.api.services.freshrss.adapters.FreshRSSFoldersAdapter
|
||||||
|
import com.readrops.api.services.freshrss.adapters.FreshRSSItemsAdapter
|
||||||
|
import com.readrops.api.services.freshrss.adapters.FreshRSSItemsIdsAdapter
|
||||||
|
import com.readrops.api.services.freshrss.adapters.FreshRSSUserInfoAdapter
|
||||||
import com.readrops.api.services.nextcloudnews.NextNewsDataSource
|
import com.readrops.api.services.nextcloudnews.NextNewsDataSource
|
||||||
import com.readrops.api.services.nextcloudnews.NextNewsService
|
import com.readrops.api.services.nextcloudnews.NextNewsService
|
||||||
import com.readrops.api.services.nextcloudnews.adapters.NextNewsFeedsAdapter
|
import com.readrops.api.services.nextcloudnews.adapters.NextNewsFeedsAdapter
|
||||||
|
@ -27,12 +32,12 @@ val apiModule = module {
|
||||||
|
|
||||||
single {
|
single {
|
||||||
OkHttpClient.Builder()
|
OkHttpClient.Builder()
|
||||||
.callTimeout(1, TimeUnit.MINUTES)
|
.callTimeout(1, TimeUnit.MINUTES)
|
||||||
.readTimeout(1, TimeUnit.HOURS)
|
.readTimeout(1, TimeUnit.HOURS)
|
||||||
.addInterceptor(get<AuthInterceptor>())
|
.addInterceptor(get<AuthInterceptor>())
|
||||||
.addInterceptor(get<ErrorInterceptor>())
|
.addInterceptor(get<ErrorInterceptor>())
|
||||||
//.addInterceptor(NiddlerOkHttpInterceptor(get(), "niddler"))
|
//.addInterceptor(NiddlerOkHttpInterceptor(get(), "niddler"))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
single { AuthInterceptor() }
|
single { AuthInterceptor() }
|
||||||
|
@ -45,14 +50,15 @@ val apiModule = module {
|
||||||
|
|
||||||
factory { params -> FreshRSSDataSource(get(parameters = { params })) }
|
factory { params -> FreshRSSDataSource(get(parameters = { params })) }
|
||||||
|
|
||||||
|
factory { params -> NewFreshRSSDataSource(get(parameters = { params })) }
|
||||||
|
|
||||||
factory { (credentials: Credentials) ->
|
factory { (credentials: Credentials) ->
|
||||||
Retrofit.Builder()
|
Retrofit.Builder()
|
||||||
.baseUrl(credentials.url)
|
.baseUrl(credentials.url)
|
||||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
.client(get())
|
||||||
.client(get())
|
.addConverterFactory(MoshiConverterFactory.create(get(named("freshrssMoshi"))))
|
||||||
.addConverterFactory(MoshiConverterFactory.create(get(named("freshrssMoshi"))))
|
.build()
|
||||||
.build()
|
.create(NewFreshRSSService::class.java)
|
||||||
.create(FreshRSSService::class.java)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
single(named("freshrssMoshi")) {
|
single(named("freshrssMoshi")) {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package com.readrops.api.utils.exceptions
|
package com.readrops.api.utils.exceptions
|
||||||
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
class HttpException(val response: Response) : Exception() {
|
class HttpException(val response: Response) : IOException() {
|
||||||
|
|
||||||
val code: Int
|
val code: Int
|
||||||
get() = response.code
|
get() = response.code
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package com.readrops.app.compose
|
package com.readrops.app.compose
|
||||||
|
|
||||||
|
import com.readrops.api.services.Credentials
|
||||||
import com.readrops.app.compose.account.AccountScreenModel
|
import com.readrops.app.compose.account.AccountScreenModel
|
||||||
import com.readrops.app.compose.account.credentials.AccountCredentialsScreenModel
|
import com.readrops.app.compose.account.credentials.AccountCredentialsScreenModel
|
||||||
import com.readrops.app.compose.account.selection.AccountSelectionScreenModel
|
import com.readrops.app.compose.account.selection.AccountSelectionScreenModel
|
||||||
import com.readrops.app.compose.feeds.FeedScreenModel
|
import com.readrops.app.compose.feeds.FeedScreenModel
|
||||||
import com.readrops.app.compose.item.ItemScreenModel
|
import com.readrops.app.compose.item.ItemScreenModel
|
||||||
import com.readrops.app.compose.repositories.BaseRepository
|
import com.readrops.app.compose.repositories.BaseRepository
|
||||||
|
import com.readrops.app.compose.repositories.FreshRSSRepository
|
||||||
import com.readrops.app.compose.repositories.GetFoldersWithFeeds
|
import com.readrops.app.compose.repositories.GetFoldersWithFeeds
|
||||||
import com.readrops.app.compose.repositories.LocalRSSRepository
|
import com.readrops.app.compose.repositories.LocalRSSRepository
|
||||||
import com.readrops.app.compose.timelime.TimelineScreenModel
|
import com.readrops.app.compose.timelime.TimelineScreenModel
|
||||||
import com.readrops.db.entities.account.Account
|
import com.readrops.db.entities.account.Account
|
||||||
import com.readrops.db.entities.account.AccountType
|
import com.readrops.db.entities.account.AccountType
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val composeAppModule = module {
|
val composeAppModule = module {
|
||||||
|
@ -23,18 +26,22 @@ val composeAppModule = module {
|
||||||
|
|
||||||
factory { AccountScreenModel(get()) }
|
factory { AccountScreenModel(get()) }
|
||||||
|
|
||||||
factory { (itemId: Int) ->
|
factory { (itemId: Int) -> ItemScreenModel(get(), itemId) }
|
||||||
ItemScreenModel(get(), itemId)
|
|
||||||
}
|
|
||||||
|
|
||||||
factory { (accountType: AccountType) -> AccountCredentialsScreenModel(accountType) }
|
factory { (accountType: AccountType) -> AccountCredentialsScreenModel(accountType, get()) }
|
||||||
|
|
||||||
single { GetFoldersWithFeeds(get()) }
|
single { GetFoldersWithFeeds(get()) }
|
||||||
|
|
||||||
// repositories
|
// repositories
|
||||||
|
|
||||||
factory<BaseRepository> { (account: Account) ->
|
factory<BaseRepository> { (account: Account) ->
|
||||||
LocalRSSRepository(get(), get(), account)
|
when (account.accountType) {
|
||||||
|
AccountType.LOCAL -> LocalRSSRepository(get(), get(), account)
|
||||||
|
AccountType.FRESHRSS -> FreshRSSRepository(
|
||||||
|
get(), account,
|
||||||
|
get(parameters = { parametersOf(Credentials.toCredentials(account)) })
|
||||||
|
)
|
||||||
|
else -> throw IllegalArgumentException("Unknown account type")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -35,7 +35,9 @@ import cafe.adriel.voyager.koin.getScreenModel
|
||||||
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 com.readrops.app.compose.R
|
import com.readrops.app.compose.R
|
||||||
|
import com.readrops.app.compose.home.HomeScreen
|
||||||
import com.readrops.app.compose.util.components.AndroidScreen
|
import com.readrops.app.compose.util.components.AndroidScreen
|
||||||
|
import com.readrops.app.compose.util.components.errorText
|
||||||
import com.readrops.app.compose.util.theme.ShortSpacer
|
import com.readrops.app.compose.util.theme.ShortSpacer
|
||||||
import com.readrops.app.compose.util.theme.VeryLargeSpacer
|
import com.readrops.app.compose.util.theme.VeryLargeSpacer
|
||||||
import com.readrops.app.compose.util.theme.spacing
|
import com.readrops.app.compose.util.theme.spacing
|
||||||
|
@ -54,6 +56,10 @@ class AccountCredentialsScreen(
|
||||||
|
|
||||||
val state by screenModel.state.collectAsStateWithLifecycle()
|
val state by screenModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
if (state.goToHomeScreen) {
|
||||||
|
navigator.replaceAll(HomeScreen())
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.imePadding()
|
modifier = Modifier.imePadding()
|
||||||
) {
|
) {
|
||||||
|
@ -129,9 +135,9 @@ class AccountCredentialsScreen(
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if (state.isPasswordVisible)
|
id = if (state.isPasswordVisible) {
|
||||||
R.drawable.ic_visible_off
|
R.drawable.ic_visible_off
|
||||||
else R.drawable.ic_visible
|
} else R.drawable.ic_visible
|
||||||
),
|
),
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
|
@ -157,7 +163,7 @@ class AccountCredentialsScreen(
|
||||||
onClick = { screenModel.login() },
|
onClick = { screenModel.login() },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
if (state.isLoginStarted) {
|
if (state.isLoginOnGoing) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
strokeWidth = 2.dp,
|
strokeWidth = 2.dp,
|
||||||
|
@ -167,6 +173,16 @@ class AccountCredentialsScreen(
|
||||||
Text(text = stringResource(id = R.string.validate))
|
Text(text = stringResource(id = R.string.validate))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.loginException != null) {
|
||||||
|
ShortSpacer()
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = errorText(exception = state.loginException!!),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,38 @@ package com.readrops.app.compose.account.credentials
|
||||||
|
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
|
import com.readrops.app.compose.repositories.BaseRepository
|
||||||
import com.readrops.app.compose.util.components.TextFieldError
|
import com.readrops.app.compose.util.components.TextFieldError
|
||||||
|
import com.readrops.db.Database
|
||||||
import com.readrops.db.entities.account.Account
|
import com.readrops.db.entities.account.Account
|
||||||
import com.readrops.db.entities.account.AccountType
|
import com.readrops.db.entities.account.AccountType
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.get
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
|
|
||||||
class AccountCredentialsScreenModel(
|
class AccountCredentialsScreenModel(
|
||||||
private val accountType: AccountType
|
private val accountType: AccountType,
|
||||||
) : StateScreenModel<AccountCredentialsState>(AccountCredentialsState(name = accountType.name)) {
|
private val database: Database,
|
||||||
|
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||||
|
) : StateScreenModel<AccountCredentialsState>(AccountCredentialsState(name = accountType.name)),
|
||||||
|
KoinComponent {
|
||||||
|
|
||||||
fun onEvent(event: Event): Unit = with(mutableState) {
|
fun onEvent(event: Event): Unit = with(mutableState) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is Event.LoginEvent -> update { it.copy(login = event.value, loginError = null) }
|
is Event.LoginEvent -> update { it.copy(login = event.value, loginError = null) }
|
||||||
is Event.NameEvent -> update { it.copy(name = event.value, nameError = null) }
|
is Event.NameEvent -> update { it.copy(name = event.value, nameError = null) }
|
||||||
is Event.PasswordEvent -> update { it.copy(password = event.value, passwordError = null) }
|
is Event.PasswordEvent -> update {
|
||||||
|
it.copy(
|
||||||
|
password = event.value,
|
||||||
|
passwordError = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is Event.URLEvent -> update { it.copy(url = event.value, urlError = null) }
|
is Event.URLEvent -> update { it.copy(url = event.value, urlError = null) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +44,7 @@ class AccountCredentialsScreenModel(
|
||||||
|
|
||||||
fun login() {
|
fun login() {
|
||||||
if (validateFields()) {
|
if (validateFields()) {
|
||||||
mutableState.update { it.copy(isLoginStarted = true) }
|
mutableState.update { it.copy(isLoginOnGoing = true) }
|
||||||
|
|
||||||
with(state.value) {
|
with(state.value) {
|
||||||
val account = Account(
|
val account = Account(
|
||||||
|
@ -34,8 +52,29 @@ class AccountCredentialsScreenModel(
|
||||||
accountName = name,
|
accountName = name,
|
||||||
login = login,
|
login = login,
|
||||||
password = password,
|
password = password,
|
||||||
accountType = accountType
|
accountType = accountType,
|
||||||
|
isCurrentAccount = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val repository = get<BaseRepository> { parametersOf(account) }
|
||||||
|
|
||||||
|
screenModelScope.launch(dispatcher) {
|
||||||
|
try {
|
||||||
|
repository.login(account)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
mutableState.update {
|
||||||
|
it.copy(
|
||||||
|
loginException = e,
|
||||||
|
isLoginOnGoing = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
database.newAccountDao().insert(account)
|
||||||
|
mutableState.update { it.copy(goToHomeScreen = true) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +121,9 @@ data class AccountCredentialsState(
|
||||||
val password: String = "",
|
val password: String = "",
|
||||||
val passwordError: TextFieldError? = null,
|
val passwordError: TextFieldError? = null,
|
||||||
val isPasswordVisible: Boolean = false,
|
val isPasswordVisible: Boolean = false,
|
||||||
val isLoginStarted: Boolean = false
|
val isLoginOnGoing: Boolean = false,
|
||||||
|
val goToHomeScreen: Boolean = false,
|
||||||
|
val loginException: Exception? = null
|
||||||
) {
|
) {
|
||||||
val isUrlError = urlError != null
|
val isUrlError = urlError != null
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ abstract class ARepository(
|
||||||
/**
|
/**
|
||||||
* This method is intended for remote accounts.
|
* This method is intended for remote accounts.
|
||||||
*/
|
*/
|
||||||
abstract suspend fun login()
|
abstract suspend fun login(account: Account)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global synchronization for the local account.
|
* Global synchronization for the local account.
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.readrops.app.compose.repositories
|
||||||
|
|
||||||
|
import com.readrops.api.services.Credentials
|
||||||
|
import com.readrops.api.services.SyncResult
|
||||||
|
import com.readrops.api.services.freshrss.NewFreshRSSDataSource
|
||||||
|
import com.readrops.api.utils.AuthInterceptor
|
||||||
|
import com.readrops.db.Database
|
||||||
|
import com.readrops.db.entities.Feed
|
||||||
|
import com.readrops.db.entities.account.Account
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
|
||||||
|
class FreshRSSRepository(
|
||||||
|
database: Database,
|
||||||
|
account: Account,
|
||||||
|
private val dataSource: NewFreshRSSDataSource,
|
||||||
|
) : BaseRepository(database, account), KoinComponent {
|
||||||
|
|
||||||
|
override suspend fun login(account: Account) {
|
||||||
|
val authInterceptor = getKoin().get<AuthInterceptor>()
|
||||||
|
authInterceptor.credentials = Credentials.toCredentials(account)
|
||||||
|
|
||||||
|
val authToken = dataSource.login(account.login!!, account.password!!)
|
||||||
|
account.token = authToken
|
||||||
|
// we got the authToken, time to provide it to make real calls
|
||||||
|
authInterceptor.credentials = Credentials.toCredentials(account)
|
||||||
|
|
||||||
|
val userInfo = dataSource.getUserInfo()
|
||||||
|
account.displayedName = userInfo.userName
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun synchronize(
|
||||||
|
selectedFeeds: List<Feed>,
|
||||||
|
onUpdate: (Feed) -> Unit
|
||||||
|
): Pair<SyncResult, ErrorResult> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun synchronize(): SyncResult {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertNewFeeds(
|
||||||
|
newFeeds: List<Feed>,
|
||||||
|
onUpdate: (Feed) -> Unit
|
||||||
|
): ErrorResult {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ class LocalRSSRepository(
|
||||||
account: Account
|
account: Account
|
||||||
) : BaseRepository(database, account), KoinComponent {
|
) : BaseRepository(database, account), KoinComponent {
|
||||||
|
|
||||||
override suspend fun login() { /* useless here */
|
override suspend fun login(account: Account) { /* useless here */
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun synchronize(
|
override suspend fun synchronize(
|
||||||
|
|
Loading…
Reference in New Issue