From c0c0f90b042f3da543de0df600a76effcd583034 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 10 Aug 2022 11:26:25 +0200 Subject: [PATCH 01/30] Adds NewChatBottomSheet --- .../room/list/home/HomeRoomListFragment.kt | 4 +- .../home/room/list/home/NewChatBottomSheet.kt | 38 +++++++++++++++++++ .../layout/fragment_new_chat_bottom_sheet.xml | 27 +++++++++++++ vector/src/main/res/values/strings.xml | 2 + 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt create mode 100644 vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 43a6f25841..0c9cb3136e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -68,6 +68,8 @@ class HomeRoomListFragment @Inject constructor( private lateinit var stateRestorer: LayoutManagerStateRestorer + private val newChatBottomSheet = NewChatBottomSheet() + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomListBinding { return FragmentRoomListBinding.inflate(inflater, container, false) } @@ -117,7 +119,7 @@ class HomeRoomListFragment @Inject constructor( showFABs() views.newLayoutCreateChatButton.setOnClickListener { - // Click action for create chat modal goes here (Issue #6717) + newChatBottomSheet.show(requireActivity().supportFragmentManager, NewChatBottomSheet.TAG) } views.newLayoutOpenSpacesButton.setOnClickListener { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt new file mode 100644 index 0000000000..836346e40e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.home.room.list.home + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import im.vector.app.databinding.FragmentNewChatBottomSheetBinding + +class NewChatBottomSheet : BottomSheetDialogFragment() { + + private lateinit var binding: FragmentNewChatBottomSheetBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FragmentNewChatBottomSheetBinding.inflate(inflater, container, false) + return binding.root + } + + companion object { + const val TAG = "NewChatBottomSheet" + } +} diff --git a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml new file mode 100644 index 0000000000..8cfd4b3f31 --- /dev/null +++ b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 9fc9756897..9ea90a07d4 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -137,6 +137,8 @@ All Chats + Start chat + Create room From 4f4d7f111d1106a17ebc819195ed75f1362c5628 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 10 Aug 2022 15:25:33 +0200 Subject: [PATCH 02/30] Adds listeners to FABs --- .../im/vector/app/core/di/FragmentModule.kt | 6 +++++ .../home/room/list/home/NewChatBottomSheet.kt | 19 +++++++++++++- .../layout/fragment_new_chat_bottom_sheet.xml | 26 ++++++++++++------- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index e86b350534..fb6d6d64b8 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -63,6 +63,7 @@ import im.vector.app.features.home.room.detail.TimelineFragment import im.vector.app.features.home.room.detail.search.SearchFragment import im.vector.app.features.home.room.list.RoomListFragment import im.vector.app.features.home.room.list.home.HomeRoomListFragment +import im.vector.app.features.home.room.list.home.NewChatBottomSheet import im.vector.app.features.home.room.threads.list.views.ThreadListFragment import im.vector.app.features.location.LocationSharingFragment import im.vector.app.features.location.preview.LocationPreviewFragment @@ -209,6 +210,11 @@ interface FragmentModule { @FragmentKey(RoomListFragment::class) fun bindRoomListFragment(fragment: RoomListFragment): Fragment + @Binds + @IntoMap + @FragmentKey(NewChatBottomSheet::class) + fun bindNewChatBottomSheetFragment(fragment: NewChatBottomSheet): Fragment + @Binds @IntoMap @FragmentKey(LocalePickerFragment::class) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt index 836346e40e..93558f0d9d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt @@ -21,17 +21,34 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.databinding.FragmentNewChatBottomSheetBinding +import im.vector.app.features.navigation.Navigator +import javax.inject.Inject -class NewChatBottomSheet : BottomSheetDialogFragment() { +@AndroidEntryPoint +class NewChatBottomSheet @Inject constructor() : BottomSheetDialogFragment() { + + @Inject lateinit var navigator: Navigator private lateinit var binding: FragmentNewChatBottomSheetBinding override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = FragmentNewChatBottomSheetBinding.inflate(inflater, container, false) + initFABs() return binding.root } + private fun initFABs() { + binding.startChat.setOnClickListener { + navigator.openCreateDirectRoom(requireActivity()) + } + + binding.createRoom.setOnClickListener { + navigator.openCreateRoom(requireActivity(), "") + } + } + companion object { const val TAG = "NewChatBottomSheet" } diff --git a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml index 8cfd4b3f31..dcb727d73f 100644 --- a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml +++ b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml @@ -5,23 +5,29 @@ android:orientation="vertical"> + android:textStyle="bold" /> + android:textStyle="bold" /> From 7a50e25bea563a230593ab13f49fae505b11f095 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 10 Aug 2022 17:34:16 +0200 Subject: [PATCH 03/30] Unbolds text --- .../src/main/res/layout/fragment_new_chat_bottom_sheet.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml index dcb727d73f..266f5d7213 100644 --- a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml +++ b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml @@ -14,8 +14,7 @@ android:paddingVertical="16dp" android:text="@string/start_chat" android:textAppearance="@style/TextAppearance.Vector.Body" - android:textColor="?vctr_content_primary" - android:textStyle="bold" /> + android:textColor="?vctr_content_primary" /> + android:textColor="?vctr_content_primary" /> From 989471a409e217bacdd18ee998d639178cd29c89 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 10 Aug 2022 17:44:33 +0200 Subject: [PATCH 04/30] Adds changelog file --- changelog.d/6801.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6801.wip diff --git a/changelog.d/6801.wip b/changelog.d/6801.wip new file mode 100644 index 0000000000..bb3bc9d7a1 --- /dev/null +++ b/changelog.d/6801.wip @@ -0,0 +1 @@ +Adds new chat bottom sheet as the click action of the main FAB in the new app layout From b64c756a23a237ca9929fd799dce64ac2ca5de06 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 15 Aug 2022 09:36:58 +0100 Subject: [PATCH 05/30] showing SSO auth view when the SSO providers are null - this is a valid case where we show a fallback option instead --- .../onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt | 6 ++++-- .../onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt index c2d2346765..75b73f887e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt @@ -139,8 +139,8 @@ class FtueAuthCombinedLoginFragment @Inject constructor( } private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) { - views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true - views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible + views.ssoGroup.isVisible = true + views.ssoButtonsHeader.isVisible = isUsernameAndPasswordVisible() views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, @@ -163,6 +163,8 @@ class FtueAuthCombinedLoginFragment @Inject constructor( views.loginEntryGroup.isVisible = true } + private fun isUsernameAndPasswordVisible() = views.loginEntryGroup.isVisible + private fun setupAutoFill() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { views.loginInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt index 8340fb903a..7338de163b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt @@ -211,7 +211,7 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu } private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) { - views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true + views.ssoGroup.isVisible = true views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider -> viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, From 4ed8fe0be8814150c3b7bb30e43f20176ce9609b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 15 Aug 2022 13:00:13 +0100 Subject: [PATCH 06/30] lifting the login buttons view state to a dedicated model to make the fallback more obvious - Updates tests to take into account the fallback case --- .../login/AbstractSSOLoginFragment.kt | 2 +- .../app/features/login/LoginFragment.kt | 30 ++++----- .../im/vector/app/features/login/LoginMode.kt | 12 ++-- .../LoginSignUpSignInSelectionFragment.kt | 18 +++-- .../app/features/login/LoginViewModel.kt | 6 +- .../features/login/SocialLoginButtonsView.kt | 7 +- .../im/vector/app/features/login/SsoState.kt | 41 ++++++++++++ .../StartAuthenticationFlowUseCase.kt | 5 +- .../ftueauth/AbstractSSOFtueAuthFragment.kt | 4 +- .../ftueauth/FtueAuthCombinedLoginFragment.kt | 10 +-- .../FtueAuthCombinedRegisterFragment.kt | 7 +- .../ftueauth/FtueAuthLoginFragment.kt | 34 ++++------ .../FtueAuthSignUpSignInSelectionFragment.kt | 22 +++---- .../signout/soft/SoftLogoutFragment.kt | 4 +- .../signout/soft/SoftLogoutViewModel.kt | 5 +- .../StartAuthenticationFlowUseCaseTest.kt | 65 ++++++++++++++----- 16 files changed, 167 insertions(+), 105 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/login/SsoState.kt diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt index b18df6c9cf..ddab65d981 100644 --- a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt @@ -85,7 +85,7 @@ abstract class AbstractSSOLoginFragment : AbstractLoginFragmen private fun prefetchIfNeeded() { withState(loginViewModel) { state -> - if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) { + if (state.loginMode.hasSso() && state.loginMode.ssoState().isFallback()) { // in this case we can prefetch (not other cases for privacy concerns) loginViewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index 9c598c400b..a7cedb5825 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -100,13 +100,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment error("developer error") - SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP - SignMode.SignIn, - SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN - } + private fun ssoMode(state: LoginViewState) = when (state.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP + SignMode.SignIn, + SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN } private fun submit() { @@ -201,16 +199,13 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment + loginViewModel.getSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + providerId = provider?.id + ) + ?.let { openInCustomTab(it) } } } else { views.loginSocialLoginContainer.isVisible = false @@ -272,7 +267,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment?) : LoginMode() - @Parcelize data class SsoAndPassword(val ssoIdentityProviders: List?) : LoginMode() + @Parcelize data class Sso(val ssoState: SsoState) : LoginMode() + @Parcelize data class SsoAndPassword(val ssoState: SsoState) : LoginMode() @Parcelize object Unsupported : LoginMode() } -fun LoginMode.ssoIdentityProviders(): List? { +fun LoginMode.ssoState(): SsoState { return when (this) { - is LoginMode.Sso -> ssoIdentityProviders - is LoginMode.SsoAndPassword -> ssoIdentityProviders - else -> null + is LoginMode.Sso -> ssoState + is LoginMode.SsoAndPassword -> ssoState + else -> SsoState.Fallback } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt index 1325ea37af..8615c3c703 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt @@ -25,6 +25,7 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding +import im.vector.app.features.login.SocialLoginButtonsView.Mode import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import javax.inject.Inject @@ -73,16 +74,13 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi when (state.loginMode) { is LoginMode.SsoAndPassword -> { views.loginSignupSigninSignInSocialLoginContainer.isVisible = true - views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted() - views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = provider?.id - ) - ?.let { openInCustomTab(it) } - } + views.loginSignupSigninSocialLoginButtons.render(state.loginMode.ssoState(), Mode.MODE_CONTINUE) { provider -> + loginViewModel.getSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + providerId = provider?.id + ) + ?.let { openInCustomTab(it) } } } else -> { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index 40f72ccc99..79d06a0864 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -223,7 +223,7 @@ class LoginViewModel @AssistedInject constructor( setState { copy( signMode = SignMode.SignIn, - loginMode = LoginMode.Sso(action.ssoIdentityProviders), + loginMode = LoginMode.Sso(action.ssoIdentityProviders.toSsoState()), homeServerUrlFromUser = action.homeServerUrl, homeServerUrl = action.homeServerUrl, deviceId = action.deviceId @@ -816,8 +816,8 @@ class LoginViewModel @AssistedInject constructor( val loginMode = when { // SSO login is taken first data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders.toSsoState()) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState()) data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password else -> LoginMode.Unsupported } diff --git a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt index 4f3b1237f2..816050420e 100644 --- a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt +++ b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt @@ -160,8 +160,11 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: } } -fun SocialLoginButtonsView.render(ssoProviders: List?, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) { +fun SocialLoginButtonsView.render(state: SsoState, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) { this.mode = mode - this.ssoIdentityProviders = ssoProviders?.sorted() + this.ssoIdentityProviders = when (state) { + SsoState.Fallback -> null + is SsoState.IdentityProviders -> state.providers.sorted() + } this.listener = SocialLoginButtonsView.InteractionListener { listener(it) } } diff --git a/vector/src/main/java/im/vector/app/features/login/SsoState.kt b/vector/src/main/java/im/vector/app/features/login/SsoState.kt new file mode 100644 index 0000000000..5f57780bd7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login/SsoState.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.login + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider + +sealed interface SsoState : Parcelable { + @Parcelize + data class IdentityProviders(val providers: List) : SsoState + + @Parcelize + object Fallback : SsoState + + fun isFallback() = this == Fallback + + fun providersOrNull() = when (this) { + Fallback -> null + is IdentityProviders -> providers.takeIf { it.isNotEmpty() } + } +} + +fun List?.toSsoState() = this + ?.takeIf { it.isNotEmpty() } + ?.let { SsoState.IdentityProviders(it) } + ?: SsoState.Fallback diff --git a/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt index 2dc9a05154..db21a53854 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt @@ -18,6 +18,7 @@ package im.vector.app.features.onboarding import im.vector.app.core.extensions.containsAllItems import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.toSsoState import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.LoginFlowResult @@ -50,8 +51,8 @@ class StartAuthenticationFlowUseCase @Inject constructor( ) private fun LoginFlowResult.findPreferredLoginMode() = when { - supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders) - supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders) + supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders.toSsoState()) + supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders.toSsoState()) supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password else -> LoginMode.Unsupported } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt index 1b764f4ce6..b1352db0cc 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt @@ -26,7 +26,7 @@ import com.airbnb.mvrx.withState import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.hasSso -import im.vector.app.features.login.ssoIdentityProviders +import im.vector.app.features.login.ssoState abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthFragment() { @@ -88,7 +88,7 @@ abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthF private fun prefetchIfNeeded() { withState(viewModel) { state -> - if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) { + if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoState().isFallback()) { // in this case we can prefetch (not other cases for privacy concerns) viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt index 75b73f887e..5a5c6a48f9 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt @@ -38,7 +38,9 @@ import im.vector.app.databinding.FragmentFtueCombinedLoginBinding import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.SocialLoginButtonsView +import im.vector.app.features.login.SsoState import im.vector.app.features.login.render +import im.vector.app.features.login.ssoState import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState @@ -125,11 +127,11 @@ class FtueAuthCombinedLoginFragment @Inject constructor( when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.SsoAndPassword -> { showUsernamePassword() - renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders) + renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState) } is LoginMode.Sso -> { hideUsernamePassword() - renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders) + renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState) } else -> { showUsernamePassword() @@ -138,10 +140,10 @@ class FtueAuthCombinedLoginFragment @Inject constructor( } } - private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) { + private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) { views.ssoGroup.isVisible = true views.ssoButtonsHeader.isVisible = isUsernameAndPasswordVisible() - views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> + views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt index 7338de163b..bce20ebb12 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt @@ -44,6 +44,7 @@ import im.vector.app.databinding.FragmentFtueCombinedRegisterBinding import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.SocialLoginButtonsView +import im.vector.app.features.login.SsoState import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction @@ -205,14 +206,14 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu } when (state.selectedHomeserver.preferredLoginMode) { - is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders) + is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState) else -> hideSsoProviders() } } - private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) { + private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) { views.ssoGroup.isVisible = true - views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider -> + views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider -> viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt index 17ceb5314c..ad159943b1 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt @@ -37,7 +37,8 @@ import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode -import im.vector.app.features.login.SocialLoginButtonsView +import im.vector.app.features.login.SocialLoginButtonsView.Mode +import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState @@ -45,7 +46,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidUsername import org.matrix.android.sdk.api.failure.isLoginEmailUnknown @@ -111,13 +111,11 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } } - private fun setupSocialLoginButtons(state: OnboardingViewState) { - views.loginSocialLoginButtons.mode = when (state.signMode) { - SignMode.Unknown -> error("developer error") - SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP - SignMode.SignIn, - SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN - } + private fun ssoMode(state: OnboardingViewState) = when (state.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> Mode.MODE_SIGN_UP + SignMode.SignIn, + SignMode.SignInWithMatrixId -> Mode.MODE_SIGN_IN } private fun submit() { @@ -215,16 +213,13 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< if (state.selectedHomeserver.preferredLoginMode is LoginMode.SsoAndPassword) { views.loginSocialLoginContainer.isVisible = true - views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted() - views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - viewModel.fetchSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - provider = provider - ) - ?.let { openInCustomTab(it) } - } + views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, ssoMode(state)) { provider -> + viewModel.fetchSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + provider = provider + ) + ?.let { openInCustomTab(it) } } } else { views.loginSocialLoginContainer.isVisible = false @@ -305,7 +300,6 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< setupUi(state) setupAutoFill(state) - setupSocialLoginButtons(state) setupButtons(state) if (state.isLoading) { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt index 6723e48bcc..dc4c6d8a34 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt @@ -30,11 +30,10 @@ import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode -import im.vector.app.features.login.SocialLoginButtonsView -import im.vector.app.features.login.ssoIdentityProviders +import im.vector.app.features.login.SocialLoginButtonsView.Mode +import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import javax.inject.Inject /** @@ -80,16 +79,13 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.SsoAndPassword -> { views.loginSignupSigninSignInSocialLoginContainer.isVisible = true - views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted() - views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - viewModel.fetchSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - provider = provider - ) - ?.let { openInCustomTab(it) } - } + views.loginSignupSigninSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, Mode.MODE_CONTINUE) { provider -> + viewModel.fetchSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + provider = provider + ) + ?.let { openInCustomTab(it) } } } else -> { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt index 5b369d4b49..483e3a140f 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt @@ -63,7 +63,7 @@ class SoftLogoutFragment @Inject constructor( LoginAction.SetupSsoForSessionRecovery( softLogoutViewState.homeServerUrl, softLogoutViewState.deviceId, - mode.ssoIdentityProviders + mode.ssoState.providersOrNull() ) ) } @@ -72,7 +72,7 @@ class SoftLogoutFragment @Inject constructor( LoginAction.SetupSsoForSessionRecovery( softLogoutViewState.homeServerUrl, softLogoutViewState.deviceId, - mode.ssoIdentityProviders + mode.ssoState.providersOrNull() ) ) } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index 9d0580638b..f3e2f82edc 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -33,6 +33,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.hasUnsavedKeys import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.toSsoState import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.LoginType @@ -115,8 +116,8 @@ class SoftLogoutViewModel @AssistedInject constructor( val loginMode = when { // SSO login is taken first data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders.toSsoState()) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState()) data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password else -> LoginMode.Unsupported } diff --git a/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt index 48cd32c84d..d15a6cf042 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt @@ -17,6 +17,7 @@ package im.vector.app.features.onboarding import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.SsoState import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult import im.vector.app.test.fakes.FakeAuthenticationService import im.vector.app.test.fakes.FakeUri @@ -32,7 +33,11 @@ import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider private const val A_DECLARED_HOMESERVER_URL = "https://foo.bar" private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(homeServerUri = FakeUri().instance) -private val SSO_IDENTITY_PROVIDERS = emptyList() +private val FALLBACK_SSO_IDENTITY_PROVIDERS = emptyList() +private val SSO_IDENTITY_PROVIDERS = listOf(SsoIdentityProvider(id = "id", "name", null, "sso-brand")) +private val SSO_LOGIN_TYPE = listOf(LoginFlowTypes.SSO) +private val SSO_AND_PASSWORD_LOGIN_TYPES = listOf(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) +private val PASSWORD_LOGIN_TYPE = listOf(LoginFlowTypes.PASSWORD) class StartAuthenticationFlowUseCaseTest { @@ -47,7 +52,7 @@ class StartAuthenticationFlowUseCaseTest { @Test fun `given empty login result when starting authentication flow then returns empty result`() = runTest { - val loginResult = aLoginResult() + val loginResult = aLoginResult(supportedLoginTypes = emptyList()) fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) val result = useCase.execute(A_HOMESERVER_CONFIG) @@ -57,55 +62,81 @@ class StartAuthenticationFlowUseCaseTest { } @Test - fun `given login supports SSO and Password when starting authentication flow then prefers SsoAndPassword`() = runTest { - val supportedLoginTypes = listOf(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) - val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes) + fun `given empty sso providers and login supports SSO and Password when starting authentication flow then prefers fallback SsoAndPassword`() = runTest { + val loginResult = aLoginResult(supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, ssoProviders = emptyList()) fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) val result = useCase.execute(A_HOMESERVER_CONFIG) result shouldBeEqualTo expectedResult( - supportedLoginTypes = supportedLoginTypes, - preferredLoginMode = LoginMode.SsoAndPassword(SSO_IDENTITY_PROVIDERS), + supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, + preferredLoginMode = LoginMode.SsoAndPassword(SsoState.Fallback), ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } @Test - fun `given login supports SSO when starting authentication flow then prefers Sso`() = runTest { - val supportedLoginTypes = listOf(LoginFlowTypes.SSO) - val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes) + fun `given sso providers and login supports SSO and Password when starting authentication flow then prefers SsoAndPassword`() = runTest { + val loginResult = aLoginResult(supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, ssoProviders = SSO_IDENTITY_PROVIDERS) fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) val result = useCase.execute(A_HOMESERVER_CONFIG) result shouldBeEqualTo expectedResult( - supportedLoginTypes = supportedLoginTypes, - preferredLoginMode = LoginMode.Sso(SSO_IDENTITY_PROVIDERS), + supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, + preferredLoginMode = LoginMode.SsoAndPassword(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)), + ) + verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) + } + + @Test + fun `given empty sso providers and login supports SSO when starting authentication flow then prefers fallback Sso`() = runTest { + val loginResult = aLoginResult(supportedLoginTypes = SSO_LOGIN_TYPE, ssoProviders = emptyList()) + fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) + + val result = useCase.execute(A_HOMESERVER_CONFIG) + + result shouldBeEqualTo expectedResult( + supportedLoginTypes = SSO_LOGIN_TYPE, + preferredLoginMode = LoginMode.Sso(SsoState.Fallback), + ) + verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) + } + + @Test + fun `given identity providers and login supports SSO when starting authentication flow then prefers Sso`() = runTest { + val loginResult = aLoginResult(supportedLoginTypes = SSO_LOGIN_TYPE, ssoProviders = SSO_IDENTITY_PROVIDERS) + fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) + + val result = useCase.execute(A_HOMESERVER_CONFIG) + + result shouldBeEqualTo expectedResult( + supportedLoginTypes = SSO_LOGIN_TYPE, + preferredLoginMode = LoginMode.Sso(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)), ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } @Test fun `given login supports Password when starting authentication flow then prefers Password`() = runTest { - val supportedLoginTypes = listOf(LoginFlowTypes.PASSWORD) - val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes) + val loginResult = aLoginResult(supportedLoginTypes = PASSWORD_LOGIN_TYPE) fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) val result = useCase.execute(A_HOMESERVER_CONFIG) result shouldBeEqualTo expectedResult( - supportedLoginTypes = supportedLoginTypes, + supportedLoginTypes = PASSWORD_LOGIN_TYPE, preferredLoginMode = LoginMode.Password, ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } private fun aLoginResult( - supportedLoginTypes: List = emptyList() + supportedLoginTypes: List, + ssoProviders: List = FALLBACK_SSO_IDENTITY_PROVIDERS ) = LoginFlowResult( supportedLoginTypes = supportedLoginTypes, - ssoIdentityProviders = SSO_IDENTITY_PROVIDERS, + ssoIdentityProviders = ssoProviders, isLoginAndRegistrationSupported = true, homeServerUrl = A_DECLARED_HOMESERVER_URL, isOutdatedHomeserver = false, From b152910f9c4f365428fe95180e5a625234cbc4f1 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 15 Aug 2022 14:12:42 +0100 Subject: [PATCH 07/30] adding changelog entry --- changelog.d/6827.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6827.bugfix diff --git a/changelog.d/6827.bugfix b/changelog.d/6827.bugfix new file mode 100644 index 0000000000..2c3e130aa3 --- /dev/null +++ b/changelog.d/6827.bugfix @@ -0,0 +1 @@ +Fixing sign in/up for homeservers that rely on the SSO fallback url From 497a6e7406d8e7d80c2c8ac8b617018225ceaa5f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 15 Aug 2022 14:52:59 +0100 Subject: [PATCH 08/30] removing unused imports --- .../src/main/java/im/vector/app/features/login/LoginFragment.kt | 1 - vector/src/main/java/im/vector/app/features/login/LoginMode.kt | 1 - .../app/features/login/LoginSignUpSignInSelectionFragment.kt | 1 - .../onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt | 2 -- .../onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt | 1 - 5 files changed, 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index a7cedb5825..03010e0a75 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -37,7 +37,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword diff --git a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt index b402f9e582..944b159441 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt @@ -18,7 +18,6 @@ package im.vector.app.features.login import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider sealed class LoginMode : Parcelable { // Parcelable because persist state diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt index 8615c3c703..aafd426335 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt @@ -26,7 +26,6 @@ import im.vector.app.R import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding import im.vector.app.features.login.SocialLoginButtonsView.Mode -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import javax.inject.Inject /** diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt index 5a5c6a48f9..b24511b4bc 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt @@ -40,13 +40,11 @@ import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.SocialLoginButtonsView import im.vector.app.features.login.SsoState import im.vector.app.features.login.render -import im.vector.app.features.login.ssoState import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt index bce20ebb12..d06d1e8051 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt @@ -52,7 +52,6 @@ import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidUsername From f119de43b78f6eeffad5cdd8170d0a744a01a2f6 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 17 Aug 2022 21:12:08 +0200 Subject: [PATCH 09/30] Adds explore rooms option to new chat bottom sheet --- .../home/room/list/home/NewChatBottomSheet.kt | 6 +++- vector/src/main/res/drawable/ic_chat.xml | 10 ++++++ vector/src/main/res/drawable/ic_room_add.xml | 10 ++++++ .../src/main/res/drawable/ic_room_explore.xml | 14 +++++++++ .../layout/fragment_new_chat_bottom_sheet.xml | 31 +++++++++++++++---- 5 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_chat.xml create mode 100644 vector/src/main/res/drawable/ic_room_add.xml create mode 100644 vector/src/main/res/drawable/ic_room_explore.xml diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt index 93558f0d9d..05b86f7393 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt @@ -45,7 +45,11 @@ class NewChatBottomSheet @Inject constructor() : BottomSheetDialogFragment() { } binding.createRoom.setOnClickListener { - navigator.openCreateRoom(requireActivity(), "") + navigator.openCreateRoom(requireActivity()) + } + + binding.exploreRooms.setOnClickListener { + navigator.openRoomDirectory(requireContext()) } } diff --git a/vector/src/main/res/drawable/ic_chat.xml b/vector/src/main/res/drawable/ic_chat.xml new file mode 100644 index 0000000000..fb10eae9c9 --- /dev/null +++ b/vector/src/main/res/drawable/ic_chat.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_room_add.xml b/vector/src/main/res/drawable/ic_room_add.xml new file mode 100644 index 0000000000..8404ff2181 --- /dev/null +++ b/vector/src/main/res/drawable/ic_room_add.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_room_explore.xml b/vector/src/main/res/drawable/ic_room_explore.xml new file mode 100644 index 0000000000..9811a09054 --- /dev/null +++ b/vector/src/main/res/drawable/ic_room_explore.xml @@ -0,0 +1,14 @@ + + + + diff --git a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml index 266f5d7213..af6f00cb9f 100644 --- a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml +++ b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml @@ -1,31 +1,50 @@ + android:textColor="?vctr_content_primary" + android:textSize="16sp" + app:drawableStartCompat="@drawable/ic_chat" /> + android:textColor="?vctr_content_primary" + android:textSize="16sp" + app:drawableStartCompat="@drawable/ic_room_add" /> + + From 899673495a1a6adb2d3dde43cb8f8c20d02dae64 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 17 Aug 2022 21:42:39 +0200 Subject: [PATCH 10/30] Changes copies on sheet actions --- .../src/main/res/layout/fragment_new_chat_bottom_sheet.xml | 2 +- vector/src/main/res/values/strings.xml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml index af6f00cb9f..07c19b43eb 100644 --- a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml +++ b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml @@ -42,7 +42,7 @@ android:drawablePadding="16dp" android:paddingHorizontal="16dp" android:paddingVertical="16dp" - android:text="@string/space_explore_activity_title" + android:text="@string/explore_rooms" android:textColor="?vctr_content_primary" android:textSize="16sp" app:drawableStartCompat="@drawable/ic_room_explore" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 54ff379d08..4502304cc7 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -137,9 +137,10 @@ All Chats - Start chat - Create room + Start Chat + Create Room Change Space + Explore Rooms From 457f7fffeecbcd403f817306264421a12fa2c4dd Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 18 Aug 2022 09:50:43 +0100 Subject: [PATCH 11/30] promoting the accept certificate to an explict ViewEvent - allows a retryAction to be provided to the event to avoid mutatble state within the view model along with providing a clear path of execution --- .../android/sdk/api/failure/Extensions.kt | 2 + .../features/onboarding/OnboardingAction.kt | 2 +- .../onboarding/OnboardingViewEvents.kt | 2 + .../onboarding/OnboardingViewModel.kt | 42 +++++++++++-------- .../ftueauth/AbstractFtueAuthFragment.kt | 12 +++--- .../onboarding/ftueauth/FtueAuthVariant.kt | 1 + 6 files changed, 36 insertions(+), 25 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index 68b931b33c..429d346a1b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -93,6 +93,8 @@ fun Throwable.isMissingEmailVerification() = this is Failure.ServerError && error.code == MatrixError.M_UNAUTHORIZED && error.message == "Unable to get validated threepid" +fun Throwable.isUnrecognisedCertificate() = this is Failure.UnrecognizedCertificateFailure + /** * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible */ diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index d07ac46b85..f1617b660b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -82,7 +82,7 @@ sealed interface OnboardingAction : VectorViewModelAction { data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction - data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction + data class UserAcceptCertificate(val fingerprint: Fingerprint, val retryAction: OnboardingAction) : OnboardingAction object PersonalizeProfile : OnboardingAction data class UpdateDisplayName(val displayName: String) : OnboardingAction diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt index bbbf13fba9..1441152128 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt @@ -21,6 +21,7 @@ import im.vector.app.core.platform.VectorViewEvents import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.failure.Failure as SdkFailure /** * Transient events for Login. @@ -29,6 +30,7 @@ sealed class OnboardingViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : OnboardingViewEvents() data class Failure(val throwable: Throwable) : OnboardingViewEvents() data class DeeplinkAuthenticationFailure(val retryAction: OnboardingAction) : OnboardingViewEvents() + data class UnrecognisedCertificateFailure(val retryAction: OnboardingAction, val cause: SdkFailure.UnrecognizedCertificateFailure) : OnboardingViewEvents() object DisplayRegistrationFallback : OnboardingViewEvents() data class DisplayRegistrationStage(val stage: Stage) : OnboardingViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 73288bd6d5..b0ba113d41 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -60,7 +60,9 @@ import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability import org.matrix.android.sdk.api.auth.registration.RegistrationWizard +import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.isHomeserverUnavailable +import org.matrix.android.sdk.api.failure.isUnrecognisedCertificate import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import timber.log.Timber @@ -113,8 +115,6 @@ class OnboardingViewModel @AssistedInject constructor( } } - // Store the last action, to redo it after user has trusted the untrusted certificate - private var lastAction: OnboardingAction? = null private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() @@ -146,9 +146,9 @@ class OnboardingViewModel @AssistedInject constructor( is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) is OnboardingAction.InitWith -> handleInitWith(action) - is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(action) } + is OnboardingAction.HomeServerChange -> handleHomeserverChange(action) is OnboardingAction.UserNameEnteredAction -> handleUserNameEntered(action) - is AuthenticateAction -> withAction(action) { handleAuthenticateAction(action) } + is AuthenticateAction -> handleAuthenticateAction(action) is OnboardingAction.LoginWithToken -> handleLoginWithToken(action) is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action) is OnboardingAction.ResetPassword -> handleResetPassword(action) @@ -221,11 +221,6 @@ class OnboardingViewModel @AssistedInject constructor( ) } - private fun withAction(action: OnboardingAction, block: (OnboardingAction) -> Unit) { - lastAction = action - block(action) - } - private fun handleAuthenticateAction(action: AuthenticateAction) { when (action) { is AuthenticateAction.Register -> handleRegisterWith(action.username, action.password, action.initialDeviceName) @@ -276,15 +271,15 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) { // It happens when we get the login flow, or during direct authentication. // So alter the homeserver config and retrieve again the login flow - when (val finalLastAction = lastAction) { - is OnboardingAction.HomeServerChange.SelectHomeServer -> { + when (action.retryAction) { + is OnboardingAction.HomeServerChange -> { currentHomeServerConnectionConfig ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } - ?.let { startAuthenticationFlow(finalLastAction, it, serverTypeOverride = null) } + ?.let { startAuthenticationFlow(action.retryAction, it, serverTypeOverride = null) } } is AuthenticateAction.LoginDirect -> handleDirectLogin( - finalLastAction, + action.retryAction, HomeServerConnectionConfig.Builder() // Will be replaced by the task .withHomeServerUri("https://dummy.org") @@ -589,9 +584,19 @@ class OnboardingViewModel @AssistedInject constructor( currentJob = viewModelScope.launch { directLoginUseCase.execute(action, homeServerConnectionConfig).fold( onSuccess = { onSessionCreated(it, authenticationDescription = AuthenticationDescription.Login) }, - onFailure = { + onFailure = { error -> setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(it)) + when { + error.isUnrecognisedCertificate() -> { + _viewEvents.post( + OnboardingViewEvents.UnrecognisedCertificateFailure( + retryAction = action, + cause = error as Failure.UnrecognizedCertificateFailure + ) + ) + } + else -> _viewEvents.post(OnboardingViewEvents.Failure(error)) + } } ) } @@ -723,9 +728,10 @@ class OnboardingViewModel @AssistedInject constructor( retryAction = (trigger as OnboardingAction.HomeServerChange.SelectHomeServer).resetToDefaultUrl() ) ) - else -> _viewEvents.post( - OnboardingViewEvents.Failure(error) - ) + error.isUnrecognisedCertificate() -> { + _viewEvents.post(OnboardingViewEvents.UnrecognisedCertificateFailure(trigger, error as Failure.UnrecognizedCertificateFailure)) + } + else -> _viewEvents.post(OnboardingViewEvents.Failure(error)) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt index 072e94bc30..f3cb326221 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt @@ -33,7 +33,6 @@ import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.CancellationException -import org.matrix.android.sdk.api.failure.Failure /** * Parent Fragment for all the login/registration screens. @@ -68,6 +67,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment showFailure(viewEvents.throwable) + is OnboardingViewEvents.UnrecognisedCertificateFailure -> showUnrecognizedCertificateFailure(viewEvents) else -> // This is handled by the Activity Unit @@ -84,20 +84,20 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment /* Ignore this error, user has cancelled the action */ Unit - is Failure.UnrecognizedCertificateFailure -> showUnrecognizedCertificateFailure(throwable) else -> onError(throwable) } } - private fun showUnrecognizedCertificateFailure(failure: Failure.UnrecognizedCertificateFailure) { + private fun showUnrecognizedCertificateFailure(event: OnboardingViewEvents.UnrecognisedCertificateFailure) { // Ask the user to accept the certificate + val cause = event.cause unrecognizedCertificateDialog.show(requireActivity(), - failure.fingerprint, - failure.url, + cause.fingerprint, + cause.url, object : UnrecognizedCertificateDialog.Callback { override fun onAccept() { // User accept the certificate - viewModel.handle(OnboardingAction.UserAcceptCertificate(failure.fingerprint)) + viewModel.handle(OnboardingAction.UserAcceptCertificate(cause.fingerprint, event.retryAction)) } override fun onIgnore() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 150ab74ec2..e568b3d92b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -202,6 +202,7 @@ class FtueAuthVariant( openMsisdnConfirmation(viewEvents.msisdn) } is OnboardingViewEvents.Failure, + is OnboardingViewEvents.UnrecognisedCertificateFailure, is OnboardingViewEvents.Loading -> // This is handled by the Fragments Unit From a6ff10cbafd57ab4ba72292b062084d9788649ff Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 18 Aug 2022 10:02:47 +0100 Subject: [PATCH 12/30] allowing fingerprint to be passed to the config factory - which in turn allows the android Uri to be bypassed and a unit test around the direct local certificate case added --- .../HomeServerConnectionConfigFactory.kt | 10 ++++- .../onboarding/OnboardingViewModel.kt | 7 +--- .../onboarding/OnboardingViewModelTest.kt | 37 ++++++++++++++++--- .../FakeHomeServerConnectionConfigFactory.kt | 5 ++- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/HomeServerConnectionConfigFactory.kt b/vector/src/main/java/im/vector/app/features/login/HomeServerConnectionConfigFactory.kt index 955c3f7290..253c514e5a 100644 --- a/vector/src/main/java/im/vector/app/features/login/HomeServerConnectionConfigFactory.kt +++ b/vector/src/main/java/im/vector/app/features/login/HomeServerConnectionConfigFactory.kt @@ -17,12 +17,13 @@ package im.vector.app.features.login import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.network.ssl.Fingerprint import timber.log.Timber import javax.inject.Inject class HomeServerConnectionConfigFactory @Inject constructor() { - fun create(url: String?): HomeServerConnectionConfig? { + fun create(url: String?, fingerprint: Fingerprint? = null): HomeServerConnectionConfig? { if (url == null) { return null } @@ -30,6 +31,13 @@ class HomeServerConnectionConfigFactory @Inject constructor() { return try { HomeServerConnectionConfig.Builder() .withHomeServerUri(url) + .run { + if (fingerprint == null) { + this + } else { + withAllowedFingerPrints(listOf(fingerprint)) + } + } .build() } catch (t: Throwable) { Timber.e(t) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index b0ba113d41..0cd5437e5b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -280,11 +280,8 @@ class OnboardingViewModel @AssistedInject constructor( is AuthenticateAction.LoginDirect -> handleDirectLogin( action.retryAction, - HomeServerConnectionConfig.Builder() - // Will be replaced by the task - .withHomeServerUri("https://dummy.org") - .withAllowedFingerPrints(listOf(action.fingerprint)) - .build() + // Will be replaced by the task + homeServerConnectionConfigFactory.create("https://dummy.org", action.fingerprint) ) else -> Unit } diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index b505d05944..f802c3e8f6 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -58,6 +58,7 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.network.ssl.Fingerprint import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities @@ -69,6 +70,7 @@ private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenValidated(delayMillis = -1L) private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.SendAgainThreePid private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true) +private val A_FINGERPRINT = Fingerprint(ByteArray(1), Fingerprint.HashType.SHA1) private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationActionHandler.Result.NextStage(Stage.Dummy(mandatory = true)) private val A_DIRECT_LOGIN = OnboardingAction.AuthenticateAction.LoginDirect("@a-user:id.org", "a-password", "a-device-name") private const val A_HOMESERVER_URL = "https://edited-homeserver.org" @@ -406,7 +408,7 @@ class OnboardingViewModelTest { @Test fun `given unavailable deeplink, when selecting homeserver, then emits failure with default homeserver as retry action`() = runTest { fakeContext.givenHasConnection() - fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, A_HOMESERVER_CONFIG) + fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, fingerprint = null, A_HOMESERVER_CONFIG) fakeStartAuthenticationFlowUseCase.givenHomeserverUnavailable(A_HOMESERVER_CONFIG) val test = viewModel.test() @@ -723,6 +725,25 @@ class OnboardingViewModelTest { .assertEvents(OnboardingViewEvents.OnPersonalizationComplete) .finish() } + + @Test + fun `given DirectLogin retry action, when accepting user certificate, then logs in directly`() = runTest { + fakeHomeServerConnectionConfigFactory.givenConfigFor("https://dummy.org", A_FINGERPRINT, A_HOMESERVER_CONFIG) + fakeDirectLoginUseCase.givenSuccessResult(A_DIRECT_LOGIN, config = A_HOMESERVER_CONFIG, result = fakeSession) + givenInitialisesSession(fakeSession) + val test = viewModel.test() + + viewModel.handle(OnboardingAction.UserAcceptCertificate(A_FINGERPRINT, A_DIRECT_LOGIN)) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.OnAccountSignedIn) + .finish() + } @Test fun `given can successfully start password reset, when resetting password, then emits confirmation email sent`() = runTest { @@ -991,15 +1012,19 @@ class OnboardingViewModelTest { fakeRegistrationActionHandler.givenResultsFor(results) } - private fun givenCanSuccessfullyUpdateHomeserver(homeserverUrl: String, resultingState: SelectedHomeserverState) { - fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, A_HOMESERVER_CONFIG) - fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(isHomeserverOutdated = false, resultingState)) + private fun givenCanSuccessfullyUpdateHomeserver( + homeserverUrl: String, + resultingState: SelectedHomeserverState, + config: HomeServerConnectionConfig = A_HOMESERVER_CONFIG + ) { + fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, fingerprint = null, config) + fakeStartAuthenticationFlowUseCase.givenResult(config, StartAuthenticationResult(isHomeserverOutdated = false, resultingState)) givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.StartRegistration) - fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString()) + fakeHomeServerHistoryService.expectUrlToBeAdded(config.homeServerUri.toString()) } private fun givenUpdatingHomeserverErrors(homeserverUrl: String, resultingState: SelectedHomeserverState, error: Throwable) { - fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, A_HOMESERVER_CONFIG) + fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, fingerprint = null, A_HOMESERVER_CONFIG) fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(isHomeserverOutdated = false, resultingState)) givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.Error(error)) fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString()) diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerConnectionConfigFactory.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerConnectionConfigFactory.kt index 553a35ad8c..c0cfe5375b 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerConnectionConfigFactory.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerConnectionConfigFactory.kt @@ -20,11 +20,12 @@ import im.vector.app.features.login.HomeServerConnectionConfigFactory import io.mockk.every import io.mockk.mockk import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.network.ssl.Fingerprint class FakeHomeServerConnectionConfigFactory { val instance: HomeServerConnectionConfigFactory = mockk() - fun givenConfigFor(url: String, config: HomeServerConnectionConfig) { - every { instance.create(url) } returns config + fun givenConfigFor(url: String, fingerprint: Fingerprint? = null, config: HomeServerConnectionConfig) { + every { instance.create(url, fingerprint) } returns config } } From e4a08d1be14c07b7795708a306f3ff25e03d4e78 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 18 Aug 2022 10:59:02 +0100 Subject: [PATCH 13/30] recreating the homeserver config from the retry action when handle certificate accept action - adds unit tests around the edit/selection cases --- .../onboarding/OnboardingViewModel.kt | 20 +++---- .../onboarding/OnboardingViewModelTest.kt | 59 ++++++++++++++++++- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 0cd5437e5b..6228b95398 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -63,6 +63,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import org.matrix.android.sdk.api.failure.isUnrecognisedCertificate +import org.matrix.android.sdk.api.network.ssl.Fingerprint import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import timber.log.Timber @@ -115,8 +116,6 @@ class OnboardingViewModel @AssistedInject constructor( } } - private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null - private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() private val defaultHomeserverUrl = matrixOrgUrl @@ -272,11 +271,7 @@ class OnboardingViewModel @AssistedInject constructor( // It happens when we get the login flow, or during direct authentication. // So alter the homeserver config and retrieve again the login flow when (action.retryAction) { - is OnboardingAction.HomeServerChange -> { - currentHomeServerConnectionConfig - ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } - ?.let { startAuthenticationFlow(action.retryAction, it, serverTypeOverride = null) } - } + is OnboardingAction.HomeServerChange -> handleHomeserverChange(action.retryAction, fingerprint = action.fingerprint) is AuthenticateAction.LoginDirect -> handleDirectLogin( action.retryAction, @@ -684,8 +679,13 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun handleHomeserverChange(action: OnboardingAction.HomeServerChange, serverTypeOverride: ServerType? = null, postAction: suspend () -> Unit = {}) { - val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl) + private fun handleHomeserverChange( + action: OnboardingAction.HomeServerChange, + serverTypeOverride: ServerType? = null, + fingerprint: Fingerprint? = null, + postAction: suspend () -> Unit = {}, + ) { + val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl, fingerprint) if (homeServerConnectionConfig == null) { // This is invalid _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) @@ -700,8 +700,6 @@ class OnboardingViewModel @AssistedInject constructor( serverTypeOverride: ServerType?, postAction: suspend () -> Unit = {}, ) { - currentHomeServerConnectionConfig = homeServerConnectionConfig - currentJob = viewModelScope.launch { setState { copy(isLoading = true) } runCatching { startAuthenticationFlowUseCase.execute(homeServerConnectionConfig) }.fold( diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index f802c3e8f6..c612644576 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -725,7 +725,59 @@ class OnboardingViewModelTest { .assertEvents(OnboardingViewEvents.OnPersonalizationComplete) .finish() } - + + + @Test + fun `given in sign in mode, when accepting user certificate with SelectHomeserver retry action, then emits OnHomeserverEdited`() = runTest { + viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignIn)) + val test = viewModel.test() + fakeVectorFeatures.givenCombinedLoginEnabled() + givenCanSuccessfullyUpdateHomeserver( + A_HOMESERVER_URL, + SELECTED_HOMESERVER_STATE, + config = A_HOMESERVER_CONFIG.copy(allowedFingerprints = listOf(A_FINGERPRINT)), + fingerprint = A_FINGERPRINT, + ) + + viewModel.handle(OnboardingAction.UserAcceptCertificate(A_FINGERPRINT, OnboardingAction.HomeServerChange.SelectHomeServer(A_HOMESERVER_URL))) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(selectedHomeserver = SELECTED_HOMESERVER_STATE) }, + { copy(signMode = SignMode.SignIn) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.OpenCombinedLogin) + .finish() + } + + @Test + fun `given in sign up mode, when accepting user certificate with EditHomeserver retry action, then emits OnHomeserverEdited`() = runTest { + viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp)) + givenCanSuccessfullyUpdateHomeserver( + A_HOMESERVER_URL, + SELECTED_HOMESERVER_STATE, + config = A_HOMESERVER_CONFIG.copy(allowedFingerprints = listOf(A_FINGERPRINT)), + fingerprint = A_FINGERPRINT, + ) + val test = viewModel.test() + + viewModel.handle(OnboardingAction.UserAcceptCertificate(A_FINGERPRINT, OnboardingAction.HomeServerChange.EditHomeServer(A_HOMESERVER_URL))) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(selectedHomeserver = SELECTED_HOMESERVER_STATE) }, + { copy(isLoading = false) } + + ) + .assertEvents(OnboardingViewEvents.OnHomeserverEdited) + .finish() + } + @Test fun `given DirectLogin retry action, when accepting user certificate, then logs in directly`() = runTest { fakeHomeServerConnectionConfigFactory.givenConfigFor("https://dummy.org", A_FINGERPRINT, A_HOMESERVER_CONFIG) @@ -1015,9 +1067,10 @@ class OnboardingViewModelTest { private fun givenCanSuccessfullyUpdateHomeserver( homeserverUrl: String, resultingState: SelectedHomeserverState, - config: HomeServerConnectionConfig = A_HOMESERVER_CONFIG + config: HomeServerConnectionConfig = A_HOMESERVER_CONFIG, + fingerprint: Fingerprint? = null, ) { - fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, fingerprint = null, config) + fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, fingerprint, config) fakeStartAuthenticationFlowUseCase.givenResult(config, StartAuthenticationResult(isHomeserverOutdated = false, resultingState)) givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.StartRegistration) fakeHomeServerHistoryService.expectUrlToBeAdded(config.homeServerUri.toString()) From 91176eca2243b77984ab52dc544330b7bf480b44 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 18 Aug 2022 11:22:38 +0100 Subject: [PATCH 14/30] adding test cases around certificate errors being thrown and mapped --- .../onboarding/OnboardingViewModelTest.kt | 60 ++++++++++++++++++- .../FakeStartAuthenticationFlowUseCase.kt | 4 ++ .../app/test/fixtures/FailureFixture.kt | 3 + 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index c612644576..216cb76084 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -48,6 +48,7 @@ import im.vector.app.test.fakes.FakeVectorOverrides import im.vector.app.test.fakes.toTestString import im.vector.app.test.fixtures.a401ServerError import im.vector.app.test.fixtures.aHomeServerCapabilities +import im.vector.app.test.fixtures.anUnrecognisedCertificateError import im.vector.app.test.test import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo @@ -66,6 +67,7 @@ private const val A_DISPLAY_NAME = "a display name" private const val A_PICTURE_FILENAME = "a-picture.png" private val A_SERVER_ERROR = a401ServerError() private val AN_ERROR = RuntimeException("an error!") +private val AN_UNRECOGNISED_CERTIFICATE_ERROR = anUnrecognisedCertificateError() private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenValidated(delayMillis = -1L) private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.SendAgainThreePid @@ -322,6 +324,25 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given has sign in with matrix id sign mode, when handling login or register action fails with certificate error, then emits error`() = runTest { + viewModelWith(initialState.copy(signMode = SignMode.SignInWithMatrixId)) + fakeDirectLoginUseCase.givenFailureResult(A_DIRECT_LOGIN, config = null, cause = AN_UNRECOGNISED_CERTIFICATE_ERROR) + givenInitialisesSession(fakeSession) + val test = viewModel.test() + + viewModel.handle(A_DIRECT_LOGIN) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.UnrecognisedCertificateFailure(A_DIRECT_LOGIN, AN_UNRECOGNISED_CERTIFICATE_ERROR)) + .finish() + } + @Test fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runTest { givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT) @@ -550,6 +571,44 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `when editing homeserver errors with certificate error, then emits error`() = runTest { + fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, fingerprint = null, A_HOMESERVER_CONFIG) + fakeStartAuthenticationFlowUseCase.givenErrors(A_HOMESERVER_CONFIG, AN_UNRECOGNISED_CERTIFICATE_ERROR) + val editAction = OnboardingAction.HomeServerChange.EditHomeServer(A_HOMESERVER_URL) + val test = viewModel.test() + + viewModel.handle(editAction) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.UnrecognisedCertificateFailure(editAction, AN_UNRECOGNISED_CERTIFICATE_ERROR)) + .finish() + } + + @Test + fun `when selecting homeserver errors with certificate error, then emits error`() = runTest { + fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, fingerprint = null, A_HOMESERVER_CONFIG) + fakeStartAuthenticationFlowUseCase.givenErrors(A_HOMESERVER_CONFIG, AN_UNRECOGNISED_CERTIFICATE_ERROR) + val selectAction = OnboardingAction.HomeServerChange.SelectHomeServer(A_HOMESERVER_URL) + val test = viewModel.test() + + viewModel.handle(selectAction) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.UnrecognisedCertificateFailure(selectAction, AN_UNRECOGNISED_CERTIFICATE_ERROR)) + .finish() + } + @Test fun `given unavailable full matrix id, when a register username is entered, then emits availability error`() = runTest { viewModelWith(initialRegistrationState("ignored-url")) @@ -726,7 +785,6 @@ class OnboardingViewModelTest { .finish() } - @Test fun `given in sign in mode, when accepting user certificate with SelectHomeserver retry action, then emits OnHomeserverEdited`() = runTest { viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignIn)) diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt index bfbef9e565..4b2709facc 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt @@ -31,6 +31,10 @@ class FakeStartAuthenticationFlowUseCase { coEvery { instance.execute(config) } returns result } + fun givenErrors(config: HomeServerConnectionConfig, error: Throwable) { + coEvery { instance.execute(config) } throws error + } + fun givenHomeserverUnavailable(config: HomeServerConnectionConfig) { coEvery { instance.execute(config) } throws aHomeserverUnavailableError() } diff --git a/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt index 0f44976ab3..8437401294 100644 --- a/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt +++ b/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt @@ -18,6 +18,7 @@ package im.vector.app.test.fixtures import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.network.ssl.Fingerprint import java.net.UnknownHostException import javax.net.ssl.HttpsURLConnection @@ -38,3 +39,5 @@ fun aLoginEmailUnknownError() = Failure.ServerError( ) fun aHomeserverUnavailableError() = Failure.NetworkConnection(UnknownHostException()) + +fun anUnrecognisedCertificateError() = Failure.UnrecognizedCertificateFailure("a-url", Fingerprint(ByteArray(1), Fingerprint.HashType.SHA1)) From e948fe05ca8a55b59ceb619a1750477548347481 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 18 Aug 2022 11:37:12 +0100 Subject: [PATCH 15/30] adding changelog entry --- changelog.d/6864.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6864.bugfix diff --git a/changelog.d/6864.bugfix b/changelog.d/6864.bugfix new file mode 100644 index 0000000000..6db3d7c074 --- /dev/null +++ b/changelog.d/6864.bugfix @@ -0,0 +1 @@ +Fixes server selection being unable to trust certificates From ec3512fd5b8c94c964eb16dc14933f775afaf0d6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Aug 2022 14:58:19 +0200 Subject: [PATCH 16/30] Increase some log level --- .../android/sdk/internal/util/BackgroundDetectionObserver.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt index 901d0eca8f..dea5f131b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt @@ -49,13 +49,13 @@ internal class DefaultBackgroundDetectionObserver : BackgroundDetectionObserver } override fun onStart(owner: LifecycleOwner) { - Timber.v("App returning to foreground…") + Timber.d("App returning to foreground…") isInBackground = false listeners.forEach { it.onMoveToForeground() } } override fun onStop(owner: LifecycleOwner) { - Timber.v("App going to background…") + Timber.d("App going to background…") isInBackground = true listeners.forEach { it.onMoveToBackground() } } From 4ffab7fc130c1600571cade42b5d336c4f415648 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Aug 2022 15:09:48 +0200 Subject: [PATCH 17/30] API: return String instead of printing out to logcat. --- .../org/matrix/android/sdk/api/debug/DebugService.kt | 4 ++-- .../java/org/matrix/android/sdk/api/session/Session.kt | 4 ++-- .../sdk/internal/database/tools/RealmDebugTools.kt | 8 +++----- .../android/sdk/internal/debug/DefaultDebugService.kt | 8 ++++---- .../android/sdk/internal/session/DefaultSession.kt | 10 +++++----- .../im/vector/app/features/rageshake/BugReporter.kt | 7 ++++++- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt index d0cee08831..7f5e4f2ee7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt @@ -28,7 +28,7 @@ interface DebugService { fun getAllRealmConfigurations(): List /** - * Prints out info on DB size to logcat. + * Get info on DB size. */ - fun logDbUsageInfo() + fun getDbUsageInfo(): String } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 63c1c25130..13993149f4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -323,9 +323,9 @@ interface Session { fun getUiaSsoFallbackUrl(authenticationSessionId: String): String /** - * Debug API, will print out info on DB size to logcat. + * Debug API, will return info about the DB. */ - fun logDbUsageInfo() + fun getDbUsageInfo(): String /** * Debug API, return the list of all RealmConfiguration used by this session. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt index dc20549eb3..2e9c3303d4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt @@ -19,16 +19,15 @@ package org.matrix.android.sdk.internal.database.tools import io.realm.Realm import io.realm.RealmConfiguration import org.matrix.android.sdk.BuildConfig -import timber.log.Timber internal class RealmDebugTools( private val realmConfiguration: RealmConfiguration ) { /** - * Log info about the DB. + * Get info about the DB. */ - fun logInfo(baseName: String) { - buildString { + fun getInfo(baseName: String): String { + return buildString { append("\n$baseName Realm located at : ${realmConfiguration.realmDirectory}/${realmConfiguration.realmFileName}") if (BuildConfig.LOG_PRIVATE_DATA) { @@ -54,7 +53,6 @@ internal class RealmDebugTools( separator() } } - .let { Timber.i(it) } } private fun StringBuilder.separator() = append("\n==============================================") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt index 3f2e6fafc8..46479c3db6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt @@ -36,9 +36,9 @@ internal class DefaultDebugService @Inject constructor( realmConfigurationGlobal } - override fun logDbUsageInfo() { - RealmDebugTools(realmConfigurationAuth).logInfo("Auth") - RealmDebugTools(realmConfigurationGlobal).logInfo("Global") - sessionManager.getLastSession()?.logDbUsageInfo() + override fun getDbUsageInfo() = buildString { + append(RealmDebugTools(realmConfigurationAuth).getInfo("Auth")) + append(RealmDebugTools(realmConfigurationGlobal).getInfo("Global")) + append(sessionManager.getLastSession()?.getDbUsageInfo()) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 57db187bdc..679c5085ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -263,11 +263,11 @@ internal class DefaultSession @Inject constructor( } } - override fun logDbUsageInfo() { - RealmDebugTools(realmConfiguration).logInfo("Session") - RealmDebugTools(realmConfigurationCrypto).logInfo("Crypto") - RealmDebugTools(realmConfigurationIdentity).logInfo("Identity") - RealmDebugTools(realmConfigurationContentScanner).logInfo("ContentScanner") + override fun getDbUsageInfo() = buildString { + append(RealmDebugTools(realmConfiguration).getInfo("Session")) + append(RealmDebugTools(realmConfigurationCrypto).getInfo("Crypto")) + append(RealmDebugTools(realmConfigurationIdentity).getInfo("Identity")) + append(RealmDebugTools(realmConfigurationContentScanner).getInfo("ContentScanner")) } override fun getRealmConfigurations(): List { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index ad09593ebd..50bb1f8368 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -499,10 +499,15 @@ class BugReporter @Inject constructor( */ fun openBugReportScreen(activity: FragmentActivity, reportType: ReportType = ReportType.BUG_REPORT) { screenshot = takeScreenshot(activity) - matrix.debugService().logDbUsageInfo() + logDbInfo() activity.startActivity(BugReportActivity.intent(activity, reportType)) } + private fun logDbInfo() { + val dbInfo = matrix.debugService().getDbUsageInfo() + Timber.i(dbInfo) + } + private fun rageShakeAppNameForReport(reportType: ReportType): String { // As per https://github.com/matrix-org/rageshake // app: Identifier for the application (eg 'riot-web'). From 03d83b2aff1470825dac243d67be34d5dc5ebd67 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Aug 2022 15:52:19 +0200 Subject: [PATCH 18/30] Log info about process and thread in the Rageshake --- .../app/features/rageshake/BugReporter.kt | 7 ++ .../app/features/rageshake/ProcessInfo.kt | 71 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/rageshake/ProcessInfo.kt diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index 50bb1f8368..44fa4137e1 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -78,6 +78,7 @@ class BugReporter @Inject constructor( private val systemLocaleProvider: SystemLocaleProvider, private val matrix: Matrix, private val buildMeta: BuildMeta, + private val processInfo: ProcessInfo, private val sdkIntProvider: BuildVersionSdkIntProvider, ) { var inMultiWindowMode = false @@ -500,6 +501,7 @@ class BugReporter @Inject constructor( fun openBugReportScreen(activity: FragmentActivity, reportType: ReportType = ReportType.BUG_REPORT) { screenshot = takeScreenshot(activity) logDbInfo() + logProcessInfo() activity.startActivity(BugReportActivity.intent(activity, reportType)) } @@ -508,6 +510,11 @@ class BugReporter @Inject constructor( Timber.i(dbInfo) } + private fun logProcessInfo() { + val pInfo = processInfo.getInfo() + Timber.i(pInfo) + } + private fun rageShakeAppNameForReport(reportType: ReportType): String { // As per https://github.com/matrix-org/rageshake // app: Identifier for the application (eg 'riot-web'). diff --git a/vector/src/main/java/im/vector/app/features/rageshake/ProcessInfo.kt b/vector/src/main/java/im/vector/app/features/rageshake/ProcessInfo.kt new file mode 100644 index 0000000000..78e49a2e65 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/rageshake/ProcessInfo.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.rageshake + +import android.annotation.SuppressLint +import android.app.Application +import android.os.Build +import android.os.Process +import java.lang.reflect.Method +import javax.inject.Inject + +class ProcessInfo @Inject constructor() { + fun getInfo() = buildString { + append("===========================================\n") + append("* PROCESS INFO *\n") + append("===========================================\n") + val processId = Process.myPid() + append("ProcessId: $processId\n") + append("ProcessName: ${getProcessName()}\n") + append(getThreadInfo()) + append("===========================================\n") + } + + @SuppressLint("PrivateApi") + private fun getProcessName(): String? { + return if (Build.VERSION.SDK_INT >= 28) { + Application.getProcessName() + } else { + try { + val activityThread = Class.forName("android.app.ActivityThread") + val getProcessName: Method = activityThread.getDeclaredMethod("currentProcessName") + getProcessName.invoke(null) as? String + } catch (t: Throwable) { + null + } + } + } + + private fun getThreadInfo() = buildString { + append("Thread activeCount: ${Thread.activeCount()}\n") + Thread.getAllStackTraces().keys + .sortedBy { it.name } + .forEach { thread -> append(thread.getInfo()) } + } +} + +private fun Thread.getInfo() = buildString { + append("Thread '$name':") + append(" id: $id") + append(" priority: $priority") + append(" group name: ${threadGroup?.name ?: "null"}") + append(" state: $state") + append(" isAlive: $isAlive") + append(" isDaemon: $isDaemon") + append(" isInterrupted: $isInterrupted") + append("\n") +} From f5104c7511e08e896b52f6d797187e7869b670fb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Aug 2022 16:44:04 +0200 Subject: [PATCH 19/30] Log parameter --- vector/src/main/java/im/vector/app/core/extensions/Session.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/Session.kt b/vector/src/main/java/im/vector/app/core/extensions/Session.kt index caed413e2b..cb1d46efce 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Session.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.session.sync.FilterService import timber.log.Timber fun Session.configureAndStart(context: Context, startSyncing: Boolean = true) { - Timber.i("Configure and start session for $myUserId") + Timber.i("Configure and start session for $myUserId. startSyncing: $startSyncing") open() filterService().setFilter(FilterService.FilterPreset.ElementFilter) if (startSyncing) { From 0b1b22852415ff11c42262faa9ed1ce9d89f524c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Aug 2022 16:56:08 +0200 Subject: [PATCH 20/30] Fix #6782. Ensure SyncThread is started --- .../android/sdk/api/session/sync/SyncService.kt | 5 +++++ .../internal/session/sync/DefaultSyncService.kt | 2 ++ .../im/vector/app/core/di/ActiveSessionHolder.kt | 15 +++++++++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt index 71f7ab8494..6640b8a9af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt @@ -53,6 +53,11 @@ interface SyncService { */ fun getSyncState(): SyncState + /** + * This method returns true if the sync thread is alive, i.e. started. + */ + fun isSyncThreadAlive(): Boolean + /** * This method allows to listen the sync state. * @return a [LiveData] of [SyncState]. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt index 691dd7b20d..76c3c38abf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt @@ -73,6 +73,8 @@ internal class DefaultSyncService @Inject constructor( override fun getSyncState() = getSyncThread().currentState() + override fun isSyncThreadAlive() = getSyncThread().isAlive + override fun getSyncRequestStateFlow() = syncRequestStateTracker.syncRequestState override fun hasAlreadySynced(): Boolean { diff --git a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt index 7a1d613ab9..bb2ca97aad 100644 --- a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt +++ b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt @@ -20,6 +20,7 @@ import android.content.Context import arrow.core.Option import im.vector.app.ActiveSessionDataSource import im.vector.app.core.extensions.configureAndStart +import im.vector.app.core.extensions.startSyncing import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.services.GuardServiceStarter import im.vector.app.features.call.webrtc.WebRtcCallManager @@ -100,10 +101,16 @@ class ActiveSessionHolder @Inject constructor( } suspend fun getOrInitializeSession(startSync: Boolean): Session? { - return activeSessionReference.get() ?: sessionInitializer.tryInitialize(readCurrentSession = { activeSessionReference.get() }) { session -> - setActiveSession(session) - session.configureAndStart(applicationContext, startSyncing = startSync) - } + return activeSessionReference.get() + ?.also { + if (startSync && !it.syncService().isSyncThreadAlive()) { + it.startSyncing(applicationContext) + } + } + ?: sessionInitializer.tryInitialize(readCurrentSession = { activeSessionReference.get() }) { session -> + setActiveSession(session) + session.configureAndStart(applicationContext, startSyncing = startSync) + } } fun isWaitingForSessionInitialization() = activeSessionReference.get() == null && authenticationService.hasAuthenticatedSessions() From eeeb569ae086b06cdc3fdfb8d6f4320c22b47536 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Aug 2022 10:07:34 +0200 Subject: [PATCH 21/30] Add prefix to the name of the Thread the app is creating --- .../android/sdk/internal/database/RealmLiveEntityObserver.kt | 2 +- .../java/org/matrix/android/sdk/internal/di/MatrixModule.kt | 2 +- .../sdk/internal/session/room/timeline/DefaultTimeline.kt | 2 +- vector/src/main/java/im/vector/app/VectorApplication.kt | 2 +- .../home/room/detail/timeline/helper/TimelineAsyncHelper.kt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt index f2f88e216b..020b42b3b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt @@ -38,7 +38,7 @@ internal abstract class RealmLiveEntityObserver(protected val r LiveEntityObserver, RealmChangeListener> { private companion object { - val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND") + val BACKGROUND_HANDLER = createBackgroundHandler("Matrix-LIVE_ENTITY_BACKGROUND") } protected val observerScope = CoroutineScope(SupervisorJob() + BACKGROUND_HANDLER.asCoroutineDispatcher()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt index 49713a1d7f..f2f8a5dc04 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt @@ -40,7 +40,7 @@ internal object MatrixModule { io = Dispatchers.IO, computation = Dispatchers.Default, main = Dispatchers.Main, - crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(), + crypto = createBackgroundHandler("Matrix-Crypto_Thread").asCoroutineDispatcher(), dmVerif = Executors.newSingleThreadExecutor().asCoroutineDispatcher() ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 4eaac67e5a..c380ccf14f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -76,7 +76,7 @@ internal class DefaultTimeline( ) : Timeline { companion object { - val BACKGROUND_HANDLER = createBackgroundHandler("DefaultTimeline_Thread") + val BACKGROUND_HANDLER = createBackgroundHandler("Matrix-DefaultTimeline_Thread") } override val timelineID = UUID.randomUUID().toString() diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 4655de7377..46cb6ec79b 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -266,7 +266,7 @@ class VectorApplication : } private fun createFontThreadHandler(): Handler { - val handlerThread = HandlerThread("fonts") + val handlerThread = HandlerThread("Vector-fonts") handlerThread.start() return Handler(handlerThread.looper) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineAsyncHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineAsyncHelper.kt index 655d46194d..87c6a1efda 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineAsyncHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineAsyncHelper.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.helper import android.os.Handler import android.os.HandlerThread -private const val THREAD_NAME = "Timeline_Building_Thread" +private const val THREAD_NAME = "Vector-Timeline_Building_Thread" object TimelineAsyncHelper { From f75b2e6ef15c492ad6d36aafa4e346fcc6a54963 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Aug 2022 10:11:31 +0200 Subject: [PATCH 22/30] Changelog files. --- changelog.d/6884.bugfix | 1 + changelog.d/6884.sdk | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog.d/6884.bugfix create mode 100644 changelog.d/6884.sdk diff --git a/changelog.d/6884.bugfix b/changelog.d/6884.bugfix new file mode 100644 index 0000000000..6c6b286688 --- /dev/null +++ b/changelog.d/6884.bugfix @@ -0,0 +1 @@ +Ensure SyncThread is started when the app is launched after a Push has been received. diff --git a/changelog.d/6884.sdk b/changelog.d/6884.sdk new file mode 100644 index 0000000000..0de43420f0 --- /dev/null +++ b/changelog.d/6884.sdk @@ -0,0 +1 @@ +Rename `DebugService.logDbUsageInfo` (resp. `Session.logDbUsageInfo`) to `DebugService.getDbUsageInfo` (resp. `Session.getDbUsageInfo`) and return a String instead of logging. The caller may want to log the String. From a251002808921a1da44556795b073a7449257c27 Mon Sep 17 00:00:00 2001 From: Germain Date: Mon, 1 Aug 2022 13:36:30 +0100 Subject: [PATCH 23/30] Add app layout delight automation --- .github/workflows/triage-labelled.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 90f03779a6..f478d2bd7b 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -98,7 +98,8 @@ jobs: # Skip in forks if: > github.repository == 'vector-im/element-android' && - (contains(github.event.issue.labels.*.name, 'Team: Delight')) + (contains(github.event.issue.labels.*.name, 'Team: Delight') || + contains(github.event.issue.labels.*.name, 'Z-AppLayout')) steps: - uses: octokit/graphql-action@v2.x with: From fc4f4f713149dda5bcfe56c6c237e76dc8599100 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Aug 2022 14:46:51 +0200 Subject: [PATCH 24/30] Add prefix to the name of the Thread the app is creating --- .../session/room/send/queue/EventSenderProcessorThread.kt | 2 +- .../matrix/android/sdk/internal/session/sync/job/SyncThread.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt index 51107c9655..55363a7251 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt @@ -55,7 +55,7 @@ internal class EventSenderProcessorThread @Inject constructor( private val queuedTaskFactory: QueuedTaskFactory, private val taskExecutor: TaskExecutor, private val memento: QueueMemento -) : Thread("SENDER_THREAD_SID_${sessionParams.credentials.sessionId()}"), EventSenderProcessor { +) : Thread("Matrix-SENDER_THREAD_SID_${sessionParams.credentials.sessionId()}"), EventSenderProcessor { private fun markAsManaged(task: QueuedTask) { memento.track(task) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index 24a60a80da..b47b215655 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -62,7 +62,7 @@ internal class SyncThread @Inject constructor( private val backgroundDetectionObserver: BackgroundDetectionObserver, private val activeCallHandler: ActiveCallHandler, private val lightweightSettingsStorage: DefaultLightweightSettingsStorage -) : Thread("SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { +) : Thread("Matrix-SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { private var state: SyncState = SyncState.Idle private var liveState = MutableLiveData(state) From 015aaa0516525258afdd66289f246609262f2741 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Aug 2022 15:02:01 +0200 Subject: [PATCH 25/30] Log SyncThread state --- .../java/im/vector/app/features/rageshake/BugReporter.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index 44fa4137e1..eefbf63a12 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -502,9 +502,14 @@ class BugReporter @Inject constructor( screenshot = takeScreenshot(activity) logDbInfo() logProcessInfo() + logOtherInfo() activity.startActivity(BugReportActivity.intent(activity, reportType)) } + private fun logOtherInfo() { + Timber.i("SyncThread state: " + activeSessionHolder.getSafeActiveSession()?.syncService()?.getSyncState()) + } + private fun logDbInfo() { val dbInfo = matrix.debugService().getDbUsageInfo() Timber.i(dbInfo) From 0629cae183f3fba6591986cee4f4529d16b652e9 Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Fri, 19 Aug 2022 17:53:48 +0200 Subject: [PATCH 26/30] added dialog to change app layout settings (#6840) --- changelog.d/6506.wip | 1 + .../vector/app/features/home/HomeActivity.kt | 10 +++ .../list/home/HomeLayoutPreferencesStore.kt | 70 +++++++++++++++++ .../room/list/home/HomeRoomListFragment.kt | 15 +++- .../room/list/home/HomeRoomListViewModel.kt | 78 ++++++++++++------- .../home/room/list/home/HomeRoomSection.kt | 4 +- .../filter/HomeFilteredRoomsController.kt | 5 +- .../HomeLayoutSettingBottomDialogFragment.kt | 78 +++++++++++++++++++ .../recent/RecentRoomCarouselController.kt | 8 +- .../bottom_sheet_home_layout_settings.xml | 67 ++++++++++++++++ vector/src/main/res/menu/menu_new_home.xml | 6 +- vector/src/main/res/values/strings.xml | 9 +++ 12 files changed, 314 insertions(+), 37 deletions(-) create mode 100644 changelog.d/6506.wip create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml diff --git a/changelog.d/6506.wip b/changelog.d/6506.wip new file mode 100644 index 0000000000..344c0bca2f --- /dev/null +++ b/changelog.d/6506.wip @@ -0,0 +1 @@ +[App Layout] added dialog to configure app layout diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index fe57b9f735..553b45ad81 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -56,6 +56,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.disclaimer.showDisclaimerDialog +import im.vector.app.features.home.room.list.home.layout.HomeLayoutSettingBottomDialogFragment import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.navigation.Navigator @@ -283,6 +284,11 @@ class HomeActivity : .show(supportFragmentManager, "SPACE_SETTINGS") } + private fun showLayoutSettings() { + HomeLayoutSettingBottomDialogFragment() + .show(supportFragmentManager, "LAYOUT_SETTINGS") + } + private fun openSpaceInvite(spaceId: String) { SpaceInviteBottomSheet.newInstance(spaceId) .show(supportFragmentManager, "SPACE_INVITE") @@ -596,6 +602,10 @@ class HomeActivity : navigator.openSettings(this) true } + R.id.menu_home_layout_settings -> { + showLayoutSettings() + true + } R.id.menu_home_invite_friends -> { launchInviteFriends() true diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt new file mode 100644 index 0000000000..11cd892406 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.home.room.list.home + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.extensions.orFalse +import javax.inject.Inject + +private val Context.dataStore: DataStore by preferencesDataStore(name = "layout_preferences") + +class HomeLayoutPreferencesStore @Inject constructor( + private val context: Context +) { + + private val areRecentsEnbabled = booleanPreferencesKey("SETTINGS_PREFERENCES_HOME_RECENTS") + private val areFiltersEnabled = booleanPreferencesKey("SETTINGS_PREFERENCES_HOME_FILTERS") + private val isAZOrderingEnabled = booleanPreferencesKey("SETTINGS_PREFERENCES_USE_AZ_ORDER") + + val areRecentsEnabledFlow: Flow = context.dataStore.data + .map { preferences -> preferences[areRecentsEnbabled].orFalse() } + .distinctUntilChanged() + + val areFiltersEnabledFlow: Flow = context.dataStore.data + .map { preferences -> preferences[areFiltersEnabled].orFalse() } + .distinctUntilChanged() + + val isAZOrderingEnabledFlow: Flow = context.dataStore.data + .map { preferences -> preferences[isAZOrderingEnabled].orFalse() } + .distinctUntilChanged() + + suspend fun setRecentsEnabled(isEnabled: Boolean) { + context.dataStore.edit { settings -> + settings[areRecentsEnbabled] = isEnabled + } + } + + suspend fun setFiltersEnabled(isEnabled: Boolean) { + context.dataStore.edit { settings -> + settings[areFiltersEnabled] = isEnabled + } + } + + suspend fun setAZOrderingEnabled(isEnabled: Boolean) { + context.dataStore.edit { settings -> + settings[isAZOrderingEnabled] = isEnabled + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 1d9342b45b..9464033896 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -162,6 +162,15 @@ class HomeRoomListFragment @Inject constructor( }.launchIn(lifecycleScope) views.roomListView.adapter = concatAdapter + + // we need to force scroll when recents/filter tabs are added to make them visible + concatAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + if (positionStart == 0) { + layoutManager.scrollToPosition(0) + } + } + }) } private fun setupFabs() { @@ -205,6 +214,9 @@ class HomeRoomListFragment @Inject constructor( } private fun setUpAdapters(sections: Set) { + concatAdapter.adapters.forEach { + concatAdapter.removeAdapter(it) + } sections.forEach { concatAdapter.addAdapter(getAdapterForData(it)) } @@ -234,12 +246,11 @@ class HomeRoomListFragment @Inject constructor( is HomeRoomSection.RoomSummaryData -> { HomeFilteredRoomsController( roomSummaryItemFactory, - showFilters = section.showFilters, ).also { controller -> controller.listener = this controller.onFilterChanged = ::onRoomFilterChanged section.filtersData.onEach { - controller.submitFiltersData(it) + controller.submitFiltersData(it.getOrNull()) }.launchIn(lifecycleScope) section.list.observe(viewLifecycleOwner) { list -> controller.submitList(list) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index 1fed9eba86..711ba0c10a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -53,12 +54,14 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.state.isPublic +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.flow.flow class HomeRoomListViewModel @AssistedInject constructor( @Assisted initialState: HomeRoomListViewState, private val session: Session, private val spaceStateHandler: SpaceStateHandler, + private val preferencesStore: HomeLayoutPreferencesStore, ) : VectorViewModel(initialState) { @AssistedFactory @@ -82,17 +85,30 @@ class HomeRoomListViewModel @AssistedInject constructor( init { configureSections() + observePreferences() } - private fun configureSections() { + private fun observePreferences() { + preferencesStore.areRecentsEnabledFlow.onEach { + configureSections() + }.launchIn(viewModelScope) + + preferencesStore.isAZOrderingEnabledFlow.onEach { + configureSections() + }.launchIn(viewModelScope) + } + + private fun configureSections() = viewModelScope.launch { val newSections = mutableSetOf() - newSections.add(getRecentRoomsSection()) + val areSettingsEnabled = preferencesStore.areRecentsEnabledFlow.first() + + if (areSettingsEnabled) { + newSections.add(getRecentRoomsSection()) + } newSections.add(getFilteredRoomsSection()) - viewModelScope.launch { - _sections.emit(newSections) - } + _sections.emit(newSections) setState { copy(state = StateView.State.Content) @@ -111,13 +127,17 @@ class HomeRoomListViewModel @AssistedInject constructor( ) } - private fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData { + private suspend fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData { val builder = RoomSummaryQueryParams.Builder().also { it.memberships = listOf(Membership.JOIN) } val params = getFilteredQueryParams(HomeRoomFilter.ALL, builder.build()) - val sortOrder = RoomSortOrder.ACTIVITY // #6506 + val sortOrder = if (preferencesStore.isAZOrderingEnabledFlow.first()) { + RoomSortOrder.NAME + } else { + RoomSortOrder.ACTIVITY + } val liveResults = session.roomService().getFilteredPagedRoomSummariesLive( params, @@ -135,19 +155,18 @@ class HomeRoomListViewModel @AssistedInject constructor( .onEach { selectedSpaceOption -> val selectedSpace = selectedSpaceOption.orNull() liveResults.queryParams = liveResults.queryParams.copy( - spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter() + spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter() ) }.launchIn(viewModelScope) return HomeRoomSection.RoomSummaryData( list = liveResults.livePagedList, - showFilters = true, // #6506 filtersData = getFiltersDataFlow() ) } - private fun getFiltersDataFlow(): SharedFlow> { - val flow = MutableSharedFlow>(replay = 1) + private fun getFiltersDataFlow(): SharedFlow>> { + val flow = MutableSharedFlow>>(replay = 1) val favouritesFlow = session.flow() .liveRoomSummaries( @@ -168,25 +187,28 @@ class HomeRoomListViewModel @AssistedInject constructor( .map { it.isNotEmpty() } .distinctUntilChanged() - favouritesFlow.combine(dmsFLow) { hasFavourite, hasDm -> - hasFavourite to hasDm - }.onEach { (hasFavourite, hasDm) -> - val filtersData = mutableListOf( - HomeRoomFilter.ALL, - HomeRoomFilter.UNREADS - ) - if (hasFavourite) { - filtersData.add( - HomeRoomFilter.FAVOURITES + combine(favouritesFlow, dmsFLow, preferencesStore.areFiltersEnabledFlow) { hasFavourite, hasDm, areFiltersEnabled -> + Triple(hasFavourite, hasDm, areFiltersEnabled) + }.onEach { (hasFavourite, hasDm, areFiltersEnabled) -> + if (areFiltersEnabled) { + val filtersData = mutableListOf( + HomeRoomFilter.ALL, + HomeRoomFilter.UNREADS ) + if (hasFavourite) { + filtersData.add( + HomeRoomFilter.FAVOURITES + ) + } + if (hasDm) { + filtersData.add( + HomeRoomFilter.PEOPlE + ) + } + flow.emit(Optional.from(filtersData)) + } else { + flow.emit(Optional.empty()) } - if (hasDm) { - filtersData.add( - HomeRoomFilter.PEOPlE - ) - } - - flow.emit(filtersData) }.launchIn(viewModelScope) return flow diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt index f51b479d37..74ec46d6b7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt @@ -21,12 +21,12 @@ import androidx.paging.PagedList import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter import kotlinx.coroutines.flow.SharedFlow import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.Optional sealed class HomeRoomSection { data class RoomSummaryData( val list: LiveData>, - val showFilters: Boolean, - val filtersData: SharedFlow> + val filtersData: SharedFlow>>, ) : HomeRoomSection() data class RecentRoomsData( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt index 7c1f154d52..2d673bc089 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt @@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary class HomeFilteredRoomsController( private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val showFilters: Boolean, ) : PagedListEpoxyController( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() @@ -48,7 +47,7 @@ class HomeFilteredRoomsController( override fun addModels(models: List>) { val host = this - if (showFilters) { + if (host.filtersData != null) { roomFilterHeaderItem { id("filter_header") filtersData(host.filtersData) @@ -58,7 +57,7 @@ class HomeFilteredRoomsController( super.addModels(models) } - fun submitFiltersData(data: List) { + fun submitFiltersData(data: List?) { this.filtersData = data requestForcedModelBuild() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt new file mode 100644 index 0000000000..0c4d64a1cc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.home.room.list.home.layout + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.databinding.BottomSheetHomeLayoutSettingsBinding +import im.vector.app.features.home.room.list.home.HomeLayoutPreferencesStore +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AndroidEntryPoint +class HomeLayoutSettingBottomDialogFragment : VectorBaseBottomSheetDialogFragment() { + + @Inject lateinit var preferencesStore: HomeLayoutPreferencesStore + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetHomeLayoutSettingsBinding { + return BottomSheetHomeLayoutSettingsBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewLifecycleOwner.lifecycleScope.launch { + views.homeLayoutSettingsRecents.isChecked = preferencesStore.areRecentsEnabledFlow.first() + views.homeLayoutSettingsFilters.isChecked = preferencesStore.areFiltersEnabledFlow.first() + + if (preferencesStore.isAZOrderingEnabledFlow.first()) { + views.homeLayoutSettingsSortName.isChecked = true + } else { + views.homeLayoutSettingsSortActivity.isChecked = true + } + } + + views.homeLayoutSettingsRecents.setOnCheckedChangeListener { _, isChecked -> + setRecentsEnabled(isChecked) + } + views.homeLayoutSettingsFilters.setOnCheckedChangeListener { _, isChecked -> + setFiltersEnabled(isChecked) + } + views.homeLayoutSettingsSortGroup.setOnCheckedChangeListener { _, checkedId -> + setAzOrderingEnabled(checkedId == R.id.home_layout_settings_sort_name) + } + } + + private fun setRecentsEnabled(isEnabled: Boolean) = lifecycleScope.launch { + preferencesStore.setRecentsEnabled(isEnabled) + } + + private fun setFiltersEnabled(isEnabled: Boolean) = lifecycleScope.launch { + preferencesStore.setFiltersEnabled(isEnabled) + } + + private fun setAzOrderingEnabled(isEnabled: Boolean) = lifecycleScope.launch { + preferencesStore.setAZOrderingEnabled(isEnabled) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt index 53832bbc74..ebec912779 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt @@ -59,7 +59,13 @@ class RecentRoomCarouselController @Inject constructor( data?.let { data -> carousel { id("recents_carousel") - padding(Carousel.Padding(host.hPadding, host.itemSpacing)) + padding(Carousel.Padding( + host.hPadding, + 0, + host.hPadding, + 0, + host.itemSpacing) + ) withModelsFrom(data) { roomSummary -> val onClick = host.listener?.let { it::onRoomClicked } val onLongClick = host.listener?.let { it::onRoomLongClicked } diff --git a/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml b/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml new file mode 100644 index 0000000000..1766695354 --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/menu/menu_new_home.xml b/vector/src/main/res/menu/menu_new_home.xml index e893624d54..6cd52e5608 100644 --- a/vector/src/main/res/menu/menu_new_home.xml +++ b/vector/src/main/res/menu/menu_new_home.xml @@ -3,6 +3,10 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> + + + app:showAsAction="ifRoom" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 4502304cc7..0b62c16f92 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -427,6 +427,15 @@ Filter room names + Layout preferences + + + + Show filters + Show recents + Sort by + Activity + A - Z Invites From cc49e96d36eac3a17f93e2e108ebafed2eaca179 Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Fri, 19 Aug 2022 17:55:43 +0200 Subject: [PATCH 27/30] dialpad moved from bottom navigation tab to a separate activity (#6887) --- changelog.d/6787.wip | 1 + vector/src/main/AndroidManifest.xml | 1 + .../features/call/dialpad/PstnDialActivity.kt | 109 ++++++++++++++++++ .../features/home/NewHomeDetailFragment.kt | 51 ++------ vector/src/main/res/menu/room_list.xml | 12 +- 5 files changed, 131 insertions(+), 43 deletions(-) create mode 100644 changelog.d/6787.wip create mode 100644 vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt diff --git a/changelog.d/6787.wip b/changelog.d/6787.wip new file mode 100644 index 0000000000..ace2b04d9e --- /dev/null +++ b/changelog.d/6787.wip @@ -0,0 +1 @@ +[App Layout] Dialpad moved from bottom navigation tab to a separate activity accessed via home screen context menu diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index beee800b4a..bed0b618d0 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -348,6 +348,7 @@ + diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt new file mode 100644 index 0000000000..a0d6e29849 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.call.dialpad + +import android.os.Bundle +import androidx.appcompat.app.AppCompatDialog +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.SimpleFragmentActivity +import im.vector.app.features.call.webrtc.WebRtcCallManager +import im.vector.app.features.createdirect.DirectRoomHelper +import im.vector.app.features.settings.VectorLocale +import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +@AndroidEntryPoint +class PstnDialActivity : SimpleFragmentActivity() { + + @Inject lateinit var callManager: WebRtcCallManager + @Inject lateinit var directRoomHelper: DirectRoomHelper + @Inject lateinit var session: Session + @Inject lateinit var errorFormatter: ErrorFormatter + + private var progress: AppCompatDialog? = null + + override fun getTitleRes(): Int = R.string.call + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (isFirstCreation()) { + addFragment( + views.container, + createDialPadFragment() + ) + } + } + + private fun handleStartCallWithPhoneNumber(rawNumber: String) { + lifecycleScope.launch { + try { + showLoadingDialog() + val result = DialPadLookup(session, callManager, directRoomHelper).lookupPhoneNumber(rawNumber) + callManager.startOutgoingCall(result.roomId, result.userId, isVideoCall = false) + dismissLoadingDialog() + finish() + } catch (failure: Throwable) { + dismissLoadingDialog() + displayErrorDialog(failure) + } + } + } + + private fun createDialPadFragment(): Fragment { + val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, DialPadFragment::class.java.name) + return (fragment as DialPadFragment).apply { + arguments = Bundle().apply { + putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true) + putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true) + putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country) + } + callback = object : DialPadFragment.Callback { + override fun onOkClicked(formatted: String?, raw: String?) { + if (raw.isNullOrEmpty()) return + handleStartCallWithPhoneNumber(raw) + } + } + } + } + + private fun showLoadingDialog() { + progress?.dismiss() + progress = MaterialProgressDialog(this) + .show(getString(R.string.please_wait)) + } + + private fun dismissLoadingDialog() { + progress?.dismiss() + } + + private fun displayErrorDialog(throwable: Throwable) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_title_error) + .setMessage(errorFormatter.toHumanReadable(throwable)) + .setPositiveButton(R.string.ok, null) + .show() + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt index 839ae6da88..d45aaeb762 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt @@ -16,13 +16,13 @@ package im.vector.app.features.home +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel @@ -42,12 +42,11 @@ import im.vector.app.core.ui.views.KeysBackupBanner import im.vector.app.databinding.FragmentNewHomeDetailBinding import im.vector.app.features.call.SharedKnownCallsViewModel import im.vector.app.features.call.VectorCallActivity -import im.vector.app.features.call.dialpad.DialPadFragment +import im.vector.app.features.call.dialpad.PstnDialActivity import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.room.list.home.HomeRoomListFragment import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert -import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS import im.vector.app.features.themes.ThemeUtils @@ -99,6 +98,10 @@ class NewHomeDetailFragment @Inject constructor( viewModel.handle(HomeDetailAction.MarkAllRoomsRead) true } + R.id.menu_home_dialpad -> { + startActivity(Intent(requireContext(), PstnDialActivity::class.java)) + true + } else -> false } } @@ -107,6 +110,7 @@ class NewHomeDetailFragment @Inject constructor( withState(viewModel) { state -> val isRoomList = state.currentTab is HomeTab.RoomList menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = isRoomList && hasUnreadRooms + menu.findItem(R.id.menu_home_dialpad).isVisible = state.showDialPadTab } } @@ -138,14 +142,10 @@ class NewHomeDetailFragment @Inject constructor( updateUIForTab(currentTab) } - viewModel.onEach(HomeDetailViewState::showDialPadTab) { showDialPadTab -> - updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab) - } - viewModel.observeViewEvents { viewEvent -> when (viewEvent) { - HomeDetailViewEvents.CallStarted -> handleCallStarted() - is HomeDetailViewEvents.FailToCall -> showFailure(viewEvent.failure) + HomeDetailViewEvents.CallStarted -> Unit + is HomeDetailViewEvents.FailToCall -> Unit HomeDetailViewEvents.Loading -> showLoadingDialog() } } @@ -186,12 +186,6 @@ class NewHomeDetailFragment @Inject constructor( sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace) } - private fun handleCallStarted() { - dismissLoadingDialog() - val fragmentTag = HomeTab.DialPad.toFragmentTag() - (childFragmentManager.findFragmentByTag(fragmentTag) as? DialPadFragment)?.clear() - } - override fun onDestroyView() { currentCallsViewPresenter.unBind() super.onDestroyView() @@ -339,30 +333,15 @@ class NewHomeDetailFragment @Inject constructor( add(R.id.roomListContainer, HomeRoomListFragment::class.java, null, fragmentTag) } is HomeTab.DialPad -> { - add(R.id.roomListContainer, createDialPadFragment(), fragmentTag) + throw NotImplementedError("this tab shouldn't exists when app layout is enabled") } } } else { - if (tab is HomeTab.DialPad) { - (fragmentToShow as? DialPadFragment)?.applyCallback() - } attach(fragmentToShow) } } } - private fun createDialPadFragment(): Fragment { - val fragment = childFragmentManager.fragmentFactory.instantiate(vectorBaseActivity.classLoader, DialPadFragment::class.java.name) - return (fragment as DialPadFragment).apply { - arguments = Bundle().apply { - putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true) - putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true) - putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country) - } - applyCallback() - } - } - private fun updateTabVisibilitySafely(tabId: Int, isVisible: Boolean) { val wasVisible = views.bottomNavigationView.menu.findItem(tabId).isVisible views.bottomNavigationView.menu.findItem(tabId).isVisible = isVisible @@ -439,16 +418,6 @@ class NewHomeDetailFragment @Inject constructor( } } - private fun DialPadFragment.applyCallback(): DialPadFragment { - callback = object : DialPadFragment.Callback { - override fun onOkClicked(formatted: String?, raw: String?) { - if (raw.isNullOrEmpty()) return - viewModel.handle(HomeDetailAction.StartCallWithPhoneNumber(raw)) - } - } - return this - } - override fun onBackPressed(toolbarButton: Boolean) = if (spaceStateHandler.getCurrentSpace() != null) { navigateBack() true diff --git a/vector/src/main/res/menu/room_list.xml b/vector/src/main/res/menu/room_list.xml index 60ffdcd87b..ad375d241b 100644 --- a/vector/src/main/res/menu/room_list.xml +++ b/vector/src/main/res/menu/room_list.xml @@ -1,9 +1,17 @@ - + - \ No newline at end of file + + From 0a2ce698becd39abddc85ca4263ea57a211ca052 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 19 Aug 2022 16:57:08 +0100 Subject: [PATCH 28/30] fixing default distributor not being registered on login - we were saving the default distributor before asking the user, which meant when the user selected the same option it was skipped from being registered - only unregisters during the force flow, otherwise we'll crash due to no app being registered --- .../app/core/pushers/UnifiedPushHelper.kt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt index 1f44ab3686..0993485471 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt @@ -92,8 +92,6 @@ class UnifiedPushHelper @Inject constructor( return@launch } - // By default, use internal solution (fcm/background sync) - UnifiedPush.saveDistributor(context, context.packageName) val distributors = UnifiedPush.getDistributors(context) if (distributors.size == 1 && !force) { @@ -101,7 +99,14 @@ class UnifiedPushHelper @Inject constructor( UnifiedPush.registerApp(context) onDoneRunnable?.run() } else { - openDistributorDialogInternal(activity, pushersManager, onDoneRunnable, distributors, !force, !force) + openDistributorDialogInternal( + activity = activity, + pushersManager = pushersManager, + onDoneRunnable = onDoneRunnable, + distributors = distributors, + unregisterFirst = force, + cancellable = !force + ) } } } @@ -165,6 +170,12 @@ class UnifiedPushHelper @Inject constructor( onDoneRunnable?.run() } } + .setOnCancelListener { + // By default, use internal solution (fcm/background sync) + UnifiedPush.saveDistributor(context, context.packageName) + UnifiedPush.registerApp(context) + onDoneRunnable?.run() + } .setCancelable(cancellable) .show() } From 06c77e4b5e28ba3bc39c79b3626847054f22a244 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 19 Aug 2022 17:14:20 +0100 Subject: [PATCH 29/30] adding changelog entry --- changelog.d/6891.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6891.bugfix diff --git a/changelog.d/6891.bugfix b/changelog.d/6891.bugfix new file mode 100644 index 0000000000..b6b46e1d93 --- /dev/null +++ b/changelog.d/6891.bugfix @@ -0,0 +1 @@ +Fixes missing firebase notifications after logging in when UnifiedPush distributor is installed From 8a2894f0383b617aeacb75703cb8cf41ce78378d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 05:58:19 +0000 Subject: [PATCH 30/30] Bump libphonenumber from 8.12.53 to 8.12.54 Bumps [libphonenumber](https://github.com/google/libphonenumber) from 8.12.53 to 8.12.54. - [Release notes](https://github.com/google/libphonenumber/releases) - [Changelog](https://github.com/google/libphonenumber/blob/master/making-metadata-changes.md) - [Commits](https://github.com/google/libphonenumber/compare/v8.12.53...v8.12.54) --- updated-dependencies: - dependency-name: com.googlecode.libphonenumber:libphonenumber dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index ed3e3cdd7b..aa8731da2a 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -199,7 +199,7 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.54' testImplementation libs.tests.junit // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 diff --git a/vector/build.gradle b/vector/build.gradle index 18e094523e..7296262019 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -413,7 +413,7 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.54' // FlowBinding implementation libs.github.flowBinding