Skip to content

Commit e42075a

Browse files
authored
Merge pull request #8983 from element-hq/feature/bma/sunsetApplication
Sunset application - first step
2 parents 037958f + 0e4c39b commit e42075a

22 files changed

+482
-18
lines changed

library/ui-strings/src/main/res/values/strings.xml

+6
Original file line numberDiff line numberDiff line change
@@ -3641,4 +3641,10 @@
36413641
<string name="pill_message_in_room">Message in %s</string>
36423642
<string name="pill_message_in_unknown_room">Message in room</string>
36433643
<string name="pill_message_unknown_room_or_space">Room/Space</string>
3644+
3645+
<string name="error_mas_not_supported_title">You can no longer create an account with %1$s using this app</string>
3646+
<string name="error_mas_not_supported_subtitle">Download %1$s to use %2$s for your account or choose a different homeserver.</string>
3647+
<string name="view_download_replacement_app_title">Download %1$s</string>
3648+
<string name="view_download_replacement_app_subtitle">Faster, more secure, and packed with powerful collaboration tools.</string>
3649+
36443650
</resources>

library/ui-styles/src/main/res/values/colors.xml

+21
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,25 @@
158158
<color name="vctr_rich_text_editor_menu_button_background_light">#EEF8F4</color>
159159
<color name="vctr_rich_text_editor_menu_button_background_dark">#1D292A</color>
160160

161+
<!-- Other colors -->
162+
<attr name="vctr_bg_critical_subtle" format="color" />
163+
<color name="vctr_bg_critical_subtle_light">#FFF7F6</color>
164+
<color name="vctr_bg_critical_subtle_dark">#3E0000</color>
165+
166+
<attr name="vctr_border_critical_subtle" format="color" />
167+
<color name="vctr_border_critical_subtle_light">#FFC5BC</color>
168+
<color name="vctr_border_critical_subtle_dark">#710000</color>
169+
170+
<attr name= "vctr_icon_critical_primary" format="color" />
171+
<color name="vctr_icon_critical_primary_light">#D51928</color>
172+
<color name="vctr_icon_critical_primary_dark">#FD3E3C</color>
173+
174+
<attr name= "vctr_text_critical_primary" format="color" />
175+
<color name="vctr_text_critical_primary_light">#D51928</color>
176+
<color name="vctr_text_critical_primary_dark">#FD3E3C</color>
177+
178+
<attr name= "vctr_text_primary" format="color" />
179+
<color name="vctr_text_primary_light">#1B1D22</color>
180+
<color name="vctr_text_primary_dark">#EBEEF2</color>
181+
161182
</resources>

library/ui-styles/src/main/res/values/theme_dark.xml

+7
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@
5555
<item name="vctr_list_separator_on_surface">?vctr_system</item>
5656
<item name="vctr_unread_background">?vctr_notice_secondary</item>
5757

58+
<!-- Other colors -->
59+
<item name="vctr_bg_critical_subtle" >@color/vctr_bg_critical_subtle_dark</item>
60+
<item name="vctr_border_critical_subtle" >@color/vctr_border_critical_subtle_dark</item>
61+
<item name= "vctr_icon_critical_primary" >@color/vctr_icon_critical_primary_dark</item>
62+
<item name= "vctr_text_critical_primary" >@color/vctr_text_critical_primary_dark</item>
63+
<item name= "vctr_text_primary" >@color/vctr_text_primary_dark</item>
64+
5865
<!-- Material color -->
5966
<item name="colorPrimary">@color/element_accent_dark</item>
6067
<item name="colorPrimaryVariant">@color/element_accent_dark</item>

library/ui-styles/src/main/res/values/theme_light.xml

+7
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@
5555
<item name="vctr_list_separator_on_surface">?vctr_system</item>
5656
<item name="vctr_unread_background">?vctr_notice_secondary</item>
5757

58+
<!-- Other colors -->
59+
<item name="vctr_bg_critical_subtle" >@color/vctr_bg_critical_subtle_light</item>
60+
<item name="vctr_border_critical_subtle" >@color/vctr_border_critical_subtle_light</item>
61+
<item name= "vctr_icon_critical_primary" >@color/vctr_icon_critical_primary_light</item>
62+
<item name= "vctr_text_critical_primary" >@color/vctr_text_critical_primary_light</item>
63+
<item name= "vctr_text_primary" >@color/vctr_text_primary_light</item>
64+
5865
<!-- Material color -->
5966
<item name="colorPrimary">@color/element_accent_light</item>
6067
<item name="colorPrimaryVariant">@color/element_accent_light</item>

vector-config/src/main/java/im/vector/app/config/Config.kt

+11
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,15 @@ object Config {
9797
val ER_DEBUG_ANALYTICS_CONFIG = DEBUG_ANALYTICS_CONFIG.copy(sentryEnvironment = "element-r")
9898

9999
val SHOW_UNVERIFIED_SESSIONS_ALERT_AFTER_MILLIS = 7.days.inWholeMilliseconds // 1 Week
100+
101+
/**
102+
* Sunsetting the application.
103+
* Fork maintainers can use this to inform users about their new application if any. Note that you probably also want
104+
* to replace the resource `replacement_app_icon` too.
105+
*/
106+
val sunsetConfig: SunsetConfig = SunsetConfig.Enabled(
107+
learnMoreLink = "https://element.io/app-for-productivity",
108+
replacementApplicationName = "Element X",
109+
replacementApplicationId = "io.element.android.x",
110+
)
100111
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2025 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.config
18+
19+
sealed interface SunsetConfig {
20+
/**
21+
* Sunsetting the application is disabled.
22+
*/
23+
data object Disabled : SunsetConfig
24+
25+
/**
26+
* Sunsetting the application is enabled and can be configured by implementing this class.
27+
*/
28+
data class Enabled(
29+
/**
30+
* The URL target to learn more.
31+
*/
32+
val learnMoreLink: String,
33+
34+
/**
35+
* The replacement application ID.
36+
* Example: for Element application, the replacement application ID is the id of Element X: "Element X".
37+
*/
38+
val replacementApplicationName: String,
39+
40+
/**
41+
* The replacement application ID.
42+
* Example: for Element App, the replacement application ID is the id of Element X: "io.element.android.x".
43+
*/
44+
val replacementApplicationId: String,
45+
) : SunsetConfig
46+
}

vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt

+12-3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import androidx.core.app.ShareCompat
3232
import androidx.core.content.FileProvider
3333
import androidx.core.content.getSystemService
3434
import im.vector.app.R
35+
import im.vector.app.core.resources.BuildMeta
3536
import im.vector.app.features.notifications.NotificationUtils
3637
import im.vector.app.features.themes.ThemeUtils
3738
import im.vector.lib.strings.CommonStrings
@@ -367,13 +368,21 @@ private fun addToGallery(savedFile: File, mediaMimeType: String?, context: Conte
367368
}
368369

369370
/**
370-
* Open the play store to the provided application Id, default to this app.
371+
* Open the play store or the F-Droid to the provided application Id, default to this app.
371372
*/
372-
fun openPlayStore(activity: Activity, appId: String) {
373+
fun openApplicationStore(
374+
activity: Activity,
375+
buildMeta: BuildMeta,
376+
appId: String = buildMeta.applicationId,
377+
) {
373378
try {
374379
activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$appId")))
375380
} catch (activityNotFoundException: ActivityNotFoundException) {
376-
activity.safeStartActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$appId")))
381+
if (buildMeta.flavorDescription == "FDroid") {
382+
activity.safeStartActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://f-droid.org/packages/$appId")))
383+
} else {
384+
activity.safeStartActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$appId")))
385+
}
377386
}
378387
}
379388

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright (c) 2025 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.onboarding
18+
19+
class MasSupportRequiredException : Exception("Please use replacement app")

vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt

+24-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import com.airbnb.mvrx.MavericksViewModelFactory
1212
import dagger.assisted.Assisted
1313
import dagger.assisted.AssistedFactory
1414
import dagger.assisted.AssistedInject
15+
import im.vector.app.config.Config
16+
import im.vector.app.config.SunsetConfig
1517
import im.vector.app.core.di.ActiveSessionHolder
1618
import im.vector.app.core.di.MavericksAssistedViewModelFactory
1719
import im.vector.app.core.di.hiltMavericksViewModelFactory
@@ -761,7 +763,13 @@ class OnboardingViewModel @AssistedInject constructor(
761763
}
762764
OnboardingFlow.SignUp -> {
763765
updateSignMode(SignMode.SignUp)
764-
internalRegisterAction(RegisterAction.StartRegistration)
766+
if (authResult.selectedHomeserver.hasOidcCompatibilityFlow && Config.sunsetConfig is SunsetConfig.Enabled) {
767+
// Navigate to the screen to create an account, it will show the error
768+
setState { copy(isLoading = false) }
769+
_viewEvents.post(OnboardingViewEvents.OpenCombinedRegister)
770+
} else {
771+
internalRegisterAction(RegisterAction.StartRegistration)
772+
}
765773
}
766774
OnboardingFlow.SignInSignUp,
767775
null -> {
@@ -775,9 +783,17 @@ class OnboardingViewModel @AssistedInject constructor(
775783

776784
private suspend fun onHomeServerEdited(config: HomeServerConnectionConfig, serverTypeOverride: ServerType?, authResult: StartAuthenticationResult) {
777785
when (awaitState().onboardingFlow) {
778-
OnboardingFlow.SignUp -> internalRegisterAction(RegisterAction.StartRegistration) {
779-
updateServerSelection(config, serverTypeOverride, authResult)
780-
_viewEvents.post(OnboardingViewEvents.OnHomeserverEdited)
786+
OnboardingFlow.SignUp -> {
787+
if (authResult.selectedHomeserver.hasOidcCompatibilityFlow && Config.sunsetConfig is SunsetConfig.Enabled) {
788+
// An error is displayed now
789+
setState { copy(isLoading = false) }
790+
_viewEvents.post(OnboardingViewEvents.Failure(MasSupportRequiredException()))
791+
} else {
792+
internalRegisterAction(RegisterAction.StartRegistration) {
793+
updateServerSelection(config, serverTypeOverride, authResult)
794+
_viewEvents.post(OnboardingViewEvents.OnHomeserverEdited)
795+
}
796+
}
781797
}
782798
OnboardingFlow.SignIn -> {
783799
updateServerSelection(config, serverTypeOverride, authResult)
@@ -924,7 +940,10 @@ private fun LoginMode.supportsSignModeScreen(): Boolean {
924940
return when (this) {
925941
LoginMode.Password,
926942
is LoginMode.SsoAndPassword -> true
927-
is LoginMode.Sso,
943+
is LoginMode.Sso -> {
944+
// In this case, an error will be displayed in the next screen
945+
hasOidcCompatibilityFlow
946+
}
928947
LoginMode.Unknown,
929948
LoginMode.Unsupported -> false
930949
}

vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt

+52-1
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ import android.os.Bundle
1212
import android.view.LayoutInflater
1313
import android.view.View
1414
import android.view.ViewGroup
15+
import android.widget.TextView
1516
import androidx.autofill.HintConstants
1617
import androidx.core.text.isDigitsOnly
1718
import androidx.core.view.isVisible
1819
import androidx.lifecycle.lifecycleScope
1920
import com.airbnb.mvrx.withState
2021
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2122
import dagger.hilt.android.AndroidEntryPoint
23+
import im.vector.app.R
24+
import im.vector.app.config.Config
25+
import im.vector.app.config.SunsetConfig
2226
import im.vector.app.core.extensions.clearErrorOnChange
2327
import im.vector.app.core.extensions.content
2428
import im.vector.app.core.extensions.editText
@@ -31,6 +35,9 @@ import im.vector.app.core.extensions.realignPercentagesToParent
3135
import im.vector.app.core.extensions.setOnFocusLostListener
3236
import im.vector.app.core.extensions.setOnImeDoneListener
3337
import im.vector.app.core.extensions.toReducedUrl
38+
import im.vector.app.core.resources.BuildMeta
39+
import im.vector.app.core.utils.openApplicationStore
40+
import im.vector.app.core.utils.openUrlInChromeCustomTab
3441
import im.vector.app.databinding.FragmentFtueCombinedRegisterBinding
3542
import im.vector.app.features.login.LoginMode
3643
import im.vector.app.features.login.SSORedirectRouterActivity
@@ -52,12 +59,14 @@ import org.matrix.android.sdk.api.failure.isRegistrationDisabled
5259
import org.matrix.android.sdk.api.failure.isUsernameInUse
5360
import org.matrix.android.sdk.api.failure.isWeakPassword
5461
import reactivecircus.flowbinding.android.widget.textChanges
62+
import javax.inject.Inject
5563

5664
private const val MINIMUM_PASSWORD_LENGTH = 8
5765

5866
@AndroidEntryPoint
5967
class FtueAuthCombinedRegisterFragment :
6068
AbstractSSOFtueAuthFragment<FragmentFtueCombinedRegisterBinding>() {
69+
@Inject lateinit var buildMeta: BuildMeta
6170

6271
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedRegisterBinding {
6372
return FragmentFtueCombinedRegisterBinding.inflate(inflater, container, false)
@@ -181,7 +190,8 @@ class FtueAuthCombinedRegisterFragment :
181190
}
182191

183192
private fun setupUi(state: OnboardingViewState) {
184-
views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl()
193+
val serverName = state.selectedHomeserver.userFacingUrl.toReducedUrl()
194+
views.selectedServerName.text = serverName
185195

186196
if (state.isLoading) {
187197
// Ensure password is hidden
@@ -201,6 +211,47 @@ class FtueAuthCombinedRegisterFragment :
201211
is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode)
202212
else -> hideSsoProviders()
203213
}
214+
215+
(Config.sunsetConfig as? SunsetConfig.Enabled)?.let { config ->
216+
val isMasSupportRequired = state.selectedHomeserver.hasOidcCompatibilityFlow
217+
views.serverSelectionSpacing.isVisible = !isMasSupportRequired
218+
views.serverSelectionDivider.isVisible = !isMasSupportRequired
219+
views.chooseServerCardErrorMas.isVisible = isMasSupportRequired
220+
views.chooseServerCardDownloadReplacementApp.isVisible = isMasSupportRequired
221+
222+
if (isMasSupportRequired) {
223+
views.chooseServerCardErrorMas.findViewById<TextView>(R.id.view_card_error_title).text =
224+
getString(CommonStrings.error_mas_not_supported_title, serverName)
225+
views.chooseServerCardErrorMas.findViewById<TextView>(R.id.view_card_error_subtitle).text =
226+
getString(
227+
CommonStrings.error_mas_not_supported_subtitle,
228+
config.replacementApplicationName,
229+
serverName,
230+
)
231+
views.chooseServerCardDownloadReplacementApp.findViewById<TextView>(R.id.view_download_replacement_app_title).text =
232+
getString(CommonStrings.view_download_replacement_app_title, config.replacementApplicationName)
233+
234+
views.chooseServerCardDownloadReplacementApp.debouncedClicks {
235+
openApplicationStore(
236+
activity = requireActivity(),
237+
buildMeta = buildMeta,
238+
appId = config.replacementApplicationId,
239+
)
240+
}
241+
views.chooseServerCardDownloadReplacementApp.findViewById<View>(R.id.view_download_replacement_app_learn_more)?.debouncedClicks {
242+
openUrlInChromeCustomTab(
243+
context = requireContext(),
244+
session = null,
245+
url = config.learnMoreLink,
246+
)
247+
}
248+
249+
// Disable form
250+
views.createAccountInput.isEnabled = false
251+
views.createAccountPasswordInput.isEnabled = false
252+
views.createAccountSubmit.isEnabled = false
253+
}
254+
}
204255
}
205256

206257
private fun renderSsoProviders(deviceId: String?, loginMode: LoginMode) {

0 commit comments

Comments
 (0)