From c816b8f5628431aca8a6d73e03efd39e5571ea8a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 22 Jun 2022 17:37:56 +0200 Subject: [PATCH 001/317] Partial implementation of MSC3824 - Add action param on SSO redirect - Only show SSO button if indicated - Label button as "Continue" --- .../sdk/api/auth/AuthenticationService.kt | 3 ++- .../android/sdk/api/auth/data/LoginFlowResult.kt | 3 ++- .../android/sdk/internal/auth/Constants.kt | 8 ++++++++ .../auth/DefaultAuthenticationService.kt | 16 ++++++++++++---- .../sdk/internal/auth/data/LoginFlowResponse.kt | 9 ++++++++- .../features/login/AbstractSSOLoginFragment.kt | 4 +++- .../vector/app/features/login/LoginFragment.kt | 4 +++- .../login/LoginSignUpSignInSelectionFragment.kt | 7 +++++-- .../vector/app/features/login/LoginViewModel.kt | 5 +++-- .../features/login2/AbstractSSOLoginFragment2.kt | 5 ++++- .../login2/LoginFragmentSignupUsername2.kt | 5 ++++- .../app/features/login2/LoginFragmentToAny2.kt | 5 ++++- .../app/features/login2/LoginSsoOnlyFragment2.kt | 5 ++++- .../app/features/login2/LoginViewModel2.kt | 7 +++++-- .../features/onboarding/OnboardingViewModel.kt | 5 +++-- .../features/onboarding/OnboardingViewState.kt | 1 + .../onboarding/StartAuthenticationFlowUseCase.kt | 3 ++- .../ftueauth/AbstractSSOFtueAuthFragment.kt | 6 +++++- .../ftueauth/FtueAuthCombinedLoginFragment.kt | 6 +++++- .../ftueauth/FtueAuthCombinedRegisterFragment.kt | 5 ++++- .../onboarding/ftueauth/FtueAuthLoginFragment.kt | 4 +++- .../FtueAuthSignUpSignInSelectionFragment.kt | 12 +++++++++--- .../StartAuthenticationFlowUseCaseTest.kt | 6 ++++-- 23 files changed, 103 insertions(+), 31 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 5ae70e1978..1d307422b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.internal.auth.SSOAction /** * This interface defines methods to authenticate or to create an account to a matrix server. @@ -44,7 +45,7 @@ interface AuthenticationService { /** * Get a SSO url. */ - fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? + fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?, action: SSOAction): String? /** * Get the sign in or sign up fallback URL. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt index 7d1407c0d8..883da54b80 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt @@ -21,5 +21,6 @@ data class LoginFlowResult( val ssoIdentityProviders: List?, val isLoginAndRegistrationSupported: Boolean, val homeServerUrl: String, - val isOutdatedHomeserver: Boolean + val isOutdatedHomeserver: Boolean, + val hasOidcCompatibilityFlow: Boolean ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt index 97f8a9d512..a1c0100d0b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt @@ -38,3 +38,11 @@ internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl" // Ref: https://matrix.org/docs/spec/client_server/r0.6.1#single-sign-on internal const val SSO_UIA_FALLBACK_PATH = "/_matrix/client/r0/auth/m.login.sso/fallback/web" + +/** + * See https://github.com/matrix-org/matrix-spec-proposals/pull/3824 + */ +enum class SSOAction { + login, + register; +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 61a423669c..2330512d35 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -86,7 +86,7 @@ internal class DefaultAuthenticationService @Inject constructor( return getLoginFlow(homeServerConnectionConfig) } - override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? { + override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?, action: SSOAction): String? { val homeServerUrlBase = getHomeServerUrlBase() ?: return null return buildString { @@ -101,6 +101,8 @@ internal class DefaultAuthenticationService @Inject constructor( // But https://github.com/matrix-org/synapse/issues/5755 appendParamToUrl("device_id", it) } + + appendParamToUrl("action", action.toString()) } } @@ -290,12 +292,18 @@ internal class DefaultAuthenticationService @Inject constructor( val loginFlowResponse = executeRequest(null) { authAPI.getLoginFlows() } + + // If an m.login.sso flow is present that is flagged as being for MSC3824 OIDC compatibility then we only return that flow + val oidcCompatibilityFlow = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == "m.login.sso" && it.delegatedOidcCompatibilty == true } + val flows = if (oidcCompatibilityFlow != null) listOf(oidcCompatibilityFlow) else loginFlowResponse.flows + return LoginFlowResult( - supportedLoginTypes = loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, - ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, + supportedLoginTypes = flows.orEmpty().mapNotNull { it.type }, + ssoIdentityProviders = flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl = homeServerUrl, - isOutdatedHomeserver = !versions.isSupportedBySdk() + isOutdatedHomeserver = !versions.isSupportedBySdk(), + hasOidcCompatibilityFlow = oidcCompatibilityFlow != null ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt index df10e110d1..4562696dda 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt @@ -43,6 +43,13 @@ internal data class LoginFlow( * See MSC #2858 */ @Json(name = "identity_providers") - val ssoIdentityProvider: List? = null + val ssoIdentityProvider: List? = null, + /** + * Whether this login flow is preferred for OIDC-aware clients. + * + * See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824) + */ + @Json(name = "org.matrix.msc3824.delegated_oidc_compatibility") + val delegatedOidcCompatibilty: Boolean? ) 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..03bdc9fced 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 @@ -24,6 +24,7 @@ import androidx.browser.customtabs.CustomTabsSession import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.withState import im.vector.app.core.utils.openUrlInChromeCustomTab +import org.matrix.android.sdk.internal.auth.SSOAction abstract class AbstractSSOLoginFragment : AbstractLoginFragment() { @@ -90,7 +91,8 @@ abstract class AbstractSSOLoginFragment : AbstractLoginFragmen loginViewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = null + providerId = null, + action = if (state.signMode == SignMode.SignUp) SSOAction.register else SSOAction.login ) ?.let { prefetchUrl(it) } } 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..3d536cc280 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 @@ -41,6 +41,7 @@ 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 +import org.matrix.android.sdk.internal.auth.SSOAction import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -207,7 +208,8 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment : AbstractLoginFragment2() { @@ -93,7 +95,8 @@ abstract class AbstractSSOLoginFragment2 : AbstractLoginFragme loginViewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = null + providerId = null, + action = if (state.signMode == SignMode2.SignUp) SSOAction.register else SSOAction.login ) ?.let { prefetchUrl(it) } } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt index a7c4b25344..925db85251 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt @@ -31,11 +31,13 @@ import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupUsername2Binding import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity +import im.vector.app.features.login.SignMode import im.vector.app.features.login.SocialLoginButtonsView 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.internal.auth.SSOAction import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -101,7 +103,8 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm loginViewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = provider?.id + providerId = provider?.id, + action = if (state.signMode == SignMode2.SignUp) SSOAction.register else SSOAction.login ) ?.let { openInCustomTab(it) } } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt index cc143b9255..e5b2067504 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt @@ -32,6 +32,7 @@ import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSigninToAny2Binding import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity +import im.vector.app.features.login.SignMode import im.vector.app.features.login.SocialLoginButtonsView import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn @@ -41,6 +42,7 @@ 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 +import org.matrix.android.sdk.internal.auth.SSOAction import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -128,7 +130,8 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2 = emptyList(), + val hasOidcCompatibilityFlow: Boolean = false ) : Parcelable @Parcelize 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 922258778b..37b28ec577 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 @@ -53,7 +53,8 @@ class StartAuthenticationFlowUseCase @Inject constructor( userFacingUrl = config.homeServerUri.toString(), upstreamUrl = authFlow.homeServerUrl, preferredLoginMode = preferredLoginMode, - supportedLoginTypes = authFlow.supportedLoginTypes + supportedLoginTypes = authFlow.supportedLoginTypes, + hasOidcCompatibilityFlow = authFlow.hasOidcCompatibilityFlow ) private fun matrixOrgUrl() = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() 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..338446300b 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 @@ -25,8 +25,11 @@ import androidx.viewbinding.ViewBinding 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.SignMode import im.vector.app.features.login.hasSso import im.vector.app.features.login.ssoIdentityProviders +import im.vector.app.features.onboarding.OnboardingFlow +import org.matrix.android.sdk.internal.auth.SSOAction abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthFragment() { @@ -93,7 +96,8 @@ abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthF viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - provider = null + provider = null, + action = if (state.onboardingFlow == OnboardingFlow.SignUp) SSOAction.register else SSOAction.login ) ?.let { prefetchUrl(it) } } 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 10b9cf4683..ede6007de9 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 @@ -35,13 +35,16 @@ import im.vector.app.core.extensions.toReducedUrl 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.SignMode import im.vector.app.features.login.SocialLoginButtonsView import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.flow.launchIn import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider +import org.matrix.android.sdk.internal.auth.SSOAction import javax.inject.Inject class FtueAuthCombinedLoginFragment @Inject constructor( @@ -134,7 +137,8 @@ class FtueAuthCombinedLoginFragment @Inject constructor( viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, - provider = id + provider = id, + action = SSOAction.login )?.let { openInCustomTab(it) } } } 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 e19f7837c3..ca43ef7220 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 @@ -39,6 +39,7 @@ import im.vector.app.core.extensions.toReducedUrl 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.SignMode import im.vector.app.features.login.SocialLoginButtonsView import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction @@ -53,6 +54,7 @@ import org.matrix.android.sdk.api.failure.isLoginEmailUnknown import org.matrix.android.sdk.api.failure.isRegistrationDisabled import org.matrix.android.sdk.api.failure.isUsernameInUse import org.matrix.android.sdk.api.failure.isWeakPassword +import org.matrix.android.sdk.internal.auth.SSOAction import javax.inject.Inject class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAuthFragment() { @@ -168,7 +170,8 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, - provider = provider + provider = provider, + action = SSOAction.register )?.let { openInCustomTab(it) } } } 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 9f551f9f25..aa652304a4 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 @@ -52,6 +52,7 @@ import org.matrix.android.sdk.api.failure.isLoginEmailUnknown import org.matrix.android.sdk.api.failure.isRegistrationDisabled import org.matrix.android.sdk.api.failure.isUsernameInUse import org.matrix.android.sdk.api.failure.isWeakPassword +import org.matrix.android.sdk.internal.auth.SSOAction import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -221,7 +222,8 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - provider = provider + provider = provider, + action = if (state.signMode == SignMode.SignUp) SSOAction.register else SSOAction.login ) ?.let { openInCustomTab(it) } } 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..20421ec45e 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 @@ -33,8 +33,10 @@ 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.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingViewState import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider +import org.matrix.android.sdk.internal.auth.SSOAction import javax.inject.Inject /** @@ -86,7 +88,8 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - provider = provider + provider = provider, + action = if (state.signMode == SignMode.SignUp) SSOAction.register else SSOAction.login ) ?.let { openInCustomTab(it) } } @@ -112,7 +115,7 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.Sso -> { // change to only one button that is sign in with sso - views.loginSignupSigninSubmit.text = getString(R.string.login_signin_sso) + views.loginSignupSigninSubmit.text = if (state.selectedHomeserver.hasOidcCompatibilityFlow) getString(R.string.login_continue) else getString(R.string.login_signin_sso) views.loginSignupSigninSignIn.isVisible = false } else -> { @@ -127,7 +130,8 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - provider = null + provider = null, + action = if (state.onboardingFlow == OnboardingFlow.SignUp) SSOAction.register else SSOAction.login ) ?.let { openInCustomTab(it) } } else { @@ -146,5 +150,7 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF override fun updateWithState(state: OnboardingViewState) { render(state) setupButtons(state) + // if talking to OIDC enabled homeserver in compatibility mode then immediately start SSO + if (state.selectedHomeserver.hasOidcCompatibilityFlow) submit() } } 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 b75ec231fd..ddb1beabba 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 @@ -128,7 +128,8 @@ class StartAuthenticationFlowUseCaseTest { ssoIdentityProviders = SSO_IDENTITY_PROVIDERS, isLoginAndRegistrationSupported = true, homeServerUrl = A_DECLARED_HOMESERVER_URL, - isOutdatedHomeserver = false + isOutdatedHomeserver = false, + hasOidcCompatibilityFlow = false ) private fun expectedResult( @@ -144,7 +145,8 @@ class StartAuthenticationFlowUseCaseTest { userFacingUrl = homeserverSourceUrl, upstreamUrl = A_DECLARED_HOMESERVER_URL, preferredLoginMode = preferredLoginMode, - supportedLoginTypes = supportedLoginTypes + supportedLoginTypes = supportedLoginTypes, + hasOidcCompatibilityFlow = false ) ) From 4ae6365034d18d420ca3c1c89eefbb563078aec8 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 22 Jun 2022 17:59:50 +0200 Subject: [PATCH 002/317] Use unstable prefix for SSO redirect action param --- .../android/sdk/internal/auth/DefaultAuthenticationService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 2330512d35..20adb83205 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -102,7 +102,8 @@ internal class DefaultAuthenticationService @Inject constructor( appendParamToUrl("device_id", it) } - appendParamToUrl("action", action.toString()) + // MSC3824 action param + appendParamToUrl("org.matrix.msc3824.action", action.toString()) } } From 11df717cc79faf317249adf1c0dd06a2633ff798 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 8 Aug 2022 16:54:47 +0100 Subject: [PATCH 003/317] Changelog --- changelog.d/6367.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6367.feature diff --git a/changelog.d/6367.feature b/changelog.d/6367.feature new file mode 100644 index 0000000000..5d4b46ca99 --- /dev/null +++ b/changelog.d/6367.feature @@ -0,0 +1 @@ +Adds MSC3824 OIDC-awareness when talking to an OIDC-enabled homeservers From c53e36513a7b62e23cf545794b7779169f4b19dd Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 11 Aug 2022 18:17:11 +0100 Subject: [PATCH 004/317] Lint fixes --- .../vector/app/features/login2/AbstractSSOLoginFragment2.kt | 1 - .../app/features/login2/LoginFragmentSignupUsername2.kt | 1 - .../java/im/vector/app/features/login2/LoginFragmentToAny2.kt | 1 - .../im/vector/app/features/login2/LoginSsoOnlyFragment2.kt | 1 - .../java/im/vector/app/features/login2/LoginViewModel2.kt | 2 -- .../im/vector/app/features/onboarding/OnboardingViewModel.kt | 4 ++-- .../onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt | 1 - .../onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt | 2 -- .../onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt | 1 - 9 files changed, 2 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt index 489f9027bb..465b595ca3 100644 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt @@ -25,7 +25,6 @@ import androidx.viewbinding.ViewBinding 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.SignMode import im.vector.app.features.login.hasSso import im.vector.app.features.login.ssoIdentityProviders import org.matrix.android.sdk.internal.auth.SSOAction diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt index 925db85251..6b2f0f6bd1 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt @@ -31,7 +31,6 @@ import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupUsername2Binding import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity -import im.vector.app.features.login.SignMode import im.vector.app.features.login.SocialLoginButtonsView import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt index e5b2067504..0fb0d77734 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt @@ -32,7 +32,6 @@ import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSigninToAny2Binding import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity -import im.vector.app.features.login.SignMode import im.vector.app.features.login.SocialLoginButtonsView import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt index 9676a527e1..8b38d9395a 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt @@ -25,7 +25,6 @@ import im.vector.app.R import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSsoOnly2Binding import im.vector.app.features.login.SSORedirectRouterActivity -import im.vector.app.features.login.SignMode import org.matrix.android.sdk.internal.auth.SSOAction import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt index 557103e77c..d2012c703b 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt @@ -36,8 +36,6 @@ import im.vector.app.features.login.HomeServerConnectionConfigFactory import im.vector.app.features.login.LoginConfig import im.vector.app.features.login.LoginMode import im.vector.app.features.login.ReAuthHelper -import im.vector.app.features.login.SignMode -import im.vector.app.features.onboarding.OnboardingFlow import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getServerName 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 7bff08b50a..0e2964a9fd 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 @@ -48,6 +48,8 @@ import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult +import java.util.UUID +import java.util.concurrent.CancellationException import kotlinx.coroutines.Job import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch @@ -65,8 +67,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.auth.SSOAction import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import timber.log.Timber -import java.util.UUID -import java.util.concurrent.CancellationException /** * 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 338446300b..355d9b25d6 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 @@ -25,7 +25,6 @@ import androidx.viewbinding.ViewBinding 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.SignMode import im.vector.app.features.login.hasSso import im.vector.app.features.login.ssoIdentityProviders import im.vector.app.features.onboarding.OnboardingFlow 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 d2247d0d1d..1fa588f7ef 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 @@ -37,11 +37,9 @@ import im.vector.app.core.extensions.toReducedUrl 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.SignMode import im.vector.app.features.login.SocialLoginButtonsView import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction -import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.flow.combine 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 5e90db802a..53c413d692 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 @@ -43,7 +43,6 @@ import im.vector.app.core.extensions.toReducedUrl 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.SignMode import im.vector.app.features.login.SocialLoginButtonsView import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction From efe9832444bef0a677b00972820617ec248c6aa5 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Sun, 14 Aug 2022 19:15:37 +0100 Subject: [PATCH 005/317] Add missing action param --- .../src/main/java/im/vector/app/features/login/LoginActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index 763d1eed38..bc3f07bacd 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -49,6 +49,7 @@ import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.internal.auth.SSOAction /** * The LoginActivity manages the fragment navigation and also display the loading View. @@ -299,6 +300,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = null, + action = SSOAction.login )?.let { ssoUrl -> openUrlInChromeCustomTab(this, null, ssoUrl) } From ec4ed88ee3204e646da3dbec993684b61f0b91db Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Sun, 14 Aug 2022 19:25:24 +0100 Subject: [PATCH 006/317] Fix lint errors --- .../java/org/matrix/android/sdk/internal/auth/Constants.kt | 4 ++-- .../im/vector/app/features/login/AbstractSSOLoginFragment.kt | 2 +- .../main/java/im/vector/app/features/login/LoginActivity.kt | 2 +- .../main/java/im/vector/app/features/login/LoginFragment.kt | 2 +- .../app/features/login/LoginSignUpSignInSelectionFragment.kt | 4 ++-- .../vector/app/features/login2/AbstractSSOLoginFragment2.kt | 2 +- .../app/features/login2/LoginFragmentSignupUsername2.kt | 2 +- .../java/im/vector/app/features/login2/LoginFragmentToAny2.kt | 2 +- .../im/vector/app/features/login2/LoginSsoOnlyFragment2.kt | 2 +- .../onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt | 2 +- .../onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt | 2 +- .../onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt | 2 +- .../app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt | 2 +- .../ftueauth/FtueAuthSignUpSignInSelectionFragment.kt | 4 ++-- 14 files changed, 17 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt index a1c0100d0b..4a9317be03 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt @@ -43,6 +43,6 @@ internal const val SSO_UIA_FALLBACK_PATH = "/_matrix/client/r0/auth/m.login.sso/ * See https://github.com/matrix-org/matrix-spec-proposals/pull/3824 */ enum class SSOAction { - login, - register; + LOGIN, + REGISTER; } 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 03bdc9fced..88d9bb23ba 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 @@ -92,7 +92,7 @@ abstract class AbstractSSOLoginFragment : AbstractLoginFragmen redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = null, - action = if (state.signMode == SignMode.SignUp) SSOAction.register else SSOAction.login + action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { prefetchUrl(it) } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index bc3f07bacd..d0a3c6a85c 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -300,7 +300,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = null, - action = SSOAction.login + action = SSOAction.LOGIN )?.let { ssoUrl -> openUrlInChromeCustomTab(this, null, ssoUrl) } 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 3d536cc280..fbe2b7b3d6 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 @@ -209,7 +209,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment : AbstractLoginFragme redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = null, - action = if (state.signMode == SignMode2.SignUp) SSOAction.register else SSOAction.login + action = if (state.signMode == SignMode2.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { prefetchUrl(it) } } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt index 6b2f0f6bd1..1f6d40fb49 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt @@ -103,7 +103,7 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, providerId = provider?.id, - action = if (state.signMode == SignMode2.SignUp) SSOAction.register else SSOAction.login + action = if (state.signMode == SignMode2.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { openInCustomTab(it) } } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt index 0fb0d77734..6c64733fbc 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt @@ -130,7 +130,7 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2 : AbstractFtueAuthF redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, provider = null, - action = if (state.onboardingFlow == OnboardingFlow.SignUp) SSOAction.register else SSOAction.login + action = if (state.onboardingFlow == OnboardingFlow.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { prefetchUrl(it) } } 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 1fa588f7ef..a79a894434 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 @@ -147,7 +147,7 @@ class FtueAuthCombinedLoginFragment @Inject constructor( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, provider = id, - action = SSOAction.login + action = SSOAction.LOGIN )?.let { openInCustomTab(it) } } } 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 53c413d692..5d3ee33f3e 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 @@ -218,7 +218,7 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, provider = provider, - action = SSOAction.register + action = SSOAction.REGISTER )?.let { openInCustomTab(it) } } } 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 1373a79ffa..e274f06291 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 @@ -223,7 +223,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, provider = provider, - action = if (state.signMode == SignMode.SignUp) SSOAction.register else SSOAction.login + action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { openInCustomTab(it) } } 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 20421ec45e..08432d851c 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 @@ -89,7 +89,7 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, provider = provider, - action = if (state.signMode == SignMode.SignUp) SSOAction.register else SSOAction.login + action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { openInCustomTab(it) } } @@ -131,7 +131,7 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, provider = null, - action = if (state.onboardingFlow == OnboardingFlow.SignUp) SSOAction.register else SSOAction.login + action = if (state.onboardingFlow == OnboardingFlow.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { openInCustomTab(it) } } else { From 21b41cd3f1a90a41fffc41f29b4dde175e577326 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Sun, 14 Aug 2022 19:29:15 +0100 Subject: [PATCH 007/317] Fix lint errors --- .../vector/app/features/onboarding/OnboardingViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 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 0e2964a9fd..f6fcddba48 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 @@ -48,8 +48,6 @@ import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult -import java.util.UUID -import java.util.concurrent.CancellationException import kotlinx.coroutines.Job import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch @@ -64,9 +62,11 @@ 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.isHomeserverUnavailable import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.internal.auth.SSOAction import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider +import org.matrix.android.sdk.internal.auth.SSOAction import timber.log.Timber +import java.util.UUID +import java.util.concurrent.CancellationException /** * From d0d75e79a56cd0ccadfc46e130ea0faec5eb06ca Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Sun, 14 Aug 2022 20:13:15 +0100 Subject: [PATCH 008/317] Lint fix --- .../ftueauth/FtueAuthSignUpSignInSelectionFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 08432d851c..a081e3df77 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 @@ -115,7 +115,8 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.Sso -> { // change to only one button that is sign in with sso - views.loginSignupSigninSubmit.text = if (state.selectedHomeserver.hasOidcCompatibilityFlow) getString(R.string.login_continue) else getString(R.string.login_signin_sso) + views.loginSignupSigninSubmit.text = + if (state.selectedHomeserver.hasOidcCompatibilityFlow) getString(R.string.login_continue) else getString(R.string.login_signin_sso) views.loginSignupSigninSignIn.isVisible = false } else -> { From 74146f4f8ea3ce6af4b93e67bc0ca9f8a576c005 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Sun, 14 Aug 2022 20:15:40 +0100 Subject: [PATCH 009/317] Migrate SSOAction to api package hierachy --- .../sdk/api/auth/AuthenticationService.kt | 1 - .../matrix/android/sdk/api/auth/SSOAction.kt | 25 +++++++++++++++++++ .../android/sdk/internal/auth/Constants.kt | 8 ------ .../auth/DefaultAuthenticationService.kt | 1 + .../login/AbstractSSOLoginFragment.kt | 2 +- .../app/features/login/LoginActivity.kt | 2 +- .../app/features/login/LoginFragment.kt | 2 +- .../LoginSignUpSignInSelectionFragment.kt | 2 +- .../app/features/login/LoginViewModel.kt | 2 +- .../login2/AbstractSSOLoginFragment2.kt | 2 +- .../login2/LoginFragmentSignupUsername2.kt | 2 +- .../features/login2/LoginFragmentToAny2.kt | 2 +- .../features/login2/LoginSsoOnlyFragment2.kt | 2 +- .../app/features/login2/LoginViewModel2.kt | 2 +- .../onboarding/OnboardingViewModel.kt | 2 +- .../ftueauth/AbstractSSOFtueAuthFragment.kt | 2 +- .../ftueauth/FtueAuthCombinedLoginFragment.kt | 2 +- .../FtueAuthCombinedRegisterFragment.kt | 2 +- .../ftueauth/FtueAuthLoginFragment.kt | 2 +- .../FtueAuthSignUpSignInSelectionFragment.kt | 2 +- 20 files changed, 42 insertions(+), 25 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 1d307422b4..e67d67c7f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.internal.auth.SSOAction /** * This interface defines methods to authenticate or to create an account to a matrix server. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt new file mode 100644 index 0000000000..259680fd52 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt @@ -0,0 +1,25 @@ +/* + * 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 org.matrix.android.sdk.api.auth + +/** + * See https://github.com/matrix-org/matrix-spec-proposals/pull/3824 + */ +enum class SSOAction { + LOGIN, + REGISTER; +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt index 4a9317be03..97f8a9d512 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt @@ -38,11 +38,3 @@ internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl" // Ref: https://matrix.org/docs/spec/client_server/r0.6.1#single-sign-on internal const val SSO_UIA_FALLBACK_PATH = "/_matrix/client/r0/auth/m.login.sso/fallback/web" - -/** - * See https://github.com/matrix-org/matrix-spec-proposals/pull/3824 - */ -enum class SSOAction { - LOGIN, - REGISTER; -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 344945e981..479ba2810a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.LoginType +import org.matrix.android.sdk.api.auth.SSOAction import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.LoginFlowResult 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 88d9bb23ba..5ba4513fbe 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 @@ -24,7 +24,7 @@ import androidx.browser.customtabs.CustomTabsSession import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.withState import im.vector.app.core.utils.openUrlInChromeCustomTab -import org.matrix.android.sdk.internal.auth.SSOAction +import org.matrix.android.sdk.api.auth.SSOAction abstract class AbstractSSOLoginFragment : AbstractLoginFragment() { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index d0a3c6a85c..d28495a57e 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -45,11 +45,11 @@ import im.vector.app.features.login.terms.LoginTermsFragment import im.vector.app.features.login.terms.LoginTermsFragmentArgument import im.vector.app.features.onboarding.AuthenticationDescription import im.vector.app.features.pin.UnlockedActivity +import org.matrix.android.sdk.api.auth.SSOAction import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.internal.auth.SSOAction /** * The LoginActivity manages the fragment navigation and also display the loading View. 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 fbe2b7b3d6..d27fbf7693 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,11 +37,11 @@ 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.SSOAction 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 -import org.matrix.android.sdk.internal.auth.SSOAction import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject 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 84fe820c9c..d7b63d53fb 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,8 +25,8 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding +import org.matrix.android.sdk.api.auth.SSOAction import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import org.matrix.android.sdk.internal.auth.SSOAction import javax.inject.Inject /** 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 9a48a47c23..65b388bf99 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 @@ -39,6 +39,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService +import org.matrix.android.sdk.api.auth.SSOAction import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.login.LoginWizard @@ -50,7 +51,6 @@ import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixIdFailure import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.internal.auth.SSOAction import timber.log.Timber import java.util.concurrent.CancellationException diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt index 736693d050..ffd27a0c51 100644 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt @@ -27,7 +27,7 @@ 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 org.matrix.android.sdk.internal.auth.SSOAction +import org.matrix.android.sdk.api.auth.SSOAction abstract class AbstractSSOLoginFragment2 : AbstractLoginFragment2() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt index 1f6d40fb49..18645235f5 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt @@ -35,8 +35,8 @@ import im.vector.app.features.login.SocialLoginButtonsView import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.auth.SSOAction import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import org.matrix.android.sdk.internal.auth.SSOAction import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt index 6c64733fbc..089822fc74 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt @@ -37,11 +37,11 @@ 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.SSOAction 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 -import org.matrix.android.sdk.internal.auth.SSOAction import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt index 3ee4d8a452..e631aff7d1 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt @@ -25,7 +25,7 @@ import im.vector.app.R import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSsoOnly2Binding import im.vector.app.features.login.SSORedirectRouterActivity -import org.matrix.android.sdk.internal.auth.SSOAction +import org.matrix.android.sdk.api.auth.SSOAction import javax.inject.Inject /** diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt index d2012c703b..71129f05e9 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt @@ -41,6 +41,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService +import org.matrix.android.sdk.api.auth.SSOAction import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.login.LoginWizard @@ -51,7 +52,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.internal.auth.SSOAction import timber.log.Timber import java.util.concurrent.CancellationException 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 f6fcddba48..0d62a9958a 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 @@ -55,6 +55,7 @@ import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService +import org.matrix.android.sdk.api.auth.SSOAction 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.login.LoginWizard @@ -63,7 +64,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider -import org.matrix.android.sdk.internal.auth.SSOAction import timber.log.Timber import java.util.UUID import java.util.concurrent.CancellationException 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 2d5f0d6a70..4ded4dbfb7 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 @@ -28,7 +28,7 @@ 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.onboarding.OnboardingFlow -import org.matrix.android.sdk.internal.auth.SSOAction +import org.matrix.android.sdk.api.auth.SSOAction abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthFragment() { 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 a79a894434..7a38a539bb 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 @@ -44,8 +44,8 @@ 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.SSOAction import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import org.matrix.android.sdk.internal.auth.SSOAction 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 5d3ee33f3e..7c052baddf 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 @@ -51,6 +51,7 @@ 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.SSOAction 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 @@ -59,7 +60,6 @@ import org.matrix.android.sdk.api.failure.isLoginEmailUnknown import org.matrix.android.sdk.api.failure.isRegistrationDisabled import org.matrix.android.sdk.api.failure.isUsernameInUse import org.matrix.android.sdk.api.failure.isWeakPassword -import org.matrix.android.sdk.internal.auth.SSOAction import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject 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 e274f06291..3774a9de74 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 @@ -45,6 +45,7 @@ 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.SSOAction 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 @@ -52,7 +53,6 @@ import org.matrix.android.sdk.api.failure.isLoginEmailUnknown import org.matrix.android.sdk.api.failure.isRegistrationDisabled import org.matrix.android.sdk.api.failure.isUsernameInUse import org.matrix.android.sdk.api.failure.isWeakPassword -import org.matrix.android.sdk.internal.auth.SSOAction import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject 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 a081e3df77..3a4a4d8098 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 @@ -35,8 +35,8 @@ import im.vector.app.features.login.ssoIdentityProviders import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingViewState +import org.matrix.android.sdk.api.auth.SSOAction import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import org.matrix.android.sdk.internal.auth.SSOAction import javax.inject.Inject /** From f6016d7b5541a58a483bc0a67e88d44eaead6071 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Sun, 14 Aug 2022 20:36:11 +0100 Subject: [PATCH 010/317] Correct copyright on SDK file --- .../src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt index 259680fd52..db2dd870d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/SSOAction.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From f18cc5e53b38ae5f94895ecb7a141f442d3fa04e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Sun, 14 Aug 2022 21:57:35 +0100 Subject: [PATCH 011/317] Fix unit tests --- .../app/features/onboarding/OnboardingViewModelTest.kt | 5 +++-- .../im/vector/app/test/fakes/FakeAuthenticationService.kt | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) 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 b8a0be9529..6107af1551 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 @@ -54,6 +54,7 @@ import org.amshove.kluent.shouldBeEqualTo import org.junit.Before import org.junit.Rule import org.junit.Test +import org.matrix.android.sdk.api.auth.SSOAction 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 @@ -896,9 +897,9 @@ class OnboardingViewModelTest { fun `given returns Sso url, when fetching Sso url, then updates authentication state and returns supplied Sso url`() = runTest { val test = viewModel.test() val provider = SsoIdentityProvider(id = "provider_id", null, null, null) - fakeAuthenticationService.givenSsoUrl(A_REDIRECT_URI, A_DEVICE_ID, provider.id, result = A_SSO_URL) + fakeAuthenticationService.givenSsoUrl(A_REDIRECT_URI, A_DEVICE_ID, provider.id, SSOAction.LOGIN, result = A_SSO_URL) - val result = viewModel.fetchSsoUrl(A_REDIRECT_URI, A_DEVICE_ID, provider) + val result = viewModel.fetchSsoUrl(A_REDIRECT_URI, A_DEVICE_ID, provider, SSOAction.LOGIN) result shouldBeEqualTo A_SSO_URL test diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt index af53913169..fa446537c8 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt @@ -22,6 +22,7 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.SSOAction import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.auth.login.LoginWizard @@ -78,7 +79,7 @@ class FakeAuthenticationService : AuthenticationService by mockk() { coVerify { cancelPendingLoginOrRegistration() } } - fun givenSsoUrl(redirectUri: String, deviceId: String, providerId: String, result: String) { - coEvery { getSsoUrl(redirectUri, deviceId, providerId) } returns result + fun givenSsoUrl(redirectUri: String, deviceId: String, providerId: String, action: SSOAction, result: String) { + coEvery { getSsoUrl(redirectUri, deviceId, providerId, action) } returns result } } From 26d71e214a4d3e73c19246e842b3b577ee23b307 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 6 Jan 2023 17:43:14 +0000 Subject: [PATCH 012/317] Updated implementation including outbound link for account management --- .../src/main/res/values/strings.xml | 3 ++ .../sdk/api/auth/data/DelegatedAuthConfig.kt | 40 +++++++++++++++++++ .../android/sdk/api/auth/data/WellKnown.kt | 8 +++- .../homeserver/HomeServerCapabilities.kt | 4 ++ .../auth/DefaultAuthenticationService.kt | 2 +- .../database/RealmSessionStoreMigration.kt | 4 +- .../mapper/HomeServerCapabilitiesMapper.kt | 1 + .../database/migration/MigrateSessionTo048.kt | 31 ++++++++++++++ .../model/HomeServerCapabilitiesEntity.kt | 1 + .../GetHomeServerCapabilitiesTask.kt | 1 + .../vector/app/features/login/LoginAction.kt | 3 +- .../app/features/login/LoginActivity.kt | 2 +- .../app/features/login/LoginFragment.kt | 5 +-- .../im/vector/app/features/login/LoginMode.kt | 4 +- .../LoginSignUpSignInSelectionFragment.kt | 8 ++-- .../app/features/login/LoginViewModel.kt | 6 +-- .../features/login/SocialLoginButtonsView.kt | 14 ++++++- .../StartAuthenticationFlowUseCase.kt | 8 ++-- .../ftueauth/AbstractSSOFtueAuthFragment.kt | 3 +- .../ftueauth/FtueAuthCombinedLoginFragment.kt | 10 ++--- .../FtueAuthCombinedRegisterFragment.kt | 8 ++-- .../ftueauth/FtueAuthLoginFragment.kt | 5 +-- .../FtueAuthSignUpSignInSelectionFragment.kt | 6 +-- .../features/settings/VectorPreferences.kt | 1 + .../settings/VectorSettingsGeneralFragment.kt | 23 +++++++++++ .../signout/soft/SoftLogoutFragment.kt | 9 +++-- .../signout/soft/SoftLogoutViewModel.kt | 7 +++- .../main/res/xml/vector_settings_general.xml | 8 +++- .../StartAuthenticationFlowUseCaseTest.kt | 4 +- .../fixtures/HomeserverCapabilityFixture.kt | 2 + 30 files changed, 179 insertions(+), 52 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 8c5874f233..146df5054a 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1062,6 +1062,9 @@ Discovery Manage your discovery settings. + Account + Manage your account at %1$s. + Analytics Send analytics data diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt new file mode 100644 index 0000000000..b57472ab7c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/DelegatedAuthConfig.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2023 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.auth.data + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * https://github.com/matrix-org/matrix-spec-proposals/pull/2965 + *
+ * {
+ *     "issuer": "https://id.server.org",
+ *     "account": "https://id.server.org/my-account",
+ * }
+ * 
+ * . + */ + +@JsonClass(generateAdapter = true) +data class DelegatedAuthConfig( + @Json(name = "issuer") + val issuer: String, + + @Json(name = "account") + val accountManagementUrl: String, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt index 10c7d51392..9243fa7c54 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt @@ -54,5 +54,11 @@ data class WellKnown( val identityServer: WellKnownBaseConfig? = null, @Json(name = "m.integrations") - val integrations: JsonDict? = null + val integrations: JsonDict? = null, + + /** + * For delegation of auth via OIDC as per [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965) + */ + @Json(name = "org.matrix.msc2965.authentication") + val unstableDelegatedAuthConfig: DelegatedAuthConfig? = null, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index 11638837cc..a0f76bad21 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -75,6 +75,10 @@ data class HomeServerCapabilities( * True if the home server supports remote toggle of Pusher for a given device. */ val canRemotelyTogglePushNotificationsOfDevices: Boolean = false, + /** + * External account management url for use with MSC3824 delegated OIDC, provided in Wellknown. + */ + val externalAccountManagementUrl: String? = null, ) { enum class RoomCapabilitySupport { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index acbfc42dcc..d1dd0238ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -105,7 +105,7 @@ internal class DefaultAuthenticationService @Inject constructor( appendParamToUrl("device_id", it) } - // MSC3824 action param + // unstable MSC3824 action param appendParamToUrl("org.matrix.msc3824.action", action.toString()) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index ba102a7a48..2b7e9a04a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -72,7 +73,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 47L, + schemaVersion = 48L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -129,5 +130,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 45) MigrateSessionTo045(realm).perform() if (oldVersion < 46) MigrateSessionTo046(realm).perform() if (oldVersion < 47) MigrateSessionTo047(realm).perform() + if (oldVersion < 48) MigrateSessionTo048(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 89657ad882..40b33cd13b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -47,6 +47,7 @@ internal object HomeServerCapabilitiesMapper { canLoginWithQrCode = entity.canLoginWithQrCode, canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications, canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices, + externalAccountManagementUrl = entity.externalAccountManagementUrl, ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt new file mode 100644 index 0000000000..3304f5adad --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 40) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.EXTERNAL_ACCOUNT_MANAGEMENT_URL, String::class.java) + ?.forceRefreshOfHomeServerCapabilities() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 2b60f7723c..c081535cf4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -34,6 +34,7 @@ internal open class HomeServerCapabilitiesEntity( var canLoginWithQrCode: Boolean = false, var canUseThreadReadReceiptsAndNotifications: Boolean = false, var canRemotelyTogglePushNotificationsOfDevices: Boolean = false, + var externalAccountManagementUrl: String? = null, ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 11e86a5c51..72b638481b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -164,6 +164,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( Timber.v("Extracted integration config : $config") realm.insertOrUpdate(config) } + homeServerCapabilitiesEntity.externalAccountManagementUrl = getWellknownResult.wellKnown.unstableDelegatedAuthConfig?.accountManagementUrl } homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt index 5947fa0cb5..984c3694e8 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt @@ -69,7 +69,8 @@ sealed class LoginAction : VectorViewModelAction { data class SetupSsoForSessionRecovery( val homeServerUrl: String, val deviceId: String, - val ssoIdentityProviders: List? + val ssoIdentityProviders: List?, + val hasOidcCompatibilityFlow: Boolean ) : LoginAction() data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction() diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index c70e503d81..9dfae7ff5f 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -45,8 +45,8 @@ import im.vector.app.features.login.terms.LoginTermsFragment import im.vector.app.features.login.terms.LoginTermsFragmentArgument import im.vector.app.features.onboarding.AuthenticationDescription import im.vector.app.features.pin.UnlockedActivity -import org.matrix.android.sdk.api.auth.SSOAction import im.vector.lib.core.utils.compat.getParcelableExtraCompat +import org.matrix.android.sdk.api.auth.SSOAction import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms 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 9bdf2f593f..01d15db3d1 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 @@ -39,7 +39,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.auth.SSOAction -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 @@ -202,11 +201,11 @@ class LoginFragment : if (state.loginMode is LoginMode.SsoAndPassword) { views.loginSocialLoginContainer.isVisible = true - views.loginSocialLoginButtons.render(state.loginMode.ssoState, ssoMode(state)) { provider -> + views.loginSocialLoginButtons.render(state.loginMode, ssoMode(state)) { provider -> loginViewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = provider?.id + providerId = provider?.id, action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { openInCustomTab(it) } 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 944b159441..384108e6a8 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 @@ -23,8 +23,8 @@ sealed class LoginMode : Parcelable { // Parcelable because persist state @Parcelize object Unknown : LoginMode() @Parcelize object Password : LoginMode() - @Parcelize data class Sso(val ssoState: SsoState) : LoginMode() - @Parcelize data class SsoAndPassword(val ssoState: SsoState) : LoginMode() + @Parcelize data class Sso(val ssoState: SsoState, val hasOidcCompatibilityFlow: Boolean) : LoginMode() + @Parcelize data class SsoAndPassword(val ssoState: SsoState, val hasOidcCompatibilityFlow: Boolean) : LoginMode() @Parcelize object Unsupported : LoginMode() } 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 b9e2e5fca4..5ed806622f 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,10 +26,8 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding -import org.matrix.android.sdk.api.auth.SSOAction -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import javax.inject.Inject import im.vector.app.features.login.SocialLoginButtonsView.Mode +import org.matrix.android.sdk.api.auth.SSOAction /** * In this screen, the user is asked to sign up or to sign in to the homeserver. @@ -78,11 +76,11 @@ class LoginSignUpSignInSelectionFragment : when (state.loginMode) { is LoginMode.SsoAndPassword -> { views.loginSignupSigninSignInSocialLoginContainer.isVisible = true - views.loginSignupSigninSocialLoginButtons.render(state.loginMode.ssoState(), Mode.MODE_CONTINUE) { provider -> + views.loginSignupSigninSocialLoginButtons.render(state.loginMode, Mode.MODE_CONTINUE) { provider -> loginViewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - providerId = provider?.id + providerId = provider?.id, action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { openInCustomTab(it) } 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 aefaeceae7..298db3ad48 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 @@ -225,7 +225,7 @@ class LoginViewModel @AssistedInject constructor( setState { copy( signMode = SignMode.SignIn, - loginMode = LoginMode.Sso(action.ssoIdentityProviders.toSsoState()), + loginMode = LoginMode.Sso(action.ssoIdentityProviders.toSsoState(), action.hasOidcCompatibilityFlow), homeServerUrlFromUser = action.homeServerUrl, homeServerUrl = action.homeServerUrl, deviceId = action.deviceId @@ -818,8 +818,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.toSsoState()) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState()) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders.toSsoState(), data.hasOidcCompatibilityFlow) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState(), data.hasOidcCompatibilityFlow) 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 816050420e..383fcf43e7 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 @@ -56,6 +56,14 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: } } + var hasOidcCompatibilityFlow: Boolean = false + set(value) { + if (value != hasOidcCompatibilityFlow) { + field = value + update() + } + } + var listener: InteractionListener? = null private fun update() { @@ -70,7 +78,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: transformationMethod = null textAlignment = View.TEXT_ALIGNMENT_CENTER }.let { - it.text = getButtonTitle(context.getString(R.string.login_social_sso)) + it.text = if (hasOidcCompatibilityFlow) context.getString(R.string.login_continue) else getButtonTitle(context.getString(R.string.login_social_sso)) it.textAlignment = View.TEXT_ALIGNMENT_CENTER it.setOnClickListener { listener?.onProviderSelected(null) @@ -160,11 +168,13 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: } } -fun SocialLoginButtonsView.render(state: SsoState, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) { +fun SocialLoginButtonsView.render(loginMode: LoginMode, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) { this.mode = mode + val state = loginMode.ssoState() this.ssoIdentityProviders = when (state) { SsoState.Fallback -> null is SsoState.IdentityProviders -> state.providers.sorted() } + this.hasOidcCompatibilityFlow = (loginMode is LoginMode.Sso && loginMode.hasOidcCompatibilityFlow) || (loginMode is LoginMode.SsoAndPassword && loginMode.hasOidcCompatibilityFlow) this.listener = SocialLoginButtonsView.InteractionListener { listener(it) } } 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 15f3aeca47..28beca8269 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 @@ -48,13 +48,13 @@ class StartAuthenticationFlowUseCase @Inject constructor( preferredLoginMode = preferredLoginMode, supportedLoginTypes = authFlow.supportedLoginTypes, hasOidcCompatibilityFlow = authFlow.hasOidcCompatibilityFlow, - isLogoutDevicesSupported = authFlow.isLogoutDevicesSupported - isLoginWithQrSupported = authFlow.isLoginWithQrSupported, + isLogoutDevicesSupported = authFlow.isLogoutDevicesSupported, + isLoginWithQrSupported = authFlow.isLoginWithQrSupported ) private fun LoginFlowResult.findPreferredLoginMode() = when { - supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders.toSsoState()) - supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders.toSsoState()) + supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders.toSsoState(), hasOidcCompatibilityFlow) + supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders.toSsoState(), hasOidcCompatibilityFlow) 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 7257a21434..211c630320 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,10 +26,9 @@ 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 import im.vector.app.features.onboarding.OnboardingFlow import org.matrix.android.sdk.api.auth.SSOAction -import im.vector.app.features.login.ssoState abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthFragment() { 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 f05af3cc7b..c860530dc7 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,7 +40,6 @@ import im.vector.app.features.VectorFeatures 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.qr.QrCodeLoginArgs import im.vector.app.features.login.qr.QrCodeLoginType import im.vector.app.features.login.render @@ -50,7 +49,6 @@ import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import org.matrix.android.sdk.api.auth.SSOAction -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -154,11 +152,11 @@ class FtueAuthCombinedLoginFragment : when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.SsoAndPassword -> { showUsernamePassword() - renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState) + renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode) } is LoginMode.Sso -> { hideUsernamePassword() - renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState) + renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode) } else -> { showUsernamePassword() @@ -167,10 +165,10 @@ class FtueAuthCombinedLoginFragment : } } - private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) { + private fun renderSsoProviders(deviceId: String?, loginMode: LoginMode) { views.ssoGroup.isVisible = true views.ssoButtonsHeader.isVisible = isUsernameAndPasswordVisible() - views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> + views.ssoButtons.render(loginMode, 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 479e8da77b..83a9a9c00b 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 @@ -45,7 +45,6 @@ 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 @@ -54,7 +53,6 @@ import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import org.matrix.android.sdk.api.auth.SSOAction -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 @@ -209,14 +207,14 @@ class FtueAuthCombinedRegisterFragment : } when (state.selectedHomeserver.preferredLoginMode) { - is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState) + is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode) else -> hideSsoProviders() } } - private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) { + private fun renderSsoProviders(deviceId: String?, loginMode: LoginMode) { views.ssoGroup.isVisible = true - views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider -> + views.ssoButtons.render(loginMode, 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 5d17924504..8cf8dffaf3 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 @@ -48,7 +48,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.auth.SSOAction -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 @@ -217,11 +216,11 @@ class FtueAuthLoginFragment : if (state.selectedHomeserver.preferredLoginMode is LoginMode.SsoAndPassword) { views.loginSocialLoginContainer.isVisible = true - views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, ssoMode(state)) { provider -> + views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode, ssoMode(state)) { provider -> viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - provider = provider + provider = provider, action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { openInCustomTab(it) } 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 cbdf8603c7..cd387f5f6b 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 @@ -37,8 +37,6 @@ import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingViewState import org.matrix.android.sdk.api.auth.SSOAction -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import javax.inject.Inject /** * In this screen, the user is asked to sign up or to sign in to the homeserver. @@ -85,11 +83,11 @@ class FtueAuthSignUpSignInSelectionFragment : when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.SsoAndPassword -> { views.loginSignupSigninSignInSocialLoginContainer.isVisible = true - views.loginSignupSigninSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, Mode.MODE_CONTINUE) { provider -> + views.loginSignupSigninSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode, Mode.MODE_CONTINUE) { provider -> viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, - provider = provider + provider = provider, action = if (state.signMode == SignMode.SignUp) SSOAction.REGISTER else SSOAction.LOGIN ) ?.let { openInCustomTab(it) } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 2d5fb351f9..6b68993b4f 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -58,6 +58,7 @@ class VectorPreferences @Inject constructor( const val SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY = "SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY" const val SETTINGS_DISCOVERY_PREFERENCE_KEY = "SETTINGS_DISCOVERY_PREFERENCE_KEY" const val SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY = "SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY" + const val SETTINGS_EXTERNAL_ACCOUNT_MANAGEMENT_KEY = "SETTINGS_EXTERNAL_ACCOUNT_MANAGEMENT_KEY" const val SETTINGS_CLEAR_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_CACHE_PREFERENCE_KEY" const val SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index 548a7be180..f28cf1374c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -48,6 +48,7 @@ import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.utils.TextUtils import im.vector.app.core.utils.getSizeOfFiles +import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.core.utils.toast import im.vector.app.databinding.DialogChangePasswordBinding import im.vector.app.features.MainActivity @@ -71,6 +72,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap import java.io.File +import java.net.URL import java.util.UUID import javax.inject.Inject @@ -101,6 +103,9 @@ class VectorSettingsGeneralFragment : private val mIdentityServerPreference by lazy { findPreference(VectorPreferences.SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY)!! } + private val mExternalAccountManagementPreference by lazy { + findPreference(VectorPreferences.SETTINGS_EXTERNAL_ACCOUNT_MANAGEMENT_KEY)!! + } // Local contacts private val mContactSettingsCategory by lazy { @@ -204,6 +209,24 @@ class VectorSettingsGeneralFragment : mIdentityServerPreference.onPreferenceClickListener = openDiscoveryScreenPreferenceClickListener + // External account management URL for delegated OIDC auth + // Hide the preference if no URL is given by server + if (homeServerCapabilities.externalAccountManagementUrl != null) { + mExternalAccountManagementPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + openUrlInExternalBrowser(it.context, homeServerCapabilities.externalAccountManagementUrl) + true + } + + val hostname = URL(homeServerCapabilities.externalAccountManagementUrl).host + + mExternalAccountManagementPreference.summary = requireContext().getString( + R.string.settings_external_account_management, + hostname + ) + } else { + mExternalAccountManagementPreference.isVisible = false + } + // Advanced settings // user account 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 47670b486a..af337b5be2 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 @@ -66,7 +66,8 @@ class SoftLogoutFragment : LoginAction.SetupSsoForSessionRecovery( softLogoutViewState.homeServerUrl, softLogoutViewState.deviceId, - mode.ssoState.providersOrNull() + mode.ssoState.providersOrNull(), + mode.hasOidcCompatibilityFlow ) ) } @@ -75,7 +76,8 @@ class SoftLogoutFragment : LoginAction.SetupSsoForSessionRecovery( softLogoutViewState.homeServerUrl, softLogoutViewState.deviceId, - mode.ssoState.providersOrNull() + mode.ssoState.providersOrNull(), + mode.hasOidcCompatibilityFlow ) ) } @@ -85,7 +87,8 @@ class SoftLogoutFragment : LoginAction.SetupSsoForSessionRecovery( softLogoutViewState.homeServerUrl, softLogoutViewState.deviceId, - null + null, + false ) ) } 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 117c298878..af84231af8 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 @@ -118,8 +118,11 @@ 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.toSsoState()) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState()) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword( + data.ssoIdentityProviders.toSsoState(), + data.hasOidcCompatibilityFlow + ) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState(), data.hasOidcCompatibilityFlow) data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password else -> LoginMode.Unsupported } diff --git a/vector/src/main/res/xml/vector_settings_general.xml b/vector/src/main/res/xml/vector_settings_general.xml index 30ef4337dc..8600dbc1be 100644 --- a/vector/src/main/res/xml/vector_settings_general.xml +++ b/vector/src/main/res/xml/vector_settings_general.xml @@ -33,6 +33,12 @@ android:summary="@string/settings_discovery_manage" android:title="@string/settings_discovery_category" /> + + - \ No newline at end of file + 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 6b0c893f2b..cc395afd18 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 @@ -98,7 +98,7 @@ class StartAuthenticationFlowUseCaseTest { result shouldBeEqualTo expectedResult( supportedLoginTypes = SSO_LOGIN_TYPE, - preferredLoginMode = LoginMode.Sso(SsoState.Fallback), + preferredLoginMode = LoginMode.Sso(SsoState.Fallback, false), ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } @@ -112,7 +112,7 @@ class StartAuthenticationFlowUseCaseTest { result shouldBeEqualTo expectedResult( supportedLoginTypes = SSO_LOGIN_TYPE, - preferredLoginMode = LoginMode.Sso(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)), + preferredLoginMode = LoginMode.Sso(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS), false), ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } diff --git a/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt index c9f32c2cf2..f8e8a03706 100644 --- a/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt +++ b/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt @@ -29,6 +29,7 @@ fun aHomeServerCapabilities( defaultIdentityServerUrl: String? = null, roomVersions: RoomVersionCapabilities? = null, canRemotelyTogglePushNotificationsOfDevices: Boolean = true, + externalAccountManagementUrl: String? = null, ) = HomeServerCapabilities( canChangePassword = canChangePassword, canChangeDisplayName = canChangeDisplayName, @@ -39,4 +40,5 @@ fun aHomeServerCapabilities( defaultIdentityServerUrl = defaultIdentityServerUrl, roomVersions = roomVersions, canRemotelyTogglePushNotificationsOfDevices = canRemotelyTogglePushNotificationsOfDevices, + externalAccountManagementUrl = externalAccountManagementUrl, ) From 4d6bbbbe8902e20760572739f4617e93e507e9df Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 6 Jan 2023 17:50:38 +0000 Subject: [PATCH 013/317] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit a588989d13aa9f3e6b17a6178b9cbde5b030a914 Merge: 56067300e0 5ee3eefe96 Author: Benoit Marty Date: Fri Jan 6 18:45:18 2023 +0100 Merge pull request #7875 from vector-im/feature/bma/releaseScript3 Release script update commit 56067300e0d9d67910b58915f3f262009738f2d6 Merge: baa46634b5 330a9be787 Author: Benoit Marty Date: Fri Jan 6 18:44:55 2023 +0100 Merge pull request #7905 from RiotTranslateBot/weblate-element-android-element-app Translations update from Weblate commit baa46634b5fb33f4d9131cf66e448bec28a75429 Merge: 93021a6028 0d2fb8e3d0 Author: Benoit Marty Date: Fri Jan 6 18:44:37 2023 +0100 Merge pull request #7885 from vector-im/feature/bma/fixLint Fix lint false positive commit 93021a6028f5103a568e61446d9d0859c14fff6f Merge: f856142cdc e9d1de8fba Author: Benoit Marty Date: Fri Jan 6 18:43:53 2023 +0100 Merge pull request #7724 from vector-im/feature/bma/launchWhenResumed Observe ViewEvents only when resumed commit e9d1de8fbac8d93f6a98aa3cc706f6ad16ea8f10 Author: Benoit Marty Date: Fri Jan 6 17:36:40 2023 +0100 Fix compilation issue after rebase. commit 330a9be7877d2f3b704fb94f307958e4853e532a Merge: f856142cdc 4f2550ae92 Author: Weblate Date: Fri Jan 6 16:33:47 2023 +0000 Merge branch 'origin/develop' into Weblate. commit 4f2550ae923b1be83c41c501a2283f37d3bfe4f7 Author: Linerly Date: Wed Jan 4 22:47:24 2023 +0000 Translated using Weblate (Indonesian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ commit 5734a270d8b1630f43543747174336191fe2bdb5 Author: waclaw66 Date: Wed Jan 4 16:04:26 2023 +0000 Translated using Weblate (Czech) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ commit 0882e1bf81b17f4afb84cb2dcf11c4bae38d44c9 Author: Jeff Huang Date: Thu Jan 5 02:10:13 2023 +0000 Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ commit 53db9885252338dd8aecc7ce88f1832ff00401fd Author: Christian Paul Date: Thu Jan 5 16:06:48 2023 +0000 Translated using Weblate (Esperanto) Currently translated at 2.2% (2 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/eo/ commit ff9cf8fd2f0e3edcfb0d55bddf86dad31938cdb3 Author: Danial Behzadi Date: Wed Jan 4 17:43:34 2023 +0000 Translated using Weblate (Persian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ commit 8a5aad1ba0a3ee66e40b10f05fafa22641cf63dc Author: Priit Jõerüüt Date: Thu Jan 5 07:22:25 2023 +0000 Translated using Weblate (Estonian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ commit 2903a644f2e258f6509e7dfa126bca5bcfed9c37 Author: Ihor Hordiichuk Date: Wed Jan 4 19:24:53 2023 +0000 Translated using Weblate (Ukrainian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ commit 96363fb789f82acbb24d1bb55f1c3bad505431c5 Author: Jozef Gaal Date: Wed Jan 4 16:18:18 2023 +0000 Translated using Weblate (Slovak) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/ commit 271b828be09c638cbc43a30708be8404493ac255 Author: Szimszon Date: Wed Jan 4 18:55:35 2023 +0000 Translated using Weblate (Hungarian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/hu/ commit bd21f032d4a53d0292f4c580c237cf573ba74361 Author: Glandos Date: Thu Jan 5 08:42:07 2023 +0000 Translated using Weblate (French) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ commit 860df019025ac652ef265814afa8eec759ce099d Author: Vri Date: Wed Jan 4 16:20:15 2023 +0000 Translated using Weblate (German) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/de/ commit ed84212c78d176c06760c9be65a097d11781e7ed Author: Besnik Bleta Date: Wed Jan 4 17:01:03 2023 +0000 Translated using Weblate (Albanian) Currently translated at 99.3% (2558 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ commit f790921785b91b1d46fa5a48199047abf9420ac5 Author: Mateus Rodrigues Costa Date: Wed Jan 4 21:12:32 2023 +0000 Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2576 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ commit 3098ec140d384a7705d384444c30e7d8a44b81df Author: overtinkering Date: Thu Jan 5 18:02:33 2023 +0000 Translated using Weblate (Spanish) Currently translated at 90.7% (2338 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/es/ commit 725722d3f29b42e033925491c8e6ca1d492aaf0e Author: Christian Paul Date: Thu Jan 5 16:14:10 2023 +0000 Translated using Weblate (Esperanto) Currently translated at 76.0% (1960 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/eo/ commit ea924642ce0e4eca0537f5f752e63c27d5fe5e3e Author: Christian Paul Date: Thu Jan 5 16:51:43 2023 +0000 Translated using Weblate (Danish) Currently translated at 10.2% (264 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/da/ commit f856142cdc4edfbbdc7047a81a41ef3ea9cedea4 Merge: b7076a13dc 85cfa433d9 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Jan 6 16:07:44 2023 +0100 Merge pull request #7886 from vector-im/feature/mna/past-polls-ui [Poll] Render past polls list of a room (PSG-1029) commit b7076a13dcab7a961079ef47bbd866c42d805397 Merge: 41bcdd7232 dbf3b76331 Author: Benoit Marty Date: Fri Jan 6 15:16:16 2023 +0100 Merge pull request #7879 from vector-im/feature/bma/still_investigating Reduce number of crypto database transactions when handling the sync response commit 7b1724f6dd1aa3651fc7a36890565cf9d213aa27 Author: Benoit Marty Date: Fri Jan 6 15:13:01 2023 +0100 changelog commit 9768430d5c19cc78a7d21c7dce375bb39890d26b Author: Benoit Marty Date: Mon Dec 19 18:32:07 2022 +0100 Fix test compilation issue commit 71bd4f457a8093683c6b9d046352e032c92d21eb Author: Benoit Marty Date: Wed Dec 7 17:48:25 2022 +0100 Ensure posted events from the ViewModel are consumed (once) by the UI Inspired from https://github.com/Kotlin/kotlinx.coroutines/issues/3002 commit 9c79d234440310bf41e4964c78dc48e8bbb89c15 Author: Benoit Marty Date: Fri Dec 16 21:02:33 2022 +0100 Ensure event are not sent if the lifecycle state is not RESUMED commit 0dd1abb9262a9ecf28ac85c99958e238d1459a95 Author: Benoit Marty Date: Tue Dec 6 13:02:02 2022 +0100 Rename method commit 41bcdd723239ff8df487debdacab3d042bf09743 Merge: b8da53b3bb 7fc9705f3a Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Jan 6 14:18:20 2023 +0100 Merge pull request #7867 from vector-im/feature/mna/active-polls-ui [Poll] Render active polls list of a room (PSG-908) commit 85cfa433d9e15362c0b907d19e253c01cff665c5 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Jan 6 14:13:58 2023 +0100 Using ordinal of enum to render tabs commit b8da53b3bb4a7e62e0fa20aeaab425c4782742ce Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Jan 6 11:56:11 2023 +0000 Bump checker from 3.27.0 to 3.29.0 (#7903) Bumps [checker](https://github.com/typetools/checker-framework) from 3.27.0 to 3.29.0. - [Release notes](https://github.com/typetools/checker-framework/releases) - [Changelog](https://github.com/typetools/checker-framework/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/typetools/checker-framework/compare/checker-framework-3.27.0...checker-framework-3.29.0) --- updated-dependencies: - dependency-name: org.checkerframework:checker dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit e88e874697a504896f1de2a36597d54607ffdb80 Merge: 2e95d4f97c c3ad7faa2c Author: Benoit Marty Date: Fri Jan 6 10:35:14 2023 +0100 Merge pull request #7865 from vector-im/dependabot/gradle/org.owasp-dependency-check-gradle-7.4.3 Bump dependency-check-gradle from 7.4.1 to 7.4.3 commit 2e95d4f97cf2bea6dddcb1de330394229f023d40 Merge: f1bd9b2cf3 87e661e3b5 Author: Florian Renaud Date: Fri Jan 6 09:10:00 2023 +0100 Merge pull request #7899 from vector-im/bugfix/fre/buffering_on_last_chunk [Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number commit 9b5fda2689531063847707804100be2576990afa Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 15:45:35 2023 +0100 Fix after rebase commit a5d076a28a2c82483cdaea9114f1e0cc0cc561da Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed Jan 4 10:49:07 2023 +0100 Adding total votes status for ended poll items commit 05363dc8ca26609ff41f746869a720ccb7da6135 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed Jan 4 10:30:57 2023 +0100 Adding winner option views for ended poll items commit 1cc26449f3f9145abe79e105a9635aa1f4152dde Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed Jan 4 10:12:34 2023 +0100 Renaming some ui fields commit 3deae1101c317375dbf1f14ddbcde8e40fa5a6c9 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue Jan 3 17:32:41 2023 +0100 Adding extra data for ended poll commit cf82486efa20613b7130399928c094eb031a790b Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 17:38:55 2022 +0100 Adding mocked data for ended polls commit 740591cd38a01d8bf469cac3d78cf162e4a71c89 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 17:31:23 2022 +0100 Updating unit tests commit cb45056c1a311ec652d72a198c089abb66f78669 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 17:28:57 2022 +0100 Mutualizing list fragments and add ended polls tab commit 0b535910d649d5c9dbe3777f15971aabb4cc973c Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 15:50:32 2022 +0100 Adding changelog entry commit 7fc9705f3a92d392b0ce8855ecd79e6abb32cc40 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 16:37:06 2023 +0100 Adding importantForAccessibility attribute to icon commit 2dab6ed052912e6296b643d15b30c69ce0df7516 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 15:27:11 2023 +0100 Fix horizontal margin of tabs commit ff9e78be42c500fd3a0985cadb9db67be8c54df3 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 15:20:20 2023 +0100 Use classical for loop instead of forEach commit d60403545c4f323f7067bef06776660cdcf4b4d3 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 15:09:41 2023 +0100 Renaming of filter enum commit 87e661e3b5d895990648f5cc0e71e60d08d10982 Author: Florian Renaud Date: Thu Jan 5 14:36:22 2023 +0100 Add changelog file commit 0d2fb8e3d089bce9c47f3ace8b170be6c661ffe5 Author: Benoit Marty Date: Wed Jan 4 10:17:35 2023 +0100 Lint: fix KotlinNullnessAnnotation warning commit dbf3b763311ea0faae4df39a178ec584f5537f95 Author: Benoit Marty Date: Thu Jan 5 11:54:19 2023 +0100 Update doc. commit 27d32188bfd4af638af0a43b8044ce1a2a8701d7 Author: Benoit Marty Date: Thu Jan 5 11:04:20 2023 +0100 Aggregate data outside of the RealmCryptoStore. commit 682bb8bde09fb8f083a7442091b91a0214fe2cf9 Author: Florian Renaud Date: Wed Jan 4 14:06:58 2023 +0100 VB - Stop listening if we reach the last received chunk and there is no last sequence number commit 30940cb9370587b50fe2b5f05652032eaf0c1062 Author: Benoit Marty Date: Thu Jan 5 09:53:12 2023 +0100 Rename `UserCrossSigningKeys` to `UserIdentity` commit 7e26c4b6f2533fde4db32d9fc986481f4e22bd4d Author: Benoit Marty Date: Thu Jan 5 09:48:25 2023 +0100 Rename fun commit 354554e8435bbd0934066c130f63adb47df5e0ee Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 16:45:28 2022 +0100 Ignore missing ContentDescription commit e82c7afdae45860ee54aae8bf665305b723c9d4d Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 15:48:14 2022 +0100 Replace usage of colorAccent commit 6c0c5e506408d242ce1792ddd833c41836e573c3 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 15:12:12 2022 +0100 Rename poll item layout to be more generic commit bd9c53a96c96aa870d3edcb8db44a52f6afb2d42 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 14:57:37 2022 +0100 Show message when list is empty commit e0b77936c1e4325387a1e06e4d4fef34bd7acfdd Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 14:27:11 2022 +0100 Changing the date format commit bc985aa1ef310e5fe45cbaefc4b4cab71fa71dc8 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 14:19:50 2022 +0100 Adding unit tests for ViewModel commit 71b7edc6f2c095f476fda596cdc6cbaf2b3ca159 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 12:12:57 2022 +0100 Adding debug log commit bf67d2529f84899f1118f050d01b54cc0bd312a1 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 12:08:55 2022 +0100 Allow access of poll history only in debug variant commit 8de86e74807339f5a02abcea6fd5c1452ffc68c9 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 11:59:48 2022 +0100 Render mocked data get from use case commit 77d3b7da04b0e8e2cd9ff6ab44392565675eef11 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 11:40:41 2022 +0100 Fix missing id in Epoxy model commit f20513eb16602a7b05d02231ca784215f0a28551 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 10:56:44 2022 +0100 Render the active polls list on fragment commit 7b63f891c33ffbb28d42491ac4ac48b89aef0a4e Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 10:42:42 2022 +0100 Epoxy controller to render active poll list commit 9f97579f9dc4c928fd8f95449fde3a10b8f77040 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 10:07:50 2022 +0100 Epoxy model for active poll commit 10133bd20ffe12d059049d906079f3e74e57990f Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Dec 29 17:46:07 2022 +0100 Setup tab layout when landing on the room polls screen commit 7436c2e1f5a60ed8507c9df53b49bec014182e07 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Dec 29 16:41:42 2022 +0100 Navigate to new empty screen commit cba960fbd782c91ab7de7e84c3635d3374ce3de2 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Dec 29 16:05:52 2022 +0100 Adding new entry "Poll history" into room profile screen commit e903dac22480525c97ba7ccc90cde41eeb811491 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Dec 29 15:40:59 2022 +0100 Adding changelog entry commit 437b93cc18751777e92f7a249dd67c93460382e0 Author: Benoit Marty Date: Wed Jan 4 11:35:04 2023 +0100 Add some doc commit 06f3c1101042500973c1999f8b9ae0f97a42332e Author: Benoit Marty Date: Tue Jan 3 16:43:09 2023 +0100 Changelog commit 02e7157206404ea07c2544ac6e590c30ae201f98 Author: Benoit Marty Date: Tue Jan 3 16:16:17 2023 +0100 Introduce CryptoCrossSigningKeys container commit 4c4ef0d73eca19e846c553456d9f9e500276328c Author: Benoit Marty Date: Tue Jan 3 15:57:39 2023 +0100 Batch insertion of user data after downloading keys. commit f26178fc211efb9f0fa7fe56267401fd2a2a494a Author: Benoit Marty Date: Tue Jan 3 15:21:03 2023 +0100 Avoid useless transaction commit a386a4762c13ac13922382d7ee119de10b67dd67 Author: Benoit Marty Date: Tue Jan 3 15:18:32 2023 +0100 Crypto store: Log realm transactions and the duration commit c1a8bf828b9b5569a2e6498c7eec99a1d2c8597b Author: Benoit Marty Date: Tue Jan 3 15:15:15 2023 +0100 Batch insertion of `shouldEncryptForInvitedMembers` commit 6f384c799f46fe63c55b71a3590a8b0349861bbd Author: Benoit Marty Date: Tue Jan 3 15:02:45 2023 +0100 Batch insertion of `shouldShareHistory` commit 0e504e90145f021a7a1867184cc1a373dc5a5c2d Author: Benoit Marty Date: Tue Jan 3 11:55:41 2023 +0100 Format commit 837590104d1a9c7cd06ae2b6908eb7c1eebfbd89 Author: Benoit Marty Date: Tue Jan 3 11:55:32 2023 +0100 Avoid launching coroutine for nothing. commit 56986c3a77b77b19ea9be782543719e0bdffccf5 Author: Benoit Marty Date: Mon Jan 2 21:15:08 2023 +0100 Add a way to get the access token from the advances settings. commit 5ee3eefe964a4dc77f2cae24dafe3a1088fa2332 Author: Benoit Marty Date: Mon Jan 2 16:55:25 2023 +0100 Pull branch sooner to ensure release version is correctly guessed commit c3ad7faa2c951d232471a52d68c870a2bb347400 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Dec 29 23:02:43 2022 +0000 Bump dependency-check-gradle from 7.4.1 to 7.4.3 Bumps dependency-check-gradle from 7.4.1 to 7.4.3. --- updated-dependencies: - dependency-name: org.owasp:dependency-check-gradle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit b3d578d6b831972c4aa7e6dd3a7429347a0e5bd0 Author: Benoit Marty Date: Thu Dec 15 12:44:40 2022 +0100 Release script: Improve creation of the release on GitHub. commit 5e1d3e6c8df68ac05b46802679bd0508914a32c4 Author: Benoit Marty Date: Thu Dec 15 12:09:27 2022 +0100 Escape % --- build.gradle | 2 +- changelog.d/7724.bugfix | 1 + changelog.d/7864.wip | 2 + changelog.d/7879.bugfix | 1 + changelog.d/7899.bugfix | 1 + .../android/cs-CZ/changelogs/40105160.txt | 2 + .../android/cs-CZ/changelogs/40105180.txt | 2 + .../android/de-DE/changelogs/40105160.txt | 2 + .../android/de-DE/changelogs/40105180.txt | 2 + .../metadata/android/eo/short_description.txt | 2 +- fastlane/metadata/android/eo/title.txt | 2 +- .../android/et/changelogs/40105160.txt | 2 + .../android/et/changelogs/40105180.txt | 2 + .../android/fa/changelogs/40105160.txt | 2 + .../android/fa/changelogs/40105180.txt | 2 + .../android/fr-FR/changelogs/40105160.txt | 2 + .../android/fr-FR/changelogs/40105180.txt | 2 + .../android/hu-HU/changelogs/40105160.txt | 2 + .../android/hu-HU/changelogs/40105180.txt | 2 + .../android/id/changelogs/40105160.txt | 2 + .../android/id/changelogs/40105180.txt | 2 + .../android/sk/changelogs/40105160.txt | 2 + .../android/sk/changelogs/40105180.txt | 2 + .../android/uk/changelogs/40105160.txt | 2 + .../android/uk/changelogs/40105180.txt | 2 + .../android/zh-TW/changelogs/40105160.txt | 2 + .../android/zh-TW/changelogs/40105180.txt | 2 + .../src/main/res/values-da/strings.xml | 17 +- .../src/main/res/values-eo/strings.xml | 6 +- .../src/main/res/values-es/strings.xml | 109 +++--- .../src/main/res/values-pt-rBR/strings.xml | 42 +-- .../src/main/res/values-sq/strings.xml | 2 +- .../src/main/res/values/strings.xml | 8 + .../crypto/crosssigning/UserIdentity.kt | 26 ++ .../internal/crypto/DefaultCryptoService.kt | 32 +- .../sdk/internal/crypto/DeviceListManager.kt | 17 +- .../internal/crypto/store/IMXCryptoStore.kt | 29 +- .../internal/crypto/store/UserDataToStore.kt | 31 ++ .../crypto/store/db/CryptoStoreAggregator.kt | 27 ++ .../sdk/internal/crypto/store/db/Helper.kt | 12 +- .../crypto/store/db/RealmCryptoStore.kt | 342 ++++++++++-------- .../android/sdk/internal/di/SerializeNulls.kt | 2 - .../interceptors/FormattedJsonHttpLogger.kt | 3 +- .../network/parsing/CheckNumberType.kt | 3 - .../internal/session/StreamEventsManager.kt | 10 +- .../room/create/CreateLocalRoomTask.kt | 2 +- .../session/sync/SyncResponseHandler.kt | 7 +- .../SyncResponsePostTreatmentAggregator.kt | 5 + .../session/sync/handler/CryptoSyncHandler.kt | 5 +- .../sync/handler/room/RoomSyncHandler.kt | 19 +- tools/lint/lint.xml | 1 + tools/release/releaseScript.sh | 32 +- vector/build.gradle | 2 +- .../app/core/di/MavericksViewModelModule.kt | 6 + .../app/core/platform/VectorBaseActivity.kt | 24 +- .../VectorBaseBottomSheetDialogFragment.kt | 23 +- .../core/platform/VectorBaseDialogFragment.kt | 19 +- .../app/core/platform/VectorBaseFragment.kt | 24 +- .../app/core/platform/VectorViewModel.kt | 9 +- .../app/core/resources/StringArrayProvider.kt | 2 - .../app/core/resources/StringProvider.kt | 4 - .../im/vector/app/core/utils/SharedEvent.kt | 58 +++ .../im/vector/app/features/MainActivity.kt | 8 +- .../quads/SharedSecureStorageActivity.kt | 4 +- .../media/VectorAttachmentViewerActivity.kt | 17 +- .../roomprofile/RoomProfileActivity.kt | 6 + .../roomprofile/RoomProfileController.kt | 11 + .../roomprofile/RoomProfileFragment.kt | 4 + .../roomprofile/RoomProfileSharedAction.kt | 1 + .../roomprofile/polls/GetPollsUseCase.kt | 114 ++++++ .../features/roomprofile/polls/PollSummary.kt | 39 ++ .../roomprofile/polls/RoomPollsAction.kt | 21 ++ .../roomprofile/polls/RoomPollsFragment.kt | 74 ++++ .../polls/RoomPollsPagerAdapter.kt | 41 +++ .../roomprofile/polls/RoomPollsType.kt | 22 ++ .../roomprofile/polls/RoomPollsViewEvent.kt | 21 ++ .../roomprofile/polls/RoomPollsViewModel.kt | 54 +++ .../roomprofile/polls/RoomPollsViewState.kt | 28 ++ .../polls/active/RoomActivePollsFragment.kt | 34 ++ .../polls/ended/RoomEndedPollsFragment.kt | 34 ++ .../roomprofile/polls/list/RoomPollItem.kt | 72 ++++ .../polls/list/RoomPollsController.kt | 76 ++++ .../polls/list/RoomPollsListFragment.kt | 90 +++++ .../VectorSettingsAdvancedSettingsFragment.kt | 9 + .../settings/VectorSettingsBaseFragment.kt | 23 +- .../listening/VoiceBroadcastPlayerImpl.kt | 4 +- .../main/res/layout/fragment_room_polls.xml | 54 +++ .../res/layout/fragment_room_polls_list.xml | 42 +++ vector/src/main/res/layout/item_poll.xml | 69 ++++ .../xml/vector_settings_advanced_settings.xml | 6 + .../polls/RoomPollsViewModelTest.kt | 69 ++++ .../java/im/vector/app/test/Extensions.kt | 2 +- 92 files changed, 1696 insertions(+), 364 deletions(-) create mode 100644 changelog.d/7724.bugfix create mode 100644 changelog.d/7864.wip create mode 100644 changelog.d/7879.bugfix create mode 100644 changelog.d/7899.bugfix create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/et/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/et/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/hu-HU/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/hu-HU/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/id/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/id/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/sk/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/sk/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/uk/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/uk/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40105180.txt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt create mode 100644 vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt create mode 100644 vector/src/main/res/layout/fragment_room_polls.xml create mode 100644 vector/src/main/res/layout/fragment_room_polls_list.xml create mode 100644 vector/src/main/res/layout/item_poll.xml create mode 100644 vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt diff --git a/build.gradle b/build.gradle index 0f94fc418c..7e5d659c8b 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.2.3" - classpath 'org.owasp:dependency-check-gradle:7.4.1' + classpath 'org.owasp:dependency-check-gradle:7.4.3' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' diff --git a/changelog.d/7724.bugfix b/changelog.d/7724.bugfix new file mode 100644 index 0000000000..685f7ad4e2 --- /dev/null +++ b/changelog.d/7724.bugfix @@ -0,0 +1 @@ + Observe ViewEvents only when resumed and ensure ViewEvents are not lost. diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip new file mode 100644 index 0000000000..e1187ee1e7 --- /dev/null +++ b/changelog.d/7864.wip @@ -0,0 +1,2 @@ +[Poll] Render active polls list of a room +[Poll] Render past polls list of a room diff --git a/changelog.d/7879.bugfix b/changelog.d/7879.bugfix new file mode 100644 index 0000000000..be828ec2cc --- /dev/null +++ b/changelog.d/7879.bugfix @@ -0,0 +1 @@ +Reduce number of crypto database transactions when handling the sync response diff --git a/changelog.d/7899.bugfix b/changelog.d/7899.bugfix new file mode 100644 index 0000000000..d95af29d8d --- /dev/null +++ b/changelog.d/7899.bugfix @@ -0,0 +1 @@ +[Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105160.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105160.txt new file mode 100644 index 0000000000..69c2b3304c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Vlákna jsou nyní povolena ve výchozím nastavení. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105180.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105180.txt new file mode 100644 index 0000000000..69c2b3304c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Vlákna jsou nyní povolena ve výchozím nastavení. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105160.txt b/fastlane/metadata/android/de-DE/changelogs/40105160.txt new file mode 100644 index 0000000000..c55d8d998f --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Threads sind nun standardmäßig aktiviert. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105180.txt b/fastlane/metadata/android/de-DE/changelogs/40105180.txt new file mode 100644 index 0000000000..c55d8d998f --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Threads sind nun standardmäßig aktiviert. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/eo/short_description.txt b/fastlane/metadata/android/eo/short_description.txt index 33013ce78f..05a4aaf191 100644 --- a/fastlane/metadata/android/eo/short_description.txt +++ b/fastlane/metadata/android/eo/short_description.txt @@ -1 +1 @@ -Sekura kaj sencentrigita vokado kaj babilado. Tenu viajn datumojn sekuraj. +Grupa mesaĝisto - ĉifrita mesaĝado, grupa babilejo kaj videovokoj diff --git a/fastlane/metadata/android/eo/title.txt b/fastlane/metadata/android/eo/title.txt index f56927e529..85b92c693b 100644 --- a/fastlane/metadata/android/eo/title.txt +++ b/fastlane/metadata/android/eo/title.txt @@ -1 +1 @@ -Element (antaŭe Riot.im) +Element - Sekura Tujmesaĝilo diff --git a/fastlane/metadata/android/et/changelogs/40105160.txt b/fastlane/metadata/android/et/changelogs/40105160.txt new file mode 100644 index 0000000000..9aadf5dae8 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jutulõngad on vaikimisi kasutusel. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105180.txt b/fastlane/metadata/android/et/changelogs/40105180.txt new file mode 100644 index 0000000000..9aadf5dae8 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jutulõngad on vaikimisi kasutusel. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105160.txt b/fastlane/metadata/android/fa/changelogs/40105160.txt new file mode 100644 index 0000000000..0c3cc5aa31 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105160.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رشته‌ها اکنون به صورت پیش‌گزیده به کار افتاده‌اند. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105180.txt b/fastlane/metadata/android/fa/changelogs/40105180.txt new file mode 100644 index 0000000000..0c3cc5aa31 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105180.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رشته‌ها اکنون به صورت پیش‌گزیده به کار افتاده‌اند. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105160.txt b/fastlane/metadata/android/fr-FR/changelogs/40105160.txt new file mode 100644 index 0000000000..4101bb0c86 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Fils de discussion activés par défaut. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105180.txt b/fastlane/metadata/android/fr-FR/changelogs/40105180.txt new file mode 100644 index 0000000000..4101bb0c86 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Fils de discussion activés par défaut. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40105160.txt b/fastlane/metadata/android/hu-HU/changelogs/40105160.txt new file mode 100644 index 0000000000..c5dc38bc8f --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Legnagyobb változtatás ebben a verzióban: Új üzenetszálak alapból bekapcsolva! +Teljes változási napló: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40105180.txt b/fastlane/metadata/android/hu-HU/changelogs/40105180.txt new file mode 100644 index 0000000000..cc70967e58 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Legnagyobb változtatás ebben a verzióban: Az üzenetszálak alapból bekapcsolva! +Teljes változási napló: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105160.txt b/fastlane/metadata/android/id/changelogs/40105160.txt new file mode 100644 index 0000000000..173a1bfb1b --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Utasan sekarang diaktifkan secara bawaan. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105180.txt b/fastlane/metadata/android/id/changelogs/40105180.txt new file mode 100644 index 0000000000..173a1bfb1b --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Utasan sekarang diaktifkan secara bawaan. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105160.txt b/fastlane/metadata/android/sk/changelogs/40105160.txt new file mode 100644 index 0000000000..d5b5ad330d --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Vlákna sú teraz predvolene zapnuté. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105180.txt b/fastlane/metadata/android/sk/changelogs/40105180.txt new file mode 100644 index 0000000000..d5b5ad330d --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Vlákna sú teraz predvolene zapnuté. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105160.txt b/fastlane/metadata/android/uk/changelogs/40105160.txt new file mode 100644 index 0000000000..edbd209d17 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Гілки відтепер типово ввімкнено. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105180.txt b/fastlane/metadata/android/uk/changelogs/40105180.txt new file mode 100644 index 0000000000..edbd209d17 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Гілки відтепер типово ввімкнено. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105160.txt b/fastlane/metadata/android/zh-TW/changelogs/40105160.txt new file mode 100644 index 0000000000..9c66f3c2ad --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105160.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:討論串現在預設啟用。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105180.txt b/fastlane/metadata/android/zh-TW/changelogs/40105180.txt new file mode 100644 index 0000000000..9c66f3c2ad --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105180.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:討論串現在預設啟用。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/library/ui-strings/src/main/res/values-da/strings.xml b/library/ui-strings/src/main/res/values-da/strings.xml index 13d53b7bb2..35c93949f9 100644 --- a/library/ui-strings/src/main/res/values-da/strings.xml +++ b/library/ui-strings/src/main/res/values-da/strings.xml @@ -39,7 +39,6 @@ Telefonnummer Invitation til rum %1$s og %2$s - Tomt rum Lyst Tema Mørkt Tema @@ -82,7 +81,6 @@ Kun Matrix kontakter Ingen resultater Rum - Send logfiler Send crashlogfiler Send screenshot @@ -110,10 +108,8 @@ Dette ligner ikke en gyldig emailadresse Den emailadresse er allerede i brug. Glemt adgangskode? - Denne Home Server vil gerne være sikker på du ikke er en robot Kunne ikke verificere emailadresse: vær sikker på du klikkede på linket i emailen - Skriv gyldig URL Fejlformet JSON Indeholdt ikke gyldig JSON @@ -134,15 +130,10 @@ Opkald I Gang Den anden side tog den ikke. Information - - ${app_name} skal bruge tilladelse til at bruge din mikrofon for at lave lydopkald. - ${app_name} skal bruge tilladelse til at bruge dit kamera og din mikrofon for at lave videoopkald. Giv venligst tilladelse ved næste pop-up for at lave opkaldet. - - JA NEJ Fortsæt @@ -150,7 +141,6 @@ Giv venligst tilladelse ved næste pop-up for at lave opkaldet. Forbind Afvis Spring til første ulæste besked. - Forlad rum Er du sikker på at du vil forlade rummet? DIREKTE CHATS @@ -163,7 +153,7 @@ Giv venligst tilladelse ved næste pop-up for at lave opkaldet. Du vil ikke kunne omgøre denne ændring da du forfremmer brugeren til at have samme magt niveau som dig selv. Er du sikker? %s skriver… - "%1$s & %2$s skriver…" + %1$s & %2$s skriver… %1$s, %2$s og andre skriver… Du har ikke tilladelse til at skrive i dette rum Stol på @@ -186,7 +176,6 @@ Er du sikker? %d medlemsændringer Medlemsoversigt - 1 medlem %d medlemmer @@ -199,8 +188,6 @@ Er du sikker? Søg Filtrer medlemmer i rum Ingen resultater - - Alle meddelelser Opret genvej på startskærm Profilbillede @@ -291,4 +278,4 @@ Er du sikker? %1$s oprettede rummet Din invitation Forbind denne email med din konto - + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-eo/strings.xml b/library/ui-strings/src/main/res/values-eo/strings.xml index f536ca00f9..4521e840a6 100644 --- a/library/ui-strings/src/main/res/values-eo/strings.xml +++ b/library/ui-strings/src/main/res/values-eo/strings.xml @@ -718,8 +718,8 @@ Bonvolu enigi la URL-on de identiga servilo Ne povis konektiĝi al identiga servilo Enigu URL-on de identiga servilo - Ni sendis al vi konfirman retleteron al %s, bonvolu unue kontroli vian retpoŝton kaj klaki la konfirman ligilon - Ni sendis al vi konfirman retleteron al %s; kontrolu vian retpoŝton kaj klaku la konfirman ligilon + Ni sendis retleteron al %s, bonvolu unue kontroli vian retpoŝton kaj klaki la konfirman ligilon + Ni sendis retleteron al %s; kontrolu vian retpoŝton kaj klaku la konfirman ligilon Troveblaj telefonnumeroj Malkonekto de via identiga servilo signifas, ke vi ne estos trovebla de aliaj uzantoj kaj ne povos inviti aliulojn per retpoŝtadreso aŭ telefono. Elektebloj pri trovado aperos post aldono de telefonnumero. @@ -2201,4 +2201,4 @@ Sonorante… Aroj - Iom uzantoj reatentita - + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-es/strings.xml b/library/ui-strings/src/main/res/values-es/strings.xml index e07c21d6a5..c06442b5d0 100644 --- a/library/ui-strings/src/main/res/values-es/strings.xml +++ b/library/ui-strings/src/main/res/values-es/strings.xml @@ -50,7 +50,7 @@ %1$s ha invitado a %2$s. Razón: %3$s %1$s te ha invitado. Razón: %2$s %1$s se ha unido. Razón: %2$s - %1$s se ha ido. Razón: %2$s + %1$s dejó la sala. Razón: %2$s %1$s ha rechadazo la invitación. Razón: %2$s %1$s expulsó a %2$s. Razón: %3$s %1$s ha baneado a %2$s. Razón: %3$s @@ -81,17 +81,17 @@ %1$s ha permitido que los invitados se unan a la sala. %1$s ha impedido que los invitados se unan a la sala. %1$s ha activado el cifrado Extremo-a-Extremo. - %1$s ha activado el cifrado Extremo-a-Extremo (algoritmo no reconocido %2$s). + %1$s ha activado el cifrado extremo-a-extremo (algoritmo no reconocido %2$s). Tu invitación %1$s creó la sala Creaste la sala Invitaste a %1$s Te uniste a la Sala - Dejaste la Sala + Dejaste la sala Rechazaste la invitación Tu pateaste a %1$s Tu desbanaste a %1$s - Usted prohibió a %1$s + Excluiste a %1$s Retiró la invitación de %1$s\'s Cambiaste tu avatar Establece su nombre de visualización en %1$s @@ -152,10 +152,10 @@ Agregaste %1$s y quitaste %2$s como direcciones para esta sala. Estableciste la dirección principal de esta sala en %1$s. Quitaste la dirección principal de esta sala. - Ha permitido que los invitados se unan a la sala. - Ha impedido que los invitados se unan a la sala. + Has permitido que los invitados se unan a la sala. + Has impedido que los invitados se unan a la sala. Has activado el cifrado Extremo-a-Extremo. - Has activado el cifrado Extremo-a-Extremo (algoritmo %1$s no reconocido). + Has activado el cifrado extremo-a-extremo (algoritmo %1$s no reconocido). Has impedido que invitados se unan a la sala. Has permitido a invitados unirse aquí. Te has ido. Razón: %1$s @@ -163,7 +163,7 @@ Has invitado a %1$s Has actualizado aquí. Has hecho futuros mensajes visibles a %1$s - Te saliste de la sala + Has dejado la sala Te uniste Creaste la conversación %1$s ha impedido que invitados se unan a la sala. @@ -255,7 +255,7 @@ Salas y Grupos Filtrar salas Invitaciones - Prioridad baja + Baja prioridad Conversaciones Solo contactos de Matrix No hay resultados @@ -429,7 +429,7 @@ Importar Cifrar solo a sesiones verificadas Nunca enviar mensajes cifrados a sesiones sin verificar desde esta sesión. - SIN Verificar + Sin Verificar Verificado Verificar Para verificar que esta sesión es confiable, por favor contacta a su dueño por algún otro medio (ej. cara a cara o por teléfono) y pregúntale si la clave que ve en sus Ajustes de Usuario para esta sesión coincide con la clave a continuación: @@ -819,7 +819,7 @@ La copia de seguridad tiene una firma valida de la sesión no verificada %s La copia de seguridad tiene una firma inválida de la sesión verificada %s La copia de seguridad tiene una firma inválida de la sesión no verificada %s - Para usar la copia de seguridad de la clave en esta sesión introduzca su contraseña o su clave de recuperación ahora. + Para usar la copia de seguridad de la clave en esta sesión introduce tu contraseña o tu clave de recuperación ahora. ¿Deseas borrar tus claves de cifrado guardadas en el servidor\? No podrás usar tu clave de recuperación para leer el historial de mensajes cifrados. Reproducir sonido de cámara ip desconocida @@ -1182,7 +1182,7 @@ %s cancelada Cancelado por usted %s aceptada - Aceptado por usted + Aceptaste Verificacion enviada Solicitud de verificación Verifica esta Sesion @@ -1239,7 +1239,7 @@ Precaucion Error al obtener sesiones Sesiones - Confirmado + Confiable No es confiable Inicializar Firmas Cruzadas Restablecer claves @@ -1255,7 +1255,7 @@ Razón para redactar ${app_name} Android Refrescar - Nuevo inicio de sesión detectado . ¿Fue usted\? + Nuevo inicio de sesión detectado . ¿Has sido tú\? Este no era yo Su cuenta puede estar comprometida Verificación cancelada @@ -1263,7 +1263,7 @@ Clave de mensaje ¡Listo! Cifrado habilitado - Sala creada y configurada por usted. + Creaste y configuraste la sala. Esperando por %s… Ajuste de Notificaciones Mensaje… @@ -1279,7 +1279,7 @@ Si decea resetear su PIN, toque Olvidé PIN para cerrar sesión y restablecer. Numeros telefonicos Correos y numeros telefonicos - Administre el correo y numero telefonico de su cuenta + Administra las direcciones de correo y/o números telefónicos relacionados a tu cuenta de Matrix Mostrar mensajes eliminados Indicar marca de mensaje eliminado ARCHIVOS @@ -1357,7 +1357,7 @@ Hiciste la sala solo por invitación. Únase gratis a millones de personas en el mayor servidor público Continuar con SSO - Dirección de servicios de Element Matrix + Dirección de Element Matrix Services Ingrese la dirección del servidor que desea utilizar Se enviará un correo electrónico de verificación a su bandeja de entrada para confirmar la configuración de su nueva contraseña. Siguiente @@ -1407,7 +1407,7 @@ Advertencia Tu cuenta aún no está creada. ¿Detener el proceso de registro\? Seleccione matrix.org - Seleccionar servicios de matriz de elementos + Seleccionar Element Matrix Services Seleccione un servidor doméstico personalizado Realiza el desafío de captcha Acepta los términos para continuar @@ -1453,7 +1453,7 @@ \nVuelva a iniciar sesión para acceder a los datos y mensajes de su cuenta. Perderás el acceso a los mensajes seguros a menos que inicies sesión para recuperar tus claves de cifrado. La sesión actual es para el usuario %1$s y usted proporciona las credenciales para el usuario %2$s. Esto no está suportado por ${app_name}. -\nPrimero borre los datos, luego inicie sesión nuevamente con otra cuenta. +\nPrimero borra los datos, luego inicia sesión nuevamente con otra cuenta. Su enlace matrix.to estaba mal formado El modo desarrollador activa funciones ocultas y también puede hacer que la aplicación sea menos estable. ¡Solo para desarrolladores! Uno de los siguientes puede verse comprometido: @@ -1462,9 +1462,9 @@ \n- El servidor privado al que está conectado el usuario que estás verificando \n- Su conexión a internet o la de otros usuarios \n- Su dispositivo o el de otros usuarios - Los mensajes de esta sala están cifrados Extremo-a-Extremo. + Los mensajes de esta sala están cifrados de extremo-a-extremo. \n -\nSus mensajes están protegidos y sólo usted y el destinatario tienen las claves únicas para descifrarlos. +\nTus mensajes están protegidos y sólo tu y el destinatario tienen las claves únicas para descifrarlos. Esta sesión no puede compartir esta verificación con sus otras sesiones. \nLa verificación se guardará localmente y se compartirá en una versión futura de la aplicación. Envía el emote dado coloreado como un arcoíris @@ -1474,7 +1474,7 @@ Verifica si los mismos emojis aparecen en el mismo orden en ambos usuarios. Compare el código con el que se muestra en la pantalla del otro usuario. Los mensajes con este usuario están cifrados Extremo-a-Extremo y no pueden ser leídos por terceros. - Su nueva sesión ahora está verificada. Tiene acceso a sus mensajes cifrados y otros usuarios lo verán como de confianza. + Tu nueva sesión acaba de verificarse y ahora tiene acceso a tus mensajes cifrados y otros usuarios la verán como de confianza. La firma cruzada está habilitada \n Claves privadas en el dispositivo. La firma cruzada está habilitada @@ -1484,8 +1484,8 @@ \nLas claves no son de confianza El administrador de su servidor ha desactivado el cifrado Extremo-a-Extremo de forma predeterminada en salas privadas y mensajes directos. No hay información criptográfica disponible - Esta sesión es confiable para mensajería segura porque usted la verificó: - Verifique esta sesión para marcarla como confiable y otorgarle acceso a mensajes cifrados. Si no inició sesión en esta sesión, su cuenta puede verse comprometida: + Esta sesión es confiable para mensajería segura porque la verificaste: + Verifica esta sesión para marcarla como confiable y otorgarle acceso a mensajes cifrados. Si no iniciaste sesión en esta sesión, su cuenta puede haber sido comprometida: %d sesión activa %d sesiones activas @@ -1510,8 +1510,8 @@ Solicitudes clave Desbloquear el historial de mensajes cifrados Utilice esta sesión para verificar su nuevo, otorgándole acceso a mensajes cifrados. - Si cancela, no podrá leer mensajes cifrados en este dispositivo y otros usuarios no confiarán en él - Si cancela, no podrá leer mensajes cifrados en su nuevo dispositivo y otros usuarios no confiarán en él + Si cancelas, no podrás leer mensajes cifrados en este dispositivo y otros usuarios no confiarán en este + Si cancelas, no podrás leer mensajes cifrados en tu nuevo dispositivo y otros usuarios no confiarán en este No verificarás %1$s (%2$s) si cancelas ahora. Comience de nuevo en su perfil de usuario. Uno de los siguientes puede verse comprometido: \n @@ -1524,7 +1524,7 @@ Se canceló la verificación. Puede iniciar la verificación de nuevo. Ingrese su %s para continuar. No use la contraseña de su cuenta. - Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor. + Ingresa una frase de seguridad que solo tú conozcas, que se usa para proteger secretos en tu servidor. Esto puede tardar varios segundos, tenga paciencia. Configurando la recuperación. Manténlo seguro @@ -1561,7 +1561,7 @@ Nombre de usuario y / o contraseña incorrectos. La contraseña ingresada comienza o termina con espacios, verifíquela. Esta cuenta ha sido desactivada. Mejora de cifrado disponible - Verifíquese a usted mismo y a los demás para mantener sus chats seguros + Verifícate a ti mismo y a los demás para mantener tus chats seguros No es una clave de recuperación válida Por favor introduce una clave de recuperación Comprobando la clave de respaldo @@ -1622,11 +1622,11 @@ Usa una llave de seguridad Genere una clave de seguridad para almacenar en un lugar seguro, como un administrador de contraseñas o una caja fuerte. Utilice una frase de seguridad - Ingrese una frase secreta que solo usted conozca y genere una clave de respaldo. + Ingresa una frase secreta que solo tú conozcas y genera una clave para tu copia de respaldo. Guarde su llave de seguridad Guarde su llave de seguridad en un lugar seguro, como un administrador de contraseñas o una caja fuerte. Establecer una frase de seguridad - Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor. + Ingresa una frase de seguridad que sólo tú conozcas, que se usa para proteger secretos en tu servidor. Frase de seguridad Ingrese su Frase de seguridad nuevamente para confirmarla. Nombre de la Sala @@ -1636,7 +1636,7 @@ Esperando este mensaje, esto puede tardar un poco Debido al cifrado Extremo-a-Extremo, es posible que deba esperar a que llegue el mensaje de alguien porque las claves de cifrado no se le enviaron correctamente. No puede acceder a este mensaje porque ha sido bloqueado por el remitente - No puede acceder a este mensaje porque el remitente no confía en su sesión + No puedes acceder a este mensaje porque el remitente no confía en tu sesión No puede acceder a este mensaje porque el remitente no envió las claves a propósito Esperando al historial de cifrado ¡Nos complace anunciar que hemos cambiado de nombre! Tu aplicación está actualizada y accediste a tu cuenta. @@ -1711,7 +1711,7 @@ Mostrar el dispositivo con el que puede verificar ahora Mostrar %d dispositivos con los que puede verificar ahora - Reiniciará sin historia, mensajes, dispositivos o usuarios verificados + Reiniciarás sin historial, ni mensajes, ni dispositivos o usuarios verificados Si resetea todo Solo haga esto si no tiene otro dispositivo con el que verificar éste. Resetear todo @@ -1748,7 +1748,7 @@ Se necesita una nueva autenticación ¡Código QR no escaneado! Código QR no válido (URL no válida)! - No puede DM usted mismo! + No puedes MD a ti mismo! Compartir por texto Cambiar PIN Cambie su PIN actual @@ -1960,7 +1960,7 @@ Acceso a la sala Siempre preguntar Espacios - mostrar todas las salas en el directorio de salas, incluyendo salas con contenido explícito. + Mostrar todas las salas en el directorio de salas, incluyendo salas con contenido explícito. Mostrar salas con contenido explícito Directorio de la sala Salas sugeridas @@ -2181,7 +2181,7 @@ Agregar nuevas palabra clave Tus palabras clave Habilitar notificación por correo electrónico para %s - Para recibir un correo electrónico con una notificación, asocie un correo electrónico a su cuenta de matrix + Para recibir notificaciones por correo electrónico, asocia una direccion de correo electrónico a tu cuenta de Matrix Notificación de correo electrónico Ninguno Solo menciones y palabras clave @@ -2219,7 +2219,7 @@ No se puede grabar un mensaje de voz No se puede reproducir este mensaje de voz Toca tu grabación para detenerla o escucharla - %1$ds dejado + Restan %1$ds Mantenga presionado para grabar, suelte para enviar Eliminar grabación Grabación de mensaje de voz @@ -2238,7 +2238,7 @@ ¡Se ha cerrado la sesión! ¡Se ha abandonado la sala! Consejo: Pulse prolongadamente un mensaje y use \"%s\" . - Mantén las conversaciones organizadas con hilos + Mantén las conversaciones organizadas usando hilos Muestra todos los hilos en que has participado Mis Hilos Muestra todos los hilos de la sala actual @@ -2457,7 +2457,7 @@ BETA Comentarios de la beta de hilos Beta de hilos - - Algunos usuarios han sido dejados de ignorar + - Algunos usuarios han dejado de ser ignorados La compartición de pantalla está en progreso ${app_name} Compartición de pantalla Dejar de compartir pantalla @@ -2474,7 +2474,7 @@ Actualizado hace %1$s Implementación temporal: las ubicaciones persisten en el historial de la sala Activar compartir ubicación en tiempo real - Queda %1$s + Restan %1$s Compartiendo hasta %1$s Ver ubicación en tiempo real La ubicación en tiempo real ha terminado @@ -2625,8 +2625,8 @@ \nPor favor, inténtelo de nuevo.%s Usar ajustes por defecto del sistema Escoger manualmente - Tamaño automático de fuente - Escoger tamaño de la fuente + Tamaño automático + Escoge tamaño del tipo de letra %1$s y %2$d otro %1$s y %2$d otros @@ -2659,18 +2659,33 @@ Otorgar permiso ${app_name} necesita permiso para mostrar notificaciones. \nPor favor, otórgalo. - ${app_name} necesita permisos para mostrar notificaciones. Las notificaciones pueden mostrar tus mensajes, invitaciones, etc. + ${app_name} necesita permiso para mostrar notificaciones. Las notificaciones pueden mostrar tus mensajes, invitaciones, etc. \n -\nPor favor, permite el acceso en las siguientes ventanas emergentes para poder visualizar notificaciones. - Prueba el editor de texto enriquecido (pronto llegará la opción de texto sin formato plain text) +\nPor favor, a continuacion, en las ventanas emergentes, permite el acceso para poder visualizar notificaciones. + Prueba el editor de texto enriquecido (pronto llegará la opción de texto simple, sin formato) Habilitar editor de texto enriquecido (rich text) Crear MD únicamente al primer mensaje Una versión simplificada de Element con pestañas opcionales Habilitar nueva disposición - Sí, Parar + Sí, Detener Deseleccionar todo - Ocultar los hijos de %s - Mostrar los hijos de %s + Ocultar los subespacios de %s + Mostrar los subespacios de %s Has finalizado una transmisión de voz. %1$s ha finalizado una transmisión de voz. + Element Matrix Services (EMS) es un servicio de alojamiento para tus comunicaciones en tiempo real. Robusto, confiable, rápido y seguro. Para saber cómo, ve a <a href=\"${ftue_ems_url}\">element.io/ems</a> + Difusión de voz + Habilitado: + ID de sesión: + Algo falló. Por favor, comprueba tu conexión de red e inténtalo nuevamente. + Citando + Respondiendo a %s + Editando + Abrir pantalla de herramientas de desarrollador + 🔒 Tienes habilitado el cifrado a sesiones verificadas sólo para todas las salas en Ajustes de Seguridad. + ⚠ Hay dispositivos sin verificar en esta sala, los cuales no seran capaces de descifrar los mensajes que envías. + Habilita MDs pospuestos + Mostrar chats recientes en el menú de compartir sistema + No enviar nunca mensajes cifrados a sesiones sin verificar en esta sala. + Restan %1$s \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index bc617c62f0..a5aa778156 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -5,24 +5,24 @@ %1$s convidou você %1$s entrou na sala %1$s saiu da sala - %1$s rejeitou o convite + %1$s recusou o convite %1$s removeu %2$s %1$s desbaniu %2$s %1$s baniu %2$s - %1$s retirou o convite de %2$s - %1$s mudou o avatar dela(e) - %1$s definiu o nome de exibição dela(e) para %2$s - %1$s mudou o nome de exibição dela(e) de %2$s para %3$s - %1$s removeu o nome de exibição dela(e) (era %2$s) + %1$s desfez o convite para %2$s + %1$s mudou seu avatar + %1$s definiu seu nome de exibição para %2$s + %1$s mudou seu nome de exibição de %2$s para %3$s + %1$s removeu seu nome de exibição (era %2$s) %1$s mudou o tópico para: %2$s %1$s mudou o nome da sala para: %2$s %s começou uma chamada de vídeo. %s começou uma chamada de voz. %s atendeu a chamada. - %s terminou a chamada. - %1$s fez histórico futuro da sala visível para %2$s - todos os membros da sala, do ponto que foram convidados. - todos os membros da sala, do ponto que se juntaram. + %s encerrou a chamada. + %1$s tornou o histórico futuro da sala visível para %2$s + todos os membros da sala, a partir do ponto que foram convidados. + todos os membros da sala, a partir do ponto que entraram. todos os membros da sala. qualquer pessoa. (avatar mudou também) @@ -45,11 +45,11 @@ Você convidou %1$s Você entrou na sala Você saiu da sala - Você rejeitou o convite + Você recusou o convite Você removeu %1$s Você desbaniu %1$s Você baniu %1$s - Você retirou o convite de %1$s + Você desfez o convite para %1$s Você mudou seu avatar Você definiu seu nome de exibição para %1$s Você mudou seu nome de exibição de %1$s para %2$s @@ -63,8 +63,8 @@ %s enviou dados para configurar a chamada. Você enviou dados para configurar a chamada. Você atendeu a chamada. - Você terminou a chamada. - Você fez histórico futuro da sala visível para %1$s + Você encerrou a chamada. + Você tornou o histórico futuro da sala visível para %1$s %s fez o upgrade desta sala. Você fez o upgrade desta sala. Você removeu o nome da sala @@ -170,8 +170,8 @@ %1$s convidou %2$s Você fez o upgrade aqui. %s fez o upgrade aqui. - Você fez mensagens futuras visíveis para %1$s - %1$s fez mensagens futuras visíveis para %2$s + Você tornou as mensagens futuras visíveis para %1$s + %1$s tornou as mensagens futuras visíveis para %2$s Você saiu da sala %1$s saiu da sala Você entrou @@ -408,7 +408,7 @@ Qualquer pessoa Membros somente (desde o ponto no tempo de seleção desta opção) Membros somente (desde que eles foram convidados) - Membros somente (desde que eles se juntaram) + Membros somente (desde que eles entraram) Usuárias(os) banidas(os) Avançadas ID interno desta sala @@ -2009,8 +2009,8 @@ Adicionar salas Explorar salas - %d pessoa que você conhece já tem se juntado - %d pessoas que você conhece já têm se juntado + %d pessoa que você conhece já entrou + %d pessoas que você conhece já entraram Juntar-Se a Espaço Criar espaço @@ -2833,8 +2833,8 @@ Desselecionar todas(os) Selecionar todas(os) - %1$d selecionada(o) - %1$d selecionadas(os) + %1$d selecionado(a) + %1$d selecionados(as) Alguma outra pessoa já está gravando um broadcast de voz. Espere que o broadcast de voz dela termine para começar um novo. Alternar modo de tela cheia diff --git a/library/ui-strings/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml index 3b233c087c..b170d306e4 100644 --- a/library/ui-strings/src/main/res/values-sq/strings.xml +++ b/library/ui-strings/src/main/res/values-sq/strings.xml @@ -2504,7 +2504,7 @@ %s \nduket paksa si i zbrazët. Jini në gjendje të incizoni dhe dërgoni transmetim zanor në rrjedhën kohore të dhomës. - Aktivizoni transmetim zanor + Aktivizoni transmetim zanor (nën zhvillim aktiv) Aktivizo regjistrim hollësish klienti Shihini më qartë dhe kontrolloni më mirë krejt sesionet tuaj. Aktivizo përgjegjës të ri sesionesh diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 146df5054a..6c6474cbb3 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2338,6 +2338,7 @@ "One person" "%1$d people" + Poll history Uploads Leave Room Leave @@ -3193,6 +3194,10 @@ Voters see results as soon as they have voted Closed poll Results are only revealed when you end the poll + Active polls + There are no active polls in this room + Past polls + There are no past polls in this room Share location @@ -3506,4 +3511,7 @@ sent a video. sent a sticker. created a poll. + + Access Token + Your access token gives full access to your account. Do not share it with anyone. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt new file mode 100644 index 0000000000..071db7f902 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2023 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.crypto.crosssigning + +/** + * Container for the three cross signing keys: master, self signing and user signing. + */ +data class UserIdentity( + val masterKey: CryptoCrossSigningKey?, + val selfSigningKey: CryptoCrossSigningKey?, + val userSigningKey: CryptoCrossSigningKey?, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 7862da1c17..50497e3a27 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -89,6 +89,7 @@ import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask @@ -192,21 +193,21 @@ internal class DefaultCryptoService @Inject constructor( private val isStarting = AtomicBoolean(false) private val isStarted = AtomicBoolean(false) - fun onStateEvent(roomId: String, event: Event) { + fun onStateEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator) } } - fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) { + fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean, cryptoStoreAggregator: CryptoStoreAggregator?) { // handle state events if (event.isStateEvent()) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator) } } @@ -430,8 +431,10 @@ internal class DefaultCryptoService @Inject constructor( * A sync response has been received. * * @param syncResponse the syncResponse + * @param cryptoStoreAggregator data aggregated during the sync response treatment to store */ - fun onSyncCompleted(syncResponse: SyncResponse) { + fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { + cryptoStore.storeData(cryptoStoreAggregator) cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { runCatching { if (syncResponse.deviceLists != null) { @@ -998,15 +1001,26 @@ internal class DefaultCryptoService @Inject constructor( } } - private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) { + private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { if (!event.isStateEvent()) return val eventContent = event.content.toModel() val historyVisibility = eventContent?.historyVisibility if (historyVisibility == null) { - cryptoStore.setShouldShareHistory(roomId, false) + if (cryptoStoreAggregator != null) { + cryptoStoreAggregator.setShouldShareHistoryData[roomId] = false + } else { + // Store immediately + cryptoStore.setShouldShareHistory(roomId, false) + } } else { - cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) - cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) + if (cryptoStoreAggregator != null) { + cryptoStoreAggregator.setShouldEncryptForInvitedMembersData[roomId] = historyVisibility != RoomHistoryVisibility.JOINED + cryptoStoreAggregator.setShouldShareHistoryData[roomId] = historyVisibility.shouldShareHistory() + } else { + // Store immediately + cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) + cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 7e9e156003..364d77f7ac 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -25,11 +25,13 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.extensions.measureMetric import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.UserDataToStore import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.sync.SyncTokenStore @@ -371,6 +373,8 @@ internal class DeviceListManager @Inject constructor( Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") } + val userDataToStore = UserDataToStore() + for (userId in filteredUsers) { // al devices = val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) } @@ -404,7 +408,7 @@ internal class DeviceListManager @Inject constructor( } // Update the store // Note that devices which aren't in the response will be removed from the stores - cryptoStore.storeUserDevices(userId, workingCopy) + userDataToStore.userDevices[userId] = workingCopy } val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also { @@ -416,14 +420,15 @@ internal class DeviceListManager @Inject constructor( val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") } - cryptoStore.storeUserCrossSigningKeys( - userId, - masterKey, - selfSigningKey, - userSigningKey + userDataToStore.userIdentities[userId] = UserIdentity( + masterKey = masterKey, + selfSigningKey = selfSigningKey, + userSigningKey = userSigningKey ) } + cryptoStore.storeData(userDataToStore) + // Update devices trust for these users // dispatchDeviceChange(downloadUsers) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 21e3342365..0305f73a7b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -22,9 +22,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.olm.OlmAccount import org.matrix.olm.OlmOutboundGroupSession @@ -230,11 +231,12 @@ internal interface IMXCryptoStore { */ fun storeUserDevices(userId: String, devices: Map?) - fun storeUserCrossSigningKeys( + /** + * Store the cross signing keys for the user userId. + */ + fun storeUserIdentity( userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey? + userIdentity: UserIdentity ) /** @@ -290,6 +292,13 @@ internal interface IMXCryptoStore { fun shouldEncryptForInvitedMembers(roomId: String): Boolean + /** + * Sets a boolean flag that will determine whether or not this device should encrypt Events for + * invited members. + * + * @param roomId the room id + * @param shouldEncryptForInvitedMembers The boolean flag + */ fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) fun shouldShareHistory(roomId: String): Boolean @@ -580,4 +589,14 @@ internal interface IMXCryptoStore { fun areDeviceKeysUploaded(): Boolean fun tidyUpDataBase() fun getOutgoingRoomKeyRequests(inStates: Set): List + + /** + * Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator]. + */ + fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) + + /** + * Store a bunch of data related to the users. @See [UserDataToStore]. + */ + fun storeData(userDataToStore: UserDataToStore) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt new file mode 100644 index 0000000000..914ce4704e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.crypto.store + +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo + +internal data class UserDataToStore( + /** + * Map of userId -> (Map of deviceId -> [CryptoDeviceInfo]). + */ + val userDevices: MutableMap> = mutableMapOf(), + /** + * Map of userId -> [UserIdentity]. + */ + val userIdentities: MutableMap = mutableMapOf(), +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt new file mode 100644 index 0000000000..687ec95ec3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.crypto.store.db + +data class CryptoStoreAggregator( + val setShouldShareHistoryData: MutableMap = mutableMapOf(), + val setShouldEncryptForInvitedMembersData: MutableMap = mutableMapOf(), +) { + fun isEmpty(): Boolean { + return setShouldShareHistoryData.isEmpty() && + setShouldEncryptForInvitedMembersData.isEmpty() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt index 2d66ce1488..6412df205f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt @@ -20,10 +20,12 @@ import android.util.Base64 import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObject +import timber.log.Timber import java.io.ByteArrayOutputStream import java.io.ObjectOutputStream import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream +import kotlin.system.measureTimeMillis /** * Get realm, invoke the action, close realm, and return the result of the action. @@ -55,10 +57,12 @@ internal fun doRealmQueryAndCopyList(realmConfiguration: Realm /** * Get realm instance, invoke the action in a transaction and close realm. */ -internal fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { - Realm.getInstance(realmConfiguration).use { realm -> - realm.executeTransaction { action.invoke(it) } - } +internal fun doRealmTransaction(tag: String, realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { + measureTimeMillis { + Realm.getInstance(realmConfiguration).use { realm -> + realm.executeTransaction { action.invoke(it) } + } + }.also { Timber.w("doRealmTransaction for $tag took $it millis") } } internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 1b52b79746..b4368467a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -33,9 +33,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrappe import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.UserDataToStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity @@ -147,7 +148,7 @@ internal class RealmCryptoStore @Inject constructor( init { // Ensure CryptoMetadataEntity is inserted in DB - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("init", realmConfiguration) { realm -> var currentMetadata = realm.where().findFirst() var deleteAll = false @@ -189,7 +190,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteStore() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("deleteStore", realmConfiguration) { it.deleteAll() } } @@ -218,7 +219,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeDeviceId(deviceId: String) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("storeDeviceId", realmConfiguration) { it.where().findFirst()?.deviceId = deviceId } } @@ -230,7 +231,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveOlmAccount() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("saveOlmAccount", realmConfiguration) { it.where().findFirst()?.putOlmAccount(olmAccount) } } @@ -248,7 +249,7 @@ internal class RealmCryptoStore @Inject constructor( @Synchronized override fun getOrCreateOlmAccount(): OlmAccount { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("getOrCreateOlmAccount", realmConfiguration) { val metaData = it.where().findFirst() val existing = metaData!!.getOlmAccount() if (existing == null) { @@ -288,129 +289,139 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeUserDevices(userId: String, devices: Map?) { - doRealmTransaction(realmConfiguration) { realm -> - if (devices == null) { - Timber.d("Remove user $userId") - // Remove the user - UserEntity.delete(realm, userId) - } else { - val userEntity = UserEntity.getOrCreate(realm, userId) - // First delete the removed devices - val deviceIds = devices.keys - userEntity.devices.toTypedArray().iterator().let { - while (it.hasNext()) { - val deviceInfoEntity = it.next() - if (deviceInfoEntity.deviceId !in deviceIds) { - Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") - deviceInfoEntity.deleteOnCascade() - } + doRealmTransaction("storeUserDevices", realmConfiguration) { realm -> + storeUserDevices(realm, userId, devices) + } + } + + private fun storeUserDevices(realm: Realm, userId: String, devices: Map?) { + if (devices == null) { + Timber.d("Remove user $userId") + // Remove the user + UserEntity.delete(realm, userId) + } else { + val userEntity = UserEntity.getOrCreate(realm, userId) + // First delete the removed devices + val deviceIds = devices.keys + userEntity.devices.toTypedArray().iterator().let { + while (it.hasNext()) { + val deviceInfoEntity = it.next() + if (deviceInfoEntity.deviceId !in deviceIds) { + Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") + deviceInfoEntity.deleteOnCascade() } } - // Then update existing devices or add new one - devices.values.forEach { cryptoDeviceInfo -> - val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } - if (existingDeviceInfoEntity == null) { - // Add the device - Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") - val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) - newEntity.firstTimeSeenLocalTs = clock.epochMillis() - userEntity.devices.add(newEntity) - } else { - // Update the device - Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") - CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) - } + } + // Then update existing devices or add new one + devices.values.forEach { cryptoDeviceInfo -> + val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } + if (existingDeviceInfoEntity == null) { + // Add the device + Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") + val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) + newEntity.firstTimeSeenLocalTs = clock.epochMillis() + userEntity.devices.add(newEntity) + } else { + // Update the device + Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") + CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) } } } } - override fun storeUserCrossSigningKeys( + override fun storeUserIdentity( userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey? + userIdentity: UserIdentity, ) { - doRealmTransaction(realmConfiguration) { realm -> - UserEntity.getOrCreate(realm, userId) - .let { userEntity -> - if (masterKey == null || selfSigningKey == null) { - // The user has disabled cross signing? - userEntity.crossSigningInfoEntity?.deleteOnCascade() - userEntity.crossSigningInfoEntity = null - } else { - var shouldResetMyDevicesLocalTrust = false - CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> - // What should we do if we detect a change of the keys? - val existingMaster = signingInfo.getMasterKey() - if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingMaster, masterKey) - } else { - Timber.d("## CrossSigning MSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(masterKey) - signingInfo.setMasterKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my msk has changed! clear my private key - // Could we have some race here? e.g I am the one that did change the keys - // could i get this update to early and clear the private keys? - // -> initializeCrossSigning is guarding for that by storing all at once - realm.where().findFirst()?.apply { - xSignMasterPrivateKey = null - } + doRealmTransaction("storeUserIdentity", realmConfiguration) { realm -> + storeUserIdentity(realm, userId, userIdentity) + } + } + + private fun storeUserIdentity( + realm: Realm, + userId: String, + userIdentity: UserIdentity, + ) { + UserEntity.getOrCreate(realm, userId) + .let { userEntity -> + if (userIdentity.masterKey == null || userIdentity.selfSigningKey == null) { + // The user has disabled cross signing? + userEntity.crossSigningInfoEntity?.deleteOnCascade() + userEntity.crossSigningInfoEntity = null + } else { + var shouldResetMyDevicesLocalTrust = false + CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> + // What should we do if we detect a change of the keys? + val existingMaster = signingInfo.getMasterKey() + if (existingMaster != null && existingMaster.publicKeyBase64 == userIdentity.masterKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingMaster, userIdentity.masterKey) + } else { + Timber.d("## CrossSigning MSK change for $userId") + val keyEntity = crossSigningKeysMapper.map(userIdentity.masterKey) + signingInfo.setMasterKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my msk has changed! clear my private key + // Could we have some race here? e.g I am the one that did change the keys + // could i get this update to early and clear the private keys? + // -> initializeCrossSigning is guarding for that by storing all at once + realm.where().findFirst()?.apply { + xSignMasterPrivateKey = null } } - - val existingSelfSigned = signingInfo.getSelfSignedKey() - if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) - } else { - Timber.d("## CrossSigning SSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(selfSigningKey) - signingInfo.setSelfSignedKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my ssk has changed! clear my private key - realm.where().findFirst()?.apply { - xSignSelfSignedPrivateKey = null - } - } - } - - // Only for me - if (userSigningKey != null) { - val existingUSK = signingInfo.getUserSigningKey() - if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingUSK, userSigningKey) - } else { - Timber.d("## CrossSigning USK change for $userId") - val keyEntity = crossSigningKeysMapper.map(userSigningKey) - signingInfo.setUserSignedKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my usk has changed! clear my private key - realm.where().findFirst()?.apply { - xSignUserPrivateKey = null - } - } - } - } - - // When my cross signing keys are reset, we consider clearing all existing device trust - if (shouldResetMyDevicesLocalTrust) { - realm.where() - .equalTo(UserEntityFields.USER_ID, this.userId) - .findFirst() - ?.devices?.forEach { - it?.trustLevelEntity?.crossSignedVerified = false - it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId - } - } - userEntity.crossSigningInfoEntity = signingInfo } + + val existingSelfSigned = signingInfo.getSelfSignedKey() + if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == userIdentity.selfSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingSelfSigned, userIdentity.selfSigningKey) + } else { + Timber.d("## CrossSigning SSK change for $userId") + val keyEntity = crossSigningKeysMapper.map(userIdentity.selfSigningKey) + signingInfo.setSelfSignedKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my ssk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignSelfSignedPrivateKey = null + } + } + } + + // Only for me + if (userIdentity.userSigningKey != null) { + val existingUSK = signingInfo.getUserSigningKey() + if (existingUSK != null && existingUSK.publicKeyBase64 == userIdentity.userSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingUSK, userIdentity.userSigningKey) + } else { + Timber.d("## CrossSigning USK change for $userId") + val keyEntity = crossSigningKeysMapper.map(userIdentity.userSigningKey) + signingInfo.setUserSignedKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my usk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignUserPrivateKey = null + } + } + } + } + + // When my cross signing keys are reset, we consider clearing all existing device trust + if (shouldResetMyDevicesLocalTrust) { + realm.where() + .equalTo(UserEntityFields.USER_ID, this.userId) + .findFirst() + ?.devices?.forEach { + it?.trustLevelEntity?.crossSignedVerified = false + it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId + } + } + userEntity.crossSigningInfoEntity = signingInfo } } - } + } } override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { @@ -480,7 +491,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) { Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storePrivateKeysInfo", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk xSignUserPrivateKey = usk @@ -490,7 +501,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("saveBackupRecoveryKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { keyBackupRecoveryKey = recoveryKey keyBackupRecoveryKeyVersion = version @@ -516,7 +527,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeMSKPrivateKey(msk: String?) { Timber.v("## CRYPTO | *** storeMSKPrivateKey ${msk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeMSKPrivateKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk } @@ -525,7 +536,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeSSKPrivateKey(ssk: String?) { Timber.v("## CRYPTO | *** storeSSKPrivateKey ${ssk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeSSKPrivateKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignSelfSignedPrivateKey = ssk } @@ -534,7 +545,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUSKPrivateKey(usk: String?) { Timber.v("## CRYPTO | *** storeUSKPrivateKey ${usk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeUSKPrivateKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignUserPrivateKey = usk } @@ -667,7 +678,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeRoomAlgorithm(roomId: String, algorithm: String?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("storeRoomAlgorithm", realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).let { entity -> entity.algorithm = algorithm // store anyway the new algorithm, but mark the room @@ -708,7 +719,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setShouldEncryptForInvitedMembers", realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers } } @@ -716,7 +727,7 @@ internal class RealmCryptoStore @Inject constructor( override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { Timber.tag(loggerTag.value) .v("setShouldShareHistory for room $roomId is $shouldShareHistory") - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setShouldShareHistory", realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory } } @@ -733,7 +744,7 @@ internal class RealmCryptoStore @Inject constructor( if (sessionIdentifier != null) { val key = OlmSessionEntity.createPrimaryKey(sessionIdentifier, deviceKey) - doRealmTransaction(realmConfiguration) { + doRealmTransaction("storeSession", realmConfiguration) { val realmOlmSession = OlmSessionEntity().apply { primaryKey = key sessionId = sessionIdentifier @@ -790,7 +801,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeInboundGroupSessions", realmConfiguration) { realm -> sessions.forEach { wrapper -> val sessionIdentifier = try { @@ -914,7 +925,7 @@ internal class RealmCryptoStore @Inject constructor( override fun removeInboundGroupSession(sessionId: String, senderKey: String) { val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) - doRealmTransaction(realmConfiguration) { + doRealmTransaction("removeInboundGroupSession", realmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findAll() @@ -933,7 +944,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setKeyBackupVersion(keyBackupVersion: String?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setKeyBackupVersion", realmConfiguration) { it.where().findFirst()?.backupVersion = keyBackupVersion } } @@ -945,7 +956,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setKeysBackupData", realmConfiguration) { if (keysBackupData == null) { // Clear the table it.where() @@ -959,7 +970,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun resetBackupMarkers() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("resetBackupMarkers", realmConfiguration) { it.where() .findAll() .map { inboundGroupSession -> @@ -973,7 +984,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("markBackupDoneForInboundGroupSessions", realmConfiguration) { realm -> olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> try { val sessionIdentifier = @@ -1032,13 +1043,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setGlobalBlacklistUnverifiedDevices", realmConfiguration) { it.where().findFirst()?.globalBlacklistUnverifiedDevices = block } } override fun enableKeyGossiping(enable: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("enableKeyGossiping", realmConfiguration) { it.where().findFirst()?.globalEnableKeyGossiping = enable } } @@ -1062,13 +1073,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun enableShareKeyOnInvite(enable: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("enableShareKeyOnInvite", realmConfiguration) { it.where().findFirst()?.enableKeyForwardingOnInvite = enable } } override fun setDeviceKeysUploaded(uploaded: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setDeviceKeysUploaded", realmConfiguration) { it.where().findFirst()?.deviceKeysSentToServer = uploaded } } @@ -1115,7 +1126,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("blockUnverifiedDevicesInRoom", realmConfiguration) { realm -> CryptoRoomEntity.getById(realm, roomId) ?.blacklistUnverifiedDevices = block } @@ -1135,7 +1146,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("saveDeviceTrackingStatuses", realmConfiguration) { deviceTrackingStatuses .map { entry -> UserEntity.getOrCreate(it, entry.key) @@ -1268,7 +1279,7 @@ internal class RealmCryptoStore @Inject constructor( ): OutgoingKeyRequest { // Insert the request and return the one passed in parameter lateinit var request: OutgoingKeyRequest - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("getOrAddOutgoingRoomKeyRequest", realmConfiguration) { realm -> val existing = realm.where() .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, requestBody.sessionId) @@ -1306,7 +1317,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateOutgoingRoomKeyRequestState", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.apply { @@ -1320,7 +1331,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateOutgoingRoomKeyRequiredIndex", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.apply { @@ -1337,7 +1348,7 @@ internal class RealmCryptoStore @Inject constructor( fromDevice: String?, event: Event ) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateOutgoingRoomKeyReply", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId) .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId) @@ -1353,7 +1364,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteOutgoingRoomKeyRequest(requestId: String) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("deleteOutgoingRoomKeyRequest", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.deleteOnCascade() @@ -1361,7 +1372,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("deleteOutgoingRoomKeyRequestInState", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, state.name) .findAll() @@ -1497,7 +1508,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setMyCrossSigningInfo", realmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { userId -> addOrUpdateCrossSigningInfo(realm, userId, info) } @@ -1505,7 +1516,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setUserKeysAsTrusted", realmConfiguration) { realm -> val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .findFirst() @@ -1525,7 +1536,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setDeviceTrust", realmConfiguration) { realm -> realm.where(DeviceInfoEntity::class.java) .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) .findFirst()?.let { deviceInfoEntity -> @@ -1545,7 +1556,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun clearOtherUserTrust() { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("clearOtherUserTrust", realmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { info -> @@ -1560,7 +1571,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateUsersTrust(check: (String) -> Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateUsersTrust", realmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { xInfoEntity -> @@ -1668,13 +1679,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setCrossSigningInfo", realmConfiguration) { realm -> addOrUpdateCrossSigningInfo(realm, userId, info) } } override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("markMyMasterKeyAsLocallyTrusted", realmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { myUserId -> CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity -> val level = xInfoEntity.trustLevelEntity @@ -1713,7 +1724,7 @@ internal class RealmCryptoStore @Inject constructor( val roomId = withHeldContent.roomId ?: return val sessionId = withHeldContent.sessionId ?: return if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("addWithHeldMegolmSession", realmConfiguration) { realm -> WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let { it.code = withHeldContent.code it.senderKey = withHeldContent.senderKey @@ -1745,7 +1756,7 @@ internal class RealmCryptoStore @Inject constructor( deviceIdentityKey: String, chainIndex: Int ) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("markedSessionAsShared", realmConfiguration) { realm -> SharedSessionEntity.create( realm = realm, roomId = roomId, @@ -1794,7 +1805,7 @@ internal class RealmCryptoStore @Inject constructor( */ override fun tidyUpDataBase() { val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000 - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("tidyUpDataBase", realmConfiguration) { realm -> // Clean the old ones? realm.where() @@ -1815,4 +1826,31 @@ internal class RealmCryptoStore @Inject constructor( // Can we do something for WithHeldSessionEntity? } } + + override fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) { + if (cryptoStoreAggregator.isEmpty()) { + return + } + doRealmTransaction("storeData - CryptoStoreAggregator", realmConfiguration) { realm -> + // setShouldShareHistory + cryptoStoreAggregator.setShouldShareHistoryData.forEach { + CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value + } + // setShouldEncryptForInvitedMembers + cryptoStoreAggregator.setShouldEncryptForInvitedMembersData.forEach { + CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value + } + } + } + + override fun storeData(userDataToStore: UserDataToStore) { + doRealmTransaction("storeData - UserDataToStore", realmConfiguration) { realm -> + userDataToStore.userDevices.forEach { + storeUserDevices(realm, it.key, it.value) + } + userDataToStore.userIdentities.forEach { + storeUserIdentity(realm, it.key, it.value) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt index 9bd197e42e..f89221b627 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.di -import androidx.annotation.Nullable import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonQualifier import com.squareup.moshi.Moshi @@ -28,7 +27,6 @@ import java.lang.reflect.Type internal annotation class SerializeNulls { companion object { val JSON_ADAPTER_FACTORY: JsonAdapter.Factory = object : JsonAdapter.Factory { - @Nullable override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { val nextAnnotations = Types.nextAnnotations(annotations, SerializeNulls::class.java) ?: return null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 4e0525536c..334a8c5076 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.network.interceptors -import androidx.annotation.NonNull import okhttp3.logging.HttpLoggingInterceptor import org.json.JSONArray import org.json.JSONException @@ -38,7 +37,7 @@ internal class FormattedJsonHttpLogger( * @param message */ @Synchronized - override fun log(@NonNull message: String) { + override fun log(message: String) { Timber.v(message) // Try to log formatted Json only if there is a chance that [message] contains Json. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt index 8b54978279..6c28b9fcce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.network.parsing -import androidx.annotation.Nullable import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter @@ -32,14 +31,12 @@ internal interface CheckNumberType { companion object { val JSON_ADAPTER_FACTORY = object : JsonAdapter.Factory { - @Nullable override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { if (type !== Any::class.java) { return null } val delegate: JsonAdapter = moshi.nextAdapter(this, Any::class.java, emptySet()) return object : JsonAdapter() { - @Nullable @Throws(IOException::class) override fun fromJson(reader: JsonReader): Any? { return if (reader.peek() !== JsonReader.Token.NUMBER) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt index cfc26045a0..ce34b0430e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt @@ -42,14 +42,12 @@ internal class StreamEventsManager @Inject constructor() { listeners.remove(listener) } - fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) { + fun dispatchLiveEventReceived(event: Event, roomId: String) { Timber.v("## dispatchLiveEventReceived ${event.eventId}") coroutineScope.launch { - if (!initialSync) { - listeners.forEach { - tryOrNull { - it.onLiveEvent(roomId, event) - } + listeners.forEach { + tryOrNull { + it.onLiveEvent(roomId, event) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 793c2573be..653069b3c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -176,7 +176,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } // Give info to crypto module - cryptoService.onStateEvent(roomId, event) + cryptoService.onStateEvent(roomId, event, null) } roomMemberContentsByUser.getOrPut(event.senderId) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 05d50d9595..cb407bb1cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.DefaultCryptoService +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionListeners @@ -92,7 +93,7 @@ internal class SyncResponseHandler @Inject constructor( postTreatmentSyncResponse(syncResponse, isInitialSync) - markCryptoSyncCompleted(syncResponse) + markCryptoSyncCompleted(syncResponse, aggregator.cryptoStoreAggregator) handlePostSync() @@ -218,10 +219,10 @@ internal class SyncResponseHandler @Inject constructor( } } - private fun markCryptoSyncCompleted(syncResponse: SyncResponse) { + private fun markCryptoSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { relevantPlugins.measureSpan("task", "crypto_sync_handler_onSyncCompleted") { measureTimeMillis { - cryptoSyncHandler.onSyncCompleted(syncResponse) + cryptoSyncHandler.onSyncCompleted(syncResponse, cryptoStoreAggregator) }.also { Timber.v("cryptoSyncHandler.onSyncCompleted took $it ms") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index 2b7f936fa8..af05e08da3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.internal.session.sync +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator + internal class SyncResponsePostTreatmentAggregator { // List of RoomId val ephemeralFilesToDelete = mutableListOf() @@ -28,4 +30,7 @@ internal class SyncResponsePostTreatmentAggregator { // Set of users to call `crossSigningService.checkTrustAndAffectedRoomShields` once per sync val userIdsForCheckingTrustAndAffectedRoomShields = mutableSetOf() + + // For the crypto store + val cryptoStoreAggregator = CryptoStoreAggregator() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index 551db52dbd..7224b0c29c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse import org.matrix.android.sdk.internal.crypto.DefaultCryptoService +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.session.sync.ProgressReporter @@ -85,8 +86,8 @@ internal class CryptoSyncHandler @Inject constructor( } } - fun onSyncCompleted(syncResponse: SyncResponse) { - cryptoService.onSyncCompleted(syncResponse) + fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { + cryptoService.onSyncCompleted(syncResponse, cryptoStoreAggregator) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 4001ae2ccf..5e4886ce1e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -258,7 +258,7 @@ internal class RoomSyncHandler @Inject constructor( root = eventEntity } // Give info to crypto module - cryptoService.onStateEvent(roomId, event) + cryptoService.onStateEvent(roomId, event, aggregator.cryptoStoreAggregator) roomMemberEventHandler.handle(realm, roomId, event, isInitialSync, aggregator) } } @@ -376,8 +376,15 @@ internal class RoomSyncHandler @Inject constructor( roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) } roomTypingUsersHandler.handle(realm, roomId, null) roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE) - roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, - roomSync.unreadNotifications, roomSync.unreadThreadNotifications, aggregator = aggregator) + roomSummaryUpdater.update( + realm, + roomId, + membership, + roomSync.summary, + roomSync.unreadNotifications, + roomSync.unreadThreadNotifications, + aggregator = aggregator, + ) return roomEntity } @@ -423,7 +430,9 @@ internal class RoomSyncHandler @Inject constructor( val isInitialSync = insertType == EventInsertType.INITIAL_SYNC eventIds.add(event.eventId) - liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync) + if (!isInitialSync) { + liveEventService.get().dispatchLiveEventReceived(event, roomId) + } if (event.isEncrypted() && !isInitialSync) { try { @@ -486,7 +495,7 @@ internal class RoomSyncHandler @Inject constructor( } } // Give info to crypto module - cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync) + cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync, aggregator.cryptoStoreAggregator) // Try to remove local echo event.unsignedData?.transactionId?.also { txId -> diff --git a/tools/lint/lint.xml b/tools/lint/lint.xml index 3d3b073749..dbe30f2267 100644 --- a/tools/lint/lint.xml +++ b/tools/lint/lint.xml @@ -77,6 +77,7 @@ + diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh index f91e11584c..553c02101c 100755 --- a/tools/release/releaseScript.sh +++ b/tools/release/releaseScript.sh @@ -87,6 +87,14 @@ fi printf "OK\n" +printf "\n================================================================================\n" +printf "Ensuring main and develop branches are up to date...\n" + +git checkout main +git pull +git checkout develop +git pull + printf "\n================================================================================\n" # Guessing version to propose a default version versionMajorCandidate=`grep "ext.versionMajor" ./vector-app/build.gradle | cut -d " " -f3` @@ -103,14 +111,6 @@ versionMinor=`echo ${version} | cut -d "." -f2` versionPatch=`echo ${version} | cut -d "." -f3` nextPatchVersion=$((versionPatch + 2)) -printf "\n================================================================================\n" -printf "Ensuring main and develop branches are up to date...\n" - -git checkout main -git pull -git checkout develop -git pull - printf "\n================================================================================\n" printf "Starting the release ${version}\n" git flow release start ${version} @@ -190,6 +190,9 @@ yes | towncrier build --version "v${version}" printf "\n================================================================================\n" read -p "Check the file CHANGES.md consistency. It's possible to reorder items (most important changes first) or change their section if relevant. Also an opportunity to fix some typo, or rewrite things. Do not commit your change. Press enter when it's done." +# Get the changes to use it to create the GitHub release +changelogUrlEncoded=`git diff CHANGES.md | grep ^+ | tail -n +2 | cut -c2- | jq -sRr @uri | sed s/\(/%28/g | sed s/\)/%29/g` + printf "\n================================================================================\n" printf "Committing...\n" git commit -a -m "Changelog for version ${version}" @@ -263,7 +266,7 @@ else fi printf "\n================================================================================\n" -printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Amain to build the 'main' branch.\n" +printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%%3Amain to build the 'main' branch.\n" read -p "After GHA is finished, please enter the artifact URL (for 'vector-gplay-release-unsigned'): " artifactUrl printf "\n================================================================================\n" @@ -354,10 +357,15 @@ apkPath="${targetPath}/vector-gplay-arm64-v8a-release-signed.apk" adb -d install ${apkPath} read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done." -# TODO Get the block to copy from towncrier earlier (be may be edited by the release manager)? -read -p "Create the release on gitHub from the tag https://github.com/vector-im/element-android/tags, copy paste the block from the file CHANGES.md. Press enter when it's done." -read -p "Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}. Press enter when it's done." +printf "\n================================================================================\n" +githubCreateReleaseLink="https://github.com/vector-im/element-android/releases/new?tag=v${version}&title=Element%%20Android%%20v${version}&body=${changelogUrlEncoded}" +printf "Creating the release on gitHub.\n" +printf "Open this link: ${githubCreateReleaseLink}\n" +printf "Then\n" +printf " - click on the 'Generate releases notes' button\n" +printf " - Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}\n" +read -p ". Press enter when it's done. " printf "\n================================================================================\n" printf "Message for the Android internal room:\n\n" diff --git a/vector/build.gradle b/vector/build.gradle index 83af7ecc04..91d2a8c46a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -308,7 +308,7 @@ dependencies { // Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868 // Error was lots of `Duplicate class org.checkerframework.common.reflection.qual.MethodVal found in modules jetified-checker-3.1 (org.checkerframework:checker:3.1.1) and jetified-checker-qual-3.12.0 (org.checkerframework:checker-qual:3.12.0) //noinspection GradleDependency Cannot use latest 3.15.0 since it required min API 26. - implementation "org.checkerframework:checker:3.27.0" + implementation "org.checkerframework:checker:3.29.0" androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testRunner diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index d22ab51e7a..911bbfa4a3 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -84,6 +84,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListViewModel import im.vector.app.features.roomprofile.members.RoomMemberListViewModel import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewModel import im.vector.app.features.roomprofile.permissions.RoomPermissionsViewModel +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel @@ -697,4 +698,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(SetLinkViewModel::class) fun setLinkViewModelFactory(factory: SetLinkViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(RoomPollsViewModel::class) + fun roomPollsViewModelFactory(factory: RoomPollsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 4e5116eda9..1e29dfff5e 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -41,6 +41,7 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceManager import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView @@ -91,6 +92,7 @@ import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError @@ -123,14 +125,20 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected val viewModelProvider get() = ViewModelProvider(this, viewModelFactory) - fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - hideWaitingView() - observer(it) - } - .launchIn(lifecycleScope) + fun VectorViewModel<*, *, T>.observeViewEvents( + observer: (T) -> Unit, + ) { + val tag = this@VectorBaseActivity::class.simpleName.toString() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents + .stream(tag) + .collect { + hideWaitingView() + observer(it) + } + } + } } var toolbar: ToolbarConfig? = null diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index ec6f3288f8..a44fb1c9ac 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -26,8 +26,10 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.CallSuper import androidx.annotation.FloatRange +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -43,6 +45,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import io.github.hyuwah.draggableviewlib.Utils import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -199,12 +202,18 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe * ViewEvents * ========================================================================================== */ - protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - observer(it) - } - .launchIn(viewLifecycleOwner.lifecycleScope) + protected fun VectorViewModel<*, *, T>.observeViewEvents( + observer: (T) -> Unit, + ) { + val tag = this@VectorBaseBottomSheetDialogFragment::class.simpleName.toString() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents + .stream(tag) + .collect { + observer(it) + } + } + } } } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt index 5a817b989e..34e233aa7a 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt @@ -23,8 +23,10 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.CallSuper import androidx.fragment.app.DialogFragment +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import dagger.hilt.android.EntryPointAccessors @@ -37,6 +39,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.themes.ThemeUtils import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -145,11 +148,15 @@ abstract class VectorBaseDialogFragment : DialogFragment(), Ma * ========================================================================================== */ protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - observer(it) - } - .launchIn(viewLifecycleOwner.lifecycleScope) + val tag = this@VectorBaseDialogFragment::class.simpleName.toString() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents + .stream(tag) + .collect { + observer(it) + } + } + } } } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 8fe2d33f6a..a82cef54e5 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -34,6 +34,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util.assertMainThread @@ -53,6 +54,7 @@ import im.vector.app.features.navigation.Navigator import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -272,14 +274,20 @@ abstract class VectorBaseFragment : Fragment(), MavericksView * ViewEvents * ========================================================================================== */ - protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - dismissLoadingDialog() - observer(it) - } - .launchIn(viewLifecycleOwner.lifecycleScope) + protected fun VectorViewModel<*, *, T>.observeViewEvents( + observer: (T) -> Unit, + ) { + val tag = this@VectorBaseFragment::class.simpleName.toString() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents + .stream(tag) + .collect { + dismissLoadingDialog() + observer(it) + } + } + } } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index c9d58f9545..3dd38c455f 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -18,15 +18,16 @@ package im.vector.app.core.platform import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModel -import im.vector.app.core.utils.DataSource -import im.vector.app.core.utils.PublishDataSource +import im.vector.app.core.utils.EventQueue +import im.vector.app.core.utils.SharedEvents abstract class VectorViewModel(initialState: S) : MavericksViewModel(initialState) { // Used to post transient events to the View - protected val _viewEvents = PublishDataSource() - val viewEvents: DataSource = _viewEvents + protected val _viewEvents = EventQueue(capacity = 64) + val viewEvents: SharedEvents + get() = _viewEvents abstract fun handle(action: VA) } diff --git a/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt b/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt index 9ed3c02ba4..3c057e0635 100644 --- a/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt @@ -18,7 +18,6 @@ package im.vector.app.core.resources import android.content.res.Resources import androidx.annotation.ArrayRes -import androidx.annotation.NonNull import javax.inject.Inject class StringArrayProvider @Inject constructor(private val resources: Resources) { @@ -31,7 +30,6 @@ class StringArrayProvider @Inject constructor(private val resources: Resources) * @return The string array associated with the resource, stripped of styled * text information. */ - @NonNull fun getStringArray(@ArrayRes resId: Int): Array { return resources.getStringArray(resId) } diff --git a/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt b/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt index 7e322daaae..e5f48f8be0 100644 --- a/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt @@ -17,7 +17,6 @@ package im.vector.app.core.resources import android.content.res.Resources -import androidx.annotation.NonNull import androidx.annotation.PluralsRes import androidx.annotation.StringRes import javax.inject.Inject @@ -32,7 +31,6 @@ class StringProvider @Inject constructor(private val resources: Resources) { * @return The string data associated with the resource, stripped of styled * text information. */ - @NonNull fun getString(@StringRes resId: Int): String { return resources.getString(resId) } @@ -48,12 +46,10 @@ class StringProvider @Inject constructor(private val resources: Resources) { * @return The string data associated with the resource, formatted and * stripped of styled text information. */ - @NonNull fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String { return resources.getString(resId, *formatArgs) } - @NonNull fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String { return resources.getQuantityString(resId, quantity, *formatArgs) } diff --git a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt new file mode 100644 index 0000000000..e712769c48 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt @@ -0,0 +1,58 @@ +/* + * Copyright 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.core.utils + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.transform +import java.util.concurrent.CopyOnWriteArraySet + +interface SharedEvents { + fun stream(consumerId: String): Flow +} + +class EventQueue(capacity: Int) : SharedEvents { + + private val innerQueue = MutableSharedFlow>(replay = capacity) + + fun post(event: T) { + innerQueue.tryEmit(OneTimeEvent(event)) + } + + override fun stream(consumerId: String): Flow = innerQueue.filterNotHandledBy(consumerId) +} + +/** + * Event designed to be delivered only once to a concrete entity, + * but it can also be delivered to multiple different entities. + * + * Keeps track of who has already handled its content. + */ +private class OneTimeEvent(private val content: T) { + + private val handlers = CopyOnWriteArraySet() + + /** + * @param asker Used to identify, whether this "asker" has already handled this Event. + * @return Event content or null if it has been already handled by asker + */ + fun getIfNotHandled(asker: String): T? = if (handlers.add(asker)) content else null +} + +private fun Flow>.filterNotHandledBy(consumerId: String): Flow = transform { event -> + event.getIfNotHandled(consumerId)?.let { emit(it) } +} diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 8ce375122e..cffb1577cf 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -55,8 +55,6 @@ import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.ui.UiStateRepository import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -142,9 +140,9 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity startAppViewModel.onEach { renderState(it) } - startAppViewModel.viewEvents.stream() - .onEach(::handleViewEvents) - .launchIn(lifecycleScope) + startAppViewModel.observeViewEvents { + handleViewEvents(it) + } startAppViewModel.handle(StartAppAction.StartApp) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt index d393636a8e..aea87beea9 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt @@ -59,7 +59,7 @@ class SharedSecureStorageActivity : views.toolbar.visibility = View.GONE - viewModel.observeViewEvents { observeViewEvents(it) } + viewModel.observeViewEvents { onViewEvents(it) } viewModel.onEach { renderState(it) } } @@ -85,7 +85,7 @@ class SharedSecureStorageActivity : showFragment(fragment) } - private fun observeViewEvents(it: SharedSecureStorageViewEvent?) { + private fun onViewEvents(it: SharedSecureStorageViewEvent) { when (it) { is SharedSecureStorageViewEvent.Dismiss -> { finish() diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 089fdcebd4..0d240b376b 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -29,7 +29,9 @@ import androidx.core.transition.addListener import androidx.core.view.ViewCompat import androidx.core.view.isInvisible import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.transition.Transition import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint @@ -50,8 +52,6 @@ import im.vector.lib.attachmentviewer.AttachmentViewerActivity import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -239,10 +239,15 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt } private fun observeViewEvents() { - viewModel.viewEvents - .stream() - .onEach(::handleViewEvents) - .launchIn(lifecycleScope) + val tag = this::class.simpleName.toString() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel + .viewEvents + .stream(tag) + .collect(::handleViewEvents) + } + } } private fun handleViewEvents(event: VectorAttachmentViewerViewEvents) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 526d676dee..3c37c92650 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -36,6 +36,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment +import im.vector.app.features.roomprofile.polls.RoomPollsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.lib.core.utils.compat.getParcelableCompat @@ -98,6 +99,7 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() + RoomProfileSharedAction.OpenRoomPolls -> openRoomPolls() RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() @@ -126,6 +128,10 @@ class RoomProfileActivity : finish() } + private fun openRoomPolls() { + addFragmentToBackstack(views.simpleFragmentContainer, RoomPollsFragment::class.java, roomProfileArgs) + } + private fun openRoomUploads() { addFragmentToBackstack(views.simpleFragmentContainer, RoomUploadsFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index eb43a345f2..30bd6c7ed3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.roomprofile import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.epoxy.expandableTextItem import im.vector.app.core.epoxy.profiles.buildProfileAction @@ -56,6 +57,7 @@ class RoomProfileController @Inject constructor( fun onMemberListClicked() fun onBannedMemberListClicked() fun onNotificationsClicked() + fun onPollHistoryClicked() fun onUploadsClicked() fun createShortcut() fun onSettingsClicked() @@ -263,6 +265,15 @@ class RoomProfileController @Inject constructor( action = { callback?.onBannedMemberListClicked() } ) } + if (BuildConfig.DEBUG) { + // WIP, will be in release when related screens will be finished + buildProfileAction( + id = "poll_history", + title = stringProvider.getString(R.string.room_profile_section_more_polls), + icon = R.drawable.ic_attachment_poll, + action = { callback?.onPollHistoryClicked() } + ) + } buildProfileAction( id = "uploads", title = stringProvider.getString(R.string.room_profile_section_more_uploads), diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index f4394111ab..51885dbf39 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -269,6 +269,10 @@ class RoomProfileFragment : roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomNotificationSettings) } + override fun onPollHistoryClicked() { + roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomPolls) + } + override fun onUploadsClicked() { roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomUploads) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 7d62bb86a1..b243ceb206 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -25,6 +25,7 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomSettings : RoomProfileSharedAction() object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() + object OpenRoomPolls : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt new file mode 100644 index 0000000000..6f2a757ed7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt @@ -0,0 +1,114 @@ +/* + * 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.roomprofile.polls + +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class GetPollsUseCase @Inject constructor() { + + fun execute(): Flow> { + // TODO unmock and add unit tests + return flowOf(getActivePolls() + getEndedPolls()) + .map { it.sortedByDescending { poll -> poll.creationTimestamp } } + } + + private fun getActivePolls(): List { + return listOf( + PollSummary.ActivePoll( + id = "id1", + // 2022/06/28 UTC+1 + creationTimestamp = 1656367200000, + title = "Which charity would you like to support?" + ), + PollSummary.ActivePoll( + id = "id2", + // 2022/06/26 UTC+1 + creationTimestamp = 1656194400000, + title = "Which sport should the pupils do this year?" + ), + PollSummary.ActivePoll( + id = "id3", + // 2022/06/24 UTC+1 + creationTimestamp = 1656021600000, + title = "What type of food should we have at the party?" + ), + PollSummary.ActivePoll( + id = "id4", + // 2022/06/22 UTC+1 + creationTimestamp = 1655848800000, + title = "What film should we show at the end of the year party?" + ), + ) + } + + private fun getEndedPolls(): List { + return listOf( + PollSummary.EndedPoll( + id = "id1-ended", + // 2022/06/28 UTC+1 + creationTimestamp = 1656367200000, + title = "Which charity would you like to support?", + totalVotes = 22, + winnerOptions = listOf( + PollOptionViewState.PollEnded( + optionId = "id1", + optionAnswer = "Cancer research", + voteCount = 13, + votePercentage = 13 / 22.0, + isWinner = true, + ) + ), + ), + PollSummary.EndedPoll( + id = "id2-ended", + // 2022/06/26 UTC+1 + creationTimestamp = 1656194400000, + title = "Where should we do the offsite?", + totalVotes = 92, + winnerOptions = listOf( + PollOptionViewState.PollEnded( + optionId = "id1", + optionAnswer = "Hawaii", + voteCount = 43, + votePercentage = 43 / 92.0, + isWinner = true, + ) + ), + ), + PollSummary.EndedPoll( + id = "id3-ended", + // 2022/06/24 UTC+1 + creationTimestamp = 1656021600000, + title = "What type of food should we have at the party?", + totalVotes = 22, + winnerOptions = listOf( + PollOptionViewState.PollEnded( + optionId = "id1", + optionAnswer = "Brazilian", + voteCount = 13, + votePercentage = 13 / 22.0, + isWinner = true, + ) + ), + ), + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt new file mode 100644 index 0000000000..f24ac8b8a6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt @@ -0,0 +1,39 @@ +/* + * 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.roomprofile.polls + +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState + +sealed interface PollSummary { + val id: String + val creationTimestamp: Long + val title: String + + data class ActivePoll( + override val id: String, + override val creationTimestamp: Long, + override val title: String, + ) : PollSummary + + data class EndedPoll( + override val id: String, + override val creationTimestamp: Long, + override val title: String, + val totalVotes: Int, + val winnerOptions: List, + ) : PollSummary +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt new file mode 100644 index 0000000000..c18142a306 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -0,0 +1,21 @@ +/* + * 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.roomprofile.polls + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface RoomPollsAction : VectorViewModelAction diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt new file mode 100644 index 0000000000..9f7e704135 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -0,0 +1,74 @@ +/* + * 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.roomprofile.polls + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomPollsBinding +import im.vector.app.features.roomprofile.RoomProfileArgs + +@AndroidEntryPoint +class RoomPollsFragment : VectorBaseFragment() { + + private val roomProfileArgs: RoomProfileArgs by args() + + private val viewModel: RoomPollsViewModel by fragmentViewModel() + + private var tabLayoutMediator: TabLayoutMediator? = null + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsBinding { + return FragmentRoomPollsBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupToolbar() + setupTabs() + } + + override fun onDestroyView() { + views.roomPollsViewPager.adapter = null + tabLayoutMediator?.detach() + tabLayoutMediator = null + super.onDestroyView() + } + + private fun setupToolbar() { + setupToolbar(views.roomPollsToolbar) + .allowBack() + } + + private fun setupTabs() { + views.roomPollsViewPager.adapter = RoomPollsPagerAdapter(this) + + tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> + when (position) { + RoomPollsType.ACTIVE.ordinal -> tab.text = getString(R.string.room_polls_active) + RoomPollsType.ENDED.ordinal -> tab.text = getString(R.string.room_polls_ended) + } + }.also { it.attach() } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt new file mode 100644 index 0000000000..c60fc5de27 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.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.roomprofile.polls + +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import im.vector.app.features.roomprofile.polls.active.RoomActivePollsFragment +import im.vector.app.features.roomprofile.polls.ended.RoomEndedPollsFragment + +class RoomPollsPagerAdapter( + private val fragment: Fragment +) : FragmentStateAdapter(fragment) { + + override fun getItemCount() = RoomPollsType.values().size + + override fun createFragment(position: Int): Fragment { + return when (position) { + RoomPollsType.ACTIVE.ordinal -> instantiateFragment(RoomActivePollsFragment::class.java.name) + RoomPollsType.ENDED.ordinal -> instantiateFragment(RoomEndedPollsFragment::class.java.name) + else -> throw IllegalArgumentException("position should be between 0 and ${itemCount - 1}, while it was $position") + } + } + + private fun instantiateFragment(fragmentName: String): Fragment { + return fragment.childFragmentManager.fragmentFactory.instantiate(fragment.requireContext().classLoader, fragmentName) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt new file mode 100644 index 0000000000..134ef9a195 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt @@ -0,0 +1,22 @@ +/* + * 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.roomprofile.polls + +enum class RoomPollsType { + ACTIVE, + ENDED, +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt new file mode 100644 index 0000000000..231123563a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -0,0 +1,21 @@ +/* + * 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.roomprofile.polls + +import im.vector.app.core.platform.VectorViewEvents + +sealed class RoomPollsViewEvent : VectorViewEvents diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt new file mode 100644 index 0000000000..95cb4717ca --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -0,0 +1,54 @@ +/* + * 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.roomprofile.polls + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +class RoomPollsViewModel @AssistedInject constructor( + @Assisted initialState: RoomPollsViewState, + private val getPollsUseCase: GetPollsUseCase, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: RoomPollsViewState): RoomPollsViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + init { + observePolls() + } + + private fun observePolls() { + getPollsUseCase.execute() + .onEach { setState { copy(polls = it) } } + .launchIn(viewModelScope) + } + + override fun handle(action: RoomPollsAction) { + // do nothing for now + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt new file mode 100644 index 0000000000..74794c99b1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -0,0 +1,28 @@ +/* + * 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.roomprofile.polls + +import com.airbnb.mvrx.MavericksState +import im.vector.app.features.roomprofile.RoomProfileArgs + +data class RoomPollsViewState( + val roomId: String, + val polls: List = emptyList(), +) : MavericksState { + + constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt new file mode 100644 index 0000000000..1c6a03c480 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -0,0 +1,34 @@ +/* + * 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.roomprofile.polls.active + +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.features.roomprofile.polls.RoomPollsType +import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment + +@AndroidEntryPoint +class RoomActivePollsFragment : RoomPollsListFragment() { + + override fun getEmptyListTitle(): String { + return getString(R.string.room_polls_active_no_item) + } + + override fun getRoomPollsType(): RoomPollsType { + return RoomPollsType.ACTIVE + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt new file mode 100644 index 0000000000..8dd0cadadf --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt @@ -0,0 +1,34 @@ +/* + * 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.roomprofile.polls.ended + +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.features.roomprofile.polls.RoomPollsType +import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment + +@AndroidEntryPoint +class RoomEndedPollsFragment : RoomPollsListFragment() { + + override fun getEmptyListTitle(): String { + return getString(R.string.room_polls_ended_no_item) + } + + override fun getRoomPollsType(): RoomPollsType { + return RoomPollsType.ENDED + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt new file mode 100644 index 0000000000..da00fedddb --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt @@ -0,0 +1,72 @@ +/* + * 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.roomprofile.polls.list + +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.features.home.room.detail.timeline.item.PollOptionView +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState + +@EpoxyModelClass +abstract class RoomPollItem : VectorEpoxyModel(R.layout.item_poll) { + + @EpoxyAttribute + lateinit var formattedDate: String + + @EpoxyAttribute + lateinit var title: String + + @EpoxyAttribute + var winnerOptions: List = emptyList() + + @EpoxyAttribute + var totalVotesStatus: String? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var clickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.onClick(clickListener) + holder.date.text = formattedDate + holder.title.text = title + holder.winnerOptions.removeAllViews() + holder.winnerOptions.isVisible = winnerOptions.isNotEmpty() + for (winnerOption in winnerOptions) { + val optionView = PollOptionView(holder.view.context) + holder.winnerOptions.addView(optionView) + optionView.render(winnerOption) + } + holder.totalVotes.setTextOrHide(totalVotesStatus) + } + + class Holder : VectorEpoxyHolder() { + val date by bind(R.id.pollDate) + val title by bind(R.id.pollTitle) + val winnerOptions by bind(R.id.pollWinnerOptionsContainer) + val totalVotes by bind(R.id.pollTotalVotes) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt new file mode 100644 index 0000000000..f0e3b6b9a4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt @@ -0,0 +1,76 @@ +/* + * 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.roomprofile.polls.list + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.roomprofile.polls.PollSummary +import javax.inject.Inject + +class RoomPollsController @Inject constructor( + val dateFormatter: VectorDateFormatter, + val stringProvider: StringProvider, +) : TypedEpoxyController>() { + + interface Listener { + fun onPollClicked(pollId: String) + } + + var listener: Listener? = null + + override fun buildModels(data: List?) { + if (data.isNullOrEmpty()) { + return + } + + for (poll in data) { + when (poll) { + is PollSummary.ActivePoll -> buildActivePollItem(poll) + is PollSummary.EndedPoll -> buildEndedPollItem(poll) + } + } + } + + private fun buildActivePollItem(poll: PollSummary.ActivePoll) { + val host = this + roomPollItem { + id(poll.id) + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) + title(poll.title) + clickListener { + host.listener?.onPollClicked(poll.id) + } + } + } + + private fun buildEndedPollItem(poll: PollSummary.EndedPoll) { + val host = this + roomPollItem { + id(poll.id) + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) + title(poll.title) + winnerOptions(poll.winnerOptions) + totalVotesStatus(host.stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, poll.totalVotes, poll.totalVotes)) + clickListener { + host.listener?.onPollClicked(poll.id) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt new file mode 100644 index 0000000000..0d97bd8dcb --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt @@ -0,0 +1,90 @@ +/* + * 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.roomprofile.polls.list + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomPollsListBinding +import im.vector.app.features.roomprofile.polls.PollSummary +import im.vector.app.features.roomprofile.polls.RoomPollsType +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import timber.log.Timber +import javax.inject.Inject + +abstract class RoomPollsListFragment : + VectorBaseFragment(), + RoomPollsController.Listener { + + @Inject + lateinit var roomPollsController: RoomPollsController + + private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { + return FragmentRoomPollsListBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupList() + } + + abstract fun getEmptyListTitle(): String + + abstract fun getRoomPollsType(): RoomPollsType + + private fun setupList() { + roomPollsController.listener = this + views.roomPollsList.configureWith(roomPollsController) + views.roomPollsEmptyTitle.text = getEmptyListTitle() + } + + override fun onDestroyView() { + cleanUpList() + super.onDestroyView() + } + + private fun cleanUpList() { + views.roomPollsList.cleanup() + roomPollsController.listener = null + } + + override fun invalidate() = withState(viewModel) { viewState -> + when (getRoomPollsType()) { + RoomPollsType.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) + RoomPollsType.ENDED -> renderList(viewState.polls.filterIsInstance(PollSummary.EndedPoll::class.java)) + } + } + + private fun renderList(polls: List) { + roomPollsController.setData(polls) + views.roomPollsEmptyTitle.isVisible = polls.isEmpty() + } + + override fun onPollClicked(pollId: String) { + // TODO navigate to details + Timber.d("poll with id $pollId clicked") + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt index b6fa997f41..514f2529e9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt @@ -25,6 +25,7 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference +import im.vector.app.core.utils.copyToClipboard import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.NightlyProxy import im.vector.app.features.rageshake.RageShake @@ -64,6 +65,14 @@ class VectorSettingsAdvancedSettingsFragment : override fun bindPref() { setupRageShakeSection() setupNightlySection() + setupDevToolsSection() + } + + private fun setupDevToolsSection() { + findPreference("SETTINGS_ACCESS_TOKEN")?.setOnPreferenceClickListener { + copyToClipboard(requireActivity(), session.sessionParams.credentials.accessToken) + true + } } private fun setupRageShakeSection() { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt index 38ba949a49..724807a81e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt @@ -20,7 +20,9 @@ import android.content.Context import android.os.Bundle import android.view.View import androidx.annotation.CallSuper +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceFragmentCompat import com.airbnb.mvrx.MavericksView import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -35,6 +37,7 @@ import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.MobileScreen import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -66,13 +69,19 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick * ViewEvents * ========================================================================================== */ - protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - observer(it) - } - .launchIn(viewLifecycleOwner.lifecycleScope) + protected fun VectorViewModel<*, *, T>.observeViewEvents( + observer: (T) -> Unit, + ) { + val tag = this@VectorSettingsBaseFragment::class.simpleName.toString() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents + .stream(tag) + .collect { + observer(it) + } + } + } } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index d56f4ad715..9cb894bb58 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -419,7 +419,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // Next media player is already attached to this player and will start playing automatically if (nextMediaPlayer != null) return - val hasEnded = !isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence + val currentSequence = playlist.currentSequence ?: 0 + val lastChunkSequence = mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence ?: 0 + val hasEnded = !isLiveListening && currentSequence >= lastChunkSequence if (hasEnded) { // We'll not receive new chunks anymore so we can stop the live listening stop() diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml new file mode 100644 index 0000000000..396d6fd8c5 --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml new file mode 100644 index 0000000000..8eb27e5e00 --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -0,0 +1,42 @@ + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml new file mode 100644 index 0000000000..17f3b5abf5 --- /dev/null +++ b/vector/src/main/res/layout/item_poll.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + diff --git a/vector/src/main/res/xml/vector_settings_advanced_settings.xml b/vector/src/main/res/xml/vector_settings_advanced_settings.xml index 9260b33162..6399d54cbb 100644 --- a/vector/src/main/res/xml/vector_settings_advanced_settings.xml +++ b/vector/src/main/res/xml/vector_settings_advanced_settings.xml @@ -93,6 +93,12 @@ android:title="@string/settings_key_requests" app:fragment="im.vector.app.features.settings.devtools.KeyRequestsFragment" /> + + () + private val initialState = RoomPollsViewState(ROOM_ID) + + private fun createViewModel(): RoomPollsViewModel { + return RoomPollsViewModel( + initialState = initialState, + getPollsUseCase = fakeGetPollsUseCase, + ) + } + + @Test + fun `given viewModel when created then polls list is observed and viewState is updated`() { + // Given + val polls = listOf(givenAPollSummary()) + every { fakeGetPollsUseCase.execute() } returns flowOf(polls) + val expectedViewState = initialState.copy(polls = polls) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + + // Then + viewModelTest + .assertLatestState(expectedViewState) + .finish() + verify { + fakeGetPollsUseCase.execute() + } + } + + private fun givenAPollSummary(): PollSummary { + return mockk() + } +} diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 2fbab3b71b..0b1a22f75c 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -28,7 +28,7 @@ fun String.trimIndentOneLine() = trimIndent().replace("\n", "") fun VectorViewModel.test(): ViewModelTest { val testResultCollectingScope = CoroutineScope(Dispatchers.Unconfined) val state = stateFlow.test(testResultCollectingScope) - val viewEvents = viewEvents.stream().test(testResultCollectingScope) + val viewEvents = viewEvents.stream("test").test(testResultCollectingScope) return ViewModelTest(state, viewEvents) } From 7b3c3d0dbbd67fe2ac8b95460c89446c77c1dce2 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 6 Jan 2023 17:57:50 +0000 Subject: [PATCH 014/317] Revert "Squashed commit of the following:" This reverts commit 4d6bbbbe8902e20760572739f4617e93e507e9df. --- build.gradle | 2 +- changelog.d/7724.bugfix | 1 - changelog.d/7864.wip | 2 - changelog.d/7879.bugfix | 1 - changelog.d/7899.bugfix | 1 - .../android/cs-CZ/changelogs/40105160.txt | 2 - .../android/cs-CZ/changelogs/40105180.txt | 2 - .../android/de-DE/changelogs/40105160.txt | 2 - .../android/de-DE/changelogs/40105180.txt | 2 - .../metadata/android/eo/short_description.txt | 2 +- fastlane/metadata/android/eo/title.txt | 2 +- .../android/et/changelogs/40105160.txt | 2 - .../android/et/changelogs/40105180.txt | 2 - .../android/fa/changelogs/40105160.txt | 2 - .../android/fa/changelogs/40105180.txt | 2 - .../android/fr-FR/changelogs/40105160.txt | 2 - .../android/fr-FR/changelogs/40105180.txt | 2 - .../android/hu-HU/changelogs/40105160.txt | 2 - .../android/hu-HU/changelogs/40105180.txt | 2 - .../android/id/changelogs/40105160.txt | 2 - .../android/id/changelogs/40105180.txt | 2 - .../android/sk/changelogs/40105160.txt | 2 - .../android/sk/changelogs/40105180.txt | 2 - .../android/uk/changelogs/40105160.txt | 2 - .../android/uk/changelogs/40105180.txt | 2 - .../android/zh-TW/changelogs/40105160.txt | 2 - .../android/zh-TW/changelogs/40105180.txt | 2 - .../src/main/res/values-da/strings.xml | 17 +- .../src/main/res/values-eo/strings.xml | 6 +- .../src/main/res/values-es/strings.xml | 109 +++--- .../src/main/res/values-pt-rBR/strings.xml | 42 +- .../src/main/res/values-sq/strings.xml | 2 +- .../src/main/res/values/strings.xml | 8 - .../crypto/crosssigning/UserIdentity.kt | 26 -- .../internal/crypto/DefaultCryptoService.kt | 32 +- .../sdk/internal/crypto/DeviceListManager.kt | 17 +- .../internal/crypto/store/IMXCryptoStore.kt | 29 +- .../internal/crypto/store/UserDataToStore.kt | 31 -- .../crypto/store/db/CryptoStoreAggregator.kt | 27 -- .../sdk/internal/crypto/store/db/Helper.kt | 12 +- .../crypto/store/db/RealmCryptoStore.kt | 364 ++++++++---------- .../android/sdk/internal/di/SerializeNulls.kt | 2 + .../interceptors/FormattedJsonHttpLogger.kt | 3 +- .../network/parsing/CheckNumberType.kt | 3 + .../internal/session/StreamEventsManager.kt | 10 +- .../room/create/CreateLocalRoomTask.kt | 2 +- .../session/sync/SyncResponseHandler.kt | 7 +- .../SyncResponsePostTreatmentAggregator.kt | 5 - .../session/sync/handler/CryptoSyncHandler.kt | 5 +- .../sync/handler/room/RoomSyncHandler.kt | 19 +- tools/lint/lint.xml | 1 - tools/release/releaseScript.sh | 32 +- vector/build.gradle | 2 +- .../app/core/di/MavericksViewModelModule.kt | 6 - .../app/core/platform/VectorBaseActivity.kt | 24 +- .../VectorBaseBottomSheetDialogFragment.kt | 23 +- .../core/platform/VectorBaseDialogFragment.kt | 19 +- .../app/core/platform/VectorBaseFragment.kt | 24 +- .../app/core/platform/VectorViewModel.kt | 9 +- .../app/core/resources/StringArrayProvider.kt | 2 + .../app/core/resources/StringProvider.kt | 4 + .../im/vector/app/core/utils/SharedEvent.kt | 58 --- .../im/vector/app/features/MainActivity.kt | 8 +- .../quads/SharedSecureStorageActivity.kt | 4 +- .../media/VectorAttachmentViewerActivity.kt | 17 +- .../roomprofile/RoomProfileActivity.kt | 6 - .../roomprofile/RoomProfileController.kt | 11 - .../roomprofile/RoomProfileFragment.kt | 4 - .../roomprofile/RoomProfileSharedAction.kt | 1 - .../roomprofile/polls/GetPollsUseCase.kt | 114 ------ .../features/roomprofile/polls/PollSummary.kt | 39 -- .../roomprofile/polls/RoomPollsAction.kt | 21 - .../roomprofile/polls/RoomPollsFragment.kt | 74 ---- .../polls/RoomPollsPagerAdapter.kt | 41 -- .../roomprofile/polls/RoomPollsType.kt | 22 -- .../roomprofile/polls/RoomPollsViewEvent.kt | 21 - .../roomprofile/polls/RoomPollsViewModel.kt | 54 --- .../roomprofile/polls/RoomPollsViewState.kt | 28 -- .../polls/active/RoomActivePollsFragment.kt | 34 -- .../polls/ended/RoomEndedPollsFragment.kt | 34 -- .../roomprofile/polls/list/RoomPollItem.kt | 72 ---- .../polls/list/RoomPollsController.kt | 76 ---- .../polls/list/RoomPollsListFragment.kt | 90 ----- .../VectorSettingsAdvancedSettingsFragment.kt | 9 - .../settings/VectorSettingsBaseFragment.kt | 23 +- .../listening/VoiceBroadcastPlayerImpl.kt | 4 +- .../main/res/layout/fragment_room_polls.xml | 54 --- .../res/layout/fragment_room_polls_list.xml | 42 -- vector/src/main/res/layout/item_poll.xml | 69 ---- .../xml/vector_settings_advanced_settings.xml | 6 - .../polls/RoomPollsViewModelTest.kt | 69 ---- .../java/im/vector/app/test/Extensions.kt | 2 +- 92 files changed, 375 insertions(+), 1707 deletions(-) delete mode 100644 changelog.d/7724.bugfix delete mode 100644 changelog.d/7864.wip delete mode 100644 changelog.d/7879.bugfix delete mode 100644 changelog.d/7899.bugfix delete mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40105180.txt delete mode 100644 fastlane/metadata/android/de-DE/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/de-DE/changelogs/40105180.txt delete mode 100644 fastlane/metadata/android/et/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/et/changelogs/40105180.txt delete mode 100644 fastlane/metadata/android/fa/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/fa/changelogs/40105180.txt delete mode 100644 fastlane/metadata/android/fr-FR/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/fr-FR/changelogs/40105180.txt delete mode 100644 fastlane/metadata/android/hu-HU/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/hu-HU/changelogs/40105180.txt delete mode 100644 fastlane/metadata/android/id/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/id/changelogs/40105180.txt delete mode 100644 fastlane/metadata/android/sk/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/sk/changelogs/40105180.txt delete mode 100644 fastlane/metadata/android/uk/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/uk/changelogs/40105180.txt delete mode 100644 fastlane/metadata/android/zh-TW/changelogs/40105160.txt delete mode 100644 fastlane/metadata/android/zh-TW/changelogs/40105180.txt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt delete mode 100644 vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt delete mode 100644 vector/src/main/res/layout/fragment_room_polls.xml delete mode 100644 vector/src/main/res/layout/fragment_room_polls_list.xml delete mode 100644 vector/src/main/res/layout/item_poll.xml delete mode 100644 vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt diff --git a/build.gradle b/build.gradle index 7e5d659c8b..0f94fc418c 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.2.3" - classpath 'org.owasp:dependency-check-gradle:7.4.3' + classpath 'org.owasp:dependency-check-gradle:7.4.1' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' diff --git a/changelog.d/7724.bugfix b/changelog.d/7724.bugfix deleted file mode 100644 index 685f7ad4e2..0000000000 --- a/changelog.d/7724.bugfix +++ /dev/null @@ -1 +0,0 @@ - Observe ViewEvents only when resumed and ensure ViewEvents are not lost. diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip deleted file mode 100644 index e1187ee1e7..0000000000 --- a/changelog.d/7864.wip +++ /dev/null @@ -1,2 +0,0 @@ -[Poll] Render active polls list of a room -[Poll] Render past polls list of a room diff --git a/changelog.d/7879.bugfix b/changelog.d/7879.bugfix deleted file mode 100644 index be828ec2cc..0000000000 --- a/changelog.d/7879.bugfix +++ /dev/null @@ -1 +0,0 @@ -Reduce number of crypto database transactions when handling the sync response diff --git a/changelog.d/7899.bugfix b/changelog.d/7899.bugfix deleted file mode 100644 index d95af29d8d..0000000000 --- a/changelog.d/7899.bugfix +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105160.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105160.txt deleted file mode 100644 index 69c2b3304c..0000000000 --- a/fastlane/metadata/android/cs-CZ/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -Hlavní změny v této verzi: Vlákna jsou nyní povolena ve výchozím nastavení. -Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105180.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105180.txt deleted file mode 100644 index 69c2b3304c..0000000000 --- a/fastlane/metadata/android/cs-CZ/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -Hlavní změny v této verzi: Vlákna jsou nyní povolena ve výchozím nastavení. -Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105160.txt b/fastlane/metadata/android/de-DE/changelogs/40105160.txt deleted file mode 100644 index c55d8d998f..0000000000 --- a/fastlane/metadata/android/de-DE/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -Die wichtigsten Änderungen in dieser Version: Threads sind nun standardmäßig aktiviert. -Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105180.txt b/fastlane/metadata/android/de-DE/changelogs/40105180.txt deleted file mode 100644 index c55d8d998f..0000000000 --- a/fastlane/metadata/android/de-DE/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -Die wichtigsten Änderungen in dieser Version: Threads sind nun standardmäßig aktiviert. -Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/eo/short_description.txt b/fastlane/metadata/android/eo/short_description.txt index 05a4aaf191..33013ce78f 100644 --- a/fastlane/metadata/android/eo/short_description.txt +++ b/fastlane/metadata/android/eo/short_description.txt @@ -1 +1 @@ -Grupa mesaĝisto - ĉifrita mesaĝado, grupa babilejo kaj videovokoj +Sekura kaj sencentrigita vokado kaj babilado. Tenu viajn datumojn sekuraj. diff --git a/fastlane/metadata/android/eo/title.txt b/fastlane/metadata/android/eo/title.txt index 85b92c693b..f56927e529 100644 --- a/fastlane/metadata/android/eo/title.txt +++ b/fastlane/metadata/android/eo/title.txt @@ -1 +1 @@ -Element - Sekura Tujmesaĝilo +Element (antaŭe Riot.im) diff --git a/fastlane/metadata/android/et/changelogs/40105160.txt b/fastlane/metadata/android/et/changelogs/40105160.txt deleted file mode 100644 index 9aadf5dae8..0000000000 --- a/fastlane/metadata/android/et/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -Põhilised muutused selles versioonis: jutulõngad on vaikimisi kasutusel. -Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105180.txt b/fastlane/metadata/android/et/changelogs/40105180.txt deleted file mode 100644 index 9aadf5dae8..0000000000 --- a/fastlane/metadata/android/et/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -Põhilised muutused selles versioonis: jutulõngad on vaikimisi kasutusel. -Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105160.txt b/fastlane/metadata/android/fa/changelogs/40105160.txt deleted file mode 100644 index 0c3cc5aa31..0000000000 --- a/fastlane/metadata/android/fa/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -تغییرات عمده در این نگارش: رشته‌ها اکنون به صورت پیش‌گزیده به کار افتاده‌اند. -گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105180.txt b/fastlane/metadata/android/fa/changelogs/40105180.txt deleted file mode 100644 index 0c3cc5aa31..0000000000 --- a/fastlane/metadata/android/fa/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -تغییرات عمده در این نگارش: رشته‌ها اکنون به صورت پیش‌گزیده به کار افتاده‌اند. -گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105160.txt b/fastlane/metadata/android/fr-FR/changelogs/40105160.txt deleted file mode 100644 index 4101bb0c86..0000000000 --- a/fastlane/metadata/android/fr-FR/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -Principaux changements pour cette version : Fils de discussion activés par défaut. -Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105180.txt b/fastlane/metadata/android/fr-FR/changelogs/40105180.txt deleted file mode 100644 index 4101bb0c86..0000000000 --- a/fastlane/metadata/android/fr-FR/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -Principaux changements pour cette version : Fils de discussion activés par défaut. -Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40105160.txt b/fastlane/metadata/android/hu-HU/changelogs/40105160.txt deleted file mode 100644 index c5dc38bc8f..0000000000 --- a/fastlane/metadata/android/hu-HU/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -Legnagyobb változtatás ebben a verzióban: Új üzenetszálak alapból bekapcsolva! -Teljes változási napló: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40105180.txt b/fastlane/metadata/android/hu-HU/changelogs/40105180.txt deleted file mode 100644 index cc70967e58..0000000000 --- a/fastlane/metadata/android/hu-HU/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -Legnagyobb változtatás ebben a verzióban: Az üzenetszálak alapból bekapcsolva! -Teljes változási napló: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105160.txt b/fastlane/metadata/android/id/changelogs/40105160.txt deleted file mode 100644 index 173a1bfb1b..0000000000 --- a/fastlane/metadata/android/id/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -Perubahan utama dalam versi ini: Utasan sekarang diaktifkan secara bawaan. -Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105180.txt b/fastlane/metadata/android/id/changelogs/40105180.txt deleted file mode 100644 index 173a1bfb1b..0000000000 --- a/fastlane/metadata/android/id/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -Perubahan utama dalam versi ini: Utasan sekarang diaktifkan secara bawaan. -Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105160.txt b/fastlane/metadata/android/sk/changelogs/40105160.txt deleted file mode 100644 index d5b5ad330d..0000000000 --- a/fastlane/metadata/android/sk/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -Hlavné zmeny v tejto verzii: Vlákna sú teraz predvolene zapnuté. -Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105180.txt b/fastlane/metadata/android/sk/changelogs/40105180.txt deleted file mode 100644 index d5b5ad330d..0000000000 --- a/fastlane/metadata/android/sk/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -Hlavné zmeny v tejto verzii: Vlákna sú teraz predvolene zapnuté. -Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105160.txt b/fastlane/metadata/android/uk/changelogs/40105160.txt deleted file mode 100644 index edbd209d17..0000000000 --- a/fastlane/metadata/android/uk/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -Основні зміни в цій версії: Гілки відтепер типово ввімкнено. -Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105180.txt b/fastlane/metadata/android/uk/changelogs/40105180.txt deleted file mode 100644 index edbd209d17..0000000000 --- a/fastlane/metadata/android/uk/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -Основні зміни в цій версії: Гілки відтепер типово ввімкнено. -Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105160.txt b/fastlane/metadata/android/zh-TW/changelogs/40105160.txt deleted file mode 100644 index 9c66f3c2ad..0000000000 --- a/fastlane/metadata/android/zh-TW/changelogs/40105160.txt +++ /dev/null @@ -1,2 +0,0 @@ -此版本中的主要變動:討論串現在預設啟用。 -完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105180.txt b/fastlane/metadata/android/zh-TW/changelogs/40105180.txt deleted file mode 100644 index 9c66f3c2ad..0000000000 --- a/fastlane/metadata/android/zh-TW/changelogs/40105180.txt +++ /dev/null @@ -1,2 +0,0 @@ -此版本中的主要變動:討論串現在預設啟用。 -完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/library/ui-strings/src/main/res/values-da/strings.xml b/library/ui-strings/src/main/res/values-da/strings.xml index 35c93949f9..13d53b7bb2 100644 --- a/library/ui-strings/src/main/res/values-da/strings.xml +++ b/library/ui-strings/src/main/res/values-da/strings.xml @@ -39,6 +39,7 @@ Telefonnummer Invitation til rum %1$s og %2$s + Tomt rum Lyst Tema Mørkt Tema @@ -81,6 +82,7 @@ Kun Matrix kontakter Ingen resultater Rum + Send logfiler Send crashlogfiler Send screenshot @@ -108,8 +110,10 @@ Dette ligner ikke en gyldig emailadresse Den emailadresse er allerede i brug. Glemt adgangskode? + Denne Home Server vil gerne være sikker på du ikke er en robot Kunne ikke verificere emailadresse: vær sikker på du klikkede på linket i emailen + Skriv gyldig URL Fejlformet JSON Indeholdt ikke gyldig JSON @@ -130,10 +134,15 @@ Opkald I Gang Den anden side tog den ikke. Information + + ${app_name} skal bruge tilladelse til at bruge din mikrofon for at lave lydopkald. + ${app_name} skal bruge tilladelse til at bruge dit kamera og din mikrofon for at lave videoopkald. Giv venligst tilladelse ved næste pop-up for at lave opkaldet. + + JA NEJ Fortsæt @@ -141,6 +150,7 @@ Giv venligst tilladelse ved næste pop-up for at lave opkaldet. Forbind Afvis Spring til første ulæste besked. + Forlad rum Er du sikker på at du vil forlade rummet? DIREKTE CHATS @@ -153,7 +163,7 @@ Giv venligst tilladelse ved næste pop-up for at lave opkaldet. Du vil ikke kunne omgøre denne ændring da du forfremmer brugeren til at have samme magt niveau som dig selv. Er du sikker? %s skriver… - %1$s & %2$s skriver… + "%1$s & %2$s skriver…" %1$s, %2$s og andre skriver… Du har ikke tilladelse til at skrive i dette rum Stol på @@ -176,6 +186,7 @@ Er du sikker? %d medlemsændringer Medlemsoversigt + 1 medlem %d medlemmer @@ -188,6 +199,8 @@ Er du sikker? Søg Filtrer medlemmer i rum Ingen resultater + + Alle meddelelser Opret genvej på startskærm Profilbillede @@ -278,4 +291,4 @@ Er du sikker? %1$s oprettede rummet Din invitation Forbind denne email med din konto - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-eo/strings.xml b/library/ui-strings/src/main/res/values-eo/strings.xml index 4521e840a6..f536ca00f9 100644 --- a/library/ui-strings/src/main/res/values-eo/strings.xml +++ b/library/ui-strings/src/main/res/values-eo/strings.xml @@ -718,8 +718,8 @@ Bonvolu enigi la URL-on de identiga servilo Ne povis konektiĝi al identiga servilo Enigu URL-on de identiga servilo - Ni sendis retleteron al %s, bonvolu unue kontroli vian retpoŝton kaj klaki la konfirman ligilon - Ni sendis retleteron al %s; kontrolu vian retpoŝton kaj klaku la konfirman ligilon + Ni sendis al vi konfirman retleteron al %s, bonvolu unue kontroli vian retpoŝton kaj klaki la konfirman ligilon + Ni sendis al vi konfirman retleteron al %s; kontrolu vian retpoŝton kaj klaku la konfirman ligilon Troveblaj telefonnumeroj Malkonekto de via identiga servilo signifas, ke vi ne estos trovebla de aliaj uzantoj kaj ne povos inviti aliulojn per retpoŝtadreso aŭ telefono. Elektebloj pri trovado aperos post aldono de telefonnumero. @@ -2201,4 +2201,4 @@ Sonorante… Aroj - Iom uzantoj reatentita - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-es/strings.xml b/library/ui-strings/src/main/res/values-es/strings.xml index c06442b5d0..e07c21d6a5 100644 --- a/library/ui-strings/src/main/res/values-es/strings.xml +++ b/library/ui-strings/src/main/res/values-es/strings.xml @@ -50,7 +50,7 @@ %1$s ha invitado a %2$s. Razón: %3$s %1$s te ha invitado. Razón: %2$s %1$s se ha unido. Razón: %2$s - %1$s dejó la sala. Razón: %2$s + %1$s se ha ido. Razón: %2$s %1$s ha rechadazo la invitación. Razón: %2$s %1$s expulsó a %2$s. Razón: %3$s %1$s ha baneado a %2$s. Razón: %3$s @@ -81,17 +81,17 @@ %1$s ha permitido que los invitados se unan a la sala. %1$s ha impedido que los invitados se unan a la sala. %1$s ha activado el cifrado Extremo-a-Extremo. - %1$s ha activado el cifrado extremo-a-extremo (algoritmo no reconocido %2$s). + %1$s ha activado el cifrado Extremo-a-Extremo (algoritmo no reconocido %2$s). Tu invitación %1$s creó la sala Creaste la sala Invitaste a %1$s Te uniste a la Sala - Dejaste la sala + Dejaste la Sala Rechazaste la invitación Tu pateaste a %1$s Tu desbanaste a %1$s - Excluiste a %1$s + Usted prohibió a %1$s Retiró la invitación de %1$s\'s Cambiaste tu avatar Establece su nombre de visualización en %1$s @@ -152,10 +152,10 @@ Agregaste %1$s y quitaste %2$s como direcciones para esta sala. Estableciste la dirección principal de esta sala en %1$s. Quitaste la dirección principal de esta sala. - Has permitido que los invitados se unan a la sala. - Has impedido que los invitados se unan a la sala. + Ha permitido que los invitados se unan a la sala. + Ha impedido que los invitados se unan a la sala. Has activado el cifrado Extremo-a-Extremo. - Has activado el cifrado extremo-a-extremo (algoritmo %1$s no reconocido). + Has activado el cifrado Extremo-a-Extremo (algoritmo %1$s no reconocido). Has impedido que invitados se unan a la sala. Has permitido a invitados unirse aquí. Te has ido. Razón: %1$s @@ -163,7 +163,7 @@ Has invitado a %1$s Has actualizado aquí. Has hecho futuros mensajes visibles a %1$s - Has dejado la sala + Te saliste de la sala Te uniste Creaste la conversación %1$s ha impedido que invitados se unan a la sala. @@ -255,7 +255,7 @@ Salas y Grupos Filtrar salas Invitaciones - Baja prioridad + Prioridad baja Conversaciones Solo contactos de Matrix No hay resultados @@ -429,7 +429,7 @@ Importar Cifrar solo a sesiones verificadas Nunca enviar mensajes cifrados a sesiones sin verificar desde esta sesión. - Sin Verificar + SIN Verificar Verificado Verificar Para verificar que esta sesión es confiable, por favor contacta a su dueño por algún otro medio (ej. cara a cara o por teléfono) y pregúntale si la clave que ve en sus Ajustes de Usuario para esta sesión coincide con la clave a continuación: @@ -819,7 +819,7 @@ La copia de seguridad tiene una firma valida de la sesión no verificada %s La copia de seguridad tiene una firma inválida de la sesión verificada %s La copia de seguridad tiene una firma inválida de la sesión no verificada %s - Para usar la copia de seguridad de la clave en esta sesión introduce tu contraseña o tu clave de recuperación ahora. + Para usar la copia de seguridad de la clave en esta sesión introduzca su contraseña o su clave de recuperación ahora. ¿Deseas borrar tus claves de cifrado guardadas en el servidor\? No podrás usar tu clave de recuperación para leer el historial de mensajes cifrados. Reproducir sonido de cámara ip desconocida @@ -1182,7 +1182,7 @@ %s cancelada Cancelado por usted %s aceptada - Aceptaste + Aceptado por usted Verificacion enviada Solicitud de verificación Verifica esta Sesion @@ -1239,7 +1239,7 @@ Precaucion Error al obtener sesiones Sesiones - Confiable + Confirmado No es confiable Inicializar Firmas Cruzadas Restablecer claves @@ -1255,7 +1255,7 @@ Razón para redactar ${app_name} Android Refrescar - Nuevo inicio de sesión detectado . ¿Has sido tú\? + Nuevo inicio de sesión detectado . ¿Fue usted\? Este no era yo Su cuenta puede estar comprometida Verificación cancelada @@ -1263,7 +1263,7 @@ Clave de mensaje ¡Listo! Cifrado habilitado - Creaste y configuraste la sala. + Sala creada y configurada por usted. Esperando por %s… Ajuste de Notificaciones Mensaje… @@ -1279,7 +1279,7 @@ Si decea resetear su PIN, toque Olvidé PIN para cerrar sesión y restablecer. Numeros telefonicos Correos y numeros telefonicos - Administra las direcciones de correo y/o números telefónicos relacionados a tu cuenta de Matrix + Administre el correo y numero telefonico de su cuenta Mostrar mensajes eliminados Indicar marca de mensaje eliminado ARCHIVOS @@ -1357,7 +1357,7 @@ Hiciste la sala solo por invitación. Únase gratis a millones de personas en el mayor servidor público Continuar con SSO - Dirección de Element Matrix Services + Dirección de servicios de Element Matrix Ingrese la dirección del servidor que desea utilizar Se enviará un correo electrónico de verificación a su bandeja de entrada para confirmar la configuración de su nueva contraseña. Siguiente @@ -1407,7 +1407,7 @@ Advertencia Tu cuenta aún no está creada. ¿Detener el proceso de registro\? Seleccione matrix.org - Seleccionar Element Matrix Services + Seleccionar servicios de matriz de elementos Seleccione un servidor doméstico personalizado Realiza el desafío de captcha Acepta los términos para continuar @@ -1453,7 +1453,7 @@ \nVuelva a iniciar sesión para acceder a los datos y mensajes de su cuenta. Perderás el acceso a los mensajes seguros a menos que inicies sesión para recuperar tus claves de cifrado. La sesión actual es para el usuario %1$s y usted proporciona las credenciales para el usuario %2$s. Esto no está suportado por ${app_name}. -\nPrimero borra los datos, luego inicia sesión nuevamente con otra cuenta. +\nPrimero borre los datos, luego inicie sesión nuevamente con otra cuenta. Su enlace matrix.to estaba mal formado El modo desarrollador activa funciones ocultas y también puede hacer que la aplicación sea menos estable. ¡Solo para desarrolladores! Uno de los siguientes puede verse comprometido: @@ -1462,9 +1462,9 @@ \n- El servidor privado al que está conectado el usuario que estás verificando \n- Su conexión a internet o la de otros usuarios \n- Su dispositivo o el de otros usuarios - Los mensajes de esta sala están cifrados de extremo-a-extremo. + Los mensajes de esta sala están cifrados Extremo-a-Extremo. \n -\nTus mensajes están protegidos y sólo tu y el destinatario tienen las claves únicas para descifrarlos. +\nSus mensajes están protegidos y sólo usted y el destinatario tienen las claves únicas para descifrarlos. Esta sesión no puede compartir esta verificación con sus otras sesiones. \nLa verificación se guardará localmente y se compartirá en una versión futura de la aplicación. Envía el emote dado coloreado como un arcoíris @@ -1474,7 +1474,7 @@ Verifica si los mismos emojis aparecen en el mismo orden en ambos usuarios. Compare el código con el que se muestra en la pantalla del otro usuario. Los mensajes con este usuario están cifrados Extremo-a-Extremo y no pueden ser leídos por terceros. - Tu nueva sesión acaba de verificarse y ahora tiene acceso a tus mensajes cifrados y otros usuarios la verán como de confianza. + Su nueva sesión ahora está verificada. Tiene acceso a sus mensajes cifrados y otros usuarios lo verán como de confianza. La firma cruzada está habilitada \n Claves privadas en el dispositivo. La firma cruzada está habilitada @@ -1484,8 +1484,8 @@ \nLas claves no son de confianza El administrador de su servidor ha desactivado el cifrado Extremo-a-Extremo de forma predeterminada en salas privadas y mensajes directos. No hay información criptográfica disponible - Esta sesión es confiable para mensajería segura porque la verificaste: - Verifica esta sesión para marcarla como confiable y otorgarle acceso a mensajes cifrados. Si no iniciaste sesión en esta sesión, su cuenta puede haber sido comprometida: + Esta sesión es confiable para mensajería segura porque usted la verificó: + Verifique esta sesión para marcarla como confiable y otorgarle acceso a mensajes cifrados. Si no inició sesión en esta sesión, su cuenta puede verse comprometida: %d sesión activa %d sesiones activas @@ -1510,8 +1510,8 @@ Solicitudes clave Desbloquear el historial de mensajes cifrados Utilice esta sesión para verificar su nuevo, otorgándole acceso a mensajes cifrados. - Si cancelas, no podrás leer mensajes cifrados en este dispositivo y otros usuarios no confiarán en este - Si cancelas, no podrás leer mensajes cifrados en tu nuevo dispositivo y otros usuarios no confiarán en este + Si cancela, no podrá leer mensajes cifrados en este dispositivo y otros usuarios no confiarán en él + Si cancela, no podrá leer mensajes cifrados en su nuevo dispositivo y otros usuarios no confiarán en él No verificarás %1$s (%2$s) si cancelas ahora. Comience de nuevo en su perfil de usuario. Uno de los siguientes puede verse comprometido: \n @@ -1524,7 +1524,7 @@ Se canceló la verificación. Puede iniciar la verificación de nuevo. Ingrese su %s para continuar. No use la contraseña de su cuenta. - Ingresa una frase de seguridad que solo tú conozcas, que se usa para proteger secretos en tu servidor. + Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor. Esto puede tardar varios segundos, tenga paciencia. Configurando la recuperación. Manténlo seguro @@ -1561,7 +1561,7 @@ Nombre de usuario y / o contraseña incorrectos. La contraseña ingresada comienza o termina con espacios, verifíquela. Esta cuenta ha sido desactivada. Mejora de cifrado disponible - Verifícate a ti mismo y a los demás para mantener tus chats seguros + Verifíquese a usted mismo y a los demás para mantener sus chats seguros No es una clave de recuperación válida Por favor introduce una clave de recuperación Comprobando la clave de respaldo @@ -1622,11 +1622,11 @@ Usa una llave de seguridad Genere una clave de seguridad para almacenar en un lugar seguro, como un administrador de contraseñas o una caja fuerte. Utilice una frase de seguridad - Ingresa una frase secreta que solo tú conozcas y genera una clave para tu copia de respaldo. + Ingrese una frase secreta que solo usted conozca y genere una clave de respaldo. Guarde su llave de seguridad Guarde su llave de seguridad en un lugar seguro, como un administrador de contraseñas o una caja fuerte. Establecer una frase de seguridad - Ingresa una frase de seguridad que sólo tú conozcas, que se usa para proteger secretos en tu servidor. + Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor. Frase de seguridad Ingrese su Frase de seguridad nuevamente para confirmarla. Nombre de la Sala @@ -1636,7 +1636,7 @@ Esperando este mensaje, esto puede tardar un poco Debido al cifrado Extremo-a-Extremo, es posible que deba esperar a que llegue el mensaje de alguien porque las claves de cifrado no se le enviaron correctamente. No puede acceder a este mensaje porque ha sido bloqueado por el remitente - No puedes acceder a este mensaje porque el remitente no confía en tu sesión + No puede acceder a este mensaje porque el remitente no confía en su sesión No puede acceder a este mensaje porque el remitente no envió las claves a propósito Esperando al historial de cifrado ¡Nos complace anunciar que hemos cambiado de nombre! Tu aplicación está actualizada y accediste a tu cuenta. @@ -1711,7 +1711,7 @@ Mostrar el dispositivo con el que puede verificar ahora Mostrar %d dispositivos con los que puede verificar ahora - Reiniciarás sin historial, ni mensajes, ni dispositivos o usuarios verificados + Reiniciará sin historia, mensajes, dispositivos o usuarios verificados Si resetea todo Solo haga esto si no tiene otro dispositivo con el que verificar éste. Resetear todo @@ -1748,7 +1748,7 @@ Se necesita una nueva autenticación ¡Código QR no escaneado! Código QR no válido (URL no válida)! - No puedes MD a ti mismo! + No puede DM usted mismo! Compartir por texto Cambiar PIN Cambie su PIN actual @@ -1960,7 +1960,7 @@ Acceso a la sala Siempre preguntar Espacios - Mostrar todas las salas en el directorio de salas, incluyendo salas con contenido explícito. + mostrar todas las salas en el directorio de salas, incluyendo salas con contenido explícito. Mostrar salas con contenido explícito Directorio de la sala Salas sugeridas @@ -2181,7 +2181,7 @@ Agregar nuevas palabra clave Tus palabras clave Habilitar notificación por correo electrónico para %s - Para recibir notificaciones por correo electrónico, asocia una direccion de correo electrónico a tu cuenta de Matrix + Para recibir un correo electrónico con una notificación, asocie un correo electrónico a su cuenta de matrix Notificación de correo electrónico Ninguno Solo menciones y palabras clave @@ -2219,7 +2219,7 @@ No se puede grabar un mensaje de voz No se puede reproducir este mensaje de voz Toca tu grabación para detenerla o escucharla - Restan %1$ds + %1$ds dejado Mantenga presionado para grabar, suelte para enviar Eliminar grabación Grabación de mensaje de voz @@ -2238,7 +2238,7 @@ ¡Se ha cerrado la sesión! ¡Se ha abandonado la sala! Consejo: Pulse prolongadamente un mensaje y use \"%s\" . - Mantén las conversaciones organizadas usando hilos + Mantén las conversaciones organizadas con hilos Muestra todos los hilos en que has participado Mis Hilos Muestra todos los hilos de la sala actual @@ -2457,7 +2457,7 @@ BETA Comentarios de la beta de hilos Beta de hilos - - Algunos usuarios han dejado de ser ignorados + - Algunos usuarios han sido dejados de ignorar La compartición de pantalla está en progreso ${app_name} Compartición de pantalla Dejar de compartir pantalla @@ -2474,7 +2474,7 @@ Actualizado hace %1$s Implementación temporal: las ubicaciones persisten en el historial de la sala Activar compartir ubicación en tiempo real - Restan %1$s + Queda %1$s Compartiendo hasta %1$s Ver ubicación en tiempo real La ubicación en tiempo real ha terminado @@ -2625,8 +2625,8 @@ \nPor favor, inténtelo de nuevo.%s Usar ajustes por defecto del sistema Escoger manualmente - Tamaño automático - Escoge tamaño del tipo de letra + Tamaño automático de fuente + Escoger tamaño de la fuente %1$s y %2$d otro %1$s y %2$d otros @@ -2659,33 +2659,18 @@ Otorgar permiso ${app_name} necesita permiso para mostrar notificaciones. \nPor favor, otórgalo. - ${app_name} necesita permiso para mostrar notificaciones. Las notificaciones pueden mostrar tus mensajes, invitaciones, etc. + ${app_name} necesita permisos para mostrar notificaciones. Las notificaciones pueden mostrar tus mensajes, invitaciones, etc. \n -\nPor favor, a continuacion, en las ventanas emergentes, permite el acceso para poder visualizar notificaciones. - Prueba el editor de texto enriquecido (pronto llegará la opción de texto simple, sin formato) +\nPor favor, permite el acceso en las siguientes ventanas emergentes para poder visualizar notificaciones. + Prueba el editor de texto enriquecido (pronto llegará la opción de texto sin formato plain text) Habilitar editor de texto enriquecido (rich text) Crear MD únicamente al primer mensaje Una versión simplificada de Element con pestañas opcionales Habilitar nueva disposición - Sí, Detener + Sí, Parar Deseleccionar todo - Ocultar los subespacios de %s - Mostrar los subespacios de %s + Ocultar los hijos de %s + Mostrar los hijos de %s Has finalizado una transmisión de voz. %1$s ha finalizado una transmisión de voz. - Element Matrix Services (EMS) es un servicio de alojamiento para tus comunicaciones en tiempo real. Robusto, confiable, rápido y seguro. Para saber cómo, ve a <a href=\"${ftue_ems_url}\">element.io/ems</a> - Difusión de voz - Habilitado: - ID de sesión: - Algo falló. Por favor, comprueba tu conexión de red e inténtalo nuevamente. - Citando - Respondiendo a %s - Editando - Abrir pantalla de herramientas de desarrollador - 🔒 Tienes habilitado el cifrado a sesiones verificadas sólo para todas las salas en Ajustes de Seguridad. - ⚠ Hay dispositivos sin verificar en esta sala, los cuales no seran capaces de descifrar los mensajes que envías. - Habilita MDs pospuestos - Mostrar chats recientes en el menú de compartir sistema - No enviar nunca mensajes cifrados a sesiones sin verificar en esta sala. - Restan %1$s \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index a5aa778156..bc617c62f0 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -5,24 +5,24 @@ %1$s convidou você %1$s entrou na sala %1$s saiu da sala - %1$s recusou o convite + %1$s rejeitou o convite %1$s removeu %2$s %1$s desbaniu %2$s %1$s baniu %2$s - %1$s desfez o convite para %2$s - %1$s mudou seu avatar - %1$s definiu seu nome de exibição para %2$s - %1$s mudou seu nome de exibição de %2$s para %3$s - %1$s removeu seu nome de exibição (era %2$s) + %1$s retirou o convite de %2$s + %1$s mudou o avatar dela(e) + %1$s definiu o nome de exibição dela(e) para %2$s + %1$s mudou o nome de exibição dela(e) de %2$s para %3$s + %1$s removeu o nome de exibição dela(e) (era %2$s) %1$s mudou o tópico para: %2$s %1$s mudou o nome da sala para: %2$s %s começou uma chamada de vídeo. %s começou uma chamada de voz. %s atendeu a chamada. - %s encerrou a chamada. - %1$s tornou o histórico futuro da sala visível para %2$s - todos os membros da sala, a partir do ponto que foram convidados. - todos os membros da sala, a partir do ponto que entraram. + %s terminou a chamada. + %1$s fez histórico futuro da sala visível para %2$s + todos os membros da sala, do ponto que foram convidados. + todos os membros da sala, do ponto que se juntaram. todos os membros da sala. qualquer pessoa. (avatar mudou também) @@ -45,11 +45,11 @@ Você convidou %1$s Você entrou na sala Você saiu da sala - Você recusou o convite + Você rejeitou o convite Você removeu %1$s Você desbaniu %1$s Você baniu %1$s - Você desfez o convite para %1$s + Você retirou o convite de %1$s Você mudou seu avatar Você definiu seu nome de exibição para %1$s Você mudou seu nome de exibição de %1$s para %2$s @@ -63,8 +63,8 @@ %s enviou dados para configurar a chamada. Você enviou dados para configurar a chamada. Você atendeu a chamada. - Você encerrou a chamada. - Você tornou o histórico futuro da sala visível para %1$s + Você terminou a chamada. + Você fez histórico futuro da sala visível para %1$s %s fez o upgrade desta sala. Você fez o upgrade desta sala. Você removeu o nome da sala @@ -170,8 +170,8 @@ %1$s convidou %2$s Você fez o upgrade aqui. %s fez o upgrade aqui. - Você tornou as mensagens futuras visíveis para %1$s - %1$s tornou as mensagens futuras visíveis para %2$s + Você fez mensagens futuras visíveis para %1$s + %1$s fez mensagens futuras visíveis para %2$s Você saiu da sala %1$s saiu da sala Você entrou @@ -408,7 +408,7 @@ Qualquer pessoa Membros somente (desde o ponto no tempo de seleção desta opção) Membros somente (desde que eles foram convidados) - Membros somente (desde que eles entraram) + Membros somente (desde que eles se juntaram) Usuárias(os) banidas(os) Avançadas ID interno desta sala @@ -2009,8 +2009,8 @@ Adicionar salas Explorar salas - %d pessoa que você conhece já entrou - %d pessoas que você conhece já entraram + %d pessoa que você conhece já tem se juntado + %d pessoas que você conhece já têm se juntado Juntar-Se a Espaço Criar espaço @@ -2833,8 +2833,8 @@ Desselecionar todas(os) Selecionar todas(os) - %1$d selecionado(a) - %1$d selecionados(as) + %1$d selecionada(o) + %1$d selecionadas(os) Alguma outra pessoa já está gravando um broadcast de voz. Espere que o broadcast de voz dela termine para começar um novo. Alternar modo de tela cheia diff --git a/library/ui-strings/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml index b170d306e4..3b233c087c 100644 --- a/library/ui-strings/src/main/res/values-sq/strings.xml +++ b/library/ui-strings/src/main/res/values-sq/strings.xml @@ -2504,7 +2504,7 @@ %s \nduket paksa si i zbrazët. Jini në gjendje të incizoni dhe dërgoni transmetim zanor në rrjedhën kohore të dhomës. - Aktivizoni transmetim zanor (nën zhvillim aktiv) + Aktivizoni transmetim zanor Aktivizo regjistrim hollësish klienti Shihini më qartë dhe kontrolloni më mirë krejt sesionet tuaj. Aktivizo përgjegjës të ri sesionesh diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 6c6474cbb3..146df5054a 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2338,7 +2338,6 @@ "One person" "%1$d people" - Poll history Uploads Leave Room Leave @@ -3194,10 +3193,6 @@ Voters see results as soon as they have voted Closed poll Results are only revealed when you end the poll - Active polls - There are no active polls in this room - Past polls - There are no past polls in this room Share location @@ -3511,7 +3506,4 @@ sent a video. sent a sticker. created a poll. - - Access Token - Your access token gives full access to your account. Do not share it with anyone. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt deleted file mode 100644 index 071db7f902..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2023 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.api.session.crypto.crosssigning - -/** - * Container for the three cross signing keys: master, self signing and user signing. - */ -data class UserIdentity( - val masterKey: CryptoCrossSigningKey?, - val selfSigningKey: CryptoCrossSigningKey?, - val userSigningKey: CryptoCrossSigningKey?, -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 50497e3a27..7862da1c17 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -89,7 +89,6 @@ import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask @@ -193,21 +192,21 @@ internal class DefaultCryptoService @Inject constructor( private val isStarting = AtomicBoolean(false) private val isStarted = AtomicBoolean(false) - fun onStateEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { + fun onStateEvent(roomId: String, event: Event) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } } - fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean, cryptoStoreAggregator: CryptoStoreAggregator?) { + fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) { // handle state events if (event.isStateEvent()) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } } @@ -431,10 +430,8 @@ internal class DefaultCryptoService @Inject constructor( * A sync response has been received. * * @param syncResponse the syncResponse - * @param cryptoStoreAggregator data aggregated during the sync response treatment to store */ - fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { - cryptoStore.storeData(cryptoStoreAggregator) + fun onSyncCompleted(syncResponse: SyncResponse) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { runCatching { if (syncResponse.deviceLists != null) { @@ -1001,26 +998,15 @@ internal class DefaultCryptoService @Inject constructor( } } - private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { + private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) { if (!event.isStateEvent()) return val eventContent = event.content.toModel() val historyVisibility = eventContent?.historyVisibility if (historyVisibility == null) { - if (cryptoStoreAggregator != null) { - cryptoStoreAggregator.setShouldShareHistoryData[roomId] = false - } else { - // Store immediately - cryptoStore.setShouldShareHistory(roomId, false) - } + cryptoStore.setShouldShareHistory(roomId, false) } else { - if (cryptoStoreAggregator != null) { - cryptoStoreAggregator.setShouldEncryptForInvitedMembersData[roomId] = historyVisibility != RoomHistoryVisibility.JOINED - cryptoStoreAggregator.setShouldShareHistoryData[roomId] = historyVisibility.shouldShareHistory() - } else { - // Store immediately - cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) - cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) - } + cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) + cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 364d77f7ac..7e9e156003 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -25,13 +25,11 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.extensions.measureMetric import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.UserDataToStore import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.sync.SyncTokenStore @@ -373,8 +371,6 @@ internal class DeviceListManager @Inject constructor( Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") } - val userDataToStore = UserDataToStore() - for (userId in filteredUsers) { // al devices = val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) } @@ -408,7 +404,7 @@ internal class DeviceListManager @Inject constructor( } // Update the store // Note that devices which aren't in the response will be removed from the stores - userDataToStore.userDevices[userId] = workingCopy + cryptoStore.storeUserDevices(userId, workingCopy) } val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also { @@ -420,15 +416,14 @@ internal class DeviceListManager @Inject constructor( val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") } - userDataToStore.userIdentities[userId] = UserIdentity( - masterKey = masterKey, - selfSigningKey = selfSigningKey, - userSigningKey = userSigningKey + cryptoStore.storeUserCrossSigningKeys( + userId, + masterKey, + selfSigningKey, + userSigningKey ) } - cryptoStore.storeData(userDataToStore) - // Update devices trust for these users // dispatchDeviceChange(downloadUsers) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 0305f73a7b..21e3342365 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -22,9 +22,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo -import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -39,7 +39,6 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper -import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.olm.OlmAccount import org.matrix.olm.OlmOutboundGroupSession @@ -231,12 +230,11 @@ internal interface IMXCryptoStore { */ fun storeUserDevices(userId: String, devices: Map?) - /** - * Store the cross signing keys for the user userId. - */ - fun storeUserIdentity( + fun storeUserCrossSigningKeys( userId: String, - userIdentity: UserIdentity + masterKey: CryptoCrossSigningKey?, + selfSigningKey: CryptoCrossSigningKey?, + userSigningKey: CryptoCrossSigningKey? ) /** @@ -292,13 +290,6 @@ internal interface IMXCryptoStore { fun shouldEncryptForInvitedMembers(roomId: String): Boolean - /** - * Sets a boolean flag that will determine whether or not this device should encrypt Events for - * invited members. - * - * @param roomId the room id - * @param shouldEncryptForInvitedMembers The boolean flag - */ fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) fun shouldShareHistory(roomId: String): Boolean @@ -589,14 +580,4 @@ internal interface IMXCryptoStore { fun areDeviceKeysUploaded(): Boolean fun tidyUpDataBase() fun getOutgoingRoomKeyRequests(inStates: Set): List - - /** - * Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator]. - */ - fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) - - /** - * Store a bunch of data related to the users. @See [UserDataToStore]. - */ - fun storeData(userDataToStore: UserDataToStore) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt deleted file mode 100644 index 914ce4704e..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2023 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.internal.crypto.store - -import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo - -internal data class UserDataToStore( - /** - * Map of userId -> (Map of deviceId -> [CryptoDeviceInfo]). - */ - val userDevices: MutableMap> = mutableMapOf(), - /** - * Map of userId -> [UserIdentity]. - */ - val userIdentities: MutableMap = mutableMapOf(), -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt deleted file mode 100644 index 687ec95ec3..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2023 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.internal.crypto.store.db - -data class CryptoStoreAggregator( - val setShouldShareHistoryData: MutableMap = mutableMapOf(), - val setShouldEncryptForInvitedMembersData: MutableMap = mutableMapOf(), -) { - fun isEmpty(): Boolean { - return setShouldShareHistoryData.isEmpty() && - setShouldEncryptForInvitedMembersData.isEmpty() - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt index 6412df205f..2d66ce1488 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt @@ -20,12 +20,10 @@ import android.util.Base64 import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObject -import timber.log.Timber import java.io.ByteArrayOutputStream import java.io.ObjectOutputStream import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream -import kotlin.system.measureTimeMillis /** * Get realm, invoke the action, close realm, and return the result of the action. @@ -57,12 +55,10 @@ internal fun doRealmQueryAndCopyList(realmConfiguration: Realm /** * Get realm instance, invoke the action in a transaction and close realm. */ -internal fun doRealmTransaction(tag: String, realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { - measureTimeMillis { - Realm.getInstance(realmConfiguration).use { realm -> - realm.executeTransaction { action.invoke(it) } - } - }.also { Timber.w("doRealmTransaction for $tag took $it millis") } +internal fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { + Realm.getInstance(realmConfiguration).use { realm -> + realm.executeTransaction { action.invoke(it) } + } } internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index b4368467a2..1b52b79746 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -33,9 +33,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo -import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -55,7 +55,6 @@ import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrappe import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.UserDataToStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity @@ -148,7 +147,7 @@ internal class RealmCryptoStore @Inject constructor( init { // Ensure CryptoMetadataEntity is inserted in DB - doRealmTransaction("init", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> var currentMetadata = realm.where().findFirst() var deleteAll = false @@ -190,7 +189,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteStore() { - doRealmTransaction("deleteStore", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.deleteAll() } } @@ -219,7 +218,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeDeviceId(deviceId: String) { - doRealmTransaction("storeDeviceId", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.where().findFirst()?.deviceId = deviceId } } @@ -231,7 +230,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveOlmAccount() { - doRealmTransaction("saveOlmAccount", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.where().findFirst()?.putOlmAccount(olmAccount) } } @@ -249,7 +248,7 @@ internal class RealmCryptoStore @Inject constructor( @Synchronized override fun getOrCreateOlmAccount(): OlmAccount { - doRealmTransaction("getOrCreateOlmAccount", realmConfiguration) { + doRealmTransaction(realmConfiguration) { val metaData = it.where().findFirst() val existing = metaData!!.getOlmAccount() if (existing == null) { @@ -289,139 +288,129 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeUserDevices(userId: String, devices: Map?) { - doRealmTransaction("storeUserDevices", realmConfiguration) { realm -> - storeUserDevices(realm, userId, devices) - } - } - - private fun storeUserDevices(realm: Realm, userId: String, devices: Map?) { - if (devices == null) { - Timber.d("Remove user $userId") - // Remove the user - UserEntity.delete(realm, userId) - } else { - val userEntity = UserEntity.getOrCreate(realm, userId) - // First delete the removed devices - val deviceIds = devices.keys - userEntity.devices.toTypedArray().iterator().let { - while (it.hasNext()) { - val deviceInfoEntity = it.next() - if (deviceInfoEntity.deviceId !in deviceIds) { - Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") - deviceInfoEntity.deleteOnCascade() - } - } - } - // Then update existing devices or add new one - devices.values.forEach { cryptoDeviceInfo -> - val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } - if (existingDeviceInfoEntity == null) { - // Add the device - Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") - val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) - newEntity.firstTimeSeenLocalTs = clock.epochMillis() - userEntity.devices.add(newEntity) - } else { - // Update the device - Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") - CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) - } - } - } - } - - override fun storeUserIdentity( - userId: String, - userIdentity: UserIdentity, - ) { - doRealmTransaction("storeUserIdentity", realmConfiguration) { realm -> - storeUserIdentity(realm, userId, userIdentity) - } - } - - private fun storeUserIdentity( - realm: Realm, - userId: String, - userIdentity: UserIdentity, - ) { - UserEntity.getOrCreate(realm, userId) - .let { userEntity -> - if (userIdentity.masterKey == null || userIdentity.selfSigningKey == null) { - // The user has disabled cross signing? - userEntity.crossSigningInfoEntity?.deleteOnCascade() - userEntity.crossSigningInfoEntity = null - } else { - var shouldResetMyDevicesLocalTrust = false - CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> - // What should we do if we detect a change of the keys? - val existingMaster = signingInfo.getMasterKey() - if (existingMaster != null && existingMaster.publicKeyBase64 == userIdentity.masterKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingMaster, userIdentity.masterKey) - } else { - Timber.d("## CrossSigning MSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(userIdentity.masterKey) - signingInfo.setMasterKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my msk has changed! clear my private key - // Could we have some race here? e.g I am the one that did change the keys - // could i get this update to early and clear the private keys? - // -> initializeCrossSigning is guarding for that by storing all at once - realm.where().findFirst()?.apply { - xSignMasterPrivateKey = null - } - } - } - - val existingSelfSigned = signingInfo.getSelfSignedKey() - if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == userIdentity.selfSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingSelfSigned, userIdentity.selfSigningKey) - } else { - Timber.d("## CrossSigning SSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(userIdentity.selfSigningKey) - signingInfo.setSelfSignedKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my ssk has changed! clear my private key - realm.where().findFirst()?.apply { - xSignSelfSignedPrivateKey = null - } - } - } - - // Only for me - if (userIdentity.userSigningKey != null) { - val existingUSK = signingInfo.getUserSigningKey() - if (existingUSK != null && existingUSK.publicKeyBase64 == userIdentity.userSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingUSK, userIdentity.userSigningKey) - } else { - Timber.d("## CrossSigning USK change for $userId") - val keyEntity = crossSigningKeysMapper.map(userIdentity.userSigningKey) - signingInfo.setUserSignedKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my usk has changed! clear my private key - realm.where().findFirst()?.apply { - xSignUserPrivateKey = null - } - } - } - } - - // When my cross signing keys are reset, we consider clearing all existing device trust - if (shouldResetMyDevicesLocalTrust) { - realm.where() - .equalTo(UserEntityFields.USER_ID, this.userId) - .findFirst() - ?.devices?.forEach { - it?.trustLevelEntity?.crossSignedVerified = false - it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId - } - } - userEntity.crossSigningInfoEntity = signingInfo + doRealmTransaction(realmConfiguration) { realm -> + if (devices == null) { + Timber.d("Remove user $userId") + // Remove the user + UserEntity.delete(realm, userId) + } else { + val userEntity = UserEntity.getOrCreate(realm, userId) + // First delete the removed devices + val deviceIds = devices.keys + userEntity.devices.toTypedArray().iterator().let { + while (it.hasNext()) { + val deviceInfoEntity = it.next() + if (deviceInfoEntity.deviceId !in deviceIds) { + Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") + deviceInfoEntity.deleteOnCascade() } } } + // Then update existing devices or add new one + devices.values.forEach { cryptoDeviceInfo -> + val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } + if (existingDeviceInfoEntity == null) { + // Add the device + Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") + val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) + newEntity.firstTimeSeenLocalTs = clock.epochMillis() + userEntity.devices.add(newEntity) + } else { + // Update the device + Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") + CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) + } + } + } + } + } + + override fun storeUserCrossSigningKeys( + userId: String, + masterKey: CryptoCrossSigningKey?, + selfSigningKey: CryptoCrossSigningKey?, + userSigningKey: CryptoCrossSigningKey? + ) { + doRealmTransaction(realmConfiguration) { realm -> + UserEntity.getOrCreate(realm, userId) + .let { userEntity -> + if (masterKey == null || selfSigningKey == null) { + // The user has disabled cross signing? + userEntity.crossSigningInfoEntity?.deleteOnCascade() + userEntity.crossSigningInfoEntity = null + } else { + var shouldResetMyDevicesLocalTrust = false + CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> + // What should we do if we detect a change of the keys? + val existingMaster = signingInfo.getMasterKey() + if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingMaster, masterKey) + } else { + Timber.d("## CrossSigning MSK change for $userId") + val keyEntity = crossSigningKeysMapper.map(masterKey) + signingInfo.setMasterKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my msk has changed! clear my private key + // Could we have some race here? e.g I am the one that did change the keys + // could i get this update to early and clear the private keys? + // -> initializeCrossSigning is guarding for that by storing all at once + realm.where().findFirst()?.apply { + xSignMasterPrivateKey = null + } + } + } + + val existingSelfSigned = signingInfo.getSelfSignedKey() + if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) + } else { + Timber.d("## CrossSigning SSK change for $userId") + val keyEntity = crossSigningKeysMapper.map(selfSigningKey) + signingInfo.setSelfSignedKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my ssk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignSelfSignedPrivateKey = null + } + } + } + + // Only for me + if (userSigningKey != null) { + val existingUSK = signingInfo.getUserSigningKey() + if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingUSK, userSigningKey) + } else { + Timber.d("## CrossSigning USK change for $userId") + val keyEntity = crossSigningKeysMapper.map(userSigningKey) + signingInfo.setUserSignedKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my usk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignUserPrivateKey = null + } + } + } + } + + // When my cross signing keys are reset, we consider clearing all existing device trust + if (shouldResetMyDevicesLocalTrust) { + realm.where() + .equalTo(UserEntityFields.USER_ID, this.userId) + .findFirst() + ?.devices?.forEach { + it?.trustLevelEntity?.crossSignedVerified = false + it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId + } + } + userEntity.crossSigningInfoEntity = signingInfo + } + } + } + } } override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { @@ -491,7 +480,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) { Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}") - doRealmTransaction("storePrivateKeysInfo", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk xSignUserPrivateKey = usk @@ -501,7 +490,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { - doRealmTransaction("saveBackupRecoveryKey", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where().findFirst()?.apply { keyBackupRecoveryKey = recoveryKey keyBackupRecoveryKeyVersion = version @@ -527,7 +516,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeMSKPrivateKey(msk: String?) { Timber.v("## CRYPTO | *** storeMSKPrivateKey ${msk != null} ") - doRealmTransaction("storeMSKPrivateKey", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk } @@ -536,7 +525,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeSSKPrivateKey(ssk: String?) { Timber.v("## CRYPTO | *** storeSSKPrivateKey ${ssk != null} ") - doRealmTransaction("storeSSKPrivateKey", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignSelfSignedPrivateKey = ssk } @@ -545,7 +534,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUSKPrivateKey(usk: String?) { Timber.v("## CRYPTO | *** storeUSKPrivateKey ${usk != null} ") - doRealmTransaction("storeUSKPrivateKey", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignUserPrivateKey = usk } @@ -678,7 +667,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeRoomAlgorithm(roomId: String, algorithm: String?) { - doRealmTransaction("storeRoomAlgorithm", realmConfiguration) { + doRealmTransaction(realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).let { entity -> entity.algorithm = algorithm // store anyway the new algorithm, but mark the room @@ -719,7 +708,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { - doRealmTransaction("setShouldEncryptForInvitedMembers", realmConfiguration) { + doRealmTransaction(realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers } } @@ -727,7 +716,7 @@ internal class RealmCryptoStore @Inject constructor( override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { Timber.tag(loggerTag.value) .v("setShouldShareHistory for room $roomId is $shouldShareHistory") - doRealmTransaction("setShouldShareHistory", realmConfiguration) { + doRealmTransaction(realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory } } @@ -744,7 +733,7 @@ internal class RealmCryptoStore @Inject constructor( if (sessionIdentifier != null) { val key = OlmSessionEntity.createPrimaryKey(sessionIdentifier, deviceKey) - doRealmTransaction("storeSession", realmConfiguration) { + doRealmTransaction(realmConfiguration) { val realmOlmSession = OlmSessionEntity().apply { primaryKey = key sessionId = sessionIdentifier @@ -801,7 +790,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction("storeInboundGroupSessions", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> sessions.forEach { wrapper -> val sessionIdentifier = try { @@ -925,7 +914,7 @@ internal class RealmCryptoStore @Inject constructor( override fun removeInboundGroupSession(sessionId: String, senderKey: String) { val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) - doRealmTransaction("removeInboundGroupSession", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findAll() @@ -944,7 +933,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setKeyBackupVersion(keyBackupVersion: String?) { - doRealmTransaction("setKeyBackupVersion", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.where().findFirst()?.backupVersion = keyBackupVersion } } @@ -956,7 +945,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) { - doRealmTransaction("setKeysBackupData", realmConfiguration) { + doRealmTransaction(realmConfiguration) { if (keysBackupData == null) { // Clear the table it.where() @@ -970,7 +959,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun resetBackupMarkers() { - doRealmTransaction("resetBackupMarkers", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.where() .findAll() .map { inboundGroupSession -> @@ -984,7 +973,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction("markBackupDoneForInboundGroupSessions", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> try { val sessionIdentifier = @@ -1043,13 +1032,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) { - doRealmTransaction("setGlobalBlacklistUnverifiedDevices", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.where().findFirst()?.globalBlacklistUnverifiedDevices = block } } override fun enableKeyGossiping(enable: Boolean) { - doRealmTransaction("enableKeyGossiping", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.where().findFirst()?.globalEnableKeyGossiping = enable } } @@ -1073,13 +1062,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun enableShareKeyOnInvite(enable: Boolean) { - doRealmTransaction("enableShareKeyOnInvite", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.where().findFirst()?.enableKeyForwardingOnInvite = enable } } override fun setDeviceKeysUploaded(uploaded: Boolean) { - doRealmTransaction("setDeviceKeysUploaded", realmConfiguration) { + doRealmTransaction(realmConfiguration) { it.where().findFirst()?.deviceKeysSentToServer = uploaded } } @@ -1126,7 +1115,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) { - doRealmTransaction("blockUnverifiedDevicesInRoom", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> CryptoRoomEntity.getById(realm, roomId) ?.blacklistUnverifiedDevices = block } @@ -1146,7 +1135,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map) { - doRealmTransaction("saveDeviceTrackingStatuses", realmConfiguration) { + doRealmTransaction(realmConfiguration) { deviceTrackingStatuses .map { entry -> UserEntity.getOrCreate(it, entry.key) @@ -1279,7 +1268,7 @@ internal class RealmCryptoStore @Inject constructor( ): OutgoingKeyRequest { // Insert the request and return the one passed in parameter lateinit var request: OutgoingKeyRequest - doRealmTransaction("getOrAddOutgoingRoomKeyRequest", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> val existing = realm.where() .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, requestBody.sessionId) @@ -1317,7 +1306,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState) { - doRealmTransaction("updateOutgoingRoomKeyRequestState", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.apply { @@ -1331,7 +1320,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int) { - doRealmTransaction("updateOutgoingRoomKeyRequiredIndex", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.apply { @@ -1348,7 +1337,7 @@ internal class RealmCryptoStore @Inject constructor( fromDevice: String?, event: Event ) { - doRealmTransaction("updateOutgoingRoomKeyReply", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId) .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId) @@ -1364,7 +1353,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteOutgoingRoomKeyRequest(requestId: String) { - doRealmTransaction("deleteOutgoingRoomKeyRequest", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.deleteOnCascade() @@ -1372,7 +1361,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState) { - doRealmTransaction("deleteOutgoingRoomKeyRequestInState", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, state.name) .findAll() @@ -1508,7 +1497,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) { - doRealmTransaction("setMyCrossSigningInfo", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { userId -> addOrUpdateCrossSigningInfo(realm, userId, info) } @@ -1516,7 +1505,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) { - doRealmTransaction("setUserKeysAsTrusted", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .findFirst() @@ -1536,7 +1525,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) { - doRealmTransaction("setDeviceTrust", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where(DeviceInfoEntity::class.java) .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) .findFirst()?.let { deviceInfoEntity -> @@ -1556,7 +1545,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun clearOtherUserTrust() { - doRealmTransaction("clearOtherUserTrust", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { info -> @@ -1571,7 +1560,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateUsersTrust(check: (String) -> Boolean) { - doRealmTransaction("updateUsersTrust", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { xInfoEntity -> @@ -1679,13 +1668,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) { - doRealmTransaction("setCrossSigningInfo", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> addOrUpdateCrossSigningInfo(realm, userId, info) } } override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) { - doRealmTransaction("markMyMasterKeyAsLocallyTrusted", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { myUserId -> CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity -> val level = xInfoEntity.trustLevelEntity @@ -1724,7 +1713,7 @@ internal class RealmCryptoStore @Inject constructor( val roomId = withHeldContent.roomId ?: return val sessionId = withHeldContent.sessionId ?: return if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return - doRealmTransaction("addWithHeldMegolmSession", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let { it.code = withHeldContent.code it.senderKey = withHeldContent.senderKey @@ -1756,7 +1745,7 @@ internal class RealmCryptoStore @Inject constructor( deviceIdentityKey: String, chainIndex: Int ) { - doRealmTransaction("markedSessionAsShared", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> SharedSessionEntity.create( realm = realm, roomId = roomId, @@ -1805,7 +1794,7 @@ internal class RealmCryptoStore @Inject constructor( */ override fun tidyUpDataBase() { val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000 - doRealmTransaction("tidyUpDataBase", realmConfiguration) { realm -> + doRealmTransaction(realmConfiguration) { realm -> // Clean the old ones? realm.where() @@ -1826,31 +1815,4 @@ internal class RealmCryptoStore @Inject constructor( // Can we do something for WithHeldSessionEntity? } } - - override fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) { - if (cryptoStoreAggregator.isEmpty()) { - return - } - doRealmTransaction("storeData - CryptoStoreAggregator", realmConfiguration) { realm -> - // setShouldShareHistory - cryptoStoreAggregator.setShouldShareHistoryData.forEach { - CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value - } - // setShouldEncryptForInvitedMembers - cryptoStoreAggregator.setShouldEncryptForInvitedMembersData.forEach { - CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value - } - } - } - - override fun storeData(userDataToStore: UserDataToStore) { - doRealmTransaction("storeData - UserDataToStore", realmConfiguration) { realm -> - userDataToStore.userDevices.forEach { - storeUserDevices(realm, it.key, it.value) - } - userDataToStore.userIdentities.forEach { - storeUserIdentity(realm, it.key, it.value) - } - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt index f89221b627..9bd197e42e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.di +import androidx.annotation.Nullable import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonQualifier import com.squareup.moshi.Moshi @@ -27,6 +28,7 @@ import java.lang.reflect.Type internal annotation class SerializeNulls { companion object { val JSON_ADAPTER_FACTORY: JsonAdapter.Factory = object : JsonAdapter.Factory { + @Nullable override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { val nextAnnotations = Types.nextAnnotations(annotations, SerializeNulls::class.java) ?: return null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 334a8c5076..4e0525536c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.network.interceptors +import androidx.annotation.NonNull import okhttp3.logging.HttpLoggingInterceptor import org.json.JSONArray import org.json.JSONException @@ -37,7 +38,7 @@ internal class FormattedJsonHttpLogger( * @param message */ @Synchronized - override fun log(message: String) { + override fun log(@NonNull message: String) { Timber.v(message) // Try to log formatted Json only if there is a chance that [message] contains Json. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt index 6c28b9fcce..8b54978279 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.network.parsing +import androidx.annotation.Nullable import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter @@ -31,12 +32,14 @@ internal interface CheckNumberType { companion object { val JSON_ADAPTER_FACTORY = object : JsonAdapter.Factory { + @Nullable override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { if (type !== Any::class.java) { return null } val delegate: JsonAdapter = moshi.nextAdapter(this, Any::class.java, emptySet()) return object : JsonAdapter() { + @Nullable @Throws(IOException::class) override fun fromJson(reader: JsonReader): Any? { return if (reader.peek() !== JsonReader.Token.NUMBER) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt index ce34b0430e..cfc26045a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt @@ -42,12 +42,14 @@ internal class StreamEventsManager @Inject constructor() { listeners.remove(listener) } - fun dispatchLiveEventReceived(event: Event, roomId: String) { + fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) { Timber.v("## dispatchLiveEventReceived ${event.eventId}") coroutineScope.launch { - listeners.forEach { - tryOrNull { - it.onLiveEvent(roomId, event) + if (!initialSync) { + listeners.forEach { + tryOrNull { + it.onLiveEvent(roomId, event) + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 653069b3c8..793c2573be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -176,7 +176,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } // Give info to crypto module - cryptoService.onStateEvent(roomId, event, null) + cryptoService.onStateEvent(roomId, event) } roomMemberContentsByUser.getOrPut(event.senderId) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index cb407bb1cb..05d50d9595 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.DefaultCryptoService -import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionListeners @@ -93,7 +92,7 @@ internal class SyncResponseHandler @Inject constructor( postTreatmentSyncResponse(syncResponse, isInitialSync) - markCryptoSyncCompleted(syncResponse, aggregator.cryptoStoreAggregator) + markCryptoSyncCompleted(syncResponse) handlePostSync() @@ -219,10 +218,10 @@ internal class SyncResponseHandler @Inject constructor( } } - private fun markCryptoSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { + private fun markCryptoSyncCompleted(syncResponse: SyncResponse) { relevantPlugins.measureSpan("task", "crypto_sync_handler_onSyncCompleted") { measureTimeMillis { - cryptoSyncHandler.onSyncCompleted(syncResponse, cryptoStoreAggregator) + cryptoSyncHandler.onSyncCompleted(syncResponse) }.also { Timber.v("cryptoSyncHandler.onSyncCompleted took $it ms") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index af05e08da3..2b7f936fa8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.internal.session.sync -import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator - internal class SyncResponsePostTreatmentAggregator { // List of RoomId val ephemeralFilesToDelete = mutableListOf() @@ -30,7 +28,4 @@ internal class SyncResponsePostTreatmentAggregator { // Set of users to call `crossSigningService.checkTrustAndAffectedRoomShields` once per sync val userIdsForCheckingTrustAndAffectedRoomShields = mutableSetOf() - - // For the crypto store - val cryptoStoreAggregator = CryptoStoreAggregator() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index 7224b0c29c..551db52dbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse import org.matrix.android.sdk.internal.crypto.DefaultCryptoService -import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.session.sync.ProgressReporter @@ -86,8 +85,8 @@ internal class CryptoSyncHandler @Inject constructor( } } - fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { - cryptoService.onSyncCompleted(syncResponse, cryptoStoreAggregator) + fun onSyncCompleted(syncResponse: SyncResponse) { + cryptoService.onSyncCompleted(syncResponse) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 5e4886ce1e..4001ae2ccf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -258,7 +258,7 @@ internal class RoomSyncHandler @Inject constructor( root = eventEntity } // Give info to crypto module - cryptoService.onStateEvent(roomId, event, aggregator.cryptoStoreAggregator) + cryptoService.onStateEvent(roomId, event) roomMemberEventHandler.handle(realm, roomId, event, isInitialSync, aggregator) } } @@ -376,15 +376,8 @@ internal class RoomSyncHandler @Inject constructor( roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) } roomTypingUsersHandler.handle(realm, roomId, null) roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE) - roomSummaryUpdater.update( - realm, - roomId, - membership, - roomSync.summary, - roomSync.unreadNotifications, - roomSync.unreadThreadNotifications, - aggregator = aggregator, - ) + roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, + roomSync.unreadNotifications, roomSync.unreadThreadNotifications, aggregator = aggregator) return roomEntity } @@ -430,9 +423,7 @@ internal class RoomSyncHandler @Inject constructor( val isInitialSync = insertType == EventInsertType.INITIAL_SYNC eventIds.add(event.eventId) - if (!isInitialSync) { - liveEventService.get().dispatchLiveEventReceived(event, roomId) - } + liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync) if (event.isEncrypted() && !isInitialSync) { try { @@ -495,7 +486,7 @@ internal class RoomSyncHandler @Inject constructor( } } // Give info to crypto module - cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync, aggregator.cryptoStoreAggregator) + cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync) // Try to remove local echo event.unsignedData?.transactionId?.also { txId -> diff --git a/tools/lint/lint.xml b/tools/lint/lint.xml index dbe30f2267..3d3b073749 100644 --- a/tools/lint/lint.xml +++ b/tools/lint/lint.xml @@ -77,7 +77,6 @@ - diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh index 553c02101c..f91e11584c 100755 --- a/tools/release/releaseScript.sh +++ b/tools/release/releaseScript.sh @@ -87,14 +87,6 @@ fi printf "OK\n" -printf "\n================================================================================\n" -printf "Ensuring main and develop branches are up to date...\n" - -git checkout main -git pull -git checkout develop -git pull - printf "\n================================================================================\n" # Guessing version to propose a default version versionMajorCandidate=`grep "ext.versionMajor" ./vector-app/build.gradle | cut -d " " -f3` @@ -111,6 +103,14 @@ versionMinor=`echo ${version} | cut -d "." -f2` versionPatch=`echo ${version} | cut -d "." -f3` nextPatchVersion=$((versionPatch + 2)) +printf "\n================================================================================\n" +printf "Ensuring main and develop branches are up to date...\n" + +git checkout main +git pull +git checkout develop +git pull + printf "\n================================================================================\n" printf "Starting the release ${version}\n" git flow release start ${version} @@ -190,9 +190,6 @@ yes | towncrier build --version "v${version}" printf "\n================================================================================\n" read -p "Check the file CHANGES.md consistency. It's possible to reorder items (most important changes first) or change their section if relevant. Also an opportunity to fix some typo, or rewrite things. Do not commit your change. Press enter when it's done." -# Get the changes to use it to create the GitHub release -changelogUrlEncoded=`git diff CHANGES.md | grep ^+ | tail -n +2 | cut -c2- | jq -sRr @uri | sed s/\(/%28/g | sed s/\)/%29/g` - printf "\n================================================================================\n" printf "Committing...\n" git commit -a -m "Changelog for version ${version}" @@ -266,7 +263,7 @@ else fi printf "\n================================================================================\n" -printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%%3Amain to build the 'main' branch.\n" +printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Amain to build the 'main' branch.\n" read -p "After GHA is finished, please enter the artifact URL (for 'vector-gplay-release-unsigned'): " artifactUrl printf "\n================================================================================\n" @@ -357,15 +354,10 @@ apkPath="${targetPath}/vector-gplay-arm64-v8a-release-signed.apk" adb -d install ${apkPath} read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done." +# TODO Get the block to copy from towncrier earlier (be may be edited by the release manager)? +read -p "Create the release on gitHub from the tag https://github.com/vector-im/element-android/tags, copy paste the block from the file CHANGES.md. Press enter when it's done." -printf "\n================================================================================\n" -githubCreateReleaseLink="https://github.com/vector-im/element-android/releases/new?tag=v${version}&title=Element%%20Android%%20v${version}&body=${changelogUrlEncoded}" -printf "Creating the release on gitHub.\n" -printf "Open this link: ${githubCreateReleaseLink}\n" -printf "Then\n" -printf " - click on the 'Generate releases notes' button\n" -printf " - Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}\n" -read -p ". Press enter when it's done. " +read -p "Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}. Press enter when it's done." printf "\n================================================================================\n" printf "Message for the Android internal room:\n\n" diff --git a/vector/build.gradle b/vector/build.gradle index 91d2a8c46a..83af7ecc04 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -308,7 +308,7 @@ dependencies { // Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868 // Error was lots of `Duplicate class org.checkerframework.common.reflection.qual.MethodVal found in modules jetified-checker-3.1 (org.checkerframework:checker:3.1.1) and jetified-checker-qual-3.12.0 (org.checkerframework:checker-qual:3.12.0) //noinspection GradleDependency Cannot use latest 3.15.0 since it required min API 26. - implementation "org.checkerframework:checker:3.29.0" + implementation "org.checkerframework:checker:3.27.0" androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testRunner diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 911bbfa4a3..d22ab51e7a 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -84,7 +84,6 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListViewModel import im.vector.app.features.roomprofile.members.RoomMemberListViewModel import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewModel import im.vector.app.features.roomprofile.permissions.RoomPermissionsViewModel -import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel @@ -698,9 +697,4 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(SetLinkViewModel::class) fun setLinkViewModelFactory(factory: SetLinkViewModel.Factory): MavericksAssistedViewModelFactory<*, *> - - @Binds - @IntoMap - @MavericksViewModelKey(RoomPollsViewModel::class) - fun roomPollsViewModelFactory(factory: RoomPollsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 1e29dfff5e..4e5116eda9 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -41,7 +41,6 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceManager import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView @@ -92,7 +91,6 @@ import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError @@ -125,20 +123,14 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected val viewModelProvider get() = ViewModelProvider(this, viewModelFactory) - fun VectorViewModel<*, *, T>.observeViewEvents( - observer: (T) -> Unit, - ) { - val tag = this@VectorBaseActivity::class.simpleName.toString() - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents - .stream(tag) - .collect { - hideWaitingView() - observer(it) - } - } - } + fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { + viewEvents + .stream() + .onEach { + hideWaitingView() + observer(it) + } + .launchIn(lifecycleScope) } var toolbar: ToolbarConfig? = null diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index a44fb1c9ac..ec6f3288f8 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -26,10 +26,8 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.CallSuper import androidx.annotation.FloatRange -import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -45,7 +43,6 @@ import im.vector.app.features.analytics.plan.MobileScreen import io.github.hyuwah.draggableviewlib.Utils import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -202,18 +199,12 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe * ViewEvents * ========================================================================================== */ - protected fun VectorViewModel<*, *, T>.observeViewEvents( - observer: (T) -> Unit, - ) { - val tag = this@VectorBaseBottomSheetDialogFragment::class.simpleName.toString() - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents - .stream(tag) - .collect { - observer(it) - } - } - } + protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { + viewEvents + .stream() + .onEach { + observer(it) + } + .launchIn(viewLifecycleOwner.lifecycleScope) } } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt index 34e233aa7a..5a817b989e 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt @@ -23,10 +23,8 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.CallSuper import androidx.fragment.app.DialogFragment -import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import dagger.hilt.android.EntryPointAccessors @@ -39,7 +37,6 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.themes.ThemeUtils import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -148,15 +145,11 @@ abstract class VectorBaseDialogFragment : DialogFragment(), Ma * ========================================================================================== */ protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - val tag = this@VectorBaseDialogFragment::class.simpleName.toString() - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents - .stream(tag) - .collect { - observer(it) - } - } - } + viewEvents + .stream() + .onEach { + observer(it) + } + .launchIn(viewLifecycleOwner.lifecycleScope) } } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index a82cef54e5..8fe2d33f6a 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -34,7 +34,6 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util.assertMainThread @@ -54,7 +53,6 @@ import im.vector.app.features.navigation.Navigator import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -274,20 +272,14 @@ abstract class VectorBaseFragment : Fragment(), MavericksView * ViewEvents * ========================================================================================== */ - protected fun VectorViewModel<*, *, T>.observeViewEvents( - observer: (T) -> Unit, - ) { - val tag = this@VectorBaseFragment::class.simpleName.toString() - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents - .stream(tag) - .collect { - dismissLoadingDialog() - observer(it) - } - } - } + protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { + viewEvents + .stream() + .onEach { + dismissLoadingDialog() + observer(it) + } + .launchIn(viewLifecycleOwner.lifecycleScope) } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index 3dd38c455f..c9d58f9545 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -18,16 +18,15 @@ package im.vector.app.core.platform import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModel -import im.vector.app.core.utils.EventQueue -import im.vector.app.core.utils.SharedEvents +import im.vector.app.core.utils.DataSource +import im.vector.app.core.utils.PublishDataSource abstract class VectorViewModel(initialState: S) : MavericksViewModel(initialState) { // Used to post transient events to the View - protected val _viewEvents = EventQueue(capacity = 64) - val viewEvents: SharedEvents - get() = _viewEvents + protected val _viewEvents = PublishDataSource() + val viewEvents: DataSource = _viewEvents abstract fun handle(action: VA) } diff --git a/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt b/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt index 3c057e0635..9ed3c02ba4 100644 --- a/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt @@ -18,6 +18,7 @@ package im.vector.app.core.resources import android.content.res.Resources import androidx.annotation.ArrayRes +import androidx.annotation.NonNull import javax.inject.Inject class StringArrayProvider @Inject constructor(private val resources: Resources) { @@ -30,6 +31,7 @@ class StringArrayProvider @Inject constructor(private val resources: Resources) * @return The string array associated with the resource, stripped of styled * text information. */ + @NonNull fun getStringArray(@ArrayRes resId: Int): Array { return resources.getStringArray(resId) } diff --git a/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt b/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt index e5f48f8be0..7e322daaae 100644 --- a/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt @@ -17,6 +17,7 @@ package im.vector.app.core.resources import android.content.res.Resources +import androidx.annotation.NonNull import androidx.annotation.PluralsRes import androidx.annotation.StringRes import javax.inject.Inject @@ -31,6 +32,7 @@ class StringProvider @Inject constructor(private val resources: Resources) { * @return The string data associated with the resource, stripped of styled * text information. */ + @NonNull fun getString(@StringRes resId: Int): String { return resources.getString(resId) } @@ -46,10 +48,12 @@ class StringProvider @Inject constructor(private val resources: Resources) { * @return The string data associated with the resource, formatted and * stripped of styled text information. */ + @NonNull fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String { return resources.getString(resId, *formatArgs) } + @NonNull fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String { return resources.getQuantityString(resId, quantity, *formatArgs) } diff --git a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt deleted file mode 100644 index e712769c48..0000000000 --- a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 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.core.utils - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.transform -import java.util.concurrent.CopyOnWriteArraySet - -interface SharedEvents { - fun stream(consumerId: String): Flow -} - -class EventQueue(capacity: Int) : SharedEvents { - - private val innerQueue = MutableSharedFlow>(replay = capacity) - - fun post(event: T) { - innerQueue.tryEmit(OneTimeEvent(event)) - } - - override fun stream(consumerId: String): Flow = innerQueue.filterNotHandledBy(consumerId) -} - -/** - * Event designed to be delivered only once to a concrete entity, - * but it can also be delivered to multiple different entities. - * - * Keeps track of who has already handled its content. - */ -private class OneTimeEvent(private val content: T) { - - private val handlers = CopyOnWriteArraySet() - - /** - * @param asker Used to identify, whether this "asker" has already handled this Event. - * @return Event content or null if it has been already handled by asker - */ - fun getIfNotHandled(asker: String): T? = if (handlers.add(asker)) content else null -} - -private fun Flow>.filterNotHandledBy(consumerId: String): Flow = transform { event -> - event.getIfNotHandled(consumerId)?.let { emit(it) } -} diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index cffb1577cf..8ce375122e 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -55,6 +55,8 @@ import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.ui.UiStateRepository import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -140,9 +142,9 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity startAppViewModel.onEach { renderState(it) } - startAppViewModel.observeViewEvents { - handleViewEvents(it) - } + startAppViewModel.viewEvents.stream() + .onEach(::handleViewEvents) + .launchIn(lifecycleScope) startAppViewModel.handle(StartAppAction.StartApp) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt index aea87beea9..d393636a8e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt @@ -59,7 +59,7 @@ class SharedSecureStorageActivity : views.toolbar.visibility = View.GONE - viewModel.observeViewEvents { onViewEvents(it) } + viewModel.observeViewEvents { observeViewEvents(it) } viewModel.onEach { renderState(it) } } @@ -85,7 +85,7 @@ class SharedSecureStorageActivity : showFragment(fragment) } - private fun onViewEvents(it: SharedSecureStorageViewEvent) { + private fun observeViewEvents(it: SharedSecureStorageViewEvent?) { when (it) { is SharedSecureStorageViewEvent.Dismiss -> { finish() diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 0d240b376b..089fdcebd4 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -29,9 +29,7 @@ import androidx.core.transition.addListener import androidx.core.view.ViewCompat import androidx.core.view.isInvisible import androidx.core.view.isVisible -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.transition.Transition import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint @@ -52,6 +50,8 @@ import im.vector.lib.attachmentviewer.AttachmentViewerActivity import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -239,15 +239,10 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt } private fun observeViewEvents() { - val tag = this::class.simpleName.toString() - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewModel - .viewEvents - .stream(tag) - .collect(::handleViewEvents) - } - } + viewModel.viewEvents + .stream() + .onEach(::handleViewEvents) + .launchIn(lifecycleScope) } private fun handleViewEvents(event: VectorAttachmentViewerViewEvents) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 3c37c92650..526d676dee 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -36,7 +36,6 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment -import im.vector.app.features.roomprofile.polls.RoomPollsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.lib.core.utils.compat.getParcelableCompat @@ -99,7 +98,6 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() - RoomProfileSharedAction.OpenRoomPolls -> openRoomPolls() RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() @@ -128,10 +126,6 @@ class RoomProfileActivity : finish() } - private fun openRoomPolls() { - addFragmentToBackstack(views.simpleFragmentContainer, RoomPollsFragment::class.java, roomProfileArgs) - } - private fun openRoomUploads() { addFragmentToBackstack(views.simpleFragmentContainer, RoomUploadsFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index 30bd6c7ed3..eb43a345f2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -18,7 +18,6 @@ package im.vector.app.features.roomprofile import com.airbnb.epoxy.TypedEpoxyController -import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.epoxy.expandableTextItem import im.vector.app.core.epoxy.profiles.buildProfileAction @@ -57,7 +56,6 @@ class RoomProfileController @Inject constructor( fun onMemberListClicked() fun onBannedMemberListClicked() fun onNotificationsClicked() - fun onPollHistoryClicked() fun onUploadsClicked() fun createShortcut() fun onSettingsClicked() @@ -265,15 +263,6 @@ class RoomProfileController @Inject constructor( action = { callback?.onBannedMemberListClicked() } ) } - if (BuildConfig.DEBUG) { - // WIP, will be in release when related screens will be finished - buildProfileAction( - id = "poll_history", - title = stringProvider.getString(R.string.room_profile_section_more_polls), - icon = R.drawable.ic_attachment_poll, - action = { callback?.onPollHistoryClicked() } - ) - } buildProfileAction( id = "uploads", title = stringProvider.getString(R.string.room_profile_section_more_uploads), diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 51885dbf39..f4394111ab 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -269,10 +269,6 @@ class RoomProfileFragment : roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomNotificationSettings) } - override fun onPollHistoryClicked() { - roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomPolls) - } - override fun onUploadsClicked() { roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomUploads) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index b243ceb206..7d62bb86a1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -25,7 +25,6 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomSettings : RoomProfileSharedAction() object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() - object OpenRoomPolls : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt deleted file mode 100644 index 6f2a757ed7..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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.roomprofile.polls - -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import javax.inject.Inject - -class GetPollsUseCase @Inject constructor() { - - fun execute(): Flow> { - // TODO unmock and add unit tests - return flowOf(getActivePolls() + getEndedPolls()) - .map { it.sortedByDescending { poll -> poll.creationTimestamp } } - } - - private fun getActivePolls(): List { - return listOf( - PollSummary.ActivePoll( - id = "id1", - // 2022/06/28 UTC+1 - creationTimestamp = 1656367200000, - title = "Which charity would you like to support?" - ), - PollSummary.ActivePoll( - id = "id2", - // 2022/06/26 UTC+1 - creationTimestamp = 1656194400000, - title = "Which sport should the pupils do this year?" - ), - PollSummary.ActivePoll( - id = "id3", - // 2022/06/24 UTC+1 - creationTimestamp = 1656021600000, - title = "What type of food should we have at the party?" - ), - PollSummary.ActivePoll( - id = "id4", - // 2022/06/22 UTC+1 - creationTimestamp = 1655848800000, - title = "What film should we show at the end of the year party?" - ), - ) - } - - private fun getEndedPolls(): List { - return listOf( - PollSummary.EndedPoll( - id = "id1-ended", - // 2022/06/28 UTC+1 - creationTimestamp = 1656367200000, - title = "Which charity would you like to support?", - totalVotes = 22, - winnerOptions = listOf( - PollOptionViewState.PollEnded( - optionId = "id1", - optionAnswer = "Cancer research", - voteCount = 13, - votePercentage = 13 / 22.0, - isWinner = true, - ) - ), - ), - PollSummary.EndedPoll( - id = "id2-ended", - // 2022/06/26 UTC+1 - creationTimestamp = 1656194400000, - title = "Where should we do the offsite?", - totalVotes = 92, - winnerOptions = listOf( - PollOptionViewState.PollEnded( - optionId = "id1", - optionAnswer = "Hawaii", - voteCount = 43, - votePercentage = 43 / 92.0, - isWinner = true, - ) - ), - ), - PollSummary.EndedPoll( - id = "id3-ended", - // 2022/06/24 UTC+1 - creationTimestamp = 1656021600000, - title = "What type of food should we have at the party?", - totalVotes = 22, - winnerOptions = listOf( - PollOptionViewState.PollEnded( - optionId = "id1", - optionAnswer = "Brazilian", - voteCount = 13, - votePercentage = 13 / 22.0, - isWinner = true, - ) - ), - ), - ) - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt deleted file mode 100644 index f24ac8b8a6..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.roomprofile.polls - -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState - -sealed interface PollSummary { - val id: String - val creationTimestamp: Long - val title: String - - data class ActivePoll( - override val id: String, - override val creationTimestamp: Long, - override val title: String, - ) : PollSummary - - data class EndedPoll( - override val id: String, - override val creationTimestamp: Long, - override val title: String, - val totalVotes: Int, - val winnerOptions: List, - ) : PollSummary -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt deleted file mode 100644 index c18142a306..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.roomprofile.polls - -import im.vector.app.core.platform.VectorViewModelAction - -sealed interface RoomPollsAction : VectorViewModelAction diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt deleted file mode 100644 index 9f7e704135..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.roomprofile.polls - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.airbnb.mvrx.args -import com.airbnb.mvrx.fragmentViewModel -import com.google.android.material.tabs.TabLayoutMediator -import dagger.hilt.android.AndroidEntryPoint -import im.vector.app.R -import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.databinding.FragmentRoomPollsBinding -import im.vector.app.features.roomprofile.RoomProfileArgs - -@AndroidEntryPoint -class RoomPollsFragment : VectorBaseFragment() { - - private val roomProfileArgs: RoomProfileArgs by args() - - private val viewModel: RoomPollsViewModel by fragmentViewModel() - - private var tabLayoutMediator: TabLayoutMediator? = null - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsBinding { - return FragmentRoomPollsBinding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupToolbar() - setupTabs() - } - - override fun onDestroyView() { - views.roomPollsViewPager.adapter = null - tabLayoutMediator?.detach() - tabLayoutMediator = null - super.onDestroyView() - } - - private fun setupToolbar() { - setupToolbar(views.roomPollsToolbar) - .allowBack() - } - - private fun setupTabs() { - views.roomPollsViewPager.adapter = RoomPollsPagerAdapter(this) - - tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> - when (position) { - RoomPollsType.ACTIVE.ordinal -> tab.text = getString(R.string.room_polls_active) - RoomPollsType.ENDED.ordinal -> tab.text = getString(R.string.room_polls_ended) - } - }.also { it.attach() } - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt deleted file mode 100644 index c60fc5de27..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.roomprofile.polls - -import androidx.fragment.app.Fragment -import androidx.viewpager2.adapter.FragmentStateAdapter -import im.vector.app.features.roomprofile.polls.active.RoomActivePollsFragment -import im.vector.app.features.roomprofile.polls.ended.RoomEndedPollsFragment - -class RoomPollsPagerAdapter( - private val fragment: Fragment -) : FragmentStateAdapter(fragment) { - - override fun getItemCount() = RoomPollsType.values().size - - override fun createFragment(position: Int): Fragment { - return when (position) { - RoomPollsType.ACTIVE.ordinal -> instantiateFragment(RoomActivePollsFragment::class.java.name) - RoomPollsType.ENDED.ordinal -> instantiateFragment(RoomEndedPollsFragment::class.java.name) - else -> throw IllegalArgumentException("position should be between 0 and ${itemCount - 1}, while it was $position") - } - } - - private fun instantiateFragment(fragmentName: String): Fragment { - return fragment.childFragmentManager.fragmentFactory.instantiate(fragment.requireContext().classLoader, fragmentName) - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt deleted file mode 100644 index 134ef9a195..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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.roomprofile.polls - -enum class RoomPollsType { - ACTIVE, - ENDED, -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt deleted file mode 100644 index 231123563a..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.roomprofile.polls - -import im.vector.app.core.platform.VectorViewEvents - -sealed class RoomPollsViewEvent : VectorViewEvents diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt deleted file mode 100644 index 95cb4717ca..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.roomprofile.polls - -import com.airbnb.mvrx.MavericksViewModelFactory -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import im.vector.app.core.di.MavericksAssistedViewModelFactory -import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach - -class RoomPollsViewModel @AssistedInject constructor( - @Assisted initialState: RoomPollsViewState, - private val getPollsUseCase: GetPollsUseCase, -) : VectorViewModel(initialState) { - - @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: RoomPollsViewState): RoomPollsViewModel - } - - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - - init { - observePolls() - } - - private fun observePolls() { - getPollsUseCase.execute() - .onEach { setState { copy(polls = it) } } - .launchIn(viewModelScope) - } - - override fun handle(action: RoomPollsAction) { - // do nothing for now - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt deleted file mode 100644 index 74794c99b1..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.roomprofile.polls - -import com.airbnb.mvrx.MavericksState -import im.vector.app.features.roomprofile.RoomProfileArgs - -data class RoomPollsViewState( - val roomId: String, - val polls: List = emptyList(), -) : MavericksState { - - constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt deleted file mode 100644 index 1c6a03c480..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.roomprofile.polls.active - -import dagger.hilt.android.AndroidEntryPoint -import im.vector.app.R -import im.vector.app.features.roomprofile.polls.RoomPollsType -import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment - -@AndroidEntryPoint -class RoomActivePollsFragment : RoomPollsListFragment() { - - override fun getEmptyListTitle(): String { - return getString(R.string.room_polls_active_no_item) - } - - override fun getRoomPollsType(): RoomPollsType { - return RoomPollsType.ACTIVE - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt deleted file mode 100644 index 8dd0cadadf..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.roomprofile.polls.ended - -import dagger.hilt.android.AndroidEntryPoint -import im.vector.app.R -import im.vector.app.features.roomprofile.polls.RoomPollsType -import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment - -@AndroidEntryPoint -class RoomEndedPollsFragment : RoomPollsListFragment() { - - override fun getEmptyListTitle(): String { - return getString(R.string.room_polls_ended_no_item) - } - - override fun getRoomPollsType(): RoomPollsType { - return RoomPollsType.ENDED - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt deleted file mode 100644 index da00fedddb..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.roomprofile.polls.list - -import android.widget.LinearLayout -import android.widget.TextView -import androidx.core.view.isVisible -import com.airbnb.epoxy.EpoxyAttribute -import com.airbnb.epoxy.EpoxyModelClass -import im.vector.app.R -import im.vector.app.core.epoxy.ClickListener -import im.vector.app.core.epoxy.VectorEpoxyHolder -import im.vector.app.core.epoxy.VectorEpoxyModel -import im.vector.app.core.epoxy.onClick -import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.features.home.room.detail.timeline.item.PollOptionView -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState - -@EpoxyModelClass -abstract class RoomPollItem : VectorEpoxyModel(R.layout.item_poll) { - - @EpoxyAttribute - lateinit var formattedDate: String - - @EpoxyAttribute - lateinit var title: String - - @EpoxyAttribute - var winnerOptions: List = emptyList() - - @EpoxyAttribute - var totalVotesStatus: String? = null - - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) - var clickListener: ClickListener? = null - - override fun bind(holder: Holder) { - super.bind(holder) - holder.view.onClick(clickListener) - holder.date.text = formattedDate - holder.title.text = title - holder.winnerOptions.removeAllViews() - holder.winnerOptions.isVisible = winnerOptions.isNotEmpty() - for (winnerOption in winnerOptions) { - val optionView = PollOptionView(holder.view.context) - holder.winnerOptions.addView(optionView) - optionView.render(winnerOption) - } - holder.totalVotes.setTextOrHide(totalVotesStatus) - } - - class Holder : VectorEpoxyHolder() { - val date by bind(R.id.pollDate) - val title by bind(R.id.pollTitle) - val winnerOptions by bind(R.id.pollWinnerOptionsContainer) - val totalVotes by bind(R.id.pollTotalVotes) - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt deleted file mode 100644 index f0e3b6b9a4..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.roomprofile.polls.list - -import com.airbnb.epoxy.TypedEpoxyController -import im.vector.app.R -import im.vector.app.core.date.DateFormatKind -import im.vector.app.core.date.VectorDateFormatter -import im.vector.app.core.resources.StringProvider -import im.vector.app.features.roomprofile.polls.PollSummary -import javax.inject.Inject - -class RoomPollsController @Inject constructor( - val dateFormatter: VectorDateFormatter, - val stringProvider: StringProvider, -) : TypedEpoxyController>() { - - interface Listener { - fun onPollClicked(pollId: String) - } - - var listener: Listener? = null - - override fun buildModels(data: List?) { - if (data.isNullOrEmpty()) { - return - } - - for (poll in data) { - when (poll) { - is PollSummary.ActivePoll -> buildActivePollItem(poll) - is PollSummary.EndedPoll -> buildEndedPollItem(poll) - } - } - } - - private fun buildActivePollItem(poll: PollSummary.ActivePoll) { - val host = this - roomPollItem { - id(poll.id) - formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) - title(poll.title) - clickListener { - host.listener?.onPollClicked(poll.id) - } - } - } - - private fun buildEndedPollItem(poll: PollSummary.EndedPoll) { - val host = this - roomPollItem { - id(poll.id) - formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) - title(poll.title) - winnerOptions(poll.winnerOptions) - totalVotesStatus(host.stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, poll.totalVotes, poll.totalVotes)) - clickListener { - host.listener?.onPollClicked(poll.id) - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt deleted file mode 100644 index 0d97bd8dcb..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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.roomprofile.polls.list - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import com.airbnb.mvrx.parentFragmentViewModel -import com.airbnb.mvrx.withState -import im.vector.app.core.extensions.cleanup -import im.vector.app.core.extensions.configureWith -import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.databinding.FragmentRoomPollsListBinding -import im.vector.app.features.roomprofile.polls.PollSummary -import im.vector.app.features.roomprofile.polls.RoomPollsType -import im.vector.app.features.roomprofile.polls.RoomPollsViewModel -import timber.log.Timber -import javax.inject.Inject - -abstract class RoomPollsListFragment : - VectorBaseFragment(), - RoomPollsController.Listener { - - @Inject - lateinit var roomPollsController: RoomPollsController - - private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { - return FragmentRoomPollsListBinding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setupList() - } - - abstract fun getEmptyListTitle(): String - - abstract fun getRoomPollsType(): RoomPollsType - - private fun setupList() { - roomPollsController.listener = this - views.roomPollsList.configureWith(roomPollsController) - views.roomPollsEmptyTitle.text = getEmptyListTitle() - } - - override fun onDestroyView() { - cleanUpList() - super.onDestroyView() - } - - private fun cleanUpList() { - views.roomPollsList.cleanup() - roomPollsController.listener = null - } - - override fun invalidate() = withState(viewModel) { viewState -> - when (getRoomPollsType()) { - RoomPollsType.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) - RoomPollsType.ENDED -> renderList(viewState.polls.filterIsInstance(PollSummary.EndedPoll::class.java)) - } - } - - private fun renderList(polls: List) { - roomPollsController.setData(polls) - views.roomPollsEmptyTitle.isVisible = polls.isEmpty() - } - - override fun onPollClicked(pollId: String) { - // TODO navigate to details - Timber.d("poll with id $pollId clicked") - } -} diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt index 514f2529e9..b6fa997f41 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt @@ -25,7 +25,6 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference -import im.vector.app.core.utils.copyToClipboard import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.NightlyProxy import im.vector.app.features.rageshake.RageShake @@ -65,14 +64,6 @@ class VectorSettingsAdvancedSettingsFragment : override fun bindPref() { setupRageShakeSection() setupNightlySection() - setupDevToolsSection() - } - - private fun setupDevToolsSection() { - findPreference("SETTINGS_ACCESS_TOKEN")?.setOnPreferenceClickListener { - copyToClipboard(requireActivity(), session.sessionParams.credentials.accessToken) - true - } } private fun setupRageShakeSection() { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt index 724807a81e..38ba949a49 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt @@ -20,9 +20,7 @@ import android.content.Context import android.os.Bundle import android.view.View import androidx.annotation.CallSuper -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceFragmentCompat import com.airbnb.mvrx.MavericksView import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -37,7 +35,6 @@ import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.MobileScreen import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -69,19 +66,13 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick * ViewEvents * ========================================================================================== */ - protected fun VectorViewModel<*, *, T>.observeViewEvents( - observer: (T) -> Unit, - ) { - val tag = this@VectorSettingsBaseFragment::class.simpleName.toString() - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents - .stream(tag) - .collect { - observer(it) - } - } - } + protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { + viewEvents + .stream() + .onEach { + observer(it) + } + .launchIn(viewLifecycleOwner.lifecycleScope) } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 9cb894bb58..d56f4ad715 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -419,9 +419,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // Next media player is already attached to this player and will start playing automatically if (nextMediaPlayer != null) return - val currentSequence = playlist.currentSequence ?: 0 - val lastChunkSequence = mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence ?: 0 - val hasEnded = !isLiveListening && currentSequence >= lastChunkSequence + val hasEnded = !isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence if (hasEnded) { // We'll not receive new chunks anymore so we can stop the live listening stop() diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml deleted file mode 100644 index 396d6fd8c5..0000000000 --- a/vector/src/main/res/layout/fragment_room_polls.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml deleted file mode 100644 index 8eb27e5e00..0000000000 --- a/vector/src/main/res/layout/fragment_room_polls_list.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml deleted file mode 100644 index 17f3b5abf5..0000000000 --- a/vector/src/main/res/layout/item_poll.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - diff --git a/vector/src/main/res/xml/vector_settings_advanced_settings.xml b/vector/src/main/res/xml/vector_settings_advanced_settings.xml index 6399d54cbb..9260b33162 100644 --- a/vector/src/main/res/xml/vector_settings_advanced_settings.xml +++ b/vector/src/main/res/xml/vector_settings_advanced_settings.xml @@ -93,12 +93,6 @@ android:title="@string/settings_key_requests" app:fragment="im.vector.app.features.settings.devtools.KeyRequestsFragment" /> - - () - private val initialState = RoomPollsViewState(ROOM_ID) - - private fun createViewModel(): RoomPollsViewModel { - return RoomPollsViewModel( - initialState = initialState, - getPollsUseCase = fakeGetPollsUseCase, - ) - } - - @Test - fun `given viewModel when created then polls list is observed and viewState is updated`() { - // Given - val polls = listOf(givenAPollSummary()) - every { fakeGetPollsUseCase.execute() } returns flowOf(polls) - val expectedViewState = initialState.copy(polls = polls) - - // When - val viewModel = createViewModel() - val viewModelTest = viewModel.test() - - // Then - viewModelTest - .assertLatestState(expectedViewState) - .finish() - verify { - fakeGetPollsUseCase.execute() - } - } - - private fun givenAPollSummary(): PollSummary { - return mockk() - } -} diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 0b1a22f75c..2fbab3b71b 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -28,7 +28,7 @@ fun String.trimIndentOneLine() = trimIndent().replace("\n", "") fun VectorViewModel.test(): ViewModelTest { val testResultCollectingScope = CoroutineScope(Dispatchers.Unconfined) val state = stateFlow.test(testResultCollectingScope) - val viewEvents = viewEvents.stream("test").test(testResultCollectingScope) + val viewEvents = viewEvents.stream().test(testResultCollectingScope) return ViewModelTest(state, viewEvents) } From e375fa0e6736afb31bb9055d5861dedc8784ebba Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 6 Jan 2023 18:14:14 +0000 Subject: [PATCH 015/317] Detekt --- .../java/org/matrix/android/sdk/api/auth/data/WellKnown.kt | 2 +- .../java/im/vector/app/features/login/LoginViewModel.kt | 5 ++++- .../im/vector/app/features/login/SocialLoginButtonsView.kt | 6 ++++-- .../features/onboarding/StartAuthenticationFlowUseCase.kt | 5 ++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt index 9243fa7c54..95488bd682 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt @@ -57,7 +57,7 @@ data class WellKnown( val integrations: JsonDict? = null, /** - * For delegation of auth via OIDC as per [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965) + * For delegation of auth via OIDC as per [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965). */ @Json(name = "org.matrix.msc2965.authentication") val unstableDelegatedAuthConfig: DelegatedAuthConfig? = null, 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 298db3ad48..27a02d87a8 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 @@ -818,7 +818,10 @@ 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.toSsoState(), data.hasOidcCompatibilityFlow) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword( + data.ssoIdentityProviders.toSsoState(), + data.hasOidcCompatibilityFlow + ) data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState(), data.hasOidcCompatibilityFlow) 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 383fcf43e7..2af3b8b0ba 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 @@ -78,7 +78,8 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: transformationMethod = null textAlignment = View.TEXT_ALIGNMENT_CENTER }.let { - it.text = if (hasOidcCompatibilityFlow) context.getString(R.string.login_continue) else getButtonTitle(context.getString(R.string.login_social_sso)) + it.text = if (hasOidcCompatibilityFlow) context.getString(R.string.login_continue) + else getButtonTitle(context.getString(R.string.login_social_sso)) it.textAlignment = View.TEXT_ALIGNMENT_CENTER it.setOnClickListener { listener?.onProviderSelected(null) @@ -175,6 +176,7 @@ fun SocialLoginButtonsView.render(loginMode: LoginMode, mode: SocialLoginButtons SsoState.Fallback -> null is SsoState.IdentityProviders -> state.providers.sorted() } - this.hasOidcCompatibilityFlow = (loginMode is LoginMode.Sso && loginMode.hasOidcCompatibilityFlow) || (loginMode is LoginMode.SsoAndPassword && loginMode.hasOidcCompatibilityFlow) + this.hasOidcCompatibilityFlow = (loginMode is LoginMode.Sso && loginMode.hasOidcCompatibilityFlow) + || (loginMode is LoginMode.SsoAndPassword && loginMode.hasOidcCompatibilityFlow) this.listener = SocialLoginButtonsView.InteractionListener { listener(it) } } 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 28beca8269..14a3a9bfd0 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 @@ -53,7 +53,10 @@ class StartAuthenticationFlowUseCase @Inject constructor( ) private fun LoginFlowResult.findPreferredLoginMode() = when { - supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders.toSsoState(), hasOidcCompatibilityFlow) + supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword( + ssoIdentityProviders.toSsoState(), + hasOidcCompatibilityFlow + ) supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders.toSsoState(), hasOidcCompatibilityFlow) supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password else -> LoginMode.Unsupported From e0076c2475270aa38a4af13f62ae53ab8c92d34a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 6 Jan 2023 18:47:20 +0000 Subject: [PATCH 016/317] Fix test compilation --- .../onboarding/StartAuthenticationFlowUseCaseTest.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 cc395afd18..632272caa2 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 @@ -70,7 +70,7 @@ class StartAuthenticationFlowUseCaseTest { result shouldBeEqualTo expectedResult( supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, - preferredLoginMode = LoginMode.SsoAndPassword(SsoState.Fallback), + preferredLoginMode = LoginMode.SsoAndPassword(SsoState.Fallback, false), ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } @@ -84,7 +84,7 @@ class StartAuthenticationFlowUseCaseTest { result shouldBeEqualTo expectedResult( supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, - preferredLoginMode = LoginMode.SsoAndPassword(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)), + preferredLoginMode = LoginMode.SsoAndPassword(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS), false), ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } @@ -133,16 +133,17 @@ class StartAuthenticationFlowUseCaseTest { private fun aLoginResult( supportedLoginTypes: List, - ssoProviders: List = FALLBACK_SSO_IDENTITY_PROVIDERS + ssoProviders: List = FALLBACK_SSO_IDENTITY_PROVIDERS, + hasOidcCompatibilityFlow: Boolean = false ) = LoginFlowResult( supportedLoginTypes = supportedLoginTypes, ssoIdentityProviders = ssoProviders, isLoginAndRegistrationSupported = true, homeServerUrl = A_DECLARED_HOMESERVER_URL, isOutdatedHomeserver = false, - hasOidcCompatibilityFlow = false, + hasOidcCompatibilityFlow = hasOidcCompatibilityFlow, isLogoutDevicesSupported = false, - isLoginWithQrSupported = false + isLoginWithQrSupported = false, ) private fun expectedResult( From f3772cb8332abbbefbd53bd92b1ef13455df7388 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 9 Jan 2023 17:44:14 +0000 Subject: [PATCH 017/317] Lint --- .../im/vector/app/features/login/SocialLoginButtonsView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2af3b8b0ba..4ac98d6f2d 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 @@ -176,7 +176,7 @@ fun SocialLoginButtonsView.render(loginMode: LoginMode, mode: SocialLoginButtons SsoState.Fallback -> null is SsoState.IdentityProviders -> state.providers.sorted() } - this.hasOidcCompatibilityFlow = (loginMode is LoginMode.Sso && loginMode.hasOidcCompatibilityFlow) - || (loginMode is LoginMode.SsoAndPassword && loginMode.hasOidcCompatibilityFlow) + this.hasOidcCompatibilityFlow = (loginMode is LoginMode.Sso && loginMode.hasOidcCompatibilityFlow) || + (loginMode is LoginMode.SsoAndPassword && loginMode.hasOidcCompatibilityFlow) this.listener = SocialLoginButtonsView.InteractionListener { listener(it) } } From 624e2ffb195a27ce5f1b59a2106ef1477baba02e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 10 Jan 2023 17:09:08 +0000 Subject: [PATCH 018/317] Fixes from initial review --- .../matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt | 2 +- .../sdk/internal/database/migration/MigrateSessionTo048.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt index 4562696dda..971407388c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt @@ -51,5 +51,5 @@ internal data class LoginFlow( * See [MSC3824](https://github.com/matrix-org/matrix-spec-proposals/pull/3824) */ @Json(name = "org.matrix.msc3824.delegated_oidc_compatibility") - val delegatedOidcCompatibilty: Boolean? + val delegatedOidcCompatibilty: Boolean? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt index 3304f5adad..ae847ced94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEnti import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities import org.matrix.android.sdk.internal.util.database.RealmMigrator -internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 40) { +internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 48) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("HomeServerCapabilitiesEntity") From 51f227a13be75a3e54d44a89514cbb1c971afb6a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 11 Jan 2023 11:54:37 +0000 Subject: [PATCH 019/317] Test case for OIDC compatibility --- .../StartAuthenticationFlowUseCaseTest.kt | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) 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 632272caa2..93bfc045de 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 @@ -131,6 +131,21 @@ class StartAuthenticationFlowUseCaseTest { verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } + @Test + fun `given identity providers and login supports SSO with OIDC compatibility then prefers Sso for compatibility`() = runTest { + val loginResult = aLoginResult(supportedLoginTypes = SSO_LOGIN_TYPE, ssoProviders = SSO_IDENTITY_PROVIDERS, hasOidcCompatibilityFlow = true) + 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), hasOidcCompatibilityFlow = true), + hasOidcCompatibilityFlow = true + ) + verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) + } + private fun aLoginResult( supportedLoginTypes: List, ssoProviders: List = FALLBACK_SSO_IDENTITY_PROVIDERS, @@ -150,7 +165,8 @@ class StartAuthenticationFlowUseCaseTest { isHomeserverOutdated: Boolean = false, preferredLoginMode: LoginMode = LoginMode.Unsupported, supportedLoginTypes: List = emptyList(), - homeserverSourceUrl: String = A_HOMESERVER_CONFIG.homeServerUri.toString() + homeserverSourceUrl: String = A_HOMESERVER_CONFIG.homeServerUri.toString(), + hasOidcCompatibilityFlow: Boolean = false ) = StartAuthenticationResult( isHomeserverOutdated, SelectedHomeserverState( @@ -158,7 +174,7 @@ class StartAuthenticationFlowUseCaseTest { upstreamUrl = A_DECLARED_HOMESERVER_URL, preferredLoginMode = preferredLoginMode, supportedLoginTypes = supportedLoginTypes, - hasOidcCompatibilityFlow = false + hasOidcCompatibilityFlow = hasOidcCompatibilityFlow ) ) From 51949909463573d9e539f9d315c82cb4948171bd Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 18 Jan 2023 17:08:36 +0000 Subject: [PATCH 020/317] Merge branch 'develop' into hughns/msc3824-oidc-aware --- .../database/migration/MigrateSessionTo049.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt new file mode 100644 index 0000000000..827bd7ae15 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo049(realm: DynamicRealm) : RealmMigrator(realm, 49) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.EXTERNAL_ACCOUNT_MANAGEMENT_URL, String::class.java) + ?.forceRefreshOfHomeServerCapabilities() + } +} From 642ed613094cb82be2b5bbbc1ac65633d98473d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jan 2023 23:02:43 +0000 Subject: [PATCH 021/317] Bump paparazzi from 1.1.0 to 1.2.0 Bumps `paparazzi` from 1.1.0 to 1.2.0. Updates `paparazzi` from 1.1.0 to 1.2.0 - [Release notes](https://github.com/cashapp/paparazzi/releases) - [Changelog](https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md) - [Commits](https://github.com/cashapp/paparazzi/compare/1.1.0...1.2.0) Updates `paparazzi-gradle-plugin` from 1.1.0 to 1.2.0 - [Release notes](https://github.com/cashapp/paparazzi/releases) - [Changelog](https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md) - [Commits](https://github.com/cashapp/paparazzi/compare/1.1.0...1.2.0) --- updated-dependencies: - dependency-name: app.cash.paparazzi:paparazzi dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: app.cash.paparazzi:paparazzi-gradle-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 4977543822..5ab063bf98 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -35,7 +35,7 @@ def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest unt def espresso = "3.5.1" def androidxTest = "1.5.0" def androidxOrchestrator = "1.4.2" -def paparazzi = "1.1.0" +def paparazzi = "1.2.0" ext.libs = [ gradle : [ From 8cf29f65cd4e9347f5c3faf5ace7f7fbc9642515 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 23 Jan 2023 17:13:59 +0000 Subject: [PATCH 022/317] Design update --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 703cf011f1..49ec137b0a 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1064,7 +1064,7 @@ Manage your discovery settings. Account - Manage your account at %1$s. + Your account details are managed separately at %1$s. Analytics From bc3c253067165f98e2608de462ec7dea26092852 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Jan 2023 14:44:52 +0100 Subject: [PATCH 023/317] Fix bad import. --- .../timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt index c38afe20ec..2ae35eb000 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.render -import android.annotation.StringRes +import androidx.annotation.StringRes import im.vector.app.R import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeStringProvider From 6c5bc48c850c587b0d2b1d17823ad3e6174857c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 23:02:47 +0000 Subject: [PATCH 024/317] Bump material from 1.7.0 to 1.8.0 Bumps [material](https://github.com/material-components/material-components-android) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/material-components/material-components-android/releases) - [Commits](https://github.com/material-components/material-components-android/commits) --- updated-dependencies: - dependency-name: com.google.android.material:material dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index bc6ad1f931..2903a9a1a7 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -82,7 +82,7 @@ ext.libs = [ 'transition' : "androidx.transition:transition:1.2.0", ], google : [ - 'material' : "com.google.android.material:material:1.7.0", + 'material' : "com.google.android.material:material:1.8.0", 'firebaseBom' : "com.google.firebase:firebase-bom:$firebaseBom", 'messaging' : "com.google.firebase:firebase-messaging", 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", From 488c5703a21a48a8aadd94b8ecf164d4fae6afb5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Jan 2023 10:43:16 +0100 Subject: [PATCH 025/317] Record again the screenshot after bumping paparazzi from 1.1.0 to 1.2.0 --- ..._PaparazziExampleScreenshotTest_example paparazzi test.png | 4 ++-- ...r.app.screenshot_RoomItemScreenshotTest_item room test.png | 4 ++-- ...emScreenshotTest_item room two line and highlight test.png | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/test/snapshots/images/im.vector.app.screenshot_PaparazziExampleScreenshotTest_example paparazzi test.png b/vector/src/test/snapshots/images/im.vector.app.screenshot_PaparazziExampleScreenshotTest_example paparazzi test.png index bba610b25f..68a0daa6be 100644 --- a/vector/src/test/snapshots/images/im.vector.app.screenshot_PaparazziExampleScreenshotTest_example paparazzi test.png +++ b/vector/src/test/snapshots/images/im.vector.app.screenshot_PaparazziExampleScreenshotTest_example paparazzi test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:113cf006e9a881f19b79462297cf276aea2b82268182f9ecc297d4b31640b507 -size 11174 +oid sha256:625451c18bb83d6c07f919e0203b41be0dfea571fc19b659752cf43a9b890ff5 +size 11394 diff --git a/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room test.png b/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room test.png index 1e87449b3c..a22363b0bf 100644 --- a/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room test.png +++ b/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d33e82c6647bab9dcb3745d8c5a5448d60049279c365b9f64816eb9c958360d2 -size 15015 +oid sha256:466c86edae63935a00d28f29054a4d4c4d8cfb34f1f5e7b3ff25c66bc88090cd +size 15046 diff --git a/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room two line and highlight test.png b/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room two line and highlight test.png index 83fcb8d000..963b12bd6e 100644 --- a/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room two line and highlight test.png +++ b/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room two line and highlight test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91a106e2a3f7310ac05425a2413ccec0aaa07720609d77a2ecd9a9d0d602b296 -size 17232 +oid sha256:5deafa919d2b0753a2f2fd622b7a4457bba06aba2918a406933c2f6f213d8916 +size 17265 From a78f057381a860efd05e1909825adea64546d51e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 25 Jan 2023 14:54:12 +0000 Subject: [PATCH 026/317] Merge branch 'develop' into hughns/msc3824-oidc-aware --- .../database/migration/MigrateSessionTo050.kt | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt index c3777e776e..e33b424335 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt @@ -1,9 +1,5 @@ /* -<<<<<<< HEAD - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. -======= * Copyright (c) 2023 The Matrix.org Foundation C.I.C. ->>>>>>> develop * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,18 +21,11 @@ import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEnti import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities import org.matrix.android.sdk.internal.util.database.RealmMigrator -internal class MigrateSessionTo049(realm: DynamicRealm) : RealmMigrator(realm, 49) { +internal class MigrateSessionTo050(realm: DynamicRealm) : RealmMigrator(realm, 50) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("HomeServerCapabilitiesEntity") -<<<<<<< HEAD ?.addField(HomeServerCapabilitiesEntityFields.EXTERNAL_ACCOUNT_MANAGEMENT_URL, String::class.java) -======= - ?.addField(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, Boolean::class.java) - ?.transform { obj -> - obj.set(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, false) - } ->>>>>>> develop ?.forceRefreshOfHomeServerCapabilities() } } From b1cacb37f985a37abcf0556e9737aa6c530725e0 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 27 Jan 2023 12:19:40 +0000 Subject: [PATCH 027/317] Actually configure migration 50 to be used --- .../android/sdk/internal/database/RealmSessionStoreMigration.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index fe55beb997..1176e1cd97 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -66,6 +66,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo050 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -133,5 +134,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 47) MigrateSessionTo047(realm).perform() if (oldVersion < 48) MigrateSessionTo048(realm).perform() if (oldVersion < 49) MigrateSessionTo049(realm).perform() + if (oldVersion < 50) MigrateSessionTo050(realm).perform() } } From d5bd05d0bbdc907bab0005060fb44e49c04414b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 30 Jan 2023 12:10:28 +0100 Subject: [PATCH 028/317] Fix: cannot select text in plain text mode in Rich Text Editor --- changelog.d/7801.bugfix | 1 + library/ui-styles/src/main/res/values/styles_edit_text.xml | 1 - .../home/room/detail/composer/RichTextComposerLayout.kt | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 changelog.d/7801.bugfix diff --git a/changelog.d/7801.bugfix b/changelog.d/7801.bugfix new file mode 100644 index 0000000000..4ab8d7feda --- /dev/null +++ b/changelog.d/7801.bugfix @@ -0,0 +1 @@ +Cannot select text properly in plain text mode when using Rich Text Editor. diff --git a/library/ui-styles/src/main/res/values/styles_edit_text.xml b/library/ui-styles/src/main/res/values/styles_edit_text.xml index 6b282a7674..abb180ad87 100644 --- a/library/ui-styles/src/main/res/values/styles_edit_text.xml +++ b/library/ui-styles/src/main/res/values/styles_edit_text.xml @@ -15,7 +15,6 @@ @android:color/transparent textCapSentences|textMultiLine 10 - 40dp 10dp 10dp 12dp diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt index 1bb82b41fe..b60b1ca843 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -291,7 +291,7 @@ internal class RichTextComposerLayout @JvmOverloads constructor( private fun updateEditTextVisibility() { views.richTextComposerEditText.isVisible = isTextFormattingEnabled - views.richTextMenu.isVisible = isTextFormattingEnabled + views.richTextMenuScrollView.isVisible = isTextFormattingEnabled views.plainTextComposerEditText.isVisible = !isTextFormattingEnabled // The layouts for formatted text mode and plain text mode are different, so we need to update the constraints From c1d59d640879deb90d8bf7a7e34864723f8967c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 23:00:57 +0000 Subject: [PATCH 029/317] Bump danger/danger-js from 11.2.2 to 11.2.3 Bumps [danger/danger-js](https://github.com/danger/danger-js) from 11.2.2 to 11.2.3. - [Release notes](https://github.com/danger/danger-js/releases) - [Changelog](https://github.com/danger/danger-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/danger/danger-js/compare/11.2.2...11.2.3) --- updated-dependencies: - dependency-name: danger/danger-js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/danger.yml | 2 +- .github/workflows/quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 91352bb27b..a88a5faa9d 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -11,7 +11,7 @@ jobs: - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger - uses: danger/danger-js@11.2.2 + uses: danger/danger-js@11.2.3 with: args: "--dangerfile ./tools/danger/dangerfile.js" env: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index e8c56ba67f..b8800ea65b 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -66,7 +66,7 @@ jobs: yarn add danger-plugin-lint-report --dev - name: Danger lint if: always() - uses: danger/danger-js@11.2.2 + uses: danger/danger-js@11.2.3 with: args: "--dangerfile ./tools/danger/dangerfile-lint.js" env: From 8dae126d4cf095e4f60fc635b45a9a452146b714 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 23:12:40 +0000 Subject: [PATCH 030/317] Bump org.owasp:dependency-check-gradle from 8.0.1 to 8.0.2 Bumps org.owasp:dependency-check-gradle from 8.0.1 to 8.0.2. --- updated-dependencies: - dependency-name: org.owasp:dependency-check-gradle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3e8233fa4d..23c403da31 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6' classpath "com.likethesalad.android:stem-plugin:2.3.0" - classpath 'org.owasp:dependency-check-gradle:8.0.1' + classpath 'org.owasp:dependency-check-gradle:8.0.2' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' From a0bb3af871160ae07e96f2a8cd7c2d85271a6580 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 23:05:14 +0000 Subject: [PATCH 031/317] Bump io.element.android:wysiwyg from 0.18.0 to 0.23.0 Bumps [io.element.android:wysiwyg](https://github.com/matrix-org/matrix-wysiwyg) from 0.18.0 to 0.23.0. - [Release notes](https://github.com/matrix-org/matrix-wysiwyg/releases) - [Changelog](https://github.com/matrix-org/matrix-rich-text-editor/blob/main/CHANGELOG.md) - [Commits](https://github.com/matrix-org/matrix-wysiwyg/compare/0.18.0...0.23.0) --- updated-dependencies: - dependency-name: io.element.android:wysiwyg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index bab9229b3b..2e0a7011be 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -103,7 +103,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.18.0" + 'wysiwyg' : "io.element.android:wysiwyg:0.23.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", From 369c0f166d6113e011172adcd685c958206efdd9 Mon Sep 17 00:00:00 2001 From: yostyle Date: Wed, 1 Feb 2023 17:25:43 +0100 Subject: [PATCH 032/317] Let the user know when we are not able to decrypt the voice broadcast chunks --- changelog.d/7820.misc | 1 + .../src/main/res/values/strings.xml | 1 + .../vector/app/core/error/ErrorFormatter.kt | 4 +- .../timeline/factory/TimelineItemFactory.kt | 16 ++++++++ .../factory/VoiceBroadcastItemFactory.kt | 1 + .../timeline/helper/TimelineEventsGroups.kt | 10 +++++ .../item/AbsMessageVoiceBroadcastItem.kt | 2 + .../MessageVoiceBroadcastListeningItem.kt | 4 ++ .../voicebroadcast/VoiceBroadcastFailure.kt | 1 + .../listening/VoiceBroadcastPlayerImpl.kt | 12 ++++-- .../GetLiveVoiceBroadcastChunksUseCase.kt | 40 ++++++++++++++----- 11 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 changelog.d/7820.misc diff --git a/changelog.d/7820.misc b/changelog.d/7820.misc new file mode 100644 index 0000000000..1f59cb9afe --- /dev/null +++ b/changelog.d/7820.misc @@ -0,0 +1 @@ +Let the user know when we are not able to decrypt the voice broadcast chunks diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index e690f06bbb..de3fa20916 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3120,6 +3120,7 @@ You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. Unable to play this voice broadcast. Connection error - Recording paused + Unable to decrypt this voice broadcast. %1$s left Stop live broadcasting? diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index 0966227917..84f866d1f3 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -160,7 +160,9 @@ class DefaultErrorFormatter @Inject constructor( RecordingError.BlockedBySomeoneElse -> stringProvider.getString(R.string.error_voice_broadcast_blocked_by_someone_else_message) RecordingError.NoPermission -> stringProvider.getString(R.string.error_voice_broadcast_permission_denied_message) RecordingError.UserAlreadyBroadcasting -> stringProvider.getString(R.string.error_voice_broadcast_already_in_progress_message) - is VoiceBroadcastFailure.ListeningError -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_play) + is VoiceBroadcastFailure.ListeningError.UnableToPlay, + is VoiceBroadcastFailure.ListeningError.PrepareMediaPlayerError -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_play) + is VoiceBroadcastFailure.ListeningError.UnableToDecrypt -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_decrypt) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 61b2385d1d..9bcf3e1b6b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -19,11 +19,17 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.epoxy.TimelineEmptyItem import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.extensions.isVoiceBroadcast import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import timber.log.Timber import javax.inject.Inject @@ -39,6 +45,7 @@ class TimelineItemFactory @Inject constructor( private val callItemFactory: CallItemFactory, private val decryptionFailureTracker: DecryptionFailureTracker, private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper, + private val session: Session, ) { /** @@ -130,9 +137,18 @@ class TimelineItemFactory @Inject constructor( EventType.CALL_ANSWER -> callItemFactory.create(params) // Crypto EventType.ENCRYPTED -> { + val relationContent = event.getRelationContent() if (event.root.isRedacted()) { // Redacted event, let the MessageItemFactory handle it messageItemFactory.create(params) + } else if (relationContent?.type == RelationType.REFERENCE) { + // Hide the decryption error for VoiceBroadcast chunks + val startEvent = relationContent.eventId?.let { session.getRoom(event.roomId)?.getTimelineEvent(it) } + if (startEvent?.isVoiceBroadcast() == false) { + encryptedItemFactory.create(params) + } else { + null + } } else { encryptedItemFactory.create(params) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 3439fb1f57..7d05463b28 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -75,6 +75,7 @@ class VoiceBroadcastItemFactory @Inject constructor( voiceBroadcast = voiceBroadcast, voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState, duration = voiceBroadcastEventsGroup.getDuration(), + hasUnableToDecryptEvent = voiceBroadcastEventsGroup.hasUnableToDecryptEvent(), recorderName = params.event.senderInfo.disambiguatedDisplayName, recorder = voiceBroadcastRecorder, player = voiceBroadcastPlayer, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt index a4bfa9e155..a3e3f502b6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt @@ -25,6 +25,8 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent @@ -61,6 +63,7 @@ class TimelineEventsGroups { private fun TimelineEvent.getGroupIdOrNull(): String? { val type = root.getClearType() val content = root.getClearContent() + val relationContent = root.getRelationContent() return when { EventType.isCallEvent(type) -> (content?.get("call_id") as? String) type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId @@ -69,6 +72,9 @@ class TimelineEventsGroups { // Group voice messages with a reference to an eventId root.asMessageAudioEvent()?.getVoiceBroadcastEventId() } + type == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> { + relationContent.eventId + } else -> { null } @@ -153,4 +159,8 @@ class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) { fun getDuration(): Int { return group.events.mapNotNull { it.root.asMessageAudioEvent()?.duration }.sum() } + + fun hasUnableToDecryptEvent(): Boolean { + return group.events.any { it.root.getClearType() == EventType.ENCRYPTED } + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt index 7cde978e42..21d1abbdf2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt @@ -45,6 +45,7 @@ abstract class AbsMessageVoiceBroadcastItem + if (events.any { it.getClearType() == EventType.ENCRYPTED }) { + playingState = State.Error(VoiceBroadcastFailure.ListeningError.UnableToDecrypt) + } else { + playlist.setItems(events.mapNotNull { it.asMessageAudioEvent() }) + onPlaylistUpdated() + } } .launchIn(sessionScope) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt index 6f7444849a..05a465fb13 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt @@ -33,7 +33,10 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.runningReduce +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -49,14 +52,22 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventUseCase, ) { - fun execute(voiceBroadcast: VoiceBroadcast): Flow> { + fun execute(voiceBroadcast: VoiceBroadcast): Flow> { val session = activeSessionHolder.getSafeActiveSession() ?: return emptyFlow() val room = session.roomService().getRoom(voiceBroadcast.roomId) ?: return emptyFlow() val timeline = room.timelineService().createTimeline(null, TimelineSettings(5)) // Get initial chunks val existingChunks = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) - .mapNotNull { timelineEvent -> timelineEvent.root.asMessageAudioEvent().takeIf { it.isVoiceBroadcast() } } + .mapNotNull { timelineEvent -> + val event = timelineEvent.root + val relationContent = event.getRelationContent() + when { + event.getClearType() == EventType.MESSAGE -> event.takeIf { it.asMessageAudioEvent().isVoiceBroadcast() } + event.getClearType() == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> event + else -> null + } + } val voiceBroadcastEvent = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) val voiceBroadcastState = voiceBroadcastEvent?.content?.voiceBroadcastState @@ -93,7 +104,7 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( } // Automatically stop observing the timeline if the last chunk has been received - if (lastSequence != null && newChunks.any { it.sequence == lastSequence }) { + if (lastSequence != null && newChunks.any { it.asMessageAudioEvent()?.sequence == lastSequence }) { timeline.removeListener(this) timeline.dispose() } @@ -109,8 +120,8 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( timeline.dispose() } } - .runningReduce { accumulator: List, value: List -> accumulator.plus(value) } - .map { events -> events.distinctBy { it.sequence } } + .runningReduce { accumulator: List, value: List -> accumulator.plus(value) } + .map { events -> events.distinctBy { it.eventId } } } } @@ -124,12 +135,19 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( /** * Transform the list of [TimelineEvent] to a mapped list of [MessageAudioEvent] related to a given voice broadcast. */ - private fun List.mapToChunkEvents(voiceBroadcastId: String, senderId: String?): List = + private fun List.mapToChunkEvents(voiceBroadcastId: String, senderId: String?): List = this.mapNotNull { timelineEvent -> - timelineEvent.root.asMessageAudioEvent() - ?.takeIf { - it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && - it.root.senderId == senderId - } + val event = timelineEvent.root + val relationContent = event.getRelationContent() + when { + event.getClearType() == EventType.MESSAGE -> { + event.asMessageAudioEvent() + ?.takeIf { + it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && it.root.senderId == senderId + }?.root + } + event.getClearType() == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> event + else -> null + } } } From e7f3cf6d5781eea0cf74cb1d9e4f31fff073117f Mon Sep 17 00:00:00 2001 From: yostyle Date: Thu, 2 Feb 2023 11:28:20 +0100 Subject: [PATCH 033/317] Fix PR comments --- .../timeline/factory/TimelineItemFactory.kt | 18 ++++++--------- .../MessageVoiceBroadcastListeningItem.kt | 22 +++++++++++-------- .../GetLiveVoiceBroadcastChunksUseCase.kt | 4 +++- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 9bcf3e1b6b..d44713f404 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -138,19 +138,15 @@ class TimelineItemFactory @Inject constructor( // Crypto EventType.ENCRYPTED -> { val relationContent = event.getRelationContent() - if (event.root.isRedacted()) { + when { // Redacted event, let the MessageItemFactory handle it - messageItemFactory.create(params) - } else if (relationContent?.type == RelationType.REFERENCE) { - // Hide the decryption error for VoiceBroadcast chunks - val startEvent = relationContent.eventId?.let { session.getRoom(event.roomId)?.getTimelineEvent(it) } - if (startEvent?.isVoiceBroadcast() == false) { - encryptedItemFactory.create(params) - } else { - null + event.root.isRedacted() -> messageItemFactory.create(params) + relationContent?.type == RelationType.REFERENCE -> { + // Hide the decryption error for VoiceBroadcast chunks + val relatedEvent = relationContent.eventId?.let { session.getRoom(event.roomId)?.getTimelineEvent(it) } + if (relatedEvent?.isVoiceBroadcast() != true) encryptedItemFactory.create(params) else null } - } else { - encryptedItemFactory.create(params) + else -> encryptedItemFactory.create(params) } } EventType.KEY_VERIFICATION_CANCEL, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index 36a6eb93a2..b9d70b51cb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -137,15 +137,19 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem private fun renderPlaybackError(holder: Holder, playbackState: State) { with(holder) { - if (playbackState is State.Error) { - controlsGroup.isVisible = false - errorView.setTextOrHide(errorFormatter.toHumanReadable(playbackState.failure)) - } else if (playbackState is State.Idle && hasUnableToDecryptEvent) { - controlsGroup.isVisible = false - errorView.setTextOrHide(errorFormatter.toHumanReadable(VoiceBroadcastFailure.ListeningError.UnableToDecrypt)) - } else { - errorView.isVisible = false - controlsGroup.isVisible = true + when { + playbackState is State.Error -> { + controlsGroup.isVisible = false + errorView.setTextOrHide(errorFormatter.toHumanReadable(playbackState.failure)) + } + playbackState is State.Idle && hasUnableToDecryptEvent -> { + controlsGroup.isVisible = false + errorView.setTextOrHide(errorFormatter.toHumanReadable(VoiceBroadcastFailure.ListeningError.UnableToDecrypt)) + } + else -> { + errorView.isVisible = false + controlsGroup.isVisible = true + } } } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt index 05a465fb13..5a95f1a256 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt @@ -146,7 +146,9 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && it.root.senderId == senderId }?.root } - event.getClearType() == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> event + event.getClearType() == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> { + event.takeIf { relationContent.eventId == voiceBroadcastId } + } else -> null } } From 783596723eebc136e1868adb9173153f7ba94904 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 24 Jan 2023 17:04:53 +0300 Subject: [PATCH 034/317] Implement poll detail layout. --- .../res/layout/fragment_room_poll_detail.xml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 vector/src/main/res/layout/fragment_room_poll_detail.xml diff --git a/vector/src/main/res/layout/fragment_room_poll_detail.xml b/vector/src/main/res/layout/fragment_room_poll_detail.xml new file mode 100644 index 0000000000..7a7334bf8d --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_poll_detail.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + From c9dc570f83003687fddcecb8e52d7cc39571dbbc Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 27 Jan 2023 16:31:30 +0300 Subject: [PATCH 035/317] Navigate to poll detail fragment. --- .../roomprofile/RoomProfileActivity.kt | 6 +++ .../roomprofile/RoomProfileSharedAction.kt | 1 + .../roomprofile/polls/RoomPollsAction.kt | 1 + .../roomprofile/polls/RoomPollsViewEvent.kt | 1 + .../roomprofile/polls/RoomPollsViewModel.kt | 6 +++ .../roomprofile/polls/RoomPollsViewState.kt | 2 + .../polls/detail/RoomPollDetailFragment.kt | 38 +++++++++++++++++++ .../roomprofile/polls/list/ui/PollSummary.kt | 3 ++ .../polls/list/ui/PollSummaryMapper.kt | 4 +- .../polls/list/ui/RoomPollsListFragment.kt | 9 +++-- 10 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 3ee1ed867c..16bece891c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -37,6 +37,7 @@ import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.polls.RoomPollsFragment +import im.vector.app.features.roomprofile.polls.detail.RoomPollDetailFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.lib.core.utils.compat.getParcelableCompat @@ -105,6 +106,7 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() + is RoomProfileSharedAction.OpenPollDetails -> handleOpenPollDetails() } } .launchIn(lifecycleScope) @@ -130,6 +132,10 @@ class RoomProfileActivity : finish() } + private fun handleOpenPollDetails() { + addFragmentToBackstack(views.simpleFragmentContainer, RoomPollDetailFragment::class.java, roomProfileArgs) + } + private fun openRoomPolls() { addFragmentToBackstack(views.simpleFragmentContainer, RoomPollsFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index b243ceb206..63e2506c9a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -26,6 +26,7 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() object OpenRoomPolls : RoomProfileSharedAction() + object OpenPollDetails : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 3fedbfc4a8..b3ef4f45e2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed interface RoomPollsAction : VectorViewModelAction { object LoadMorePolls : RoomPollsAction + data class OnPollSelected(val selectedPollId: String) : RoomPollsAction } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt index cb2069d824..58b388186e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class RoomPollsViewEvent : VectorViewEvents { object LoadingError : RoomPollsViewEvent() + object NavigateToPollDetail : RoomPollsViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 2beda47816..df891e08e4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -89,9 +89,15 @@ class RoomPollsViewModel @AssistedInject constructor( override fun handle(action: RoomPollsAction) { when (action) { RoomPollsAction.LoadMorePolls -> handleLoadMore() + is RoomPollsAction.OnPollSelected -> handleOnPollSelected(action) } } + private fun handleOnPollSelected(action: RoomPollsAction.OnPollSelected) { + setState { copy(selectedPollId = action.selectedPollId) } + _viewEvents.post(RoomPollsViewEvent.NavigateToPollDetail) + } + private fun handleLoadMore() = withState { viewState -> viewModelScope.launch { setState { copy(isLoadingMore = true) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index 4a5c138b6a..dca016e529 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -27,10 +27,12 @@ data class RoomPollsViewState( val canLoadMore: Boolean = true, val nbSyncedDays: Int = 0, val isSyncing: Boolean = false, + val selectedPollId: String? = null, ) : MavericksState { constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) fun hasNoPolls() = polls.isEmpty() fun hasNoPollsAndCanLoadMore() = !isSyncing && hasNoPolls() && canLoadMore + fun getSelectedPoll() = polls.find { it.id == selectedPollId } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt new file mode 100644 index 0000000000..67e6307860 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomPollDetailBinding + +class RoomPollDetailFragment : VectorBaseFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollDetailBinding { + return FragmentRoomPollDetailBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupToolbar(views.roomPollDetailToolbar) + .allowBack() + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt index 5c1eee0d00..542ac68ad2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt @@ -22,11 +22,13 @@ sealed interface PollSummary { val id: String val creationTimestamp: Long val title: String + val optionViewStates: List data class ActivePoll( override val id: String, override val creationTimestamp: Long, override val title: String, + override val optionViewStates: List, ) : PollSummary data class EndedPoll( @@ -35,5 +37,6 @@ sealed interface PollSummary { override val title: String, val totalVotes: Int, val winnerOptions: List, + override val optionViewStates: List, ) : PollSummary } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index 64c712e61f..791f62d414 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -69,13 +69,15 @@ class PollSummaryMapper @Inject constructor( creationTimestamp = creationTimestamp, title = pollTitle, totalVotes = pollResponseData.totalVotes, - winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData) + winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData), + optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo), ) } else { PollSummary.ActivePoll( id = eventId, creationTimestamp = creationTimestamp, title = pollTitle, + optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo), ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index 1c33959824..21f55629ec 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -29,13 +29,14 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentRoomPollsListBinding +import im.vector.app.features.roomprofile.RoomProfileSharedAction +import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.polls.RoomPollsAction import im.vector.app.features.roomprofile.polls.RoomPollsLoadingError import im.vector.app.features.roomprofile.polls.RoomPollsType import im.vector.app.features.roomprofile.polls.RoomPollsViewEvent import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import im.vector.app.features.roomprofile.polls.RoomPollsViewState -import timber.log.Timber import javax.inject.Inject abstract class RoomPollsListFragment : @@ -49,6 +50,7 @@ abstract class RoomPollsListFragment : lateinit var stringProvider: StringProvider private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) + private lateinit var sharedActionViewModel: RoomProfileSharedActionViewModel override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { return FragmentRoomPollsListBinding.inflate(inflater, container, false) @@ -56,6 +58,7 @@ abstract class RoomPollsListFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + sharedActionViewModel = activityViewModelProvider[RoomProfileSharedActionViewModel::class.java] observeViewEvents() setupList() setupLoadMoreButton() @@ -65,6 +68,7 @@ abstract class RoomPollsListFragment : viewModel.observeViewEvents { viewEvent -> when (viewEvent) { RoomPollsViewEvent.LoadingError -> showErrorInSnackbar(RoomPollsLoadingError()) + RoomPollsViewEvent.NavigateToPollDetail -> sharedActionViewModel.post(RoomProfileSharedAction.OpenPollDetails) } } } @@ -126,8 +130,7 @@ abstract class RoomPollsListFragment : } override fun onPollClicked(pollId: String) { - // TODO navigate to details - Timber.d("poll with id $pollId clicked") + viewModel.handle(RoomPollsAction.OnPollSelected(pollId)) } override fun onLoadMoreClicked() { From b86f6a41bd5ea451dda146577aaa63793383b594 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 31 Jan 2023 11:13:14 +0300 Subject: [PATCH 036/317] Open poll detail screen. --- .../roomprofile/RoomProfileActivity.kt | 10 ++++-- .../roomprofile/RoomProfileFragment.kt | 3 +- .../roomprofile/RoomProfileSharedAction.kt | 2 +- .../roomprofile/polls/RoomPollsAction.kt | 1 + .../roomprofile/polls/RoomPollsFragment.kt | 33 +++++++++++++++++-- .../roomprofile/polls/RoomPollsViewEvent.kt | 2 +- .../roomprofile/polls/RoomPollsViewModel.kt | 7 +++- .../roomprofile/polls/RoomPollsViewState.kt | 1 + .../polls/detail/RoomPollDetailFragment.kt | 27 +++++++++++++++ .../polls/list/ui/RoomPollsListFragment.kt | 4 ++- 10 files changed, 80 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 16bece891c..97636acb70 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -106,7 +106,7 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() - is RoomProfileSharedAction.OpenPollDetails -> handleOpenPollDetails() + is RoomProfileSharedAction.OpenPollDetails -> handleOpenPollDetails(sharedAction) } } .launchIn(lifecycleScope) @@ -132,8 +132,12 @@ class RoomProfileActivity : finish() } - private fun handleOpenPollDetails() { - addFragmentToBackstack(views.simpleFragmentContainer, RoomPollDetailFragment::class.java, roomProfileArgs) + private fun handleOpenPollDetails(sharedAction: RoomProfileSharedAction.OpenPollDetails) { + addFragmentToBackstack( + views.simpleFragmentContainer, + RoomPollDetailFragment::class.java, + roomProfileArgs.copy(selectedPollId = sharedAction.pollId) + ) } private fun openRoomPolls() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 91f57d33e9..f90dd76d29 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -64,7 +64,8 @@ import javax.inject.Inject @Parcelize data class RoomProfileArgs( - val roomId: String + val roomId: String, + val selectedPollId: String? = null, ) : Parcelable @AndroidEntryPoint diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 63e2506c9a..7948b0dcd9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -26,7 +26,7 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() object OpenRoomPolls : RoomProfileSharedAction() - object OpenPollDetails : RoomProfileSharedAction() + data class OpenPollDetails(val pollId: String) : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index b3ef4f45e2..297e0048d7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -20,5 +20,6 @@ import im.vector.app.core.platform.VectorViewModelAction sealed interface RoomPollsAction : VectorViewModelAction { object LoadMorePolls : RoomPollsAction + data class OnRoomPollsTypeChange(val roomPollsType: RoomPollsType) : RoomPollsAction data class OnPollSelected(val selectedPollId: String) : RoomPollsAction } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index 9f7e704135..a20c5cd350 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -20,6 +20,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.viewpager2.widget.ViewPager2 import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.google.android.material.tabs.TabLayoutMediator @@ -66,9 +69,35 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { - RoomPollsType.ACTIVE.ordinal -> tab.text = getString(R.string.room_polls_active) - RoomPollsType.ENDED.ordinal -> tab.text = getString(R.string.room_polls_ended) + RoomPollsType.ACTIVE.ordinal -> { + tab.text = getString(R.string.room_polls_active) + } + RoomPollsType.ENDED.ordinal -> { + tab.text = getString(R.string.room_polls_ended) + } } }.also { it.attach() } + + val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + when (position) { + RoomPollsType.ACTIVE.ordinal -> { + viewModel.handle(RoomPollsAction.OnRoomPollsTypeChange(RoomPollsType.ACTIVE)) + } + RoomPollsType.ENDED.ordinal -> { + viewModel.handle(RoomPollsAction.OnRoomPollsTypeChange(RoomPollsType.ENDED)) + } + } + } + } + + viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + views.roomPollsViewPager.registerOnPageChangeCallback(onPageChangeCallback) + } + override fun onDestroy(owner: LifecycleOwner) { + views.roomPollsViewPager.unregisterOnPageChangeCallback(onPageChangeCallback) + } + }) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt index 58b388186e..df60d86613 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -20,5 +20,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class RoomPollsViewEvent : VectorViewEvents { object LoadingError : RoomPollsViewEvent() - object NavigateToPollDetail : RoomPollsViewEvent() + data class NavigateToPollDetail(val selectedPollId: String) : RoomPollsViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index df891e08e4..6a94f11c14 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -90,12 +90,17 @@ class RoomPollsViewModel @AssistedInject constructor( when (action) { RoomPollsAction.LoadMorePolls -> handleLoadMore() is RoomPollsAction.OnPollSelected -> handleOnPollSelected(action) + is RoomPollsAction.OnRoomPollsTypeChange -> handleOnRoomPollsTypeChange(action) } } + private fun handleOnRoomPollsTypeChange(action: RoomPollsAction.OnRoomPollsTypeChange) { + setState { copy(selectedRoomPollsType = action.roomPollsType) } + } + private fun handleOnPollSelected(action: RoomPollsAction.OnPollSelected) { setState { copy(selectedPollId = action.selectedPollId) } - _viewEvents.post(RoomPollsViewEvent.NavigateToPollDetail) + _viewEvents.post(RoomPollsViewEvent.NavigateToPollDetail(action.selectedPollId)) } private fun handleLoadMore() = withState { viewState -> diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index dca016e529..6be16d14b9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -28,6 +28,7 @@ data class RoomPollsViewState( val nbSyncedDays: Int = 0, val isSyncing: Boolean = false, val selectedPollId: String? = null, + val selectedRoomPollsType: RoomPollsType = RoomPollsType.ACTIVE, ) : MavericksState { constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index 67e6307860..684ba45892 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -20,11 +20,24 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollDetailBinding +import im.vector.app.features.roomprofile.RoomProfileArgs +import im.vector.app.features.roomprofile.polls.RoomPollsType +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +@AndroidEntryPoint class RoomPollDetailFragment : VectorBaseFragment() { + private val viewModel: RoomPollsViewModel by activityViewModel() + private val roomProfileArgs: RoomProfileArgs by args() + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollDetailBinding { return FragmentRoomPollDetailBinding.inflate(inflater, container, false) } @@ -32,7 +45,21 @@ class RoomPollDetailFragment : VectorBaseFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + initToolbar() + } + + private fun initToolbar() = withState(viewModel) { state -> + val title = if (state.selectedRoomPollsType == RoomPollsType.ACTIVE) getString(R.string.room_polls_active) + else getString(R.string.room_polls_ended) + setupToolbar(views.roomPollDetailToolbar) + .setTitle(title) .allowBack() } + + override fun invalidate() = withState(viewModel) { state -> + state.getSelectedPoll()?.let { _ -> + } + Unit + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index 21f55629ec..e4e64d229a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -68,7 +68,9 @@ abstract class RoomPollsListFragment : viewModel.observeViewEvents { viewEvent -> when (viewEvent) { RoomPollsViewEvent.LoadingError -> showErrorInSnackbar(RoomPollsLoadingError()) - RoomPollsViewEvent.NavigateToPollDetail -> sharedActionViewModel.post(RoomProfileSharedAction.OpenPollDetails) + is RoomPollsViewEvent.NavigateToPollDetail -> { + sharedActionViewModel.post(RoomProfileSharedAction.OpenPollDetails(viewEvent.selectedPollId)) + } } } } From ec4226b5d3d23740426882a7f2c5b8e6acc4dbfe Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 31 Jan 2023 12:19:30 +0300 Subject: [PATCH 037/317] Render poll detail. --- .../roomprofile/polls/RoomPollsViewState.kt | 1 + .../polls/detail/RoomPollDetailController.kt | 40 +++++++++++++++++++ .../polls/detail/RoomPollDetailFragment.kt | 16 ++++++-- .../res/layout/fragment_room_poll_detail.xml | 1 + 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index 6be16d14b9..63638257b0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -36,4 +36,5 @@ data class RoomPollsViewState( fun hasNoPolls() = polls.isEmpty() fun hasNoPollsAndCanLoadMore() = !isSyncing && hasNoPolls() && canLoadMore fun getSelectedPoll() = polls.find { it.id == selectedPollId } + fun canVoteSelectedPoll() = selectedRoomPollsType == RoomPollsType.ACTIVE } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt new file mode 100644 index 0000000000..72ce1b2f56 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.features.home.room.detail.timeline.item.PollItem_ +import im.vector.app.features.roomprofile.polls.RoomPollsViewState +import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence +import javax.inject.Inject + +class RoomPollDetailController @Inject constructor( + +) : TypedEpoxyController() { + + override fun buildModels(viewState: RoomPollsViewState?) { + viewState ?: return + val pollSummary = viewState.getSelectedPoll() ?: return + + PollItem_() + .eventId(pollSummary.id) + .pollQuestion(pollSummary.title.toEpoxyCharSequence()) + .canVote(viewState.canVoteSelectedPoll()) + .optionViewStates(pollSummary.optionViewStates) + .ended(viewState.canVoteSelectedPoll().not()) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index 684ba45892..ec8e8ea004 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -26,15 +26,19 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R +import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollDetailBinding import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.polls.RoomPollsType import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import javax.inject.Inject @AndroidEntryPoint class RoomPollDetailFragment : VectorBaseFragment() { + @Inject lateinit var roomPollDetailController: RoomPollDetailController + private val viewModel: RoomPollsViewModel by activityViewModel() private val roomProfileArgs: RoomProfileArgs by args() @@ -45,11 +49,14 @@ class RoomPollDetailFragment : VectorBaseFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initToolbar() + views.pollDetailRecyclerView.configureWith( + roomPollDetailController, + hasFixedSize = true, + ) } - private fun initToolbar() = withState(viewModel) { state -> - val title = if (state.selectedRoomPollsType == RoomPollsType.ACTIVE) getString(R.string.room_polls_active) + private fun setupToolbar(roomPollsType: RoomPollsType) { + val title = if (roomPollsType == RoomPollsType.ACTIVE) getString(R.string.room_polls_active) else getString(R.string.room_polls_ended) setupToolbar(views.roomPollDetailToolbar) @@ -58,7 +65,10 @@ class RoomPollDetailFragment : VectorBaseFragment } override fun invalidate() = withState(viewModel) { state -> + setupToolbar(state.selectedRoomPollsType) + state.getSelectedPoll()?.let { _ -> + roomPollDetailController.setData(state) } Unit } diff --git a/vector/src/main/res/layout/fragment_room_poll_detail.xml b/vector/src/main/res/layout/fragment_room_poll_detail.xml index 7a7334bf8d..7ed8d1125e 100644 --- a/vector/src/main/res/layout/fragment_room_poll_detail.xml +++ b/vector/src/main/res/layout/fragment_room_poll_detail.xml @@ -22,6 +22,7 @@ Date: Tue, 31 Jan 2023 15:01:07 +0300 Subject: [PATCH 038/317] Implement new view state. --- .../roomprofile/RoomProfileFragment.kt | 1 - .../polls/detail/RoomPollDetail.kt | 29 +++++++++++++++++++ .../polls/detail/RoomPollDetailFragment.kt | 14 ++++++--- .../polls/detail/RoomPollDetailViewState.kt | 29 +++++++++++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index f90dd76d29..9436bafc03 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -65,7 +65,6 @@ import javax.inject.Inject @Parcelize data class RoomProfileArgs( val roomId: String, - val selectedPollId: String? = null, ) : Parcelable @AndroidEntryPoint diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt new file mode 100644 index 0000000000..c149daaef2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail + +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState + +data class RoomPollDetail( + val eventId: String, + val question: String, + val canVote: Boolean, + val votesStatusSummary: String, + val optionViewStates: List, + val hasBeenEdited: Boolean, + val isEnded: Boolean, +) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index ec8e8ea004..96eea5bdd8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -17,10 +17,10 @@ package im.vector.app.features.roomprofile.polls.detail import android.os.Bundle +import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -29,18 +29,24 @@ import im.vector.app.R import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollDetailBinding -import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.polls.RoomPollsType import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import kotlinx.parcelize.Parcelize import javax.inject.Inject +@Parcelize +data class RoomPollDetailArgs( + val roomId: String, + val pollId: String, +) : Parcelable + @AndroidEntryPoint class RoomPollDetailFragment : VectorBaseFragment() { @Inject lateinit var roomPollDetailController: RoomPollDetailController - private val viewModel: RoomPollsViewModel by activityViewModel() - private val roomProfileArgs: RoomProfileArgs by args() + private val viewModel: RoomPollsViewModel by fragmentViewModel() + private val roomPollDetailArgs: RoomPollDetailArgs by args() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollDetailBinding { return FragmentRoomPollDetailBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt new file mode 100644 index 0000000000..d7e31a0424 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail + +import com.airbnb.mvrx.MavericksState + +data class RoomPollDetailViewState( + val roomId: String, + val pollId: String, + val pollDetail: RoomPollDetail? = null, +) : MavericksState { + + constructor(roomPollDetailArgs: RoomPollDetailArgs) + : this(roomId = roomPollDetailArgs.roomId, pollId = roomPollDetailArgs.pollId) +} From 429a71964d64200da7cf0b212097b8c438e179b5 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 31 Jan 2023 15:25:37 +0300 Subject: [PATCH 039/317] Create separate view model for poll detail. --- .../polls/detail/RoomPollDetailAction.kt | 23 +++++++++++++ .../polls/detail/RoomPollDetailController.kt | 7 ++-- .../polls/detail/RoomPollDetailFragment.kt | 14 +++++--- .../polls/detail/RoomPollDetailViewEvent.kt | 23 +++++++++++++ .../polls/detail/RoomPollDetailViewModel.kt | 34 +++++++++++++++++++ 5 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt new file mode 100644 index 0000000000..58a059b7a5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface RoomPollDetailAction : VectorViewModelAction { + +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt index 72ce1b2f56..647c036513 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt @@ -24,17 +24,18 @@ import javax.inject.Inject class RoomPollDetailController @Inject constructor( -) : TypedEpoxyController() { +) : TypedEpoxyController() { - override fun buildModels(viewState: RoomPollsViewState?) { + override fun buildModels(viewState: RoomPollDetailViewState?) { viewState ?: return - val pollSummary = viewState.getSelectedPoll() ?: return PollItem_() + /* .eventId(pollSummary.id) .pollQuestion(pollSummary.title.toEpoxyCharSequence()) .canVote(viewState.canVoteSelectedPoll()) .optionViewStates(pollSummary.optionViewStates) .ended(viewState.canVoteSelectedPoll().not()) + */ } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index 96eea5bdd8..cfab132947 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -45,7 +45,7 @@ class RoomPollDetailFragment : VectorBaseFragment @Inject lateinit var roomPollDetailController: RoomPollDetailController - private val viewModel: RoomPollsViewModel by fragmentViewModel() + private val viewModel: RoomPollDetailViewModel by fragmentViewModel() private val roomPollDetailArgs: RoomPollDetailArgs by args() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollDetailBinding { @@ -61,9 +61,9 @@ class RoomPollDetailFragment : VectorBaseFragment ) } - private fun setupToolbar(roomPollsType: RoomPollsType) { - val title = if (roomPollsType == RoomPollsType.ACTIVE) getString(R.string.room_polls_active) - else getString(R.string.room_polls_ended) + private fun setupToolbar(isEnded: Boolean) { + val title = if (isEnded) getString(R.string.room_polls_ended) + else getString(R.string.room_polls_active) setupToolbar(views.roomPollDetailToolbar) .setTitle(title) @@ -71,11 +71,15 @@ class RoomPollDetailFragment : VectorBaseFragment } override fun invalidate() = withState(viewModel) { state -> - setupToolbar(state.selectedRoomPollsType) + state.pollDetail ?: return@withState + setupToolbar(state.pollDetail.isEnded) + + /* state.getSelectedPoll()?.let { _ -> roomPollDetailController.setData(state) } Unit + */ } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt new file mode 100644 index 0000000000..1ee405e0c3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail + +import im.vector.app.core.platform.VectorViewEvents + +sealed class RoomPollDetailViewEvent : VectorViewEvents { + +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt new file mode 100644 index 0000000000..5d65db78b4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail + +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import im.vector.app.core.platform.VectorViewModel + +class RoomPollDetailViewModel @AssistedInject constructor( + @Assisted initialState: RoomPollDetailViewState, +) : VectorViewModel(initialState) { + + init { + // Subscribe to the poll event and map it + } + + override fun handle(action: RoomPollDetailAction) { + + } +} From 8aa89f1dfd03dfb1927900ddcc6d003a3e91fd57 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 11:45:10 +0100 Subject: [PATCH 040/317] Adding changelog entry --- changelog.d/8056.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8056.wip diff --git a/changelog.d/8056.wip b/changelog.d/8056.wip new file mode 100644 index 0000000000..5dca2d001d --- /dev/null +++ b/changelog.d/8056.wip @@ -0,0 +1 @@ +[Poll] History list: details screen of a poll From fb445628249d165e73f5e4e60339723c69ae88b0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:53:05 +0100 Subject: [PATCH 041/317] Using navigator and new activity for the new screen --- vector/src/main/AndroidManifest.xml | 1 + .../roomprofile/RoomProfileActivity.kt | 10 ---- .../roomprofile/RoomProfileSharedAction.kt | 1 - .../roomprofile/polls/RoomPollsAction.kt | 2 - .../roomprofile/polls/RoomPollsFragment.kt | 33 +---------- .../roomprofile/polls/RoomPollsViewEvent.kt | 1 - .../roomprofile/polls/RoomPollsViewModel.kt | 11 ---- .../roomprofile/polls/RoomPollsViewState.kt | 4 -- .../polls/detail/RoomPollDetailActivity.kt | 56 +++++++++++++++++++ .../polls/detail/RoomPollDetailController.kt | 5 +- .../polls/detail/RoomPollDetailFragment.kt | 1 - .../polls/detail/RoomPollDetailViewModel.kt | 5 +- .../polls/detail/RoomPollDetailViewState.kt | 8 +-- .../polls/list/ui/RoomPollsListFragment.kt | 12 ++-- .../polls/list/ui/RoomPollsListNavigator.kt | 29 ++++++++++ 15 files changed, 100 insertions(+), 79 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 9c8186b2d4..ed9800b4f7 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -327,6 +327,7 @@ + diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 97636acb70..3ee1ed867c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -37,7 +37,6 @@ import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.polls.RoomPollsFragment -import im.vector.app.features.roomprofile.polls.detail.RoomPollDetailFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.lib.core.utils.compat.getParcelableCompat @@ -106,7 +105,6 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() - is RoomProfileSharedAction.OpenPollDetails -> handleOpenPollDetails(sharedAction) } } .launchIn(lifecycleScope) @@ -132,14 +130,6 @@ class RoomProfileActivity : finish() } - private fun handleOpenPollDetails(sharedAction: RoomProfileSharedAction.OpenPollDetails) { - addFragmentToBackstack( - views.simpleFragmentContainer, - RoomPollDetailFragment::class.java, - roomProfileArgs.copy(selectedPollId = sharedAction.pollId) - ) - } - private fun openRoomPolls() { addFragmentToBackstack(views.simpleFragmentContainer, RoomPollsFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 7948b0dcd9..b243ceb206 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -26,7 +26,6 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() object OpenRoomPolls : RoomProfileSharedAction() - data class OpenPollDetails(val pollId: String) : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 297e0048d7..3fedbfc4a8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -20,6 +20,4 @@ import im.vector.app.core.platform.VectorViewModelAction sealed interface RoomPollsAction : VectorViewModelAction { object LoadMorePolls : RoomPollsAction - data class OnRoomPollsTypeChange(val roomPollsType: RoomPollsType) : RoomPollsAction - data class OnPollSelected(val selectedPollId: String) : RoomPollsAction } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index a20c5cd350..9f7e704135 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -20,9 +20,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.viewpager2.widget.ViewPager2 import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.google.android.material.tabs.TabLayoutMediator @@ -69,35 +66,9 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { - RoomPollsType.ACTIVE.ordinal -> { - tab.text = getString(R.string.room_polls_active) - } - RoomPollsType.ENDED.ordinal -> { - tab.text = getString(R.string.room_polls_ended) - } + RoomPollsType.ACTIVE.ordinal -> tab.text = getString(R.string.room_polls_active) + RoomPollsType.ENDED.ordinal -> tab.text = getString(R.string.room_polls_ended) } }.also { it.attach() } - - val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - when (position) { - RoomPollsType.ACTIVE.ordinal -> { - viewModel.handle(RoomPollsAction.OnRoomPollsTypeChange(RoomPollsType.ACTIVE)) - } - RoomPollsType.ENDED.ordinal -> { - viewModel.handle(RoomPollsAction.OnRoomPollsTypeChange(RoomPollsType.ENDED)) - } - } - } - } - - viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onCreate(owner: LifecycleOwner) { - views.roomPollsViewPager.registerOnPageChangeCallback(onPageChangeCallback) - } - override fun onDestroy(owner: LifecycleOwner) { - views.roomPollsViewPager.unregisterOnPageChangeCallback(onPageChangeCallback) - } - }) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt index df60d86613..cb2069d824 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -20,5 +20,4 @@ import im.vector.app.core.platform.VectorViewEvents sealed class RoomPollsViewEvent : VectorViewEvents { object LoadingError : RoomPollsViewEvent() - data class NavigateToPollDetail(val selectedPollId: String) : RoomPollsViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 6a94f11c14..2beda47816 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -89,20 +89,9 @@ class RoomPollsViewModel @AssistedInject constructor( override fun handle(action: RoomPollsAction) { when (action) { RoomPollsAction.LoadMorePolls -> handleLoadMore() - is RoomPollsAction.OnPollSelected -> handleOnPollSelected(action) - is RoomPollsAction.OnRoomPollsTypeChange -> handleOnRoomPollsTypeChange(action) } } - private fun handleOnRoomPollsTypeChange(action: RoomPollsAction.OnRoomPollsTypeChange) { - setState { copy(selectedRoomPollsType = action.roomPollsType) } - } - - private fun handleOnPollSelected(action: RoomPollsAction.OnPollSelected) { - setState { copy(selectedPollId = action.selectedPollId) } - _viewEvents.post(RoomPollsViewEvent.NavigateToPollDetail(action.selectedPollId)) - } - private fun handleLoadMore() = withState { viewState -> viewModelScope.launch { setState { copy(isLoadingMore = true) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index 63638257b0..4a5c138b6a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -27,14 +27,10 @@ data class RoomPollsViewState( val canLoadMore: Boolean = true, val nbSyncedDays: Int = 0, val isSyncing: Boolean = false, - val selectedPollId: String? = null, - val selectedRoomPollsType: RoomPollsType = RoomPollsType.ACTIVE, ) : MavericksState { constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) fun hasNoPolls() = polls.isEmpty() fun hasNoPollsAndCanLoadMore() = !isSyncing && hasNoPolls() && canLoadMore - fun getSelectedPoll() = polls.find { it.id == selectedPollId } - fun canVoteSelectedPoll() = selectedRoomPollsType == RoomPollsType.ACTIVE } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt new file mode 100644 index 0000000000..6995340a9e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt @@ -0,0 +1,56 @@ +/* + * Copyright 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.roomprofile.polls.detail + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.airbnb.mvrx.Mavericks +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivitySimpleBinding +import im.vector.lib.core.utils.compat.getParcelableExtraCompat + +/** + * Display the details of a given poll. + */ +@AndroidEntryPoint +class RoomPollDetailActivity : VectorBaseActivity() { + + override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (isFirstCreation()) { + addFragment( + container = views.simpleFragmentContainer, + fragmentClass = RoomPollDetailFragment::class.java, + params = intent.getParcelableExtraCompat(Mavericks.KEY_ARG) + ) + } + } + + companion object { + fun newIntent(context: Context, pollId: String): Intent { + return Intent(context, RoomPollDetailActivity::class.java).apply { + putExtra(Mavericks.KEY_ARG, RoomPollDetailArgs(pollId)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt index 647c036513..cd2ca1c711 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt @@ -22,9 +22,8 @@ import im.vector.app.features.roomprofile.polls.RoomPollsViewState import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import javax.inject.Inject -class RoomPollDetailController @Inject constructor( - -) : TypedEpoxyController() { +class RoomPollDetailController @Inject constructor() + : TypedEpoxyController() { override fun buildModels(viewState: RoomPollDetailViewState?) { viewState ?: return diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index cfab132947..dd7b40560f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -36,7 +36,6 @@ import javax.inject.Inject @Parcelize data class RoomPollDetailArgs( - val roomId: String, val pollId: String, ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt index 5d65db78b4..7368d28c8f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt @@ -25,10 +25,11 @@ class RoomPollDetailViewModel @AssistedInject constructor( ) : VectorViewModel(initialState) { init { - // Subscribe to the poll event and map it + // TODO observe poll using TimelineService.getTimelineEventLive + // TODO create a dedicated useCase and mapper } override fun handle(action: RoomPollDetailAction) { - + // TODO handle go to timeline action } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt index d7e31a0424..2d7303f555 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt @@ -19,11 +19,9 @@ package im.vector.app.features.roomprofile.polls.detail import com.airbnb.mvrx.MavericksState data class RoomPollDetailViewState( - val roomId: String, - val pollId: String, - val pollDetail: RoomPollDetail? = null, + val pollId: String, + val pollDetail: RoomPollDetail? = null, ) : MavericksState { - constructor(roomPollDetailArgs: RoomPollDetailArgs) - : this(roomId = roomPollDetailArgs.roomId, pollId = roomPollDetailArgs.pollId) + constructor(roomPollDetailArgs: RoomPollDetailArgs) : this(pollId = roomPollDetailArgs.pollId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index e4e64d229a..1c6ae518da 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -29,8 +29,6 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentRoomPollsListBinding -import im.vector.app.features.roomprofile.RoomProfileSharedAction -import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.polls.RoomPollsAction import im.vector.app.features.roomprofile.polls.RoomPollsLoadingError import im.vector.app.features.roomprofile.polls.RoomPollsType @@ -49,8 +47,10 @@ abstract class RoomPollsListFragment : @Inject lateinit var stringProvider: StringProvider + @Inject + lateinit var viewNavigator: RoomPollsListNavigator + private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) - private lateinit var sharedActionViewModel: RoomProfileSharedActionViewModel override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { return FragmentRoomPollsListBinding.inflate(inflater, container, false) @@ -58,7 +58,6 @@ abstract class RoomPollsListFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - sharedActionViewModel = activityViewModelProvider[RoomProfileSharedActionViewModel::class.java] observeViewEvents() setupList() setupLoadMoreButton() @@ -68,9 +67,6 @@ abstract class RoomPollsListFragment : viewModel.observeViewEvents { viewEvent -> when (viewEvent) { RoomPollsViewEvent.LoadingError -> showErrorInSnackbar(RoomPollsLoadingError()) - is RoomPollsViewEvent.NavigateToPollDetail -> { - sharedActionViewModel.post(RoomProfileSharedAction.OpenPollDetails(viewEvent.selectedPollId)) - } } } } @@ -132,7 +128,7 @@ abstract class RoomPollsListFragment : } override fun onPollClicked(pollId: String) { - viewModel.handle(RoomPollsAction.OnPollSelected(pollId)) + viewNavigator.goToPollDetails(requireContext(), pollId) } override fun onLoadMoreClicked() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt new file mode 100644 index 0000000000..35440e89a9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.list.ui + +import android.content.Context +import im.vector.app.features.roomprofile.polls.detail.RoomPollDetailActivity +import javax.inject.Inject + +// TODO add unit tests +class RoomPollsListNavigator @Inject constructor() { + + fun goToPollDetails(context: Context, pollId: String) { + context.startActivity(RoomPollDetailActivity.newIntent(context, pollId)) + } +} From 2ce15a1923d36158f53dc5f6e2ad8240a587d699 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:59:31 +0100 Subject: [PATCH 042/317] Set empty toolbar by default --- .../polls/detail/RoomPollDetailFragment.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index dd7b40560f..3027f30460 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -26,11 +26,10 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R +import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollDetailBinding -import im.vector.app.features.roomprofile.polls.RoomPollsType -import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import kotlinx.parcelize.Parcelize import javax.inject.Inject @@ -53,6 +52,7 @@ class RoomPollDetailFragment : VectorBaseFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setupToolbar() views.pollDetailRecyclerView.configureWith( roomPollDetailController, @@ -60,9 +60,17 @@ class RoomPollDetailFragment : VectorBaseFragment ) } - private fun setupToolbar(isEnded: Boolean) { - val title = if (isEnded) getString(R.string.room_polls_ended) - else getString(R.string.room_polls_active) + override fun onDestroyView() { + views.pollDetailRecyclerView.cleanup() + super.onDestroyView() + } + + private fun setupToolbar(isEnded: Boolean? = null) { + val title = when (isEnded) { + true -> getString(R.string.room_polls_ended) + false -> getString(R.string.room_polls_active) + else -> "" + } setupToolbar(views.roomPollDetailToolbar) .setTitle(title) From 753875ba0c87f313f67f848f2ede3e79c701a2dc Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:04:46 +0100 Subject: [PATCH 043/317] Creating subpackage ui --- vector/src/main/AndroidManifest.xml | 2 +- .../roomprofile/polls/detail/{ => ui}/RoomPollDetail.kt | 2 +- .../roomprofile/polls/detail/{ => ui}/RoomPollDetailAction.kt | 2 +- .../polls/detail/{ => ui}/RoomPollDetailActivity.kt | 4 ++-- .../polls/detail/{ => ui}/RoomPollDetailController.kt | 4 +--- .../polls/detail/{ => ui}/RoomPollDetailFragment.kt | 2 +- .../polls/detail/{ => ui}/RoomPollDetailViewEvent.kt | 2 +- .../polls/detail/{ => ui}/RoomPollDetailViewModel.kt | 2 +- .../polls/detail/{ => ui}/RoomPollDetailViewState.kt | 2 +- .../roomprofile/polls/list/ui/RoomPollsListNavigator.kt | 2 +- 10 files changed, 11 insertions(+), 13 deletions(-) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetail.kt (94%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailAction.kt (92%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailActivity.kt (95%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailController.kt (87%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailFragment.kt (97%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailViewEvent.kt (92%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailViewModel.kt (95%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailViewState.kt (93%) diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index ed9800b4f7..922d4ca292 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -327,7 +327,7 @@ - + diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt similarity index 94% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt index c149daaef2..cbbd09f065 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt similarity index 92% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt index 58a059b7a5..efb20f9144 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import im.vector.app.core.platform.VectorViewModelAction diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt index 6995340a9e..90090f7830 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 New Vector Ltd + * Copyright (c) 2023 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import android.content.Context import android.content.Intent diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt similarity index 87% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt index cd2ca1c711..08db09a5b0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt @@ -14,12 +14,10 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.features.home.room.detail.timeline.item.PollItem_ -import im.vector.app.features.roomprofile.polls.RoomPollsViewState -import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import javax.inject.Inject class RoomPollDetailController @Inject constructor() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt similarity index 97% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt index 3027f30460..a3ff6e0451 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import android.os.Bundle import android.os.Parcelable diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewEvent.kt similarity index 92% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewEvent.kt index 1ee405e0c3..01fb4698fb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewEvent.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import im.vector.app.core.platform.VectorViewEvents diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt index 7368d28c8f..a186ca3b3b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt similarity index 93% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt index 2d7303f555..6a68cdeb01 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.mvrx.MavericksState diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt index 35440e89a9..f04c9bf601 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.polls.list.ui import android.content.Context -import im.vector.app.features.roomprofile.polls.detail.RoomPollDetailActivity +import im.vector.app.features.roomprofile.polls.detail.ui.RoomPollDetailActivity import javax.inject.Inject // TODO add unit tests From 60d3ae6cc597577413989a2bdfcc0850fbb318c0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:47:13 +0100 Subject: [PATCH 044/317] Removing new added fields in PollSummary --- .../app/features/roomprofile/polls/list/ui/PollSummary.kt | 3 --- .../features/roomprofile/polls/list/ui/PollSummaryMapper.kt | 2 -- 2 files changed, 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt index 542ac68ad2..5c1eee0d00 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt @@ -22,13 +22,11 @@ sealed interface PollSummary { val id: String val creationTimestamp: Long val title: String - val optionViewStates: List data class ActivePoll( override val id: String, override val creationTimestamp: Long, override val title: String, - override val optionViewStates: List, ) : PollSummary data class EndedPoll( @@ -37,6 +35,5 @@ sealed interface PollSummary { override val title: String, val totalVotes: Int, val winnerOptions: List, - override val optionViewStates: List, ) : PollSummary } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index 791f62d414..ea22d7d5a3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -70,14 +70,12 @@ class PollSummaryMapper @Inject constructor( title = pollTitle, totalVotes = pollResponseData.totalVotes, winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData), - optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo), ) } else { PollSummary.ActivePoll( id = eventId, creationTimestamp = creationTimestamp, title = pollTitle, - optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo), ) } } From afe036dd9de4de1032eb9a006fe621333397db93 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:47:40 +0100 Subject: [PATCH 045/317] Observe timeline event of the selected poll --- .../app/core/event/GetTimelineEventUseCase.kt | 43 +++++++++++++++++++ .../polls/detail/ui/RoomPollDetailActivity.kt | 8 +++- .../polls/detail/ui/RoomPollDetailFragment.kt | 9 ++-- .../detail/ui/RoomPollDetailViewModel.kt | 14 +++++- .../detail/ui/RoomPollDetailViewState.kt | 6 ++- .../polls/list/ui/RoomPollsListFragment.kt | 8 +++- .../polls/list/ui/RoomPollsListNavigator.kt | 10 ++++- 7 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/event/GetTimelineEventUseCase.kt diff --git a/vector/src/main/java/im/vector/app/core/event/GetTimelineEventUseCase.kt b/vector/src/main/java/im/vector/app/core/event/GetTimelineEventUseCase.kt new file mode 100644 index 0000000000..9745701589 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/event/GetTimelineEventUseCase.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 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.core.event + +import androidx.lifecycle.asFlow +import im.vector.app.core.di.ActiveSessionHolder +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.flow.unwrap +import javax.inject.Inject + +// TODO add unit tests +class GetTimelineEventUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun execute(roomId: String, eventId: String): Flow { + return activeSessionHolder + .getActiveSession() + .roomService() + .getRoom(roomId) + ?.timelineService() + ?.getTimelineEventLive(eventId) + ?.asFlow() + ?.unwrap() + ?: emptyFlow() + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt index 90090f7830..ed89526349 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt @@ -47,9 +47,13 @@ class RoomPollDetailActivity : VectorBaseActivity() { } companion object { - fun newIntent(context: Context, pollId: String): Intent { + fun newIntent(context: Context, pollId: String, roomId: String): Intent { return Intent(context, RoomPollDetailActivity::class.java).apply { - putExtra(Mavericks.KEY_ARG, RoomPollDetailArgs(pollId)) + val args = RoomPollDetailArgs( + pollId = pollId, + roomId = roomId, + ) + putExtra(Mavericks.KEY_ARG, args) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt index a3ff6e0451..1589954b35 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt @@ -36,6 +36,7 @@ import javax.inject.Inject @Parcelize data class RoomPollDetailArgs( val pollId: String, + val roomId: String, ) : Parcelable @AndroidEntryPoint @@ -80,13 +81,9 @@ class RoomPollDetailFragment : VectorBaseFragment override fun invalidate() = withState(viewModel) { state -> state.pollDetail ?: return@withState + // TODO should we update the title when the poll status changes? setupToolbar(state.pollDetail.isEnded) - /* - state.getSelectedPoll()?.let { _ -> - roomPollDetailController.setData(state) - } - Unit - */ + // TODO update data of the controller } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt index a186ca3b3b..8227a55881 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt @@ -18,15 +18,25 @@ package im.vector.app.features.roomprofile.polls.detail.ui import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import im.vector.app.core.event.GetTimelineEventUseCase import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.launchIn class RoomPollDetailViewModel @AssistedInject constructor( @Assisted initialState: RoomPollDetailViewState, + private val getTimelineEventUseCase: GetTimelineEventUseCase, ) : VectorViewModel(initialState) { init { - // TODO observe poll using TimelineService.getTimelineEventLive - // TODO create a dedicated useCase and mapper + observePollDetails( + pollId = initialState.pollId, + roomId = initialState.roomId, + ) + } + + private fun observePollDetails(pollId: String, roomId: String) { + getTimelineEventUseCase.execute(roomId = roomId, eventId = pollId) + .launchIn(viewModelScope) } override fun handle(action: RoomPollDetailAction) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt index 6a68cdeb01..a2906dc88f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt @@ -20,8 +20,12 @@ import com.airbnb.mvrx.MavericksState data class RoomPollDetailViewState( val pollId: String, + val roomId: String, val pollDetail: RoomPollDetail? = null, ) : MavericksState { - constructor(roomPollDetailArgs: RoomPollDetailArgs) : this(pollId = roomPollDetailArgs.pollId) + constructor(roomPollDetailArgs: RoomPollDetailArgs) : this( + pollId = roomPollDetailArgs.pollId, + roomId = roomPollDetailArgs.roomId, + ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index 1c6ae518da..bbf058d908 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -127,8 +127,12 @@ abstract class RoomPollsListFragment : views.roomPollsLoadMoreWhenEmptyProgress.isVisible = viewState.hasNoPollsAndCanLoadMore() && viewState.isLoadingMore } - override fun onPollClicked(pollId: String) { - viewNavigator.goToPollDetails(requireContext(), pollId) + override fun onPollClicked(pollId: String) = withState(viewModel) { + viewNavigator.goToPollDetails( + context = requireContext(), + pollId = pollId, + roomId = it.roomId, + ) } override fun onLoadMoreClicked() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt index f04c9bf601..3cba2fc355 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt @@ -23,7 +23,13 @@ import javax.inject.Inject // TODO add unit tests class RoomPollsListNavigator @Inject constructor() { - fun goToPollDetails(context: Context, pollId: String) { - context.startActivity(RoomPollDetailActivity.newIntent(context, pollId)) + fun goToPollDetails(context: Context, pollId: String, roomId: String) { + context.startActivity( + RoomPollDetailActivity.newIntent( + context = context, + pollId = pollId, + roomId = roomId, + ) + ) } } From d3df58c607b747563996f3db3c06e3a8bc02c5ba Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 17:44:48 +0100 Subject: [PATCH 046/317] Render the details of the poll --- .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../timeline/factory/MessageItemFactory.kt | 6 +- .../factory/PollItemViewStateFactory.kt | 59 +++++++------ ...{PollViewState.kt => PollItemViewState.kt} | 2 +- .../polls/detail/ui/RoomPollDetail.kt | 9 +- .../polls/detail/ui/RoomPollDetailAction.kt | 23 ----- .../detail/ui/RoomPollDetailController.kt | 31 ++++--- .../polls/detail/ui/RoomPollDetailFragment.kt | 7 +- .../polls/detail/ui/RoomPollDetailItem.kt | 85 +++++++++++++++++++ .../polls/detail/ui/RoomPollDetailMapper.kt | 60 +++++++++++++ .../detail/ui/RoomPollDetailViewModel.kt | 25 +++++- .../detail/ui/RoomPollDetailViewState.kt | 1 + .../res/layout/fragment_room_poll_detail.xml | 3 +- .../factory/PollItemViewStateFactoryTest.kt | 12 +-- 14 files changed, 241 insertions(+), 88 deletions(-) rename vector/src/main/java/im/vector/app/features/poll/{PollViewState.kt => PollItemViewState.kt} (96%) delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 911bbfa4a3..c2e2f9f695 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -85,6 +85,7 @@ import im.vector.app.features.roomprofile.members.RoomMemberListViewModel import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewModel import im.vector.app.features.roomprofile.permissions.RoomPermissionsViewModel import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import im.vector.app.features.roomprofile.polls.detail.ui.RoomPollDetailViewModel import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel @@ -703,4 +704,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(RoomPollsViewModel::class) fun roomPollsViewModelFactory(factory: RoomPollsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(RoomPollDetailViewModel::class) + fun roomPollDetailViewModelFactory(factory: RoomPollDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 9cb1608415..e988e9818c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -255,7 +255,11 @@ class MessageItemFactory @Inject constructor( attributes: AbsMessageItem.Attributes, isEnded: Boolean, ): PollItem { - val pollViewState = pollItemViewStateFactory.create(pollContent, informationData) + val pollViewState = pollItemViewStateFactory.create( + pollContent = pollContent, + pollResponseData = informationData.pollResponseAggregatedSummary, + isSent = informationData.sendState.isSent(), + ) return PollItem_() .attributes(attributes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index 3c1a1cfd85..63e41d7ad2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -18,9 +18,8 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.R import im.vector.app.core.resources.StringProvider -import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.PollResponseData -import im.vector.app.features.poll.PollViewState +import im.vector.app.features.poll.PollItemViewState import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo @@ -33,27 +32,27 @@ class PollItemViewStateFactory @Inject constructor( fun create( pollContent: MessagePollContent, - informationData: MessageInformationData, - ): PollViewState { + pollResponseData: PollResponseData?, + isSent: Boolean, + ): PollItemViewState { val pollCreationInfo = pollContent.getBestPollCreationInfo() val question = pollCreationInfo?.question?.getBestQuestion().orEmpty() - val pollResponseSummary = informationData.pollResponseAggregatedSummary - val totalVotes = pollResponseSummary?.totalVotes ?: 0 + val totalVotes = pollResponseData?.totalVotes ?: 0 return when { - !informationData.sendState.isSent() -> { + !isSent -> { createSendingPollViewState(question, pollCreationInfo) } - informationData.pollResponseAggregatedSummary?.isClosed.orFalse() -> { - createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes) + pollResponseData?.isClosed.orFalse() -> { + createEndedPollViewState(question, pollCreationInfo, pollResponseData, totalVotes) } pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> { - createUndisclosedPollViewState(question, pollCreationInfo, pollResponseSummary) + createUndisclosedPollViewState(question, pollCreationInfo, pollResponseData) } - informationData.pollResponseAggregatedSummary?.myVote?.isNotEmpty().orFalse() -> { - createVotedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes) + pollResponseData?.myVote?.isNotEmpty().orFalse() -> { + createVotedPollViewState(question, pollCreationInfo, pollResponseData, totalVotes) } else -> { createReadyPollViewState(question, pollCreationInfo, totalVotes) @@ -61,8 +60,8 @@ class PollItemViewStateFactory @Inject constructor( } } - private fun createSendingPollViewState(question: String, pollCreationInfo: PollCreationInfo?): PollViewState { - return PollViewState( + private fun createSendingPollViewState(question: String, pollCreationInfo: PollCreationInfo?): PollItemViewState { + return PollItemViewState( question = question, votesStatus = stringProvider.getString(R.string.poll_no_votes_cast), canVote = false, @@ -73,51 +72,51 @@ class PollItemViewStateFactory @Inject constructor( private fun createEndedPollViewState( question: String, pollCreationInfo: PollCreationInfo?, - pollResponseSummary: PollResponseData?, + pollResponseData: PollResponseData?, totalVotes: Int, - ): PollViewState { - val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) { + ): PollItemViewState { + val totalVotesText = if (pollResponseData?.hasEncryptedRelatedEvents.orFalse()) { stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes) } - return PollViewState( + return PollItemViewState( question = question, votesStatus = totalVotesText, canVote = false, - optionViewStates = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseSummary), + optionViewStates = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData), ) } private fun createUndisclosedPollViewState( question: String, pollCreationInfo: PollCreationInfo?, - pollResponseSummary: PollResponseData? - ): PollViewState { - return PollViewState( + pollResponseData: PollResponseData? + ): PollItemViewState { + return PollItemViewState( question = question, votesStatus = stringProvider.getString(R.string.poll_undisclosed_not_ended), canVote = true, - optionViewStates = pollOptionViewStateFactory.createPollUndisclosedOptions(pollCreationInfo, pollResponseSummary), + optionViewStates = pollOptionViewStateFactory.createPollUndisclosedOptions(pollCreationInfo, pollResponseData), ) } private fun createVotedPollViewState( question: String, pollCreationInfo: PollCreationInfo?, - pollResponseSummary: PollResponseData?, + pollResponseData: PollResponseData?, totalVotes: Int - ): PollViewState { - val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) { + ): PollItemViewState { + val totalVotesText = if (pollResponseData?.hasEncryptedRelatedEvents.orFalse()) { stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes) } - return PollViewState( + return PollItemViewState( question = question, votesStatus = totalVotesText, canVote = true, - optionViewStates = pollOptionViewStateFactory.createPollVotedOptions(pollCreationInfo, pollResponseSummary), + optionViewStates = pollOptionViewStateFactory.createPollVotedOptions(pollCreationInfo, pollResponseData), ) } @@ -125,13 +124,13 @@ class PollItemViewStateFactory @Inject constructor( question: String, pollCreationInfo: PollCreationInfo?, totalVotes: Int - ): PollViewState { + ): PollItemViewState { val totalVotesText = if (totalVotes == 0) { stringProvider.getString(R.string.poll_no_votes_cast) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, totalVotes, totalVotes) } - return PollViewState( + return PollItemViewState( question = question, votesStatus = totalVotesText, canVote = true, diff --git a/vector/src/main/java/im/vector/app/features/poll/PollViewState.kt b/vector/src/main/java/im/vector/app/features/poll/PollItemViewState.kt similarity index 96% rename from vector/src/main/java/im/vector/app/features/poll/PollViewState.kt rename to vector/src/main/java/im/vector/app/features/poll/PollItemViewState.kt index ecbee7438a..e5b4f71f1d 100644 --- a/vector/src/main/java/im/vector/app/features/poll/PollViewState.kt +++ b/vector/src/main/java/im/vector/app/features/poll/PollItemViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.poll import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState -data class PollViewState( +data class PollItemViewState( val question: String, val votesStatus: String, val canVote: Boolean, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt index cbbd09f065..3389a5f473 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt @@ -16,14 +16,9 @@ package im.vector.app.features.roomprofile.polls.detail.ui -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState +import im.vector.app.features.poll.PollItemViewState data class RoomPollDetail( - val eventId: String, - val question: String, - val canVote: Boolean, - val votesStatusSummary: String, - val optionViewStates: List, - val hasBeenEdited: Boolean, val isEnded: Boolean, + val pollItemViewState: PollItemViewState, ) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt deleted file mode 100644 index efb20f9144..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2023 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.roomprofile.polls.detail.ui - -import im.vector.app.core.platform.VectorViewModelAction - -sealed interface RoomPollDetailAction : VectorViewModelAction { - -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt index 08db09a5b0..a4318151be 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt @@ -17,22 +17,29 @@ package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.epoxy.TypedEpoxyController -import im.vector.app.features.home.room.detail.timeline.item.PollItem_ import javax.inject.Inject -class RoomPollDetailController @Inject constructor() - : TypedEpoxyController() { +class RoomPollDetailController @Inject constructor() : TypedEpoxyController() { + + interface Callback { + fun vote(pollId: String, optionId: String) + } + + var callback: Callback? = null override fun buildModels(viewState: RoomPollDetailViewState?) { - viewState ?: return + val pollDetail = viewState?.pollDetail ?: return + val pollItemViewState = pollDetail.pollItemViewState + val host = this - PollItem_() - /* - .eventId(pollSummary.id) - .pollQuestion(pollSummary.title.toEpoxyCharSequence()) - .canVote(viewState.canVoteSelectedPoll()) - .optionViewStates(pollSummary.optionViewStates) - .ended(viewState.canVoteSelectedPoll().not()) - */ + roomPollDetailItem { + id(viewState.pollId) + eventId(viewState.pollId) + question(pollItemViewState.question) + canVote(pollItemViewState.canVote) + votesStatus(pollItemViewState.votesStatus) + optionViewStates(pollItemViewState.optionViewStates.orEmpty()) + callback(host.callback) + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt index 1589954b35..7d27963023 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt @@ -59,6 +59,7 @@ class RoomPollDetailFragment : VectorBaseFragment roomPollDetailController, hasFixedSize = true, ) + // TODO setup callback in controller for vote action } override fun onDestroyView() { @@ -75,15 +76,13 @@ class RoomPollDetailFragment : VectorBaseFragment setupToolbar(views.roomPollDetailToolbar) .setTitle(title) - .allowBack() + .allowBack(useCross = true) } override fun invalidate() = withState(viewModel) { state -> state.pollDetail ?: return@withState - // TODO should we update the title when the poll status changes? setupToolbar(state.pollDetail.isEnded) - - // TODO update data of the controller + roomPollDetailController.setData(state) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt new file mode 100644 index 0000000000..9a80c32640 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 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.roomprofile.polls.detail.ui + +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.features.home.room.detail.timeline.item.PollOptionView +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState + +@EpoxyModelClass +abstract class RoomPollDetailItem : VectorEpoxyModel(R.layout.item_timeline_event_poll) { + + @EpoxyAttribute + var question: String? = null + + @EpoxyAttribute + var callback: RoomPollDetailController.Callback? = null + + @EpoxyAttribute + var eventId: String? = null + + @EpoxyAttribute + var canVote: Boolean = false + + @EpoxyAttribute + var votesStatus: String? = null + + @EpoxyAttribute + lateinit var optionViewStates: List + + @EpoxyAttribute + var ended: Boolean = false + + override fun bind(holder: Holder) { + super.bind(holder) + + holder.questionTextView.text = question + holder.votesStatusTextView.text = votesStatus + holder.optionsContainer.removeAllViews() + holder.optionsContainer.isVisible = optionViewStates.isNotEmpty() + for (option in optionViewStates) { + val optionView = PollOptionView(holder.view.context) + holder.optionsContainer.addView(optionView) + optionView.render(option) + optionView.setOnClickListener { onOptionClicked(option) } + } + + holder.endedPollTextView.isVisible = false + } + + private fun onOptionClicked(optionViewState: PollOptionViewState) { + val relatedEventId = eventId + + if (canVote && relatedEventId != null) { + callback?.vote(pollId = relatedEventId, optionId = optionViewState.optionId) + } + } + + class Holder : VectorEpoxyHolder() { + val questionTextView by bind(R.id.questionTextView) + val optionsContainer by bind(R.id.optionsContainer) + val votesStatusTextView by bind(R.id.optionsVotesStatusTextView) + val endedPollTextView by bind(R.id.endedPollTextView) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt new file mode 100644 index 0000000000..931b38f243 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail.ui + +import im.vector.app.core.extensions.getVectorLastMessageContent +import im.vector.app.features.home.room.detail.timeline.factory.PollItemViewStateFactory +import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import timber.log.Timber +import javax.inject.Inject + +// TODO add unit tests +class RoomPollDetailMapper @Inject constructor( + private val pollResponseDataFactory: PollResponseDataFactory, + private val pollItemViewStateFactory: PollItemViewStateFactory, +) { + + fun map(timelineEvent: TimelineEvent): RoomPollDetail? { + val eventId = timelineEvent.root.eventId.orEmpty() + val result = runCatching { + val content = timelineEvent.getVectorLastMessageContent() + val pollResponseData = pollResponseDataFactory.create(timelineEvent) + return if (eventId.isNotEmpty() && content is MessagePollContent) { + // we assume poll message has been sent here + val pollItemViewState = pollItemViewStateFactory.create( + pollContent = content, + pollResponseData = pollResponseData, + isSent = true, + ) + RoomPollDetail( + isEnded = pollResponseData?.isClosed == true, + pollItemViewState = pollItemViewState, + ) + } else { + Timber.w("missing mandatory info about poll event with id=$eventId") + null + } + } + + if (result.isFailure) { + Timber.w("failed to map event with id $eventId") + } + return result.getOrNull() + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt index 8227a55881..e3b7631cce 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt @@ -16,16 +16,33 @@ package im.vector.app.features.roomprofile.polls.detail.ui +import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.event.GetTimelineEventUseCase +import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.auth.ReAuthState +import im.vector.app.features.auth.ReAuthViewModel import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach class RoomPollDetailViewModel @AssistedInject constructor( @Assisted initialState: RoomPollDetailViewState, private val getTimelineEventUseCase: GetTimelineEventUseCase, -) : VectorViewModel(initialState) { + private val roomPollDetailMapper: RoomPollDetailMapper, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: RoomPollDetailViewState): RoomPollDetailViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { observePollDetails( @@ -36,10 +53,12 @@ class RoomPollDetailViewModel @AssistedInject constructor( private fun observePollDetails(pollId: String, roomId: String) { getTimelineEventUseCase.execute(roomId = roomId, eventId = pollId) + .map { roomPollDetailMapper.map(it) } + .onEach { setState { copy(pollDetail = it) } } .launchIn(viewModelScope) } - override fun handle(action: RoomPollDetailAction) { - // TODO handle go to timeline action + override fun handle(action: EmptyAction) { + // do nothing for now } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt index a2906dc88f..a02db41de6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.mvrx.MavericksState +import im.vector.app.features.poll.PollItemViewState data class RoomPollDetailViewState( val pollId: String, diff --git a/vector/src/main/res/layout/fragment_room_poll_detail.xml b/vector/src/main/res/layout/fragment_room_poll_detail.xml index 7ed8d1125e..4f65b2aa0f 100644 --- a/vector/src/main/res/layout/fragment_room_poll_detail.xml +++ b/vector/src/main/res/layout/fragment_room_poll_detail.xml @@ -24,12 +24,13 @@ diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt index 512f7c8a17..bafe55f7a1 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.R import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData -import im.vector.app.features.poll.PollViewState +import im.vector.app.features.poll.PollItemViewState import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.fixtures.PollFixture.A_MESSAGE_INFORMATION_DATA import im.vector.app.test.fixtures.PollFixture.A_POLL_CONTENT @@ -57,7 +57,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast), canVote = false, @@ -90,7 +90,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_after_ended, 0, 0), canVote = false, @@ -155,7 +155,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getString(R.string.poll_undisclosed_not_ended), canVote = true, @@ -204,7 +204,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, 1, 1), canVote = true, @@ -286,7 +286,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast), canVote = true, From 922b8092aca60f224e07c37d760bb8a43cf56548 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 18:03:02 +0100 Subject: [PATCH 047/317] Render only winner options in past poll list --- .../roomprofile/polls/list/ui/PollSummaryMapper.kt | 4 +++- .../polls/list/ui/PollSummaryMapperTest.kt | 14 +++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index ea22d7d5a3..748dfd0886 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -69,7 +69,9 @@ class PollSummaryMapper @Inject constructor( creationTimestamp = creationTimestamp, title = pollTitle, totalVotes = pollResponseData.totalVotes, - winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData), + winnerOptions = pollOptionViewStateFactory + .createPollEndedOptions(pollCreationInfo, pollResponseData) + .filter { it.isWinner }, ) } else { PollSummary.ActivePoll( diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt index b523365970..2459e3c8da 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt @@ -84,10 +84,12 @@ internal class PollSummaryMapperTest { } @Test - fun `given an ended poll event when mapping to model then result is ended poll`() { + fun `given an ended poll event when mapping to model then result is ended poll with only winner options`() { // Given val totalVotes = 10 - val winnerOptions = listOf() + val option1 = givenAPollEndedOption(isWinner = false) + val option2 = givenAPollEndedOption(isWinner = true) + val winnerOptions = listOf(option1, option2) val endedPollEvent = givenAPollTimelineEvent( eventId = AN_EVENT_ID, creationTimestamp = AN_EVENT_TIMESTAMP, @@ -101,7 +103,7 @@ internal class PollSummaryMapperTest { creationTimestamp = AN_EVENT_TIMESTAMP, title = A_POLL_TITLE, totalVotes = totalVotes, - winnerOptions = winnerOptions, + winnerOptions = listOf(option2), ) // When @@ -198,4 +200,10 @@ internal class PollSummaryMapperTest { totalVotes = totalVotes, ) } + + private fun givenAPollEndedOption(isWinner: Boolean): PollOptionViewState.PollEnded { + return mockk().also { + every { it.isWinner } returns isWinner + } + } } From f855a3602209f0225c7fac382416ab0109bb3d26 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 2 Feb 2023 09:48:56 +0100 Subject: [PATCH 048/317] Adding vote action from details screen --- .../home/room/detail/TimelineViewModel.kt | 17 +++---- .../room/detail/poll/VoteToPollUseCase.kt | 51 +++++++++++++++++++ .../polls/detail/ui/RoomPollDetailAction.kt | 23 +++++++++ .../detail/ui/RoomPollDetailController.kt | 2 +- .../polls/detail/ui/RoomPollDetailFragment.kt | 25 ++++++--- .../polls/detail/ui/RoomPollDetailItem.kt | 2 +- .../detail/ui/RoomPollDetailViewModel.kt | 22 +++++--- 7 files changed, 117 insertions(+), 25 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/poll/VoteToPollUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 72d9fc8a16..5d5aae66bb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -54,6 +54,7 @@ import im.vector.app.features.crypto.verification.SupportedVerificationMethodsPr import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction import im.vector.app.features.home.room.detail.error.RoomNotFound import im.vector.app.features.home.room.detail.location.RedactLiveLocationShareEventUseCase +import im.vector.app.features.home.room.detail.poll.VoteToPollUseCase import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever @@ -90,7 +91,6 @@ import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage @@ -154,6 +154,7 @@ class TimelineViewModel @AssistedInject constructor( timelineFactory: TimelineFactory, private val spaceStateHandler: SpaceStateHandler, private val voiceBroadcastHelper: VoiceBroadcastHelper, + private val voteToPollUseCase: VoteToPollUseCase, ) : VectorViewModel(initialState), Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback { @@ -1235,15 +1236,11 @@ class TimelineViewModel @AssistedInject constructor( private fun handleVoteToPoll(action: RoomDetailAction.VoteToPoll) { if (room == null) return - // Do not allow to vote unsent local echo of the poll event - if (LocalEcho.isLocalEchoId(action.eventId)) return - // Do not allow to vote the same option twice - room.getTimelineEvent(action.eventId)?.let { pollTimelineEvent -> - val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote - if (currentVote != action.optionKey) { - room.sendService().voteToPoll(action.eventId, action.optionKey) - } - } + voteToPollUseCase.execute( + roomId = room.roomId, + pollEventId = action.eventId, + optionId = action.optionKey, + ) } private fun handleEndPoll(eventId: String) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/poll/VoteToPollUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/poll/VoteToPollUseCase.kt new file mode 100644 index 0000000000..78c647bc63 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/poll/VoteToPollUseCase.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 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.detail.poll + +import im.vector.app.core.di.ActiveSessionHolder +import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.api.session.room.getTimelineEvent +import javax.inject.Inject + +// TODO add unit tests +class VoteToPollUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun execute(roomId: String, pollEventId: String, optionId: String) { + // Do not allow to vote unsent local echo of the poll event + if (LocalEcho.isLocalEchoId(pollEventId)) return + + val room = activeSessionHolder.getActiveSession() + .roomService() + .getRoom(roomId) + + room?.getTimelineEvent(pollEventId)?.let { pollTimelineEvent -> + val currentVote = pollTimelineEvent + .annotations + ?.pollResponseSummary + ?.aggregatedContent + ?.myVote + if (currentVote != optionId) { + room.sendService().voteToPoll( + pollEventId = pollEventId, + answerId = optionId + ) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt new file mode 100644 index 0000000000..dbf8436399 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt @@ -0,0 +1,23 @@ +/* + * 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.roomprofile.polls.detail.ui + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface RoomPollDetailAction : VectorViewModelAction { + data class Vote(val pollEventId: String, val optionId: String) : RoomPollDetailAction +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt index a4318151be..61ef41b14f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt @@ -22,7 +22,7 @@ import javax.inject.Inject class RoomPollDetailController @Inject constructor() : TypedEpoxyController() { interface Callback { - fun vote(pollId: String, optionId: String) + fun vote(pollEventId: String, optionId: String) } var callback: Callback? = null diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt index 7d27963023..8340c677ef 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt @@ -40,7 +40,9 @@ data class RoomPollDetailArgs( ) : Parcelable @AndroidEntryPoint -class RoomPollDetailFragment : VectorBaseFragment() { +class RoomPollDetailFragment : + VectorBaseFragment(), + RoomPollDetailController.Callback { @Inject lateinit var roomPollDetailController: RoomPollDetailController @@ -54,17 +56,22 @@ class RoomPollDetailFragment : VectorBaseFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar() + setupDetailView() + // TODO add link to go to timeline message + create a ViewNavigator + } + override fun onDestroyView() { + roomPollDetailController.callback = null + views.pollDetailRecyclerView.cleanup() + super.onDestroyView() + } + + private fun setupDetailView() { + roomPollDetailController.callback = this views.pollDetailRecyclerView.configureWith( roomPollDetailController, hasFixedSize = true, ) - // TODO setup callback in controller for vote action - } - - override fun onDestroyView() { - views.pollDetailRecyclerView.cleanup() - super.onDestroyView() } private fun setupToolbar(isEnded: Boolean? = null) { @@ -85,4 +92,8 @@ class RoomPollDetailFragment : VectorBaseFragment setupToolbar(state.pollDetail.isEnded) roomPollDetailController.setData(state) } + + override fun vote(pollEventId: String, optionId: String) { + viewModel.handle(RoomPollDetailAction.Vote(pollEventId = pollEventId, optionId = optionId)) + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt index 9a80c32640..4420776a47 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt @@ -72,7 +72,7 @@ abstract class RoomPollDetailItem : VectorEpoxyModel( val relatedEventId = eventId if (canVote && relatedEventId != null) { - callback?.vote(pollId = relatedEventId, optionId = optionViewState.optionId) + callback?.vote(pollEventId = relatedEventId, optionId = optionViewState.optionId) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt index e3b7631cce..d76d0f7279 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt @@ -23,19 +23,19 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.event.GetTimelineEventUseCase -import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.auth.ReAuthState -import im.vector.app.features.auth.ReAuthViewModel +import im.vector.app.features.home.room.detail.poll.VoteToPollUseCase import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +// TODO add unit tests class RoomPollDetailViewModel @AssistedInject constructor( @Assisted initialState: RoomPollDetailViewState, private val getTimelineEventUseCase: GetTimelineEventUseCase, private val roomPollDetailMapper: RoomPollDetailMapper, -) : VectorViewModel(initialState) { + private val voteToPollUseCase: VoteToPollUseCase, +) : VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -58,7 +58,17 @@ class RoomPollDetailViewModel @AssistedInject constructor( .launchIn(viewModelScope) } - override fun handle(action: EmptyAction) { - // do nothing for now + override fun handle(action: RoomPollDetailAction) { + when (action) { + is RoomPollDetailAction.Vote -> handleVote(action) + } + } + + private fun handleVote(vote: RoomPollDetailAction.Vote) = withState { state -> + voteToPollUseCase.execute( + roomId = state.roomId, + pollEventId = vote.pollEventId, + optionId = vote.optionId, + ) } } From eaa9cc740ea277bf89694f932f06ac239fd25985 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 2 Feb 2023 11:16:24 +0100 Subject: [PATCH 049/317] Make the title set at the creation of the screen --- .../polls/detail/ui/RoomPollDetailActivity.kt | 3 ++- .../polls/detail/ui/RoomPollDetailFragment.kt | 9 +++------ .../roomprofile/polls/list/ui/RoomPollsListFragment.kt | 1 + .../roomprofile/polls/list/ui/RoomPollsListNavigator.kt | 3 ++- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt index ed89526349..cf29d5618a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt @@ -47,11 +47,12 @@ class RoomPollDetailActivity : VectorBaseActivity() { } companion object { - fun newIntent(context: Context, pollId: String, roomId: String): Intent { + fun newIntent(context: Context, pollId: String, roomId: String, isEnded: Boolean): Intent { return Intent(context, RoomPollDetailActivity::class.java).apply { val args = RoomPollDetailArgs( pollId = pollId, roomId = roomId, + isEnded = isEnded, ) putExtra(Mavericks.KEY_ARG, args) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt index 8340c677ef..4e2c7461f7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt @@ -37,6 +37,7 @@ import javax.inject.Inject data class RoomPollDetailArgs( val pollId: String, val roomId: String, + val isEnded: Boolean, ) : Parcelable @AndroidEntryPoint @@ -55,7 +56,7 @@ class RoomPollDetailFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupToolbar() + setupToolbar(isEnded = roomPollDetailArgs.isEnded) setupDetailView() // TODO add link to go to timeline message + create a ViewNavigator } @@ -74,11 +75,10 @@ class RoomPollDetailFragment : ) } - private fun setupToolbar(isEnded: Boolean? = null) { + private fun setupToolbar(isEnded: Boolean) { val title = when (isEnded) { true -> getString(R.string.room_polls_ended) false -> getString(R.string.room_polls_active) - else -> "" } setupToolbar(views.roomPollDetailToolbar) @@ -87,9 +87,6 @@ class RoomPollDetailFragment : } override fun invalidate() = withState(viewModel) { state -> - state.pollDetail ?: return@withState - // TODO should we update the title when the poll status changes? - setupToolbar(state.pollDetail.isEnded) roomPollDetailController.setData(state) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index bbf058d908..4324dcf124 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -132,6 +132,7 @@ abstract class RoomPollsListFragment : context = requireContext(), pollId = pollId, roomId = it.roomId, + isEnded = getRoomPollsType() == RoomPollsType.ENDED, ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt index 3cba2fc355..c377d21490 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt @@ -23,12 +23,13 @@ import javax.inject.Inject // TODO add unit tests class RoomPollsListNavigator @Inject constructor() { - fun goToPollDetails(context: Context, pollId: String, roomId: String) { + fun goToPollDetails(context: Context, pollId: String, roomId: String, isEnded: Boolean) { context.startActivity( RoomPollDetailActivity.newIntent( context = context, pollId = pollId, roomId = roomId, + isEnded = isEnded, ) ) } From 384e7f674d9ac1fad4e7f08a627b9ea35ed5c09e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 2 Feb 2023 15:55:43 +0100 Subject: [PATCH 050/317] Adding go to timeline event button --- .../src/main/res/values/strings.xml | 1 + .../domain/GetEndedPollEventIdUseCase.kt | 38 ++++++++++++++ .../polls/detail/ui/RoomPollDetail.kt | 1 + .../detail/ui/RoomPollDetailController.kt | 14 ++++++ .../polls/detail/ui/RoomPollDetailFragment.kt | 11 +++- .../polls/detail/ui/RoomPollDetailMapper.kt | 50 +++++++++++++++---- .../detail/ui/RoomPollDetailNavigator.kt | 36 +++++++++++++ .../detail/ui/RoomPollGoToTimelineItem.kt | 42 ++++++++++++++++ .../res/layout/item_poll_go_to_timeline.xml | 20 ++++++++ 9 files changed, 203 insertions(+), 10 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/domain/GetEndedPollEventIdUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailNavigator.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollGoToTimelineItem.kt create mode 100644 vector/src/main/res/layout/item_poll_go_to_timeline.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index e690f06bbb..1e540b3360 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3207,6 +3207,7 @@ Displaying polls Load more polls Error fetching polls. + View poll in timeline Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/domain/GetEndedPollEventIdUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/domain/GetEndedPollEventIdUseCase.kt new file mode 100644 index 0000000000..e32c1be963 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/domain/GetEndedPollEventIdUseCase.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail.domain + +import im.vector.app.core.di.ActiveSessionHolder +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.isPollEnd +import javax.inject.Inject + +// TODO add unit tests +class GetEndedPollEventIdUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun execute(roomId: String, startPollEventId: String): String? { + return activeSessionHolder.getActiveSession() + .roomService() + .getRoom(roomId) + ?.timelineService() + ?.getTimelineEventsRelatedTo(RelationType.REFERENCE, startPollEventId) + ?.find { it.root.isPollEnd() } + ?.eventId + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt index 3389a5f473..91d52ca08d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt @@ -20,5 +20,6 @@ import im.vector.app.features.poll.PollItemViewState data class RoomPollDetail( val isEnded: Boolean, + val endedPollEventId: String?, val pollItemViewState: PollItemViewState, ) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt index 61ef41b14f..d2c5dfb8b1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt @@ -17,12 +17,14 @@ package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.epoxy.TypedEpoxyController +import java.util.UUID import javax.inject.Inject class RoomPollDetailController @Inject constructor() : TypedEpoxyController() { interface Callback { fun vote(pollEventId: String, optionId: String) + fun goToTimelineEvent(eventId: String) } var callback: Callback? = null @@ -41,5 +43,17 @@ class RoomPollDetailController @Inject constructor() : TypedEpoxyController(), RoomPollDetailController.Callback { + @Inject lateinit var viewNavigator: RoomPollDetailNavigator @Inject lateinit var roomPollDetailController: RoomPollDetailController private val viewModel: RoomPollDetailViewModel by fragmentViewModel() @@ -58,7 +60,6 @@ class RoomPollDetailFragment : super.onViewCreated(view, savedInstanceState) setupToolbar(isEnded = roomPollDetailArgs.isEnded) setupDetailView() - // TODO add link to go to timeline message + create a ViewNavigator } override fun onDestroyView() { @@ -93,4 +94,12 @@ class RoomPollDetailFragment : override fun vote(pollEventId: String, optionId: String) { viewModel.handle(RoomPollDetailAction.Vote(pollEventId = pollEventId, optionId = optionId)) } + + override fun goToTimelineEvent(eventId: String) = withState(viewModel) { state -> + viewNavigator.goToTimelineEvent( + context = requireContext(), + roomId = state.roomId, + eventId = eventId, + ) + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt index 931b38f243..7818703716 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt @@ -19,6 +19,9 @@ package im.vector.app.features.roomprofile.polls.detail.ui import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.features.home.room.detail.timeline.factory.PollItemViewStateFactory import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory +import im.vector.app.features.home.room.detail.timeline.item.PollResponseData +import im.vector.app.features.roomprofile.polls.detail.domain.GetEndedPollEventIdUseCase +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import timber.log.Timber @@ -28,6 +31,7 @@ import javax.inject.Inject class RoomPollDetailMapper @Inject constructor( private val pollResponseDataFactory: PollResponseDataFactory, private val pollItemViewStateFactory: PollItemViewStateFactory, + private val getEndedPollEventIdUseCase: GetEndedPollEventIdUseCase, ) { fun map(timelineEvent: TimelineEvent): RoomPollDetail? { @@ -36,16 +40,13 @@ class RoomPollDetailMapper @Inject constructor( val content = timelineEvent.getVectorLastMessageContent() val pollResponseData = pollResponseDataFactory.create(timelineEvent) return if (eventId.isNotEmpty() && content is MessagePollContent) { - // we assume poll message has been sent here - val pollItemViewState = pollItemViewStateFactory.create( - pollContent = content, - pollResponseData = pollResponseData, - isSent = true, - ) - RoomPollDetail( - isEnded = pollResponseData?.isClosed == true, - pollItemViewState = pollItemViewState, + val isPollEnded = pollResponseData?.isClosed.orFalse() + val endedPollEventId = getEndedPollEventId( + isPollEnded, + startPollEventId = eventId, + roomId = timelineEvent.roomId, ) + convertToRoomPollDetail(content, pollResponseData, isPollEnded, endedPollEventId) } else { Timber.w("missing mandatory info about poll event with id=$eventId") null @@ -57,4 +58,35 @@ class RoomPollDetailMapper @Inject constructor( } return result.getOrNull() } + + private fun convertToRoomPollDetail( + content: MessagePollContent, + pollResponseData: PollResponseData?, + isPollEnded: Boolean, + endedPollEventId: String?, + ): RoomPollDetail { + // we assume the poll has been sent + val pollItemViewState = pollItemViewStateFactory.create( + pollContent = content, + pollResponseData = pollResponseData, + isSent = true, + ) + return RoomPollDetail( + isEnded = isPollEnded, + pollItemViewState = pollItemViewState, + endedPollEventId = endedPollEventId, + ) + } + + private fun getEndedPollEventId( + isPollEnded: Boolean, + startPollEventId: String, + roomId: String, + ): String? { + return if (isPollEnded) { + getEndedPollEventIdUseCase.execute(startPollEventId = startPollEventId, roomId = roomId) + } else { + null + } + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailNavigator.kt new file mode 100644 index 0000000000..402b9c4468 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailNavigator.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail.ui + +import android.content.Context +import im.vector.app.features.navigation.Navigator +import javax.inject.Inject + +// TODO add unit tests +class RoomPollDetailNavigator @Inject constructor( + private val navigator: Navigator, +) { + + fun goToTimelineEvent(context: Context, roomId: String, eventId: String) { + navigator.openRoom( + context = context, + roomId = roomId, + eventId = eventId, + buildTask = true, + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollGoToTimelineItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollGoToTimelineItem.kt new file mode 100644 index 0000000000..59a5539a4f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollGoToTimelineItem.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 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.roomprofile.polls.detail.ui + +import android.widget.Button +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick + +@EpoxyModelClass +abstract class RoomPollGoToTimelineItem : VectorEpoxyModel(R.layout.item_poll_go_to_timeline) { + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var clickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.goToTimelineButton.onClick(clickListener) + } + + class Holder : VectorEpoxyHolder() { + val goToTimelineButton by bind