Browse Source

Introduce SessionComponent

feature/bma/flipper
ganfra 2 years ago
parent
commit
9aa0ce9438
  1. 20
      app/src/main/java/io/element/android/x/ElementXApplication.kt
  2. 18
      app/src/main/java/io/element/android/x/MainViewModel.kt
  3. 9
      app/src/main/java/io/element/android/x/Navigation.kt
  4. 2
      app/src/main/java/io/element/android/x/di/AppBindings.kt
  5. 27
      app/src/main/java/io/element/android/x/di/SessionComponent.kt
  6. 45
      app/src/main/java/io/element/android/x/di/SessionComponentsOwner.kt
  7. 8
      app/src/main/java/io/element/android/x/initializer/CoilInitializer.kt
  8. 20
      features/login/src/main/java/io/element/android/x/features/login/LoginScreen.kt
  9. 12
      features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt
  10. 5
      features/login/src/main/java/io/element/android/x/features/login/LoginViewState.kt
  11. 19
      features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerViewModel.kt
  12. 7
      features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt
  13. 10
      features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewModel.kt
  14. 7
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt
  15. 33
      libraries/matrix/src/main/java/io/element/android/x/matrix/Matrix.kt
  16. 4
      libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt
  17. 19
      libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixInstance.kt
  18. 13
      libraries/matrix/src/main/java/io/element/android/x/matrix/media/MediaFetcher.kt

20
app/src/main/java/io/element/android/x/ElementXApplication.kt

@ -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)
} }
} }
} }

18
app/src/main/java/io/element/android/x/MainViewModel.kt

@ -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()
}
} }
} }

9
app/src/main/java/io/element/android/x/Navigation.kt

@ -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)
} }

2
app/src/main/java/io/element/android/x/di/AppBindings.kt

@ -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
} }

27
app/src/main/java/io/element/android/x/di/SessionComponent.kt

@ -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
}
}

45
app/src/main/java/io/element/android/x/di/SessionComponentsOwner.kt

@ -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
}
}
}

8
app/src/main/java/io/element/android/x/initializer/CoilInitializer.kt

@ -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()
} }

20
features/login/src/main/java/io/element/android/x/features/login/LoginScreen.kt

@ -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)
) )

12
features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt

@ -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) }
} }
} }

5
features/login/src/main/java/io/element/android/x/features/login/LoginViewState.kt

@ -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(

19
features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerViewModel.kt

@ -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)
} }
} }

7
features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt

@ -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)

10
features/messages/src/main/java/io/element/android/x/features/messages/textcomposer/MessageComposerViewModel.kt

@ -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(

7
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt

@ -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()

33
libraries/matrix/src/main/java/io/element/android/x/matrix/Matrix.kt

@ -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)
}
} }
} }

4
libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt

@ -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()")

19
libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixInstance.kt

@ -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
}
}

13
libraries/matrix/src/main/java/io/element/android/x/matrix/media/MediaFetcher.kt

@ -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…
Cancel
Save