Skip to content

Commit ac123bd

Browse files
authored
Merge pull request #2874 from element-hq/feature/fga/fix_2692
Fix modal contents overlapping screen lock pin #2692
2 parents 15e7a9d + 5c4326e commit ac123bd

File tree

24 files changed

+364
-99
lines changed

24 files changed

+364
-99
lines changed

app/src/main/kotlin/io/element/android/x/MainActivity.kt

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,26 @@ import androidx.compose.runtime.remember
3232
import androidx.compose.ui.Modifier
3333
import androidx.compose.ui.platform.LocalUriHandler
3434
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
35+
import androidx.lifecycle.Lifecycle
36+
import androidx.lifecycle.lifecycleScope
37+
import androidx.lifecycle.repeatOnLifecycle
3538
import com.bumble.appyx.core.integration.NodeHost
3639
import com.bumble.appyx.core.integrationpoint.NodeActivity
3740
import com.bumble.appyx.core.plugin.NodeReadyObserver
3841
import io.element.android.compound.theme.ElementTheme
3942
import io.element.android.compound.theme.Theme
4043
import io.element.android.compound.theme.isDark
4144
import io.element.android.compound.theme.mapToTheme
45+
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
46+
import io.element.android.features.lockscreen.api.LockScreenLockState
47+
import io.element.android.features.lockscreen.api.LockScreenService
4248
import io.element.android.features.lockscreen.api.handleSecureFlag
43-
import io.element.android.features.lockscreen.api.isLocked
4449
import io.element.android.libraries.architecture.bindings
4550
import io.element.android.libraries.core.log.logger.LoggerTag
4651
import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher
4752
import io.element.android.x.di.AppBindings
4853
import io.element.android.x.intent.SafeUriHandler
54+
import kotlinx.coroutines.launch
4955
import timber.log.Timber
5056

5157
private val loggerTag = LoggerTag("MainActivity")
@@ -59,27 +65,13 @@ class MainActivity : NodeActivity() {
5965
installSplashScreen()
6066
super.onCreate(savedInstanceState)
6167
appBindings = bindings()
62-
appBindings.lockScreenService().handleSecureFlag(this)
68+
setupLockManagement(appBindings.lockScreenService(), appBindings.lockScreenEntryPoint())
6369
enableEdgeToEdge()
6470
setContent {
6571
MainContent(appBindings)
6672
}
6773
}
6874

69-
@Deprecated("")
70-
override fun onBackPressed() {
71-
// If the app is locked, we need to intercept onBackPressed before it goes to OnBackPressedDispatcher.
72-
// Indeed, otherwise we would need to trick Appyx backstack management everywhere.
73-
// Without this trick, we would get pop operations on the hidden backstack.
74-
if (appBindings.lockScreenService().isLocked) {
75-
// Do not kill the app in this case, just go to background.
76-
moveTaskToBack(false)
77-
} else {
78-
@Suppress("DEPRECATION")
79-
super.onBackPressed()
80-
}
81-
}
82-
8375
@Composable
8476
private fun MainContent(appBindings: AppBindings) {
8577
val theme by remember {
@@ -96,8 +88,8 @@ class MainActivity : NodeActivity() {
9688
) {
9789
Box(
9890
modifier = Modifier
99-
.fillMaxSize()
100-
.background(MaterialTheme.colorScheme.background),
91+
.fillMaxSize()
92+
.background(MaterialTheme.colorScheme.background),
10193
) {
10294
if (migrationState.migrationAction.isSuccess()) {
10395
MainNodeHost()
@@ -131,6 +123,22 @@ class MainActivity : NodeActivity() {
131123
}
132124
}
133125

126+
private fun setupLockManagement(
127+
lockScreenService: LockScreenService,
128+
lockScreenEntryPoint: LockScreenEntryPoint
129+
) {
130+
lockScreenService.handleSecureFlag(this)
131+
lifecycleScope.launch {
132+
repeatOnLifecycle(Lifecycle.State.RESUMED) {
133+
lockScreenService.lockState.collect { state ->
134+
if (state == LockScreenLockState.Locked) {
135+
startActivity(lockScreenEntryPoint.pinUnlockIntent(this@MainActivity))
136+
}
137+
}
138+
}
139+
}
140+
}
141+
134142
/**
135143
* Called when:
136144
* - the launcher icon is clicked (if the app is already running);

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package io.element.android.x.di
1818

1919
import com.squareup.anvil.annotations.ContributesTo
2020
import io.element.android.features.api.MigrationEntryPoint
21+
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
2122
import io.element.android.features.lockscreen.api.LockScreenService
2223
import io.element.android.features.preferences.api.store.AppPreferencesStore
2324
import io.element.android.features.rageshake.api.reporter.BugReporter
@@ -38,4 +39,6 @@ interface AppBindings {
3839
fun preferencesStore(): AppPreferencesStore
3940

4041
fun migrationEntryPoint(): MigrationEntryPoint
42+
43+
fun lockScreenEntryPoint(): LockScreenEntryPoint
4144
}

appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ import io.element.android.features.createroom.api.CreateRoomEntryPoint
4747
import io.element.android.features.ftue.api.FtueEntryPoint
4848
import io.element.android.features.ftue.api.state.FtueService
4949
import io.element.android.features.ftue.api.state.FtueState
50-
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
51-
import io.element.android.features.lockscreen.api.LockScreenLockState
52-
import io.element.android.features.lockscreen.api.LockScreenService
5350
import io.element.android.features.networkmonitor.api.NetworkMonitor
5451
import io.element.android.features.networkmonitor.api.NetworkStatus
5552
import io.element.android.features.preferences.api.PreferencesEntryPoint
@@ -100,8 +97,6 @@ class LoggedInFlowNode @AssistedInject constructor(
10097
private val coroutineScope: CoroutineScope,
10198
private val networkMonitor: NetworkMonitor,
10299
private val ftueService: FtueService,
103-
private val lockScreenEntryPoint: LockScreenEntryPoint,
104-
private val lockScreenStateService: LockScreenService,
105100
private val roomDirectoryEntryPoint: RoomDirectoryEntryPoint,
106101
private val matrixClient: MatrixClient,
107102
snackbarDispatcher: SnackbarDispatcher,
@@ -111,7 +106,7 @@ class LoggedInFlowNode @AssistedInject constructor(
111106
savedStateMap = buildContext.savedStateMap,
112107
),
113108
permanentNavModel = PermanentNavModel(
114-
navTargets = setOf(NavTarget.LoggedInPermanent, NavTarget.LockPermanent),
109+
navTargets = setOf(NavTarget.LoggedInPermanent),
115110
savedStateMap = buildContext.savedStateMap,
116111
),
117112
buildContext = buildContext,
@@ -189,9 +184,6 @@ class LoggedInFlowNode @AssistedInject constructor(
189184
@Parcelize
190185
data object LoggedInPermanent : NavTarget
191186

192-
@Parcelize
193-
data object LockPermanent : NavTarget
194-
195187
@Parcelize
196188
data object RoomList : NavTarget
197189

@@ -235,11 +227,6 @@ class LoggedInFlowNode @AssistedInject constructor(
235227
NavTarget.LoggedInPermanent -> {
236228
createNode<LoggedInNode>(buildContext)
237229
}
238-
NavTarget.LockPermanent -> {
239-
lockScreenEntryPoint.nodeBuilder(this, buildContext)
240-
.target(LockScreenEntryPoint.Target.Unlock)
241-
.build()
242-
}
243230
NavTarget.RoomList -> {
244231
val callback = object : RoomListEntryPoint.Callback {
245232
override fun onRoomClicked(roomId: RoomId) {
@@ -430,15 +417,11 @@ class LoggedInFlowNode @AssistedInject constructor(
430417
@Composable
431418
override fun View(modifier: Modifier) {
432419
Box(modifier = modifier) {
433-
val lockScreenState by lockScreenStateService.lockState.collectAsState()
434420
val ftueState by ftueService.state.collectAsState()
435421
BackstackView()
436422
if (ftueState is FtueState.Complete) {
437423
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
438424
}
439-
if (lockScreenState == LockScreenLockState.Locked) {
440-
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LockPermanent)
441-
}
442425
}
443426
}
444427

changelog.d/2692.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix modal contents overlapping screen lock pin.

features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,8 @@ class FtueFlowNode @AssistedInject constructor(
132132
lifecycleScope.launch { moveToNextStep() }
133133
}
134134
}
135-
lockScreenEntryPoint.nodeBuilder(this, buildContext)
135+
lockScreenEntryPoint.nodeBuilder(this, buildContext, LockScreenEntryPoint.Target.Setup)
136136
.callback(callback)
137-
.target(LockScreenEntryPoint.Target.Setup)
138137
.build()
139138
}
140139
}

features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenEntryPoint.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616

1717
package io.element.android.features.lockscreen.api
1818

19+
import android.content.Context
20+
import android.content.Intent
1921
import com.bumble.appyx.core.modality.BuildContext
2022
import com.bumble.appyx.core.node.Node
2123
import com.bumble.appyx.core.plugin.Plugin
2224
import io.element.android.libraries.architecture.FeatureEntryPoint
2325

2426
interface LockScreenEntryPoint : FeatureEntryPoint {
25-
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
27+
fun nodeBuilder(parentNode: Node, buildContext: BuildContext, navTarget: Target): NodeBuilder
28+
fun pinUnlockIntent(context: Context): Intent
2629

2730
interface NodeBuilder {
2831
fun callback(callback: Callback): NodeBuilder
29-
fun target(target: Target): NodeBuilder
3032
fun build(): Node
3133
}
3234

@@ -37,6 +39,5 @@ interface LockScreenEntryPoint : FeatureEntryPoint {
3739
enum class Target {
3840
Settings,
3941
Setup,
40-
Unlock
4142
}
4243
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!--
2+
~ Copyright (c) 2024 New Vector Ltd
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
18+
19+
<application>
20+
<activity
21+
android:name="io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity"
22+
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"/>
23+
</application>
24+
25+
</manifest>

features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPoint.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,20 @@
1616

1717
package io.element.android.features.lockscreen.impl
1818

19+
import android.content.Context
20+
import android.content.Intent
1921
import com.bumble.appyx.core.modality.BuildContext
2022
import com.bumble.appyx.core.node.Node
2123
import com.squareup.anvil.annotations.ContributesBinding
2224
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
25+
import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
2326
import io.element.android.libraries.architecture.createNode
2427
import io.element.android.libraries.di.AppScope
2528
import javax.inject.Inject
2629

2730
@ContributesBinding(AppScope::class)
2831
class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
29-
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): LockScreenEntryPoint.NodeBuilder {
30-
var innerTarget: LockScreenEntryPoint.Target = LockScreenEntryPoint.Target.Unlock
32+
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext, navTarget: LockScreenEntryPoint.Target): LockScreenEntryPoint.NodeBuilder {
3133
val callbacks = mutableListOf<LockScreenEntryPoint.Callback>()
3234

3335
return object : LockScreenEntryPoint.NodeBuilder {
@@ -36,15 +38,9 @@ class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
3638
return this
3739
}
3840

39-
override fun target(target: LockScreenEntryPoint.Target): LockScreenEntryPoint.NodeBuilder {
40-
innerTarget = target
41-
return this
42-
}
43-
4441
override fun build(): Node {
4542
val inputs = LockScreenFlowNode.Inputs(
46-
when (innerTarget) {
47-
LockScreenEntryPoint.Target.Unlock -> LockScreenFlowNode.NavTarget.Unlock
43+
when (navTarget) {
4844
LockScreenEntryPoint.Target.Setup -> LockScreenFlowNode.NavTarget.Setup
4945
LockScreenEntryPoint.Target.Settings -> LockScreenFlowNode.NavTarget.Settings
5046
}
@@ -54,4 +50,8 @@ class DefaultLockScreenEntryPoint @Inject constructor() : LockScreenEntryPoint {
5450
}
5551
}
5652
}
53+
54+
override fun pinUnlockIntent(context: Context): Intent {
55+
return PinUnlockActivity.newIntent(context)
56+
}
5757
}

features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenFlowNode.kt

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import io.element.android.anvilannotations.ContributesNode
3030
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
3131
import io.element.android.features.lockscreen.impl.settings.LockScreenSettingsFlowNode
3232
import io.element.android.features.lockscreen.impl.setup.LockScreenSetupFlowNode
33-
import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode
3433
import io.element.android.libraries.architecture.BackstackView
3534
import io.element.android.libraries.architecture.BaseFlowNode
3635
import io.element.android.libraries.architecture.NodeInputs
@@ -44,20 +43,17 @@ class LockScreenFlowNode @AssistedInject constructor(
4443
@Assisted plugins: List<Plugin>,
4544
) : BaseFlowNode<LockScreenFlowNode.NavTarget>(
4645
backstack = BackStack(
47-
initialElement = plugins.filterIsInstance(Inputs::class.java).first().initialNavTarget,
46+
initialElement = plugins.filterIsInstance<Inputs>().first().initialNavTarget,
4847
savedStateMap = buildContext.savedStateMap,
4948
),
5049
buildContext = buildContext,
5150
plugins = plugins,
5251
) {
5352
data class Inputs(
54-
val initialNavTarget: NavTarget = NavTarget.Unlock,
53+
val initialNavTarget: NavTarget,
5554
) : NodeInputs
5655

5756
sealed interface NavTarget : Parcelable {
58-
@Parcelize
59-
data object Unlock : NavTarget
60-
6157
@Parcelize
6258
data object Setup : NavTarget
6359

@@ -75,10 +71,6 @@ class LockScreenFlowNode @AssistedInject constructor(
7571

7672
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
7773
return when (navTarget) {
78-
NavTarget.Unlock -> {
79-
val inputs = PinUnlockNode.Inputs(isInAppUnlock = false)
80-
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs))
81-
}
8274
NavTarget.Setup -> {
8375
val callback = OnSetupDoneCallback(plugins())
8476
createNode<LockScreenSetupFlowNode>(buildContext, plugins = listOf(callback))

features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,12 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
103103
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
104104
return when (navTarget) {
105105
NavTarget.Unlock -> {
106-
val inputs = PinUnlockNode.Inputs(isInAppUnlock = true)
107106
val callback = object : PinUnlockNode.Callback {
108107
override fun onUnlock() {
109108
backstack.newRoot(NavTarget.Settings)
110109
}
111110
}
112-
createNode<PinUnlockNode>(buildContext, plugins = listOf(inputs, callback))
111+
createNode<PinUnlockNode>(buildContext, plugins = listOf(callback))
113112
}
114113
NavTarget.SetupPin -> {
115114
createNode<SetupPinNode>(buildContext)

features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockNode.kt

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ import com.bumble.appyx.core.plugin.plugins
2626
import dagger.assisted.Assisted
2727
import dagger.assisted.AssistedInject
2828
import io.element.android.anvilannotations.ContributesNode
29-
import io.element.android.libraries.architecture.NodeInputs
30-
import io.element.android.libraries.architecture.inputs
3129
import io.element.android.libraries.di.SessionScope
3230

3331
@ContributesNode(SessionScope::class)
@@ -40,12 +38,6 @@ class PinUnlockNode @AssistedInject constructor(
4038
fun onUnlock()
4139
}
4240

43-
data class Inputs(
44-
val isInAppUnlock: Boolean
45-
) : NodeInputs
46-
47-
private val inputs: Inputs = inputs()
48-
4941
private fun onUnlock() {
5042
plugins<Callback>().forEach {
5143
it.onUnlock()
@@ -62,7 +54,9 @@ class PinUnlockNode @AssistedInject constructor(
6254
}
6355
PinUnlockView(
6456
state = state,
65-
isInAppUnlock = inputs.isInAppUnlock,
57+
// UnlockNode is only used for in-app unlock, so we can safely set isInAppUnlock to true.
58+
// It's set to false in PinUnlockActivity.
59+
isInAppUnlock = true,
6660
modifier = modifier
6761
)
6862
}

0 commit comments

Comments
 (0)