Skip to content

Commit a28b7db

Browse files
authored
fix: app crashing on navigation [WPB-17263] (#3994)
1 parent bd44005 commit a28b7db

File tree

9 files changed

+432
-322
lines changed

9 files changed

+432
-322
lines changed

app/src/main/kotlin/com/wire/android/ui/WireActivity.kt

Lines changed: 16 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import android.content.Intent
2323
import android.os.Build
2424
import android.os.Bundle
2525
import android.view.WindowManager
26-
import android.widget.Toast
2726
import androidx.activity.compose.setContent
2827
import androidx.activity.enableEdgeToEdge
2928
import androidx.activity.viewModels
@@ -49,6 +48,7 @@ import androidx.compose.ui.platform.LocalContext
4948
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
5049
import androidx.compose.ui.semantics.semantics
5150
import androidx.compose.ui.semantics.testTagsAsResourceId
51+
import androidx.core.content.ContextCompat.startActivity
5252
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
5353
import androidx.lifecycle.Lifecycle
5454
import androidx.lifecycle.flowWithLifecycle
@@ -59,7 +59,6 @@ import com.ramcosta.composedestinations.spec.Route
5959
import com.ramcosta.composedestinations.utils.destination
6060
import com.ramcosta.composedestinations.utils.route
6161
import com.wire.android.BuildConfig
62-
import com.wire.android.R
6362
import com.wire.android.appLogger
6463
import com.wire.android.config.CustomUiConfigurationProvider
6564
import com.wire.android.config.LocalCustomUiConfigurationProvider
@@ -76,7 +75,6 @@ import com.wire.android.navigation.startDestination
7675
import com.wire.android.navigation.style.BackgroundStyle
7776
import com.wire.android.navigation.style.BackgroundType
7877
import com.wire.android.ui.authentication.login.LoginPasswordPath
79-
import com.wire.android.ui.authentication.login.PreFilledUserIdentifierType
8078
import com.wire.android.ui.authentication.login.WireAuthBackgroundLayout
8179
import com.wire.android.ui.calling.getIncomingCallIntent
8280
import com.wire.android.ui.calling.getOutgoingCallIntent
@@ -88,16 +86,13 @@ import com.wire.android.ui.common.topappbar.CommonTopAppBar
8886
import com.wire.android.ui.common.topappbar.CommonTopAppBarState
8987
import com.wire.android.ui.common.topappbar.CommonTopAppBarViewModel
9088
import com.wire.android.ui.common.visbility.rememberVisibilityState
91-
import com.wire.android.ui.destinations.ConversationScreenDestination
9289
import com.wire.android.ui.destinations.E2EIEnrollmentScreenDestination
9390
import com.wire.android.ui.destinations.E2eiCertificateDetailsScreenDestination
9491
import com.wire.android.ui.destinations.HomeScreenDestination
95-
import com.wire.android.ui.destinations.ImportMediaScreenDestination
9692
import com.wire.android.ui.destinations.LoginScreenDestination
9793
import com.wire.android.ui.destinations.MigrationScreenDestination
9894
import com.wire.android.ui.destinations.NewLoginScreenDestination
9995
import com.wire.android.ui.destinations.NewWelcomeEmptyStartScreenDestination
100-
import com.wire.android.ui.destinations.OtherUserProfileScreenDestination
10196
import com.wire.android.ui.destinations.SelfDevicesScreenDestination
10297
import com.wire.android.ui.destinations.SelfUserProfileScreenDestination
10398
import com.wire.android.ui.destinations.WelcomeScreenDestination
@@ -290,6 +285,7 @@ class WireActivity : AppCompatActivity() {
290285
SetUpNavigation(navigator)
291286
HandleScreenshotCensoring()
292287
HandleDialogs(navigator)
288+
HandleViewActions(viewModel.actions, navigator, loginTypeSelector)
293289
}
294290
}
295291
}
@@ -713,104 +709,7 @@ class WireActivity : AppCompatActivity() {
713709
}
714710
return
715711
} else {
716-
viewModel.handleDeepLink(
717-
intent = intent,
718-
onOpenConversation = {
719-
if (it.switchedAccount) {
720-
navigate(
721-
NavigationCommand(
722-
HomeScreenDestination,
723-
BackStackMode.CLEAR_WHOLE
724-
)
725-
)
726-
}
727-
navigate(
728-
NavigationCommand(
729-
ConversationScreenDestination(it.conversationId),
730-
BackStackMode.UPDATE_EXISTED
731-
)
732-
)
733-
},
734-
onIsSharingIntent = {
735-
navigate(
736-
NavigationCommand(
737-
ImportMediaScreenDestination,
738-
BackStackMode.UPDATE_EXISTED
739-
)
740-
)
741-
},
742-
onCannotLoginDuringACall = {
743-
runOnUiThread {
744-
Toast.makeText(
745-
this,
746-
resources.getString(R.string.cant_switch_account_in_call),
747-
Toast.LENGTH_SHORT
748-
).show()
749-
}
750-
},
751-
onAuthorizationNeeded = {
752-
if (navigator.isEmptyWelcomeStartDestination()) {
753-
// log in needed so if "welcome empty start" screen then switch "start" screen to login by navigating to it
754-
navigate(NavigationCommand(NewLoginScreenDestination(), BackStackMode.CLEAR_WHOLE))
755-
}
756-
runOnUiThread {
757-
Toast.makeText(
758-
this,
759-
resources.getString(R.string.deeplink_authorization_needed),
760-
Toast.LENGTH_SHORT
761-
).show()
762-
}
763-
},
764-
onUnknown = {
765-
if (navigator.isEmptyWelcomeStartDestination()) {
766-
// log in needed so if "welcome empty start" screen then switch "start" screen to login by navigating to it
767-
navigate(NavigationCommand(NewLoginScreenDestination(), BackStackMode.CLEAR_WHOLE))
768-
}
769-
},
770-
onMigrationLogin = {
771-
navigate(
772-
NavigationCommand(
773-
when (loginTypeSelector.canUseNewLogin()) {
774-
true -> NewLoginScreenDestination(userHandle = PreFilledUserIdentifierType.PreFilled(it.userHandle))
775-
false -> LoginScreenDestination(userHandle = PreFilledUserIdentifierType.PreFilled(it.userHandle))
776-
},
777-
// if "welcome empty start" screen then switch "start" screen to proper one
778-
when (navigator.shouldReplaceWelcomeLoginStartDestination()) {
779-
true -> BackStackMode.CLEAR_WHOLE
780-
false -> BackStackMode.UPDATE_EXISTED
781-
},
782-
)
783-
)
784-
},
785-
onOpenOtherUserProfile = {
786-
if (it.switchedAccount) {
787-
navigate(
788-
NavigationCommand(
789-
HomeScreenDestination,
790-
BackStackMode.CLEAR_WHOLE
791-
)
792-
)
793-
}
794-
navigate(
795-
NavigationCommand(
796-
OtherUserProfileScreenDestination(it.userId),
797-
BackStackMode.UPDATE_EXISTED
798-
)
799-
)
800-
},
801-
onSSOLogin = {
802-
navigate(
803-
NavigationCommand(
804-
when (navigator.navController.currentBackStackEntry?.destination()?.route?.getBaseRoute()) {
805-
// if SSO login started from new login screen then go back to the new login flow
806-
NewLoginScreenDestination.route.getBaseRoute() -> NewLoginScreenDestination(ssoLoginResult = it)
807-
else -> LoginScreenDestination(ssoLoginResult = it)
808-
},
809-
BackStackMode.UPDATE_EXISTED,
810-
)
811-
)
812-
}
813-
)
712+
viewModel.handleDeepLink(intent)
814713
intent.putExtra(HANDLED_DEEPLINK_FLAG, true)
815714
}
816715
}
@@ -824,26 +723,26 @@ class WireActivity : AppCompatActivity() {
824723
}
825724
}
826725

827-
private fun Navigator.shouldReplaceWelcomeLoginStartDestination(): Boolean {
828-
val firstDestinationBaseRoute = navController.startDestination()?.route()?.route?.getBaseRoute()
829-
val welcomeScreens = listOf(WelcomeScreenDestination, NewWelcomeEmptyStartScreenDestination)
830-
val loginScreens = listOf(LoginScreenDestination, NewLoginScreenDestination)
831-
val welcomeAndLoginBaseRoutes = (welcomeScreens + loginScreens).map { it.route.getBaseRoute() }
832-
return welcomeAndLoginBaseRoutes.contains(firstDestinationBaseRoute)
833-
}
834-
835-
private fun Navigator.isEmptyWelcomeStartDestination(): Boolean {
836-
val firstDestinationBaseRoute = navController.startDestination()?.route()?.route?.getBaseRoute()
837-
return firstDestinationBaseRoute == NewWelcomeEmptyStartScreenDestination.route.getBaseRoute()
838-
}
839-
840726
companion object {
841727
private const val HANDLED_DEEPLINK_FLAG = "deeplink_handled_flag_key"
842728
private const val ORIGINAL_SAVED_INTENT_FLAG = "original_saved_intent"
843729
private const val TAG = "WireActivity"
844730
}
845731
}
846732

733+
internal fun Navigator.shouldReplaceWelcomeLoginStartDestination(): Boolean {
734+
val firstDestinationBaseRoute = navController.startDestination()?.route()?.route?.getBaseRoute()
735+
val welcomeScreens = listOf(WelcomeScreenDestination, NewWelcomeEmptyStartScreenDestination)
736+
val loginScreens = listOf(LoginScreenDestination, NewLoginScreenDestination)
737+
val welcomeAndLoginBaseRoutes = (welcomeScreens + loginScreens).map { it.route.getBaseRoute() }
738+
return welcomeAndLoginBaseRoutes.contains(firstDestinationBaseRoute)
739+
}
740+
741+
internal fun Navigator.isEmptyWelcomeStartDestination(): Boolean {
742+
val firstDestinationBaseRoute = navController.startDestination()?.route()?.route?.getBaseRoute()
743+
return firstDestinationBaseRoute == NewWelcomeEmptyStartScreenDestination.route.getBaseRoute()
744+
}
745+
847746
val LocalActivity = staticCompositionLocalOf<Activity> {
848747
error("No Activity provided")
849748
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2025 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*/
18+
package com.wire.android.ui
19+
20+
import android.content.Context
21+
import android.widget.Toast
22+
import androidx.compose.runtime.Composable
23+
import androidx.compose.runtime.LaunchedEffect
24+
import androidx.compose.ui.platform.LocalContext
25+
import androidx.lifecycle.Lifecycle
26+
import androidx.lifecycle.compose.LocalLifecycleOwner
27+
import androidx.lifecycle.repeatOnLifecycle
28+
import com.ramcosta.composedestinations.utils.destination
29+
import com.wire.android.R
30+
import com.wire.android.navigation.BackStackMode
31+
import com.wire.android.navigation.LoginTypeSelector
32+
import com.wire.android.navigation.NavigationCommand
33+
import com.wire.android.navigation.Navigator
34+
import com.wire.android.navigation.getBaseRoute
35+
import com.wire.android.ui.authentication.login.PreFilledUserIdentifierType
36+
import com.wire.android.ui.destinations.ConversationScreenDestination
37+
import com.wire.android.ui.destinations.HomeScreenDestination
38+
import com.wire.android.ui.destinations.ImportMediaScreenDestination
39+
import com.wire.android.ui.destinations.LoginScreenDestination
40+
import com.wire.android.ui.destinations.NewLoginScreenDestination
41+
import com.wire.android.ui.destinations.OtherUserProfileScreenDestination
42+
import kotlinx.coroutines.flow.Flow
43+
44+
@Composable
45+
internal fun HandleViewActions(actions: Flow<WireActivityViewAction>, navigator: Navigator, loginTypeSelector: LoginTypeSelector) {
46+
47+
val context = LocalContext.current
48+
val lifecycle = LocalLifecycleOwner.current
49+
50+
LaunchedEffect(Unit) {
51+
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
52+
actions.collect { action ->
53+
when (action) {
54+
is OnAuthorizationNeeded -> onAuthorizationNeeded(context, navigator)
55+
is OnMigrationLogin -> onMigration(navigator, loginTypeSelector, action)
56+
is OnOpenUserProfile -> openUserProfile(action, navigator)
57+
is OnSSOLogin -> openSsoLogin(navigator, action)
58+
is OnShowImportMediaScreen -> openImportMediaScreen(navigator)
59+
is OpenConversation -> openConversation(action, navigator)
60+
is OnUnknownDeepLink -> if (navigator.isEmptyWelcomeStartDestination()) {
61+
// log in needed so if "welcome empty start" screen then switch "start" screen to login by navigating to it
62+
navigator.navigate(NavigationCommand(NewLoginScreenDestination(), BackStackMode.CLEAR_WHOLE))
63+
}
64+
is ShowToast -> showToast(context, action.messageResId)
65+
}
66+
}
67+
}
68+
}
69+
}
70+
71+
private fun openConversation(action: OpenConversation, navigator: Navigator) {
72+
if (action.result.switchedAccount) {
73+
navigator.navigate(
74+
NavigationCommand(
75+
HomeScreenDestination,
76+
BackStackMode.CLEAR_WHOLE
77+
)
78+
)
79+
}
80+
navigator.navigate(
81+
NavigationCommand(
82+
ConversationScreenDestination(action.result.conversationId),
83+
BackStackMode.UPDATE_EXISTED
84+
)
85+
)
86+
}
87+
88+
private fun openImportMediaScreen(navigator: Navigator) {
89+
navigator.navigate(
90+
NavigationCommand(
91+
ImportMediaScreenDestination,
92+
BackStackMode.UPDATE_EXISTED
93+
)
94+
)
95+
}
96+
97+
private fun openSsoLogin(navigator: Navigator, action: OnSSOLogin) {
98+
navigator.navigate(
99+
NavigationCommand(
100+
when (navigator.navController.currentBackStackEntry?.destination()?.route?.getBaseRoute()) {
101+
// if SSO login started from new login screen then go back to the new login flow
102+
NewLoginScreenDestination.route.getBaseRoute() -> NewLoginScreenDestination(
103+
ssoLoginResult = action.result
104+
)
105+
106+
else -> LoginScreenDestination(
107+
ssoLoginResult = action.result
108+
)
109+
},
110+
BackStackMode.UPDATE_EXISTED,
111+
)
112+
)
113+
}
114+
115+
private fun openUserProfile(action: OnOpenUserProfile, navigator: Navigator) {
116+
if (action.result.switchedAccount) {
117+
navigator.navigate(
118+
NavigationCommand(
119+
HomeScreenDestination,
120+
BackStackMode.CLEAR_WHOLE
121+
)
122+
)
123+
}
124+
navigator.navigate(
125+
NavigationCommand(
126+
OtherUserProfileScreenDestination(action.result.userId),
127+
BackStackMode.UPDATE_EXISTED
128+
)
129+
)
130+
}
131+
132+
private fun onMigration(
133+
navigator: Navigator,
134+
loginTypeSelector: LoginTypeSelector,
135+
action: OnMigrationLogin
136+
) {
137+
navigator.navigate(
138+
NavigationCommand(
139+
when (loginTypeSelector.canUseNewLogin()) {
140+
true -> NewLoginScreenDestination(
141+
userHandle = PreFilledUserIdentifierType.PreFilled(action.result.userHandle)
142+
)
143+
144+
false -> LoginScreenDestination(
145+
userHandle = PreFilledUserIdentifierType.PreFilled(action.result.userHandle)
146+
)
147+
},
148+
// if "welcome empty start" screen then switch "start" screen to proper one
149+
when (navigator.shouldReplaceWelcomeLoginStartDestination()) {
150+
true -> BackStackMode.CLEAR_WHOLE
151+
false -> BackStackMode.UPDATE_EXISTED
152+
},
153+
)
154+
)
155+
}
156+
157+
private fun onAuthorizationNeeded(context: Context, navigator: Navigator) {
158+
if (navigator.isEmptyWelcomeStartDestination()) {
159+
// log in needed so if "welcome empty start" screen then switch "start" screen to login by navigating to it
160+
navigator.navigate(NavigationCommand(NewLoginScreenDestination(), BackStackMode.CLEAR_WHOLE))
161+
}
162+
showToast(context, R.string.deeplink_authorization_needed)
163+
}
164+
165+
private fun showToast(context: Context, messageResId: Int) {
166+
Toast.makeText(
167+
context,
168+
context.resources.getString(messageResId),
169+
Toast.LENGTH_SHORT
170+
).show()
171+
}

0 commit comments

Comments
 (0)