diff --git a/app/build.gradle b/app/build.gradle index 574f6baeb6..72f3bebf8a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -66,6 +66,7 @@ android { dependencies { implementation project(":libraries:designsystem") implementation project(":libraries:matrix") + implementation project(":libraries:core") implementation project(":features:onboarding") implementation project(":features:login") implementation project(":features:roomlist") 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 99937203d1..16d0bb0752 100644 --- a/app/src/main/java/io/element/android/x/MainActivity.kt +++ b/app/src/main/java/io/element/android/x/MainActivity.kt @@ -11,12 +11,14 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.lifecycle.Lifecycle import com.ramcosta.composedestinations.DestinationsNavHost import com.ramcosta.composedestinations.rememberNavHostEngine +import io.element.android.x.core.compose.OnLifecycleEvent import io.element.android.x.designsystem.ElementXTheme -import io.element.android.x.destinations.LoginScreenNavigationDestination import io.element.android.x.destinations.OnBoardingScreenNavigationDestination import kotlinx.coroutines.runBlocking +import timber.log.Timber class MainActivity : ComponentActivity() { @@ -52,4 +54,8 @@ private fun MainScreen(viewModel: MainViewModel) { navGraph = NavGraphs.root, startRoute = startRoute ) + + OnLifecycleEvent { _, event -> + Timber.v("OnLifecycleEvent: $event") + } } diff --git a/app/src/main/java/io/element/android/x/MainViewModel.kt b/app/src/main/java/io/element/android/x/MainViewModel.kt index d0f8bb68a1..2cdc09989c 100644 --- a/app/src/main/java/io/element/android/x/MainViewModel.kt +++ b/app/src/main/java/io/element/android/x/MainViewModel.kt @@ -1,8 +1,10 @@ package io.element.android.x import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import io.element.android.x.matrix.MatrixInstance import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch class MainViewModel : ViewModel() { private val matrix = MatrixInstance.getInstance() @@ -11,7 +13,22 @@ class MainViewModel : ViewModel() { return matrix.isLoggedIn().first() } + fun startSyncIfLogged(){ + viewModelScope.launch { + if(!isLoggedIn()) return@launch + matrix.activeClient().startSync() + } + } + + fun stopSyncIfLogged(){ + viewModelScope.launch { + if (!isLoggedIn()) return@launch + matrix.activeClient().stopSync() + } + } + suspend fun restoreSession() { matrix.restoreSession() + matrix.activeClient().startSync() } } \ No newline at end of file diff --git a/features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt b/features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt index f286942c13..89d07bfda7 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt +++ b/features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt @@ -22,7 +22,7 @@ class LoginViewModel(initialState: LoginViewState) : viewModelScope.launch { suspend { matrix.login(state.homeserver, state.login, state.password) - Unit + matrix.activeClient().startSync() }.execute { copy(isLoggedIn = it) } diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt index c62ec09528..73d4183a2e 100644 --- a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt +++ b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel -import io.element.android.x.core.data.LogCompositions +import io.element.android.x.core.compose.LogCompositions import io.element.android.x.core.data.StableCharSequence import io.element.android.x.designsystem.components.avatar.AvatarData import io.element.android.x.features.messages.components.* diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt index 9c92649be1..554030f26b 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt @@ -18,7 +18,7 @@ import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel -import io.element.android.x.core.data.LogCompositions +import io.element.android.x.core.compose.LogCompositions import io.element.android.x.designsystem.ElementXTheme import io.element.android.x.designsystem.components.ProgressDialog import io.element.android.x.designsystem.components.avatar.AvatarData diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt index 66c3ec73c0..afcbfc170f 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt @@ -42,7 +42,6 @@ class RoomListViewModel(initialState: RoomListViewState) : private fun handleInit() { viewModelScope.launch { val client = getClient() - client.startSync() suspend { val userAvatarUrl = client.loadUserAvatarURLString().getOrNull() val userDisplayName = client.loadUserDisplayName().getOrNull() diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt index 70156f8220..4a8acad1f7 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomListTopBar.kt @@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.text.font.FontWeight -import io.element.android.x.core.data.LogCompositions +import io.element.android.x.core.compose.LogCompositions import io.element.android.x.features.roomlist.model.MatrixUser @Composable diff --git a/libraries/core/src/main/java/io/element/android/x/core/data/LogCompositions.kt b/libraries/core/src/main/java/io/element/android/x/core/compose/LogCompositions.kt similarity index 93% rename from libraries/core/src/main/java/io/element/android/x/core/data/LogCompositions.kt rename to libraries/core/src/main/java/io/element/android/x/core/compose/LogCompositions.kt index c0540cc30c..7ab910b135 100644 --- a/libraries/core/src/main/java/io/element/android/x/core/data/LogCompositions.kt +++ b/libraries/core/src/main/java/io/element/android/x/core/compose/LogCompositions.kt @@ -1,4 +1,4 @@ -package io.element.android.x.core.data +package io.element.android.x.core.compose import android.util.Log import androidx.compose.runtime.Composable diff --git a/libraries/core/src/main/java/io/element/android/x/core/compose/OnLifecycleEvent.kt b/libraries/core/src/main/java/io/element/android/x/core/compose/OnLifecycleEvent.kt new file mode 100644 index 0000000000..310e85a061 --- /dev/null +++ b/libraries/core/src/main/java/io/element/android/x/core/compose/OnLifecycleEvent.kt @@ -0,0 +1,27 @@ +package io.element.android.x.core.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner + +@Composable +fun OnLifecycleEvent(onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) -> Unit) { + val eventHandler = rememberUpdatedState(onEvent) + val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current) + + DisposableEffect(lifecycleOwner.value) { + val lifecycle = lifecycleOwner.value.lifecycle + val observer = LifecycleEventObserver { owner, event -> + eventHandler.value(owner, event) + } + + lifecycle.addObserver(observer) + onDispose { + lifecycle.removeObserver(observer) + } + } +} \ No newline at end of file diff --git a/libraries/core/src/main/java/io/element/android/x/core/data/CoroutineDispatchers.kt b/libraries/core/src/main/java/io/element/android/x/core/coroutine/CoroutineDispatchers.kt similarity index 81% rename from libraries/core/src/main/java/io/element/android/x/core/data/CoroutineDispatchers.kt rename to libraries/core/src/main/java/io/element/android/x/core/coroutine/CoroutineDispatchers.kt index cea96b5e43..ba0a6a3a2f 100644 --- a/libraries/core/src/main/java/io/element/android/x/core/data/CoroutineDispatchers.kt +++ b/libraries/core/src/main/java/io/element/android/x/core/coroutine/CoroutineDispatchers.kt @@ -1,4 +1,4 @@ -package io.element.android.x.core.data +package io.element.android.x.core.coroutine import kotlinx.coroutines.CoroutineDispatcher diff --git a/libraries/core/src/main/java/io/element/android/x/core/data/flow/TimingOperators.kt b/libraries/core/src/main/java/io/element/android/x/core/coroutine/TimingOperators.kt similarity index 100% rename from libraries/core/src/main/java/io/element/android/x/core/data/flow/TimingOperators.kt rename to libraries/core/src/main/java/io/element/android/x/core/coroutine/TimingOperators.kt diff --git a/libraries/core/src/main/java/io/element/android/x/core/data/pmap.kt b/libraries/core/src/main/java/io/element/android/x/core/coroutine/pmap.kt similarity index 100% rename from libraries/core/src/main/java/io/element/android/x/core/data/pmap.kt rename to libraries/core/src/main/java/io/element/android/x/core/coroutine/pmap.kt 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 45521fc372..489df2e2ff 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 @@ -1,7 +1,7 @@ package io.element.android.x.matrix import android.content.Context -import io.element.android.x.core.data.CoroutineDispatchers +import io.element.android.x.core.coroutine.CoroutineDispatchers import io.element.android.x.matrix.session.SessionStore import io.element.android.x.matrix.util.logError import kotlinx.coroutines.CoroutineScope diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt index 8e68aa4f69..24eca22cf4 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt @@ -1,6 +1,6 @@ package io.element.android.x.matrix -import io.element.android.x.core.data.CoroutineDispatchers +import io.element.android.x.core.coroutine.CoroutineDispatchers import io.element.android.x.matrix.core.UserId import io.element.android.x.matrix.media.MediaResolver import io.element.android.x.matrix.media.RustMediaResolver @@ -14,6 +14,7 @@ import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.* import timber.log.Timber import java.io.Closeable +import java.util.concurrent.atomic.AtomicBoolean class MatrixClient internal constructor( private val client: Client, @@ -67,6 +68,7 @@ class MatrixClient internal constructor( private var slidingSyncObserverToken: StoppableSpawn? = null private val mediaResolver = RustMediaResolver(this) + private val isSyncing = AtomicBoolean(false) init { client.setDelegate(clientDelegate) @@ -84,15 +86,19 @@ class MatrixClient internal constructor( } fun startSync() { - roomSummaryDataSource.startSync() - slidingSync.setObserver(slidingSyncObserverProxy) - slidingSyncObserverToken = slidingSync.sync() + if (isSyncing.compareAndSet(false, true)) { + roomSummaryDataSource.startSync() + slidingSync.setObserver(slidingSyncObserverProxy) + slidingSyncObserverToken = slidingSync.sync() + } } fun stopSync() { - roomSummaryDataSource.stopSync() - slidingSync.setObserver(null) - slidingSyncObserverToken?.cancel() + if (isSyncing.compareAndSet(true, false)) { + roomSummaryDataSource.stopSync() + slidingSync.setObserver(null) + slidingSyncObserverToken?.cancel() + } } fun roomSummaryDataSource(): RoomSummaryDataSource = roomSummaryDataSource diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt index 21db524861..86a845a52c 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt @@ -1,6 +1,6 @@ package io.element.android.x.matrix.room -import io.element.android.x.core.data.CoroutineDispatchers +import io.element.android.x.core.coroutine.CoroutineDispatchers import io.element.android.x.matrix.core.RoomId import io.element.android.x.matrix.core.UserId import io.element.android.x.matrix.timeline.MatrixTimeline diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt index 9191c9673d..e58f6d84b8 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt @@ -1,19 +1,16 @@ package io.element.android.x.matrix.room -import io.element.android.x.core.data.CoroutineDispatchers +import io.element.android.x.core.coroutine.CoroutineDispatchers import io.element.android.x.core.data.flow.chunk -import io.element.android.x.matrix.room.message.RoomMessageFactory import io.element.android.x.matrix.sync.roomListDiff import io.element.android.x.matrix.sync.state -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel -import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import org.matrix.rustcomponents.sdk.* import timber.log.Timber import java.io.Closeable import java.util.* +import java.util.concurrent.Executors interface RoomSummaryDataSource { fun roomSummaries(): Flow> @@ -27,33 +24,43 @@ internal class RustRoomSummaryDataSource( private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory() ) : RoomSummaryDataSource, Closeable { - private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io) + private val singleDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + + private val coroutineScope = CoroutineScope(SupervisorJob() + singleDispatcher) private val roomSummaries = MutableStateFlow>(emptyList()) private val state = MutableStateFlow(SlidingSyncState.COLD) + fun startSync() { + coroutineScope.launch { + updateRoomSummaries { + clear() + addAll( + slidingSyncView.currentRoomsList().map(::buildSummaryForRoomListEntry) + ) + } - fun startSync(){ - slidingSyncView.roomListDiff() - .chunk(100) - .onEach { diffs -> - updateRoomSummaries { - diffs.forEach { - applyDiff(it) + slidingSyncView.roomListDiff() + .chunk(30) + .onEach { diffs -> + updateRoomSummaries { + diffs.forEach { + applyDiff(it) + } } - } - }.launchIn(coroutineScope) + }.collect() - slidingSyncView.state() - .onEach { slidingSyncState -> - Timber.v("New sliding sync state: $slidingSyncState") - state.value = slidingSyncState - }.launchIn(coroutineScope) + slidingSyncView.state() + .onEach { slidingSyncState -> + Timber.v("New sliding sync state: $slidingSyncState") + state.value = slidingSyncState + }.collect() - slidingSyncUpdateFlow - .onEach { - didReceiveSyncUpdate(it) - }.launchIn(coroutineScope) + slidingSyncUpdateFlow + .onEach { + didReceiveSyncUpdate(it) + }.collect() + } } fun stopSync() { diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/timeline/MatrixTimeline.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/timeline/MatrixTimeline.kt index 94bf358beb..ad3207f131 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/timeline/MatrixTimeline.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/timeline/MatrixTimeline.kt @@ -1,6 +1,6 @@ package io.element.android.x.matrix.timeline -import io.element.android.x.core.data.CoroutineDispatchers +import io.element.android.x.core.coroutine.CoroutineDispatchers import io.element.android.x.core.data.flow.chunk import io.element.android.x.matrix.core.EventId import io.element.android.x.matrix.room.MatrixRoom