Introduce SessionComponent
This commit is contained in:
parent
eba807e37a
commit
0a06ca0539
18 changed files with 176 additions and 104 deletions
|
|
@ -3,29 +3,33 @@ package io.element.android.x
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.startup.AppInitializer
|
import androidx.startup.AppInitializer
|
||||||
import io.element.android.x.core.di.DaggerComponentOwner
|
import io.element.android.x.core.di.DaggerComponentOwner
|
||||||
|
import io.element.android.x.core.di.bindings
|
||||||
|
import io.element.android.x.di.AppBindings
|
||||||
|
import io.element.android.x.di.AppComponent
|
||||||
import io.element.android.x.di.DaggerAppComponent
|
import io.element.android.x.di.DaggerAppComponent
|
||||||
|
import io.element.android.x.di.SessionComponentsOwner
|
||||||
import io.element.android.x.initializer.CoilInitializer
|
import io.element.android.x.initializer.CoilInitializer
|
||||||
import io.element.android.x.initializer.MatrixInitializer
|
import io.element.android.x.initializer.MatrixInitializer
|
||||||
import io.element.android.x.initializer.MavericksInitializer
|
import io.element.android.x.initializer.MavericksInitializer
|
||||||
import io.element.android.x.matrix.MatrixInstance
|
|
||||||
import kotlinx.coroutines.CoroutineName
|
|
||||||
import kotlinx.coroutines.MainScope
|
|
||||||
import kotlinx.coroutines.plus
|
|
||||||
|
|
||||||
class ElementXApplication : Application(), DaggerComponentOwner {
|
class ElementXApplication : Application(), DaggerComponentOwner {
|
||||||
|
|
||||||
override lateinit var daggerComponent: Any
|
private lateinit var appComponent: AppComponent
|
||||||
|
private var sessionComponentsOwner: SessionComponentsOwner? = null
|
||||||
|
|
||||||
private val applicationScope = MainScope() + CoroutineName("ElementX Scope")
|
override val daggerComponent: Any
|
||||||
|
get() = listOfNotNull(sessionComponentsOwner?.activeSessionComponent, appComponent)
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
daggerComponent = DaggerAppComponent.factory().create(applicationContext)
|
appComponent = DaggerAppComponent.factory().create(applicationContext)
|
||||||
MatrixInstance.init(this, applicationScope)
|
sessionComponentsOwner = bindings<AppBindings>().sessionComponentsOwner()
|
||||||
AppInitializer.getInstance(this).apply {
|
AppInitializer.getInstance(this).apply {
|
||||||
initializeComponent(MatrixInitializer::class.java)
|
initializeComponent(MatrixInitializer::class.java)
|
||||||
initializeComponent(CoilInitializer::class.java)
|
initializeComponent(CoilInitializer::class.java)
|
||||||
initializeComponent(MavericksInitializer::class.java)
|
initializeComponent(MavericksInitializer::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ import dagger.assisted.AssistedInject
|
||||||
import io.element.android.x.anvilannotations.ContributesViewModel
|
import io.element.android.x.anvilannotations.ContributesViewModel
|
||||||
import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
||||||
import io.element.android.x.di.AppScope
|
import io.element.android.x.di.AppScope
|
||||||
import io.element.android.x.features.messages.MessagesViewModel
|
import io.element.android.x.di.SessionComponentsOwner
|
||||||
import io.element.android.x.features.messages.model.MessagesViewState
|
|
||||||
import io.element.android.x.matrix.Matrix
|
import io.element.android.x.matrix.Matrix
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -19,10 +18,12 @@ data class MainState(val fake: Boolean = false) : MavericksState
|
||||||
@ContributesViewModel(AppScope::class)
|
@ContributesViewModel(AppScope::class)
|
||||||
class MainViewModel @AssistedInject constructor(
|
class MainViewModel @AssistedInject constructor(
|
||||||
private val matrix: Matrix,
|
private val matrix: Matrix,
|
||||||
|
private val sessionComponentsOwner: SessionComponentsOwner,
|
||||||
@Assisted initialState: MainState
|
@Assisted initialState: MainState
|
||||||
) : MavericksViewModel<MainState>(initialState) {
|
) : MavericksViewModel<MainState>(initialState) {
|
||||||
|
|
||||||
companion object : MavericksViewModelFactory<MainViewModel, MainState> by daggerMavericksViewModelFactory()
|
companion object :
|
||||||
|
MavericksViewModelFactory<MainViewModel, MainState> by daggerMavericksViewModelFactory()
|
||||||
|
|
||||||
suspend fun isLoggedIn(): Boolean {
|
suspend fun isLoggedIn(): Boolean {
|
||||||
return matrix.isLoggedIn().first()
|
return matrix.isLoggedIn().first()
|
||||||
|
|
@ -31,19 +32,22 @@ class MainViewModel @AssistedInject constructor(
|
||||||
fun startSyncIfLogged() {
|
fun startSyncIfLogged() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (!isLoggedIn()) return@launch
|
if (!isLoggedIn()) return@launch
|
||||||
matrix.activeClient().startSync()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopSyncIfLogged() {
|
fun stopSyncIfLogged() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (!isLoggedIn()) return@launch
|
if (!isLoggedIn()) return@launch
|
||||||
matrix.activeClient().stopSync()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun restoreSession() {
|
suspend fun restoreSession() {
|
||||||
matrix.restoreSession()
|
val matrixClient = matrix.restoreSession()
|
||||||
matrix.activeClient().startSync()
|
if (matrixClient == null) {
|
||||||
|
throw IllegalStateException("Couldn't restore session...")
|
||||||
|
} else {
|
||||||
|
sessionComponentsOwner.create(matrixClient)
|
||||||
|
matrixClient.startSync()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
package io.element.android.x
|
package io.element.android.x
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.ramcosta.composedestinations.navigation.popUpTo
|
import com.ramcosta.composedestinations.navigation.popUpTo
|
||||||
|
import io.element.android.x.core.di.bindings
|
||||||
import io.element.android.x.destinations.*
|
import io.element.android.x.destinations.*
|
||||||
|
import io.element.android.x.di.AppBindings
|
||||||
import io.element.android.x.features.login.LoginScreen
|
import io.element.android.x.features.login.LoginScreen
|
||||||
import io.element.android.x.features.login.changeserver.ChangeServerScreen
|
import io.element.android.x.features.login.changeserver.ChangeServerScreen
|
||||||
import io.element.android.x.features.messages.MessagesScreen
|
import io.element.android.x.features.messages.MessagesScreen
|
||||||
|
|
@ -29,11 +32,13 @@ fun OnBoardingScreenNavigation(navigator: DestinationsNavigator) {
|
||||||
@Destination
|
@Destination
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreenNavigation(navigator: DestinationsNavigator) {
|
fun LoginScreenNavigation(navigator: DestinationsNavigator) {
|
||||||
|
val sessionComponentsOwner = LocalContext.current.bindings<AppBindings>().sessionComponentsOwner()
|
||||||
LoginScreen(
|
LoginScreen(
|
||||||
onChangeServer = {
|
onChangeServer = {
|
||||||
navigator.navigate(ChangeServerScreenNavigationDestination)
|
navigator.navigate(ChangeServerScreenNavigationDestination)
|
||||||
},
|
},
|
||||||
onLoginWithSuccess = {
|
onLoginWithSuccess = {
|
||||||
|
sessionComponentsOwner.create(it)
|
||||||
navigator.navigate(RoomListScreenNavigationDestination) {
|
navigator.navigate(RoomListScreenNavigationDestination) {
|
||||||
popUpTo(OnBoardingScreenNavigationDestination) {
|
popUpTo(OnBoardingScreenNavigationDestination) {
|
||||||
inclusive = true
|
inclusive = true
|
||||||
|
|
@ -58,11 +63,13 @@ fun ChangeServerScreenNavigation(navigator: DestinationsNavigator) {
|
||||||
@Destination
|
@Destination
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomListScreenNavigation(navigator: DestinationsNavigator) {
|
fun RoomListScreenNavigation(navigator: DestinationsNavigator) {
|
||||||
|
val sessionComponentsOwner = LocalContext.current.bindings<AppBindings>().sessionComponentsOwner()
|
||||||
RoomListScreen(
|
RoomListScreen(
|
||||||
onRoomClicked = { roomId: RoomId ->
|
onRoomClicked = { roomId: RoomId ->
|
||||||
navigator.navigate(MessagesScreenNavigationDestination(roomId = roomId.value))
|
navigator.navigate(MessagesScreenNavigationDestination(roomId = roomId.value))
|
||||||
},
|
},
|
||||||
onSuccessLogout = {
|
onSuccessLogout = {
|
||||||
|
sessionComponentsOwner.releaseActiveSession()
|
||||||
navigator.navigate(OnBoardingScreenNavigationDestination) {
|
navigator.navigate(OnBoardingScreenNavigationDestination) {
|
||||||
popUpTo(RoomListScreenNavigationDestination) {
|
popUpTo(RoomListScreenNavigationDestination) {
|
||||||
inclusive = true
|
inclusive = true
|
||||||
|
|
@ -75,7 +82,7 @@ fun RoomListScreenNavigation(navigator: DestinationsNavigator) {
|
||||||
@Destination
|
@Destination
|
||||||
@Composable
|
@Composable
|
||||||
fun MessagesScreenNavigation(roomId: String, navigator: DestinationsNavigator) {
|
fun MessagesScreenNavigation(roomId: String, navigator: DestinationsNavigator) {
|
||||||
MessagesScreen(roomId, navigator::navigateUp)
|
MessagesScreen(roomId = roomId, onBackPressed = navigator::navigateUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package io.element.android.x.di
|
package io.element.android.x.di
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import io.element.android.x.di.AppScope
|
|
||||||
import io.element.android.x.matrix.Matrix
|
import io.element.android.x.matrix.Matrix
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
|
@ -9,4 +8,5 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
interface AppBindings {
|
interface AppBindings {
|
||||||
fun coroutineScope(): CoroutineScope
|
fun coroutineScope(): CoroutineScope
|
||||||
fun matrix(): Matrix
|
fun matrix(): Matrix
|
||||||
|
fun sessionComponentsOwner(): SessionComponentsOwner
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package io.element.android.x.di
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
|
import dagger.BindsInstance
|
||||||
|
import dagger.Subcomponent
|
||||||
|
import io.element.android.x.core.di.DaggerMavericksBindings
|
||||||
|
import io.element.android.x.matrix.MatrixClient
|
||||||
|
|
||||||
|
@SingleIn(SessionScope::class)
|
||||||
|
@MergeSubcomponent(SessionScope::class)
|
||||||
|
interface SessionComponent: DaggerMavericksBindings {
|
||||||
|
|
||||||
|
fun matrixClient(): MatrixClient
|
||||||
|
|
||||||
|
@Subcomponent.Builder
|
||||||
|
interface Builder {
|
||||||
|
@BindsInstance
|
||||||
|
fun client(matrixClient: MatrixClient): Builder
|
||||||
|
fun build(): SessionComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContributesTo(AppScope::class)
|
||||||
|
interface ParentBindings {
|
||||||
|
fun sessionComponentBuilder(): Builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package io.element.android.x.di
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import io.element.android.x.core.di.bindings
|
||||||
|
import io.element.android.x.matrix.MatrixClient
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SingleIn(AppScope::class)
|
||||||
|
class SessionComponentsOwner @Inject constructor(@ApplicationContext private val context: Context) {
|
||||||
|
|
||||||
|
private val sessionComponents = ConcurrentHashMap<String, SessionComponent>()
|
||||||
|
var activeSessionComponent: SessionComponent? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun setActive(sessionId: String) {
|
||||||
|
val sessionComponent = sessionComponents[sessionId]
|
||||||
|
if (activeSessionComponent != sessionComponent) {
|
||||||
|
activeSessionComponent = sessionComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(matrixClient: MatrixClient) {
|
||||||
|
val sessionId = matrixClient.sessionId
|
||||||
|
val sessionComponent =
|
||||||
|
context.bindings<SessionComponent.ParentBindings>().sessionComponentBuilder()
|
||||||
|
.client(matrixClient).build()
|
||||||
|
sessionComponents[sessionId] = sessionComponent
|
||||||
|
setActive(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun releaseActiveSession() {
|
||||||
|
activeSessionComponent?.also {
|
||||||
|
release(it.matrixClient().sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun release(sessionId: String) {
|
||||||
|
val sessionComponent = sessionComponents.remove(sessionId)
|
||||||
|
if (activeSessionComponent == sessionComponent) {
|
||||||
|
activeSessionComponent = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,13 @@ private class ElementImageLoaderFactory(
|
||||||
return ImageLoader
|
return ImageLoader
|
||||||
.Builder(context)
|
.Builder(context)
|
||||||
.components {
|
.components {
|
||||||
context.bindings<AppBindings>().matrix().registerCoilComponents(this)
|
val appBindings = context.bindings<AppBindings>()
|
||||||
|
val matrix = appBindings.matrix()
|
||||||
|
val matrixClientProvider = {
|
||||||
|
appBindings
|
||||||
|
.sessionComponentsOwner().activeSessionComponent?.matrixClient()
|
||||||
|
}
|
||||||
|
matrix.registerCoilComponents(this, matrixClientProvider)
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,14 @@ import com.airbnb.mvrx.compose.collectAsState
|
||||||
import com.airbnb.mvrx.compose.mavericksViewModel
|
import com.airbnb.mvrx.compose.mavericksViewModel
|
||||||
import io.element.android.x.designsystem.ElementXTheme
|
import io.element.android.x.designsystem.ElementXTheme
|
||||||
import io.element.android.x.features.login.error.loginError
|
import io.element.android.x.features.login.error.loginError
|
||||||
|
import io.element.android.x.matrix.MatrixClient
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(
|
fun LoginScreen(
|
||||||
viewModel: LoginViewModel = mavericksViewModel(),
|
viewModel: LoginViewModel = mavericksViewModel(),
|
||||||
onChangeServer: () -> Unit = { },
|
onChangeServer: () -> Unit = { },
|
||||||
onLoginWithSuccess: () -> Unit = { },
|
onLoginWithSuccess: (MatrixClient) -> Unit = { },
|
||||||
) {
|
) {
|
||||||
val state: LoginViewState by viewModel.collectAsState()
|
val state: LoginViewState by viewModel.collectAsState()
|
||||||
val formState: LoginFormState by viewModel.formState
|
val formState: LoginFormState by viewModel.formState
|
||||||
|
|
@ -65,7 +66,7 @@ fun LoginContent(
|
||||||
onLoginChanged: (String) -> Unit = {},
|
onLoginChanged: (String) -> Unit = {},
|
||||||
onPasswordChanged: (String) -> Unit = {},
|
onPasswordChanged: (String) -> Unit = {},
|
||||||
onSubmitClicked: () -> Unit = {},
|
onSubmitClicked: () -> Unit = {},
|
||||||
onLoginWithSuccess: () -> Unit = {},
|
onLoginWithSuccess: (MatrixClient) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -82,7 +83,7 @@ fun LoginContent(
|
||||||
)
|
)
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
) {
|
) {
|
||||||
val isError = state.isLoggedIn is Fail
|
val isError = state.loggedInClient is Fail
|
||||||
// Title
|
// Title
|
||||||
Text(
|
Text(
|
||||||
text = "Welcome back",
|
text = "Welcome back",
|
||||||
|
|
@ -137,7 +138,7 @@ fun LoginContent(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
var passwordVisible by remember { mutableStateOf(false) }
|
var passwordVisible by remember { mutableStateOf(false) }
|
||||||
if (state.isLoggedIn is Loading) {
|
if (state.loggedInClient is Loading) {
|
||||||
// Ensure password is hidden when user submits the form
|
// Ensure password is hidden when user submits the form
|
||||||
passwordVisible = false
|
passwordVisible = false
|
||||||
}
|
}
|
||||||
|
|
@ -170,9 +171,9 @@ fun LoginContent(
|
||||||
onDone = { onSubmitClicked() }
|
onDone = { onSubmitClicked() }
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (state.isLoggedIn is Fail) {
|
if (state.loggedInClient is Fail) {
|
||||||
Text(
|
Text(
|
||||||
text = loginError(state.formState, state.isLoggedIn.error),
|
text = loginError(state.formState, state.loggedInClient.error),
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
modifier = Modifier.padding(start = 16.dp)
|
modifier = Modifier.padding(start = 16.dp)
|
||||||
|
|
@ -189,11 +190,12 @@ fun LoginContent(
|
||||||
) {
|
) {
|
||||||
Text(text = "Continue")
|
Text(text = "Continue")
|
||||||
}
|
}
|
||||||
if (state.isLoggedIn is Success) {
|
when (val loggedInClient = state.loggedInClient) {
|
||||||
onLoginWithSuccess()
|
is Success -> onLoginWithSuccess(loggedInClient())
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.isLoggedIn is Loading) {
|
if (state.loggedInClient is Loading) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier.align(Alignment.Center)
|
modifier = Modifier.align(Alignment.Center)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import io.element.android.x.anvilannotations.ContributesViewModel
|
||||||
import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
||||||
import io.element.android.x.di.AppScope
|
import io.element.android.x.di.AppScope
|
||||||
import io.element.android.x.matrix.Matrix
|
import io.element.android.x.matrix.Matrix
|
||||||
import io.element.android.x.matrix.MatrixInstance
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -49,21 +48,22 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
val state = awaitState()
|
val state = awaitState()
|
||||||
// Ensure the server is provided to the Rust SDK
|
// Ensure the server is provided to the Rust SDK
|
||||||
matrix.setHomeserver(state.homeserver)
|
matrix.setHomeserver(state.homeserver)
|
||||||
matrix.login(state.formState.login.trim(), state.formState.password.trim())
|
matrix.login(state.formState.login.trim(), state.formState.password.trim()).also {
|
||||||
matrix.activeClient().startSync()
|
it.startSync()
|
||||||
|
}
|
||||||
}.execute {
|
}.execute {
|
||||||
copy(isLoggedIn = it)
|
copy(loggedInClient = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSetPassword(password: String) {
|
fun onSetPassword(password: String) {
|
||||||
formState.value = formState.value.copy(password = password)
|
formState.value = formState.value.copy(password = password)
|
||||||
setState { copy(isLoggedIn = Uninitialized) }
|
setState { copy(loggedInClient = Uninitialized) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSetName(name: String) {
|
fun onSetName(name: String) {
|
||||||
formState.value = formState.value.copy(login = name)
|
formState.value = formState.value.copy(login = name)
|
||||||
setState { copy(isLoggedIn = Uninitialized) }
|
setState { copy(loggedInClient = Uninitialized) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,14 +4,15 @@ import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import io.element.android.x.matrix.MatrixClient
|
||||||
|
|
||||||
data class LoginViewState(
|
data class LoginViewState(
|
||||||
val homeserver: String = "",
|
val homeserver: String = "",
|
||||||
val isLoggedIn: Async<Unit> = Uninitialized,
|
val loggedInClient: Async<MatrixClient> = Uninitialized,
|
||||||
val formState: LoginFormState = LoginFormState.Default,
|
val formState: LoginFormState = LoginFormState.Default,
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
val submitEnabled =
|
val submitEnabled =
|
||||||
formState.login.isNotEmpty() && formState.password.isNotEmpty() && isLoggedIn !is Loading
|
formState.login.isNotEmpty() && formState.password.isNotEmpty() && loggedInClient !is Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
data class LoginFormState(
|
data class LoginFormState(
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,25 @@
|
||||||
package io.element.android.x.features.login.changeserver
|
package io.element.android.x.features.login.changeserver
|
||||||
|
|
||||||
import com.airbnb.mvrx.MavericksViewModel
|
import com.airbnb.mvrx.MavericksViewModel
|
||||||
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import io.element.android.x.matrix.MatrixInstance
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.element.android.x.anvilannotations.ContributesViewModel
|
||||||
|
import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
||||||
|
import io.element.android.x.di.AppScope
|
||||||
|
import io.element.android.x.matrix.Matrix
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ChangeServerViewModel(initialState: ChangeServerViewState) :
|
@ContributesViewModel(AppScope::class)
|
||||||
|
class ChangeServerViewModel @AssistedInject constructor(
|
||||||
|
private val matrix: Matrix,
|
||||||
|
@Assisted initialState: ChangeServerViewState
|
||||||
|
) :
|
||||||
MavericksViewModel<ChangeServerViewState>(initialState) {
|
MavericksViewModel<ChangeServerViewState>(initialState) {
|
||||||
|
|
||||||
private val matrix = MatrixInstance.getInstance()
|
companion object :
|
||||||
|
MavericksViewModelFactory<ChangeServerViewModel, ChangeServerViewState> by daggerMavericksViewModelFactory()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setState {
|
setState {
|
||||||
|
|
@ -32,7 +43,7 @@ class ChangeServerViewModel(initialState: ChangeServerViewState) :
|
||||||
suspend {
|
suspend {
|
||||||
val state = awaitState()
|
val state = awaitState()
|
||||||
matrix.setHomeserver(state.homeserver)
|
matrix.setHomeserver(state.homeserver)
|
||||||
}.execute { it ->
|
}.execute {
|
||||||
copy(changeServerAction = it)
|
copy(changeServerAction = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.x.di.AppScope
|
import io.element.android.x.di.AppScope
|
||||||
|
import io.element.android.x.di.SessionScope
|
||||||
import io.element.android.x.features.messages.model.MessagesItemAction
|
import io.element.android.x.features.messages.model.MessagesItemAction
|
||||||
import io.element.android.x.features.messages.model.MessagesItemActionsSheetState
|
import io.element.android.x.features.messages.model.MessagesItemActionsSheetState
|
||||||
import io.element.android.x.features.messages.model.MessagesTimelineItemState
|
import io.element.android.x.features.messages.model.MessagesTimelineItemState
|
||||||
|
|
@ -17,6 +18,7 @@ import io.element.android.x.features.messages.model.MessagesViewState
|
||||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemRedactedContent
|
import io.element.android.x.features.messages.model.content.MessagesTimelineItemRedactedContent
|
||||||
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
|
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
|
||||||
import io.element.android.x.matrix.Matrix
|
import io.element.android.x.matrix.Matrix
|
||||||
|
import io.element.android.x.matrix.MatrixClient
|
||||||
import io.element.android.x.matrix.media.MediaResolver
|
import io.element.android.x.matrix.media.MediaResolver
|
||||||
import io.element.android.x.matrix.timeline.MatrixTimeline
|
import io.element.android.x.matrix.timeline.MatrixTimeline
|
||||||
import io.element.android.x.matrix.timeline.MatrixTimelineItem
|
import io.element.android.x.matrix.timeline.MatrixTimelineItem
|
||||||
|
|
@ -28,16 +30,15 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
private const val PAGINATION_COUNT = 50
|
private const val PAGINATION_COUNT = 50
|
||||||
|
|
||||||
@ContributesViewModel(AppScope::class)
|
@ContributesViewModel(SessionScope::class)
|
||||||
class MessagesViewModel @AssistedInject constructor(
|
class MessagesViewModel @AssistedInject constructor(
|
||||||
matrix: Matrix,
|
private val client: MatrixClient,
|
||||||
@Assisted private val initialState: MessagesViewState
|
@Assisted private val initialState: MessagesViewState
|
||||||
) :
|
) :
|
||||||
MavericksViewModel<MessagesViewState>(initialState) {
|
MavericksViewModel<MessagesViewState>(initialState) {
|
||||||
|
|
||||||
companion object : MavericksViewModelFactory<MessagesViewModel, MessagesViewState> by daggerMavericksViewModelFactory()
|
companion object : MavericksViewModelFactory<MessagesViewModel, MessagesViewState> by daggerMavericksViewModelFactory()
|
||||||
|
|
||||||
private val client = matrix.activeClient()
|
|
||||||
private val room = client.getRoom(initialState.roomId)!!
|
private val room = client.getRoom(initialState.roomId)!!
|
||||||
private val messageTimelineItemStateFactory =
|
private val messageTimelineItemStateFactory =
|
||||||
MessageTimelineItemStateFactory(client, room, Dispatchers.Default)
|
MessageTimelineItemStateFactory(client, room, Dispatchers.Default)
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,18 @@ import dagger.assisted.AssistedInject
|
||||||
import io.element.android.x.anvilannotations.ContributesViewModel
|
import io.element.android.x.anvilannotations.ContributesViewModel
|
||||||
import io.element.android.x.core.data.StableCharSequence
|
import io.element.android.x.core.data.StableCharSequence
|
||||||
import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
||||||
import io.element.android.x.di.AppScope
|
import io.element.android.x.di.SessionScope
|
||||||
import io.element.android.x.matrix.Matrix
|
import io.element.android.x.matrix.MatrixClient
|
||||||
|
|
||||||
@ContributesViewModel(AppScope::class)
|
@ContributesViewModel(SessionScope::class)
|
||||||
class MessageComposerViewModel @AssistedInject constructor(
|
class MessageComposerViewModel @AssistedInject constructor(
|
||||||
private val matrix: Matrix,
|
private val client: MatrixClient,
|
||||||
@Assisted private val initialState: MessageComposerViewState
|
@Assisted private val initialState: MessageComposerViewState
|
||||||
) : MavericksViewModel<MessageComposerViewState>(initialState) {
|
) : MavericksViewModel<MessageComposerViewState>(initialState) {
|
||||||
|
|
||||||
companion object :
|
companion object :
|
||||||
MavericksViewModelFactory<MessageComposerViewModel, MessageComposerViewState> by daggerMavericksViewModelFactory()
|
MavericksViewModelFactory<MessageComposerViewModel, MessageComposerViewState> by daggerMavericksViewModelFactory()
|
||||||
|
|
||||||
private val client = matrix.activeClient()
|
|
||||||
|
|
||||||
fun onComposerFullScreenChange() {
|
fun onComposerFullScreenChange() {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@ import io.element.android.x.core.di.daggerMavericksViewModelFactory
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.x.di.AppScope
|
import io.element.android.x.di.AppScope
|
||||||
|
import io.element.android.x.di.SessionScope
|
||||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
||||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummaryPlaceholders
|
import io.element.android.x.features.roomlist.model.RoomListRoomSummaryPlaceholders
|
||||||
import io.element.android.x.features.roomlist.model.RoomListViewState
|
import io.element.android.x.features.roomlist.model.RoomListViewState
|
||||||
import io.element.android.x.matrix.Matrix
|
import io.element.android.x.matrix.Matrix
|
||||||
import io.element.android.x.matrix.MatrixClient
|
import io.element.android.x.matrix.MatrixClient
|
||||||
import io.element.android.x.matrix.MatrixInstance
|
|
||||||
import io.element.android.x.matrix.media.MediaResolver
|
import io.element.android.x.matrix.media.MediaResolver
|
||||||
import io.element.android.x.matrix.room.RoomSummary
|
import io.element.android.x.matrix.room.RoomSummary
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -28,9 +28,9 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
private const val extendedRangeSize = 40
|
private const val extendedRangeSize = 40
|
||||||
|
|
||||||
@ContributesViewModel(AppScope::class)
|
@ContributesViewModel(SessionScope::class)
|
||||||
class RoomListViewModel @AssistedInject constructor(
|
class RoomListViewModel @AssistedInject constructor(
|
||||||
matrix: Matrix,
|
private val client: MatrixClient,
|
||||||
@Assisted initialState: RoomListViewState
|
@Assisted initialState: RoomListViewState
|
||||||
) :
|
) :
|
||||||
MavericksViewModel<RoomListViewState>(initialState) {
|
MavericksViewModel<RoomListViewState>(initialState) {
|
||||||
|
|
@ -38,7 +38,6 @@ class RoomListViewModel @AssistedInject constructor(
|
||||||
companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> by daggerMavericksViewModelFactory()
|
companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> by daggerMavericksViewModelFactory()
|
||||||
|
|
||||||
private val lastMessageFormatter = LastMessageFormatter()
|
private val lastMessageFormatter = LastMessageFormatter()
|
||||||
private val client = matrix.activeClient()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
handleInit()
|
handleInit()
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package io.element.android.x.matrix
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import coil.ComponentRegistry
|
import coil.ComponentRegistry
|
||||||
import io.element.android.x.core.coroutine.CoroutineDispatchers
|
import io.element.android.x.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.x.di.ApplicationContext
|
|
||||||
import io.element.android.x.di.AppScope
|
import io.element.android.x.di.AppScope
|
||||||
|
import io.element.android.x.di.ApplicationContext
|
||||||
import io.element.android.x.di.SingleIn
|
import io.element.android.x.di.SingleIn
|
||||||
import io.element.android.x.matrix.media.MediaFetcher
|
import io.element.android.x.matrix.media.MediaFetcher
|
||||||
import io.element.android.x.matrix.media.MediaKeyer
|
import io.element.android.x.matrix.media.MediaKeyer
|
||||||
|
|
@ -37,35 +37,18 @@ class Matrix @Inject constructor(
|
||||||
)
|
)
|
||||||
private val baseDirectory = File(context.filesDir, "sessions")
|
private val baseDirectory = File(context.filesDir, "sessions")
|
||||||
private val sessionStore = SessionStore(context)
|
private val sessionStore = SessionStore(context)
|
||||||
private val matrixClient = MutableStateFlow<Optional<MatrixClient>>(Optional.empty())
|
|
||||||
private val authService = AuthenticationService(baseDirectory.absolutePath)
|
private val authService = AuthenticationService(baseDirectory.absolutePath)
|
||||||
|
|
||||||
init {
|
|
||||||
sessionStore.isLoggedIn()
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.onEach { isLoggedIn ->
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
matrixClient.value = Optional.empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.launchIn(coroutineScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isLoggedIn(): Flow<Boolean> {
|
fun isLoggedIn(): Flow<Boolean> {
|
||||||
return sessionStore.isLoggedIn()
|
return sessionStore.isLoggedIn()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun client(): Flow<Optional<MatrixClient>> {
|
fun registerCoilComponents(
|
||||||
return matrixClient
|
builder: ComponentRegistry.Builder,
|
||||||
}
|
activeClientProvider: () -> MatrixClient?
|
||||||
|
) {
|
||||||
fun activeClient(): MatrixClient {
|
|
||||||
return matrixClient.value.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun registerCoilComponents(builder: ComponentRegistry.Builder) {
|
|
||||||
builder.add(MediaKeyer())
|
builder.add(MediaKeyer())
|
||||||
builder.add(MediaFetcher.Factory(this))
|
builder.add(MediaFetcher.Factory(activeClientProvider))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun restoreSession() = withContext(coroutineDispatchers.io) {
|
suspend fun restoreSession() = withContext(coroutineDispatchers.io) {
|
||||||
|
|
@ -116,8 +99,6 @@ class Matrix @Inject constructor(
|
||||||
coroutineScope = coroutineScope,
|
coroutineScope = coroutineScope,
|
||||||
dispatchers = coroutineDispatchers,
|
dispatchers = coroutineDispatchers,
|
||||||
baseDirectory = baseDirectory,
|
baseDirectory = baseDirectory,
|
||||||
).also {
|
)
|
||||||
matrixClient.value = Optional.of(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package io.element.android.x.matrix
|
package io.element.android.x.matrix
|
||||||
|
|
||||||
import io.element.android.x.core.coroutine.CoroutineDispatchers
|
import io.element.android.x.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.x.di.SingleIn
|
||||||
import io.element.android.x.matrix.core.UserId
|
import io.element.android.x.matrix.core.UserId
|
||||||
import io.element.android.x.matrix.media.MediaResolver
|
import io.element.android.x.matrix.media.MediaResolver
|
||||||
import io.element.android.x.matrix.media.RustMediaResolver
|
import io.element.android.x.matrix.media.RustMediaResolver
|
||||||
|
|
@ -25,6 +26,9 @@ class MatrixClient internal constructor(
|
||||||
private val baseDirectory: File,
|
private val baseDirectory: File,
|
||||||
) : Closeable {
|
) : Closeable {
|
||||||
|
|
||||||
|
val sessionId: String
|
||||||
|
get() = "${client.session().userId}_${client.session().deviceId}"
|
||||||
|
|
||||||
private val clientDelegate = object : ClientDelegate {
|
private val clientDelegate = object : ClientDelegate {
|
||||||
override fun didReceiveAuthError(isSoftLogout: Boolean) {
|
override fun didReceiveAuthError(isSoftLogout: Boolean) {
|
||||||
Timber.v("didReceiveAuthError()")
|
Timber.v("didReceiveAuthError()")
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package io.element.android.x.matrix
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Application
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
|
|
||||||
|
|
||||||
object MatrixInstance {
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
private lateinit var instance: Matrix
|
|
||||||
|
|
||||||
fun init(context: Application, coroutineScope: CoroutineScope) {
|
|
||||||
instance = Matrix(coroutineScope, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getInstance(): Matrix {
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,31 +4,32 @@ import coil.ImageLoader
|
||||||
import coil.fetch.FetchResult
|
import coil.fetch.FetchResult
|
||||||
import coil.fetch.Fetcher
|
import coil.fetch.Fetcher
|
||||||
import coil.request.Options
|
import coil.request.Options
|
||||||
import io.element.android.x.matrix.Matrix
|
import io.element.android.x.matrix.MatrixClient
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
internal class MediaFetcher(
|
internal class MediaFetcher(
|
||||||
private val mediaResolver: MediaResolver,
|
private val mediaResolver: MediaResolver?,
|
||||||
private val meta: MediaResolver.Meta,
|
private val meta: MediaResolver.Meta,
|
||||||
private val options: Options,
|
private val options: Options,
|
||||||
private val imageLoader: ImageLoader
|
private val imageLoader: ImageLoader
|
||||||
) : Fetcher {
|
) : Fetcher {
|
||||||
|
|
||||||
override suspend fun fetch(): FetchResult? {
|
override suspend fun fetch(): FetchResult? {
|
||||||
val byteArray = mediaResolver.resolve(meta) ?: return null
|
val byteArray = mediaResolver?.resolve(meta) ?: return null
|
||||||
val byteBuffer = ByteBuffer.wrap(byteArray)
|
val byteBuffer = ByteBuffer.wrap(byteArray)
|
||||||
return imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
|
return imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(private val matrix: Matrix) : Fetcher.Factory<MediaResolver.Meta> {
|
class Factory(private val activeClientProvider: () -> MatrixClient?) :
|
||||||
|
Fetcher.Factory<MediaResolver.Meta> {
|
||||||
override fun create(
|
override fun create(
|
||||||
data: MediaResolver.Meta,
|
data: MediaResolver.Meta,
|
||||||
options: Options,
|
options: Options,
|
||||||
imageLoader: ImageLoader
|
imageLoader: ImageLoader
|
||||||
): Fetcher {
|
): Fetcher {
|
||||||
val activeClient = matrix.activeClient()
|
val activeClient = activeClientProvider()
|
||||||
return MediaFetcher(
|
return MediaFetcher(
|
||||||
mediaResolver = activeClient.mediaResolver(),
|
mediaResolver = activeClient?.mediaResolver(),
|
||||||
meta = data,
|
meta = data,
|
||||||
options = options,
|
options = options,
|
||||||
imageLoader = imageLoader
|
imageLoader = imageLoader
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue