Create SyncOrchestrator (#4176)
* Create `SyncOrchestrator` to centralise the sync start/stop flow through the whole app: the decision is based on several inputs: sync state, network available, app in foreground, app in call, app needing to sync an event for a notification. * Make network monitor return network connectivity status, not internet connectivity * Don't stop the `SyncService` when network connection is lost, let it fail instead. This prevents an issue when using the offline mode of the SDK, which made the wrong UI states to be shown when the `SyncState` is `Idle` (that is, after the service being manually stopped). * Rename `NetworkStatus.Online/Offline` to `Connected/Disconnected` so they're not easily mistaken with internet connectivity instead
This commit is contained in:
parent
ce1c01e626
commit
3c87fb05b2
44 changed files with 851 additions and 344 deletions
|
|
@ -8,9 +8,9 @@
|
|||
package io.element.android.features.analytics.impl
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -34,7 +34,7 @@ class AnalyticsOptInNode @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
val isDark = ElementTheme.isLightTheme.not()
|
||||
val state = presenter.present()
|
||||
AnalyticsOptInView(
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ dependencies {
|
|||
implementation(projects.libraries.push.api)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.services.appnavstate.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
implementation(libs.androidx.webkit)
|
||||
implementation(libs.coil.compose)
|
||||
|
|
@ -59,6 +60,7 @@ dependencies {
|
|||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import io.element.android.libraries.matrix.api.sync.SyncState
|
|||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
import io.element.android.services.analytics.api.ScreenTracker
|
||||
import io.element.android.services.appnavstate.api.AppForegroundStateService
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
@ -58,9 +59,9 @@ class CallScreenPresenter @AssistedInject constructor(
|
|||
private val dispatchers: CoroutineDispatchers,
|
||||
private val matrixClientsProvider: MatrixClientProvider,
|
||||
private val screenTracker: ScreenTracker,
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
private val activeCallManager: ActiveCallManager,
|
||||
private val languageTagProvider: LanguageTagProvider,
|
||||
private val appForegroundStateService: AppForegroundStateService,
|
||||
) : Presenter<CallScreenState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
|
@ -226,19 +227,13 @@ class CallScreenPresenter @AssistedInject constructor(
|
|||
if (state == SyncState.Running) {
|
||||
client.notifyCallStartIfNeeded(callType.roomId)
|
||||
} else {
|
||||
client.syncService().startSync()
|
||||
appForegroundStateService.updateIsInCallState(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
onDispose {
|
||||
// We can't use the local coroutine scope here because it will be disposed before this effect
|
||||
appCoroutineScope.launch {
|
||||
client.syncService().run {
|
||||
if (syncState.value == SyncState.Running) {
|
||||
stopSync()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Make sure we mark the call as ended in the app state
|
||||
appForegroundStateService.updateIsInCallState(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,10 +32,9 @@ import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
|||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
import io.element.android.services.analytics.api.ScreenTracker
|
||||
import io.element.android.services.analytics.test.FakeScreenTracker
|
||||
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilTimeout
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
|
|
@ -243,7 +242,7 @@ class CallScreenPresenterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `present - automatically starts the Matrix client sync when on RoomCall`() = runTest {
|
||||
fun `present - automatically sets the isInCall state when starting the call and disposing the screen`() = runTest {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val startSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
|
|
@ -251,6 +250,7 @@ class CallScreenPresenterTest {
|
|||
this.startSyncLambda = startSyncLambda
|
||||
}
|
||||
val matrixClient = FakeMatrixClient(syncService = syncService)
|
||||
val appForegroundStateService = FakeAppForegroundStateService()
|
||||
val presenter = createCallScreenPresenter(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
widgetDriver = widgetDriver,
|
||||
|
|
@ -258,34 +258,7 @@ class CallScreenPresenterTest {
|
|||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) }),
|
||||
screenTracker = FakeScreenTracker {},
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
consumeItemsUntilTimeout()
|
||||
|
||||
assert(startSyncLambda).isCalledOnce()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - automatically stops the Matrix client sync on dispose`() = runTest {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val stopSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val syncService = FakeSyncService(SyncState.Running).apply {
|
||||
this.stopSyncLambda = stopSyncLambda
|
||||
}
|
||||
val matrixClient = FakeMatrixClient(syncService = syncService)
|
||||
val presenter = createCallScreenPresenter(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
widgetDriver = widgetDriver,
|
||||
navigator = navigator,
|
||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) }),
|
||||
screenTracker = FakeScreenTracker {},
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
val hasRun = Mutex(true)
|
||||
val job = launch {
|
||||
|
|
@ -296,11 +269,25 @@ class CallScreenPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
hasRun.lock()
|
||||
appForegroundStateService.isInCall.test {
|
||||
// The initial isInCall state will always be false
|
||||
assertThat(awaitItem()).isFalse()
|
||||
|
||||
job.cancelAndJoin()
|
||||
// Wait until the call starts
|
||||
hasRun.lock()
|
||||
|
||||
assert(stopSyncLambda).isCalledOnce()
|
||||
// Then it'll be true once the call is active
|
||||
assertThat(awaitItem()).isTrue()
|
||||
|
||||
// If we dispose the screen
|
||||
job.cancelAndJoin()
|
||||
|
||||
// The isInCall state is now false
|
||||
assertThat(awaitItem()).isFalse()
|
||||
|
||||
// And there are no more events
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -354,6 +341,7 @@ class CallScreenPresenterTest {
|
|||
matrixClientsProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
||||
activeCallManager: FakeActiveCallManager = FakeActiveCallManager(),
|
||||
screenTracker: ScreenTracker = FakeScreenTracker(),
|
||||
appForegroundStateService: FakeAppForegroundStateService = FakeAppForegroundStateService(),
|
||||
): CallScreenPresenter {
|
||||
val userAgentProvider = object : UserAgentProvider {
|
||||
override fun provide(): String {
|
||||
|
|
@ -369,10 +357,10 @@ class CallScreenPresenterTest {
|
|||
clock = clock,
|
||||
dispatchers = dispatchers,
|
||||
matrixClientsProvider = matrixClientsProvider,
|
||||
appCoroutineScope = this,
|
||||
activeCallManager = activeCallManager,
|
||||
screenTracker = screenTracker,
|
||||
languageTagProvider = FakeLanguageTagProvider("en-US"),
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
package io.element.android.features.createroom.impl.root
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
|
|
@ -55,7 +55,7 @@ class CreateRoomRootNode @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
CreateRoomRootView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class DefaultLockScreenService @Inject constructor(
|
|||
*/
|
||||
private fun observeAppForegroundState() {
|
||||
coroutineScope.launch {
|
||||
appForegroundStateService.start()
|
||||
appForegroundStateService.startObservingForeground()
|
||||
appForegroundStateService.isInForeground.collect { isInForeground ->
|
||||
if (isInForeground) {
|
||||
lockJob?.cancel()
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@
|
|||
|
||||
package io.element.android.features.lockscreen.impl.unlock
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -42,7 +41,7 @@ class PinUnlockNode @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
val isDark = ElementTheme.isLightTheme.not()
|
||||
LaunchedEffect(state.isUnlocked) {
|
||||
if (state.isUnlocked) {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ package io.element.android.features.login.impl
|
|||
|
||||
import android.app.Activity
|
||||
import android.os.Parcelable
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
|
|
@ -199,7 +199,7 @@ class LoginFlowNode @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
activity = LocalContext.current as? Activity
|
||||
activity = requireNotNull(LocalActivity.current)
|
||||
darkTheme = !ElementTheme.isLightTheme
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
package io.element.android.features.login.impl.screens.createaccount
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -41,7 +41,7 @@ class CreateAccountNode @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
val isDark = ElementTheme.isLightTheme.not()
|
||||
val state = presenter.present()
|
||||
CreateAccountView(
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@
|
|||
|
||||
package io.element.android.features.logout.impl
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -42,7 +41,7 @@ class LogoutNode @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
val isDark = ElementTheme.isLightTheme.not()
|
||||
LogoutView(
|
||||
state = state,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package io.element.android.features.messages.impl
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -17,7 +18,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
|
|
@ -223,7 +223,7 @@ class MessagesNode @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
val isDark = ElementTheme.isLightTheme.not()
|
||||
CompositionLocalProvider(
|
||||
LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.messages.impl.pinned
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
|
|
@ -26,6 +27,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@SingleIn(RoomScope::class)
|
||||
|
|
@ -33,6 +35,7 @@ class PinnedEventsTimelineProvider @Inject constructor(
|
|||
private val room: MatrixRoom,
|
||||
private val syncService: SyncService,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : TimelineProvider {
|
||||
private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> =
|
||||
MutableStateFlow(AsyncData.Uninitialized)
|
||||
|
|
@ -100,7 +103,9 @@ class PinnedEventsTimelineProvider @Inject constructor(
|
|||
when (timelineStateFlow.value) {
|
||||
is AsyncData.Uninitialized, is AsyncData.Failure -> {
|
||||
timelineStateFlow.emit(AsyncData.Loading())
|
||||
room.pinnedEventsTimeline()
|
||||
withContext(dispatchers.io) {
|
||||
room.pinnedEventsTimeline()
|
||||
}
|
||||
.fold(
|
||||
{ timelineStateFlow.emit(AsyncData.Success(it)) },
|
||||
{ timelineStateFlow.emit(AsyncData.Failure(it)) }
|
||||
|
|
|
|||
|
|
@ -194,7 +194,8 @@ class PinnedMessagesBannerPresenterTest {
|
|||
syncService = syncService,
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled)
|
||||
)
|
||||
),
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
)
|
||||
timelineProvider.launchIn(backgroundScope)
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import io.element.android.tests.testutils.lambda.assert
|
|||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
|
|
@ -302,7 +303,8 @@ class PinnedMessagesListPresenterTest {
|
|||
syncService = syncService,
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled)
|
||||
)
|
||||
),
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
)
|
||||
timelineProvider.launchIn(backgroundScope)
|
||||
return PinnedMessagesListPresenter(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,14 @@ package io.element.android.features.networkmonitor.api
|
|||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Monitors the network status of the device, providing the current network connectivity status as a flow.
|
||||
*
|
||||
* **Note:** network connectivity does not imply internet connectivity. The device can be connected to a network that can't reach the homeserver.
|
||||
*/
|
||||
interface NetworkMonitor {
|
||||
/**
|
||||
* A flow containing the current network connectivity status.
|
||||
*/
|
||||
val connectivity: StateFlow<NetworkStatus>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,19 @@
|
|||
|
||||
package io.element.android.features.networkmonitor.api
|
||||
|
||||
/**
|
||||
* Network connectivity status of the device.
|
||||
*
|
||||
* **Note:** this is *network* connectivity status, not *internet* connectivity status.
|
||||
*/
|
||||
enum class NetworkStatus {
|
||||
Online,
|
||||
Offline
|
||||
/**
|
||||
* The device is connected to a network.
|
||||
*/
|
||||
Connected,
|
||||
|
||||
/**
|
||||
* The device is not connected to any networks.
|
||||
*/
|
||||
Disconnected
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ package io.element.android.features.networkmonitor.impl
|
|||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
|
|
@ -55,20 +54,18 @@ class DefaultNetworkMonitor @Inject constructor(
|
|||
|
||||
override fun onLost(network: Network) {
|
||||
if (activeNetworksCount.decrementAndGet() == 0) {
|
||||
trySendBlocking(NetworkStatus.Offline)
|
||||
trySendBlocking(NetworkStatus.Disconnected)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAvailable(network: Network) {
|
||||
if (activeNetworksCount.incrementAndGet() > 0) {
|
||||
trySendBlocking(NetworkStatus.Online)
|
||||
trySendBlocking(NetworkStatus.Connected)
|
||||
}
|
||||
}
|
||||
}
|
||||
trySendBlocking(connectivityManager.activeNetworkStatus())
|
||||
val request = NetworkRequest.Builder()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
.build()
|
||||
val request = NetworkRequest.Builder().build()
|
||||
|
||||
connectivityManager.registerNetworkCallback(request, callback)
|
||||
Timber.d("Subscribe")
|
||||
|
|
@ -85,17 +82,6 @@ class DefaultNetworkMonitor @Inject constructor(
|
|||
.stateIn(appCoroutineScope, SharingStarted.WhileSubscribed(), connectivityManager.activeNetworkStatus())
|
||||
|
||||
private fun ConnectivityManager.activeNetworkStatus(): NetworkStatus {
|
||||
return activeNetwork?.let {
|
||||
getNetworkCapabilities(it)?.getNetworkStatus()
|
||||
} ?: NetworkStatus.Offline
|
||||
}
|
||||
|
||||
private fun NetworkCapabilities.getNetworkStatus(): NetworkStatus {
|
||||
val hasInternet = hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
return if (hasInternet) {
|
||||
NetworkStatus.Online
|
||||
} else {
|
||||
NetworkStatus.Offline
|
||||
}
|
||||
return if (activeNetwork != null) NetworkStatus.Connected else NetworkStatus.Disconnected
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor
|
|||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeNetworkMonitor(initialStatus: NetworkStatus = NetworkStatus.Online) : NetworkMonitor {
|
||||
class FakeNetworkMonitor(initialStatus: NetworkStatus = NetworkStatus.Connected) : NetworkMonitor {
|
||||
override val connectivity = MutableStateFlow(initialStatus)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
package io.element.android.features.preferences.impl.about
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -41,7 +41,7 @@ class AboutNode @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
val isDark = ElementTheme.isLightTheme.not()
|
||||
val state = presenter.present()
|
||||
AboutView(
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@
|
|||
|
||||
package io.element.android.features.preferences.impl.developer
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.airbnb.android.showkase.models.Showkase
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
|
|
@ -29,7 +28,7 @@ class DeveloperSettingsNode @AssistedInject constructor(
|
|||
) : Node(buildContext, plugins = plugins) {
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
fun openShowkase() {
|
||||
val intent = Showkase.getBrowserIntent(activity)
|
||||
activity.startActivity(intent)
|
||||
|
|
|
|||
|
|
@ -79,10 +79,10 @@ class DeveloperSettingsPresenter @Inject constructor(
|
|||
.doesHideImagesAndVideosFlow()
|
||||
.collectAsState(initial = false)
|
||||
|
||||
val tracingLogLevel by appPreferencesStore
|
||||
.getTracingLogLevelFlow()
|
||||
.map { AsyncData.Success(it.toLogLevelItem()) }
|
||||
.collectAsState(initial = AsyncData.Uninitialized)
|
||||
val tracingLogLevelFlow = remember {
|
||||
appPreferencesStore.getTracingLogLevelFlow().map { AsyncData.Success(it.toLogLevelItem()) }
|
||||
}
|
||||
val tracingLogLevel by tracingLogLevelFlow.collectAsState(initial = AsyncData.Uninitialized)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
FeatureFlags.entries
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
package io.element.android.features.preferences.impl.root
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -113,7 +113,7 @@ class PreferencesRootNode @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
val isDark = ElementTheme.isLightTheme.not()
|
||||
PreferencesRootView(
|
||||
state = state,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@
|
|||
|
||||
package io.element.android.features.rageshake.impl.bugreport
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -38,7 +37,7 @@ class BugReportNode @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = LocalContext.current as? Activity
|
||||
val activity = LocalActivity.current
|
||||
BugReportView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
package io.element.android.features.roomlist.impl
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
|
|
@ -92,7 +92,7 @@ class RoomListNode @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
|
||||
RoomListView(
|
||||
state = state,
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ package io.element.android.features.securebackup.impl.reset
|
|||
|
||||
import android.app.Activity
|
||||
import android.os.Parcelable
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
|
|
@ -160,7 +160,7 @@ class ResetIdentityFlowNode @AssistedInject constructor(
|
|||
override fun View(modifier: Modifier) {
|
||||
// Workaround to get the current activity
|
||||
if (!this::activity.isInitialized) {
|
||||
activity = LocalContext.current as Activity
|
||||
activity = requireNotNull(LocalActivity.current)
|
||||
}
|
||||
|
||||
val startResetState by resetIdentityFlowManager.currentHandleFlow.collectAsState()
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
package io.element.android.features.verifysession.impl.outgoing
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -45,7 +45,7 @@ class VerifySelfSessionNode @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = LocalContext.current as Activity
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
val isDark = ElementTheme.isLightTheme.not()
|
||||
VerifySelfSessionView(
|
||||
state = state,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue