diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 931c995d9a..660980d538 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -138,6 +138,10 @@
android:resource="@xml/file_providers" />
+
+
diff --git a/app/src/main/kotlin/io/element/android/x/MainActivity.kt b/app/src/main/kotlin/io/element/android/x/MainActivity.kt
index c11e63a164..7d2f404560 100644
--- a/app/src/main/kotlin/io/element/android/x/MainActivity.kt
+++ b/app/src/main/kotlin/io/element/android/x/MainActivity.kt
@@ -32,6 +32,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import com.bumble.appyx.core.integration.NodeHost
import com.bumble.appyx.core.integrationpoint.NodeActivity
import com.bumble.appyx.core.plugin.NodeReadyObserver
@@ -39,13 +42,16 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.theme.Theme
import io.element.android.compound.theme.isDark
import io.element.android.compound.theme.mapToTheme
+import io.element.android.features.lockscreen.api.LockScreenLockState
+import io.element.android.features.lockscreen.api.LockScreenService
import io.element.android.features.lockscreen.api.handleSecureFlag
-import io.element.android.features.lockscreen.api.isLocked
+import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher
import io.element.android.x.di.AppBindings
import io.element.android.x.intent.SafeUriHandler
+import kotlinx.coroutines.launch
import timber.log.Timber
private val loggerTag = LoggerTag("MainActivity")
@@ -59,27 +65,13 @@ class MainActivity : NodeActivity() {
installSplashScreen()
super.onCreate(savedInstanceState)
appBindings = bindings()
- appBindings.lockScreenService().handleSecureFlag(this)
+ setupLockManagement(appBindings.lockScreenService())
enableEdgeToEdge()
setContent {
MainContent(appBindings)
}
}
- @Deprecated("")
- override fun onBackPressed() {
- // If the app is locked, we need to intercept onBackPressed before it goes to OnBackPressedDispatcher.
- // Indeed, otherwise we would need to trick Appyx backstack management everywhere.
- // Without this trick, we would get pop operations on the hidden backstack.
- if (appBindings.lockScreenService().isLocked) {
- // Do not kill the app in this case, just go to background.
- moveTaskToBack(false)
- } else {
- @Suppress("DEPRECATION")
- super.onBackPressed()
- }
- }
-
@Composable
private fun MainContent(appBindings: AppBindings) {
val theme by remember {
@@ -96,8 +88,8 @@ class MainActivity : NodeActivity() {
) {
Box(
modifier = Modifier
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.background),
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.background),
) {
if (migrationState.migrationAction.isSuccess()) {
MainNodeHost()
@@ -131,6 +123,19 @@ class MainActivity : NodeActivity() {
}
}
+ private fun setupLockManagement(lockScreenService: LockScreenService) {
+ lockScreenService.handleSecureFlag(this)
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ lockScreenService.lockState.collect { state ->
+ if (state == LockScreenLockState.Locked) {
+ startActivity(PinUnlockActivity.newIntent(this@MainActivity))
+ }
+ }
+ }
+ }
+ }
+
/**
* Called when:
* - the launcher icon is clicked (if the app is already running);
diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt
index df86219cd1..bd68a9151e 100644
--- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt
+++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt
@@ -47,9 +47,6 @@ import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.features.ftue.api.FtueEntryPoint
import io.element.android.features.ftue.api.state.FtueService
import io.element.android.features.ftue.api.state.FtueState
-import io.element.android.features.lockscreen.api.LockScreenEntryPoint
-import io.element.android.features.lockscreen.api.LockScreenLockState
-import io.element.android.features.lockscreen.api.LockScreenService
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.preferences.api.PreferencesEntryPoint
@@ -100,8 +97,6 @@ class LoggedInFlowNode @AssistedInject constructor(
private val coroutineScope: CoroutineScope,
private val networkMonitor: NetworkMonitor,
private val ftueService: FtueService,
- private val lockScreenEntryPoint: LockScreenEntryPoint,
- private val lockScreenStateService: LockScreenService,
private val roomDirectoryEntryPoint: RoomDirectoryEntryPoint,
private val matrixClient: MatrixClient,
snackbarDispatcher: SnackbarDispatcher,
@@ -111,7 +106,7 @@ class LoggedInFlowNode @AssistedInject constructor(
savedStateMap = buildContext.savedStateMap,
),
permanentNavModel = PermanentNavModel(
- navTargets = setOf(NavTarget.LoggedInPermanent, NavTarget.LockPermanent),
+ navTargets = setOf(NavTarget.LoggedInPermanent),
savedStateMap = buildContext.savedStateMap,
),
buildContext = buildContext,
@@ -189,9 +184,6 @@ class LoggedInFlowNode @AssistedInject constructor(
@Parcelize
data object LoggedInPermanent : NavTarget
- @Parcelize
- data object LockPermanent : NavTarget
-
@Parcelize
data object RoomList : NavTarget
@@ -235,11 +227,6 @@ class LoggedInFlowNode @AssistedInject constructor(
NavTarget.LoggedInPermanent -> {
createNode(buildContext)
}
- NavTarget.LockPermanent -> {
- lockScreenEntryPoint.nodeBuilder(this, buildContext)
- .target(LockScreenEntryPoint.Target.Unlock)
- .build()
- }
NavTarget.RoomList -> {
val callback = object : RoomListEntryPoint.Callback {
override fun onRoomClicked(roomId: RoomId) {
@@ -430,15 +417,11 @@ class LoggedInFlowNode @AssistedInject constructor(
@Composable
override fun View(modifier: Modifier) {
Box(modifier = modifier) {
- val lockScreenState by lockScreenStateService.lockState.collectAsState()
val ftueState by ftueService.state.collectAsState()
BackstackView()
if (ftueState is FtueState.Complete) {
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
}
- if (lockScreenState == LockScreenLockState.Locked) {
- PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LockPermanent)
- }
}
}
diff --git a/changelog.d/2692.bugfix b/changelog.d/2692.bugfix
new file mode 100644
index 0000000000..3fefb20a07
--- /dev/null
+++ b/changelog.d/2692.bugfix
@@ -0,0 +1 @@
+Fix modal contents overlapping screen lock pin.
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt
index aebf95aa37..db56b8c17b 100644
--- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt
+++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt
@@ -29,11 +29,11 @@ import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockMana
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
+import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.bool.orFalse
-import io.element.android.libraries.matrix.api.MatrixClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -41,7 +41,7 @@ import javax.inject.Inject
class PinUnlockPresenter @Inject constructor(
private val pinCodeManager: PinCodeManager,
private val biometricUnlockManager: BiometricUnlockManager,
- private val matrixClient: MatrixClient,
+ private val signOut: SignOut,
private val coroutineScope: CoroutineScope,
private val pinUnlockHelper: PinUnlockHelper,
) : Presenter {
@@ -179,7 +179,7 @@ class PinUnlockPresenter @Inject constructor(
private fun CoroutineScope.signOut(signOutAction: MutableState>) = launch {
suspend {
- matrixClient.logout(ignoreSdkError = true)
+ signOut()
}.runCatchingUpdatingState(signOutAction)
}
}
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt
new file mode 100644
index 0000000000..0c2de97bb2
--- /dev/null
+++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2024 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.features.lockscreen.impl.unlock.activity
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import io.element.android.compound.theme.ElementTheme
+import io.element.android.features.lockscreen.api.LockScreenLockState
+import io.element.android.features.lockscreen.api.LockScreenService
+import io.element.android.features.lockscreen.impl.unlock.PinUnlockPresenter
+import io.element.android.features.lockscreen.impl.unlock.PinUnlockView
+import io.element.android.features.lockscreen.impl.unlock.di.PinUnlockBindings
+import io.element.android.libraries.architecture.bindings
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+class PinUnlockActivity : AppCompatActivity() {
+ companion object {
+ fun newIntent(context: Context): Intent {
+ return Intent(context, PinUnlockActivity::class.java)
+ }
+ }
+
+ @Inject lateinit var presenter: PinUnlockPresenter
+ @Inject lateinit var lockScreenService: LockScreenService
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+ bindings().inject(this)
+ setContent {
+ ElementTheme {
+ val state = presenter.present()
+ PinUnlockView(state = state, isInAppUnlock = false)
+ }
+ }
+ lifecycleScope.launch {
+ lockScreenService.lockState.collect { state ->
+ if (state == LockScreenLockState.Unlocked) {
+ finish()
+ }
+ }
+ }
+ val onBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ moveTaskToBack(true)
+ }
+ }
+ onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+ }
+}
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt
new file mode 100644
index 0000000000..ddd62d2fb6
--- /dev/null
+++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2024 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.features.lockscreen.impl.unlock.di
+
+import com.squareup.anvil.annotations.ContributesTo
+import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
+import io.element.android.libraries.di.AppScope
+
+@ContributesTo(AppScope::class)
+interface PinUnlockBindings {
+ fun inject(activity: PinUnlockActivity)
+}
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/DefaultSignOut.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/DefaultSignOut.kt
new file mode 100644
index 0000000000..2c541911e6
--- /dev/null
+++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/DefaultSignOut.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2024 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.features.lockscreen.impl.unlock.signout
+
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.di.AppScope
+import io.element.android.libraries.matrix.api.MatrixClientProvider
+import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
+import javax.inject.Inject
+
+@ContributesBinding(AppScope::class)
+class DefaultSignOut @Inject constructor(
+ private val authenticationService: MatrixAuthenticationService,
+ private val matrixClientProvider: MatrixClientProvider,
+) : SignOut {
+ override suspend fun invoke(): String? {
+ val currentSession = authenticationService.getLatestSessionId()
+ return if (currentSession != null) {
+ matrixClientProvider.getOrRestore(currentSession)
+ .getOrThrow()
+ .logout(ignoreSdkError = true)
+ } else {
+ error("No session to sign out")
+ }
+ }
+}
diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/SignOut.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/SignOut.kt
new file mode 100644
index 0000000000..f4c91aece6
--- /dev/null
+++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/signout/SignOut.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2024 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.features.lockscreen.impl.unlock.signout
+
+interface SignOut {
+ suspend operator fun invoke(): String?
+}
diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/FakeSignOut.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/FakeSignOut.kt
new file mode 100644
index 0000000000..883a5bf97b
--- /dev/null
+++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/FakeSignOut.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2024 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.features.lockscreen.impl.unlock
+
+import io.element.android.features.lockscreen.impl.unlock.signout.SignOut
+import io.element.android.tests.testutils.simulateLongTask
+
+class FakeSignOut(
+ var lambda: () -> String? = { null }
+) : SignOut {
+ override suspend fun invoke(): String? = simulateLongTask {
+ lambda()
+ }
+}
diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt
index 6827055b2c..60cc3fff9d 100644
--- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt
+++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt
@@ -29,7 +29,6 @@ import io.element.android.features.lockscreen.impl.pin.model.PinEntry
import io.element.android.features.lockscreen.impl.pin.model.assertText
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel
import io.element.android.libraries.architecture.AsyncData
-import io.element.android.libraries.matrix.test.FakeMatrixClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -150,7 +149,7 @@ class PinUnlockPresenterTest {
return PinUnlockPresenter(
pinCodeManager = pinCodeManager,
biometricUnlockManager = biometricUnlockManager,
- matrixClient = FakeMatrixClient(),
+ signOut = FakeSignOut(),
coroutineScope = scope,
pinUnlockHelper = PinUnlockHelper(biometricUnlockManager, pinCodeManager),
)