diff --git a/app/src/main/java/io/element/android/x/ElementXApplication.kt b/app/src/main/java/io/element/android/x/ElementXApplication.kt index f13ba5d070..923a1ddc15 100644 --- a/app/src/main/java/io/element/android/x/ElementXApplication.kt +++ b/app/src/main/java/io/element/android/x/ElementXApplication.kt @@ -18,13 +18,9 @@ package io.element.android.x import android.app.Application import androidx.startup.AppInitializer -import io.element.android.x.architecture.bindings import io.element.android.x.core.di.DaggerComponentOwner -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.SessionComponentsOwner -import io.element.android.x.initializer.CoilInitializer import io.element.android.x.initializer.CrashInitializer import io.element.android.x.initializer.MatrixInitializer import io.element.android.x.initializer.MavericksInitializer @@ -33,20 +29,17 @@ import io.element.android.x.initializer.TimberInitializer class ElementXApplication : Application(), DaggerComponentOwner { private lateinit var appComponent: AppComponent - private var sessionComponentsOwner: SessionComponentsOwner? = null override val daggerComponent: Any - get() = listOfNotNull(sessionComponentsOwner?.activeSessionComponent, appComponent) + get() = appComponent override fun onCreate() { super.onCreate() appComponent = DaggerAppComponent.factory().create(applicationContext) - sessionComponentsOwner = bindings().sessionComponentsOwner() AppInitializer.getInstance(this).apply { initializeComponent(CrashInitializer::class.java) initializeComponent(TimberInitializer::class.java) initializeComponent(MatrixInitializer::class.java) - initializeComponent(CoilInitializer::class.java) initializeComponent(MavericksInitializer::class.java) } } diff --git a/app/src/main/java/io/element/android/x/MainActivity.kt b/app/src/main/java/io/element/android/x/MainActivity.kt index 1fd827be85..3f9ff827cc 100644 --- a/app/src/main/java/io/element/android/x/MainActivity.kt +++ b/app/src/main/java/io/element/android/x/MainActivity.kt @@ -39,7 +39,6 @@ class MainActivity : NodeComponentActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) setContent { ElementXTheme { - // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background @@ -49,7 +48,6 @@ class MainActivity : NodeComponentActivity() { buildContext = it, appComponentOwner = applicationContext as DaggerComponentOwner, matrix = appBindings.matrix(), - sessionComponentsOwner = appBindings.sessionComponentsOwner(), rootPresenter = appBindings.rootPresenter() ) } diff --git a/app/src/main/java/io/element/android/x/di/AppBindings.kt b/app/src/main/java/io/element/android/x/di/AppBindings.kt index 28efc08ba8..8cebc3e838 100644 --- a/app/src/main/java/io/element/android/x/di/AppBindings.kt +++ b/app/src/main/java/io/element/android/x/di/AppBindings.kt @@ -18,7 +18,6 @@ package io.element.android.x.di import com.squareup.anvil.annotations.ContributesTo import io.element.android.x.matrix.Matrix -import io.element.android.x.matrix.ui.MatrixUi import io.element.android.x.root.RootPresenter import kotlinx.coroutines.CoroutineScope @@ -27,6 +26,4 @@ interface AppBindings { fun coroutineScope(): CoroutineScope fun rootPresenter(): RootPresenter fun matrix(): Matrix - fun matrixUi(): MatrixUi - fun sessionComponentsOwner(): SessionComponentsOwner } diff --git a/app/src/main/java/io/element/android/x/di/SessionComponent.kt b/app/src/main/java/io/element/android/x/di/SessionComponent.kt index 15fd19a9d8..93e19d082b 100644 --- a/app/src/main/java/io/element/android/x/di/SessionComponent.kt +++ b/app/src/main/java/io/element/android/x/di/SessionComponent.kt @@ -26,7 +26,7 @@ import io.element.android.x.matrix.MatrixClient @SingleIn(SessionScope::class) @MergeSubcomponent(SessionScope::class) -interface SessionComponent: DaggerMavericksBindings, NodeFactoriesBindings { +interface SessionComponent: NodeFactoriesBindings { fun matrixClient(): MatrixClient diff --git a/app/src/main/java/io/element/android/x/di/SessionComponentsOwner.kt b/app/src/main/java/io/element/android/x/di/SessionComponentsOwner.kt deleted file mode 100644 index 13b886e0a4..0000000000 --- a/app/src/main/java/io/element/android/x/di/SessionComponentsOwner.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.x.di - -import android.content.Context -import io.element.android.x.architecture.bindings -import io.element.android.x.matrix.MatrixClient -import io.element.android.x.matrix.core.SessionId -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() - var activeSessionComponent: SessionComponent? = null - private set - - fun setActive(sessionId: SessionId) { - val sessionComponent = sessionComponents[sessionId] - if (activeSessionComponent != sessionComponent) { - activeSessionComponent = sessionComponent - } - } - - fun create(matrixClient: MatrixClient) { - val sessionId = matrixClient.sessionId - val sessionComponent = - context.bindings().sessionComponentBuilder() - .client(matrixClient).build() - sessionComponents[sessionId] = sessionComponent - setActive(sessionId) - } - - fun releaseActiveSession() { - activeSessionComponent?.also { - release(it.matrixClient().sessionId) - } - } - - fun release(sessionId: SessionId) { - val sessionComponent = sessionComponents.remove(sessionId) - if (activeSessionComponent == sessionComponent) { - activeSessionComponent = null - } - } -} diff --git a/app/src/main/java/io/element/android/x/initializer/CoilInitializer.kt b/app/src/main/java/io/element/android/x/initializer/CoilInitializer.kt deleted file mode 100644 index 43d44ba867..0000000000 --- a/app/src/main/java/io/element/android/x/initializer/CoilInitializer.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.x.initializer - -import android.content.Context -import androidx.startup.Initializer -import coil.Coil -import coil.ImageLoader -import coil.ImageLoaderFactory -import io.element.android.x.architecture.bindings -import io.element.android.x.di.AppBindings - -class CoilInitializer : Initializer { - - override fun create(context: Context) { - Coil.setImageLoader(ElementImageLoaderFactory(context)) - } - - override fun dependencies(): List>> = emptyList() -} - -private class ElementImageLoaderFactory( - private val context: Context -) : ImageLoaderFactory { - override fun newImageLoader(): ImageLoader { - return ImageLoader - .Builder(context) - .components { - val appBindings = context.bindings() - val matrixUi = appBindings.matrixUi() - val matrixClientProvider = { - appBindings - .sessionComponentsOwner().activeSessionComponent?.matrixClient() - } - matrixUi.registerCoilComponents(this, matrixClientProvider) - } - .build() - } -} diff --git a/app/src/main/java/io/element/android/x/node/LoggedInFlowNode.kt b/app/src/main/java/io/element/android/x/node/LoggedInFlowNode.kt index ad8ad5743a..36e8416c16 100644 --- a/app/src/main/java/io/element/android/x/node/LoggedInFlowNode.kt +++ b/app/src/main/java/io/element/android/x/node/LoggedInFlowNode.kt @@ -3,22 +3,30 @@ package io.element.android.x.node import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import coil.Coil import com.bumble.appyx.core.composable.Children +import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push +import io.element.android.x.architecture.bindings import io.element.android.x.architecture.createNode +import io.element.android.x.core.di.DaggerComponentOwner +import io.element.android.x.di.SessionComponent import io.element.android.x.features.preferences.PreferencesFlowNode import io.element.android.x.features.roomlist.RoomListNode +import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.core.RoomId import io.element.android.x.matrix.core.SessionId +import io.element.android.x.matrix.ui.di.MatrixUIBindings import kotlinx.parcelize.Parcelize class LoggedInFlowNode( buildContext: BuildContext, val sessionId: SessionId, + private val matrixClient: MatrixClient, private val onOpenBugReport: () -> Unit, private val backstack: BackStack = BackStack( initialElement = NavTarget.RoomList, @@ -27,7 +35,26 @@ class LoggedInFlowNode( ) : ParentNode( navModel = backstack, buildContext = buildContext -) { +), DaggerComponentOwner { + + override val daggerComponent: Any by lazy { + parent!!.bindings().sessionComponentBuilder().client(matrixClient).build() + } + + override fun onBuilt() { + super.onBuilt() + lifecycle.subscribe( + onCreate = { + val imageLoaderFactory = bindings().loggedInImageLoaderFactory() + Coil.setImageLoader(imageLoaderFactory) + matrixClient.startSync() + }, + onDestroy = { + val imageLoaderFactory = bindings().notLoggedInImageLoaderFactory() + Coil.setImageLoader(imageLoaderFactory) + } + ) + } private val roomListCallback = object : RoomListNode.Callback { override fun onRoomClicked(roomId: RoomId) { diff --git a/app/src/main/java/io/element/android/x/node/RoomFlowNode.kt b/app/src/main/java/io/element/android/x/node/RoomFlowNode.kt index c7b5f21585..b3c4eebb62 100644 --- a/app/src/main/java/io/element/android/x/node/RoomFlowNode.kt +++ b/app/src/main/java/io/element/android/x/node/RoomFlowNode.kt @@ -27,6 +27,8 @@ class RoomFlowNode( buildContext = buildContext ) { + + init { lifecycle.subscribe( onCreate = { Timber.v("OnCreate") }, diff --git a/app/src/main/java/io/element/android/x/node/RootFlowNode.kt b/app/src/main/java/io/element/android/x/node/RootFlowNode.kt index 115d6fb4de..2ef4870b74 100644 --- a/app/src/main/java/io/element/android/x/node/RootFlowNode.kt +++ b/app/src/main/java/io/element/android/x/node/RootFlowNode.kt @@ -9,10 +9,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import com.bumble.appyx.core.children.whenChildAttached -import com.bumble.appyx.core.clienthelper.interactor.Interactor import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext @@ -25,13 +22,11 @@ import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import io.element.android.x.architecture.createNode import io.element.android.x.architecture.presenterConnector -import io.element.android.x.core.compose.OnLifecycleEvent import io.element.android.x.core.di.DaggerComponentOwner -import io.element.android.x.di.SessionComponentsOwner import io.element.android.x.features.rageshake.bugreport.BugReportNode import io.element.android.x.matrix.Matrix +import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.core.SessionId -import io.element.android.x.root.RootEvents import io.element.android.x.root.RootPresenter import io.element.android.x.root.RootView import kotlinx.coroutines.flow.distinctUntilChanged @@ -39,22 +34,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import timber.log.Timber - -class SessionComponentsOwnerInteractor(private val sessionComponentsOwner: SessionComponentsOwner) : Interactor() { - override fun onCreate(lifecycle: Lifecycle) { - lifecycle.subscribe(onCreate = { - whenChildAttached { commonLifecycle: Lifecycle, child: LoggedInFlowNode -> - Timber.v("LoggedInFlowNode attached: ${child.sessionId} ") - commonLifecycle.subscribe( - onDestroy = { - Timber.v("LoggedInFlowNode destroyed: ${child.sessionId}") - sessionComponentsOwner.release(child.sessionId) - } - ) - } - }) - } -} +import java.util.concurrent.ConcurrentHashMap class RootFlowNode( buildContext: BuildContext, @@ -64,20 +44,25 @@ class RootFlowNode( ), private val appComponentOwner: DaggerComponentOwner, private val matrix: Matrix, - private val sessionComponentsOwner: SessionComponentsOwner, rootPresenter: RootPresenter ) : ParentNode( navModel = backstack, buildContext = buildContext, - plugins = listOf(SessionComponentsOwnerInteractor(sessionComponentsOwner)), ), DaggerComponentOwner by appComponentOwner { + private val matrixClientsHolder = ConcurrentHashMap() private val presenterConnector = presenterConnector(rootPresenter) - init { + override fun onBuilt() { + super.onBuilt() + whenChildAttached(LoggedInFlowNode::class) { _, child -> + child.lifecycle.subscribe( + onDestroy = { matrixClientsHolder.remove(child.sessionId) } + ) + } matrix.isLoggedIn() .distinctUntilChanged() .onEach { isLoggedIn -> @@ -87,8 +72,7 @@ class RootFlowNode( if (matrixClient == null) { backstack.newRoot(NavTarget.NotLoggedInFlow) } else { - matrixClient.startSync() - sessionComponentsOwner.create(matrixClient) + matrixClientsHolder[matrixClient.sessionId] = matrixClient backstack.newRoot(NavTarget.LoggedInFlow(matrixClient.sessionId)) } } else { @@ -136,9 +120,12 @@ class RootFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { is NavTarget.LoggedInFlow -> { + val matrixClient = + matrixClientsHolder[navTarget.sessionId] ?: throw IllegalStateException("Makes sure to give a matrixClient with the given sessionId") LoggedInFlowNode( buildContext = buildContext, sessionId = navTarget.sessionId, + matrixClient = matrixClient, onOpenBugReport = this::onOpenBugReport ) } diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/Matrix.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/Matrix.kt index 6d6f49eb40..cd14368cb6 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/Matrix.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/Matrix.kt @@ -57,6 +57,10 @@ class Matrix @Inject constructor( return sessionStore.isLoggedIn() } + suspend fun getLatestSessionId(): SessionId? = withContext(coroutineDispatchers.io){ + sessionStore.getLatestSession()?.sessionId() + } + suspend fun restoreSession() = withContext(coroutineDispatchers.io) { sessionStore.getLatestSession() ?.let { session -> diff --git a/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/MatrixUi.kt b/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/MatrixUi.kt deleted file mode 100644 index db4de72d7d..0000000000 --- a/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/MatrixUi.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.x.matrix.ui - -import coil.ComponentRegistry -import io.element.android.x.matrix.MatrixClient -import io.element.android.x.matrix.ui.media.MediaFetcher -import io.element.android.x.matrix.ui.media.MediaKeyer -import javax.inject.Inject - -class MatrixUi @Inject constructor() { - - fun registerCoilComponents( - builder: ComponentRegistry.Builder, - activeClientProvider: () -> MatrixClient? - ) { - builder.add(MediaKeyer()) - builder.add(MediaFetcher.Factory(activeClientProvider)) - } -} diff --git a/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/di/MatrixUIBindings.kt b/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/di/MatrixUIBindings.kt new file mode 100644 index 0000000000..4fd7e1aa35 --- /dev/null +++ b/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/di/MatrixUIBindings.kt @@ -0,0 +1,12 @@ +package io.element.android.x.matrix.ui.di + +import com.squareup.anvil.annotations.ContributesTo +import io.element.android.x.di.SessionScope +import io.element.android.x.matrix.ui.media.LoggedInImageLoaderFactory +import io.element.android.x.matrix.ui.media.NotLoggedInImageLoaderFactory + +@ContributesTo(SessionScope::class) +interface MatrixUIBindings { + fun loggedInImageLoaderFactory(): LoggedInImageLoaderFactory + fun notLoggedInImageLoaderFactory(): NotLoggedInImageLoaderFactory +} diff --git a/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/media/ImageLoaderFactories.kt b/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/media/ImageLoaderFactories.kt new file mode 100644 index 0000000000..b014d4cc02 --- /dev/null +++ b/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/media/ImageLoaderFactories.kt @@ -0,0 +1,34 @@ +package io.element.android.x.matrix.ui.media + +import android.content.Context +import coil.ImageLoader +import coil.ImageLoaderFactory +import io.element.android.x.di.ApplicationContext +import io.element.android.x.matrix.MatrixClient +import javax.inject.Inject + +class LoggedInImageLoaderFactory @Inject constructor( + @ApplicationContext private val context: Context, + private val matrixClient: MatrixClient, +) : ImageLoaderFactory { + override fun newImageLoader(): ImageLoader { + return ImageLoader + .Builder(context) + .components { + add(MediaKeyer()) + add(MediaFetcher.Factory(matrixClient)) + } + .build() + } +} + +class NotLoggedInImageLoaderFactory @Inject constructor( + @ApplicationContext private val context: Context, +) : ImageLoaderFactory { + override fun newImageLoader(): ImageLoader { + return ImageLoader + .Builder(context) + .build() + } +} + diff --git a/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/media/MediaFetcher.kt b/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/media/MediaFetcher.kt index d345f26984..a443f7486d 100644 --- a/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/media/MediaFetcher.kt +++ b/libraries/matrixui/src/main/java/io/element/android/x/matrix/ui/media/MediaFetcher.kt @@ -37,16 +37,15 @@ internal class MediaFetcher( return imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch() } - class Factory(private val activeClientProvider: () -> MatrixClient?) : + class Factory(private val client: MatrixClient) : Fetcher.Factory { override fun create( data: MediaResolver.Meta, options: Options, imageLoader: ImageLoader ): Fetcher { - val activeClient = activeClientProvider() return MediaFetcher( - mediaResolver = activeClient?.mediaResolver(), + mediaResolver = client.mediaResolver(), meta = data, options = options, imageLoader = imageLoader