Merge pull request #4727 from vector-im/feature/adm/splash-carousel

FTUE Splash carousel MVP
This commit is contained in:
Adam Brown 2022-01-07 13:36:39 +00:00 committed by GitHub
commit 28f6d10af9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 792 additions and 18 deletions

View file

@ -50,6 +50,17 @@ It's also possible for any icon to go to the main component by right-clicking on
- open the created vector drawable - open the created vector drawable
- optionally update the color(s) to "#FF0000" (red) to ensure that the drawable is correctly tinted at runtime. - optionally update the color(s) to "#FF0000" (red) to ensure that the drawable is correctly tinted at runtime.
### Images
Android 4.3 (18+) fully supports the WebP image format which can often provide smaller image sizes without drastically impacting image quality (depending on the output encoding quality).
When importing non vector images, WebP is the preferred format.
Images can be converted to the WebP within Android Studio by
- right clicking the image file within the project file explorer
- select `Convert to WebP`
https://developer.android.com/studio/write/convert-webp
## Figma links ## Figma links
Figma links can be included in the layout, for future reference, but it is also OK to add a paragraph below here, to centralize the information Figma links can be included in the layout, for future reference, but it is also OK to add a paragraph below here, to centralize the information

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:endColor="#3372C7DA"
android:startColor="#33BBE7CF" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:endColor="#33B972DA"
android:startColor="#3372C7DA" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:endColor="#330DBD8B"
android:startColor="#33B972DA" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:endColor="#33BBE7CF"
android:startColor="#330DBD8B" />
</shape>

View file

@ -2,4 +2,8 @@
<resources> <resources>
<!-- Navigation Drawer --> <!-- Navigation Drawer -->
<dimen name="navigation_drawer_max_width">400dp</dimen> <dimen name="navigation_drawer_max_width">400dp</dimen>
<!-- Onboarding -->
<item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.25</item>
<item name="ftue_auth_gutter_end_percent" format="float" type="dimen">0.75</item>
</resources> </resources>

View file

@ -47,4 +47,8 @@
<dimen name="composer_min_height">56dp</dimen> <dimen name="composer_min_height">56dp</dimen>
<dimen name="composer_attachment_size">52dp</dimen> <dimen name="composer_attachment_size">52dp</dimen>
<dimen name="composer_attachment_margin">1dp</dimen> <dimen name="composer_attachment_margin">1dp</dimen>
<!-- Onboarding -->
<item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item>
<item name="ftue_auth_gutter_end_percent" format="float" type="dimen">0.95</item>
</resources> </resources>

View file

@ -16,8 +16,11 @@
package im.vector.app.features.debug.features package im.vector.app.features.debug.features
import androidx.datastore.preferences.core.Preferences
import im.vector.app.features.DefaultVectorFeatures import im.vector.app.features.DefaultVectorFeatures
import im.vector.app.features.VectorFeatures
import javax.inject.Inject import javax.inject.Inject
import kotlin.reflect.KFunction1
class DebugFeaturesStateFactory @Inject constructor( class DebugFeaturesStateFactory @Inject constructor(
private val debugFeatures: DebugVectorFeatures, private val debugFeatures: DebugVectorFeatures,
@ -31,18 +34,23 @@ class DebugFeaturesStateFactory @Inject constructor(
featureOverride = debugFeatures.onboardingVariant(), featureOverride = debugFeatures.onboardingVariant(),
featureDefault = defaultFeatures.onboardingVariant() featureDefault = defaultFeatures.onboardingVariant()
), ),
createBooleanFeature(
Feature.BooleanFeature(
label = "FTUE Splash - I already have an account", label = "FTUE Splash - I already have an account",
featureOverride = debugFeatures.isAlreadyHaveAccountSplashEnabled().takeIf { factory = VectorFeatures::isAlreadyHaveAccountSplashEnabled,
debugFeatures.hasOverride(DebugFeatureKeys.alreadyHaveAnAccount)
},
featureDefault = defaultFeatures.isAlreadyHaveAccountSplashEnabled(),
key = DebugFeatureKeys.alreadyHaveAnAccount key = DebugFeatureKeys.alreadyHaveAnAccount
) )
)) ))
} }
private fun createBooleanFeature(key: Preferences.Key<Boolean>, label: String, factory: KFunction1<VectorFeatures, Boolean>): Feature {
return Feature.BooleanFeature(
label = label,
featureOverride = factory.invoke(debugFeatures).takeIf { debugFeatures.hasOverride(key) },
featureDefault = factory.invoke(defaultFeatures),
key = key
)
}
private inline fun <reified T : Enum<T>> createEnumFeature(label: String, featureOverride: T, featureDefault: T): Feature { private inline fun <reified T : Enum<T>> createEnumFeature(label: String, featureOverride: T, featureDefault: T): Feature {
return Feature.EnumFeature( return Feature.EnumFeature(
label = label, label = label,

View file

@ -43,9 +43,11 @@ class DebugVectorFeatures(
return readPreferences().getEnum<VectorFeatures.OnboardingVariant>() ?: vectorFeatures.onboardingVariant() return readPreferences().getEnum<VectorFeatures.OnboardingVariant>() ?: vectorFeatures.onboardingVariant()
} }
override fun isAlreadyHaveAccountSplashEnabled(): Boolean = readPreferences()[DebugFeatureKeys.alreadyHaveAnAccount] override fun isAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.alreadyHaveAnAccount)
?: vectorFeatures.isAlreadyHaveAccountSplashEnabled() ?: vectorFeatures.isAlreadyHaveAccountSplashEnabled()
override fun isSplashCarouselEnabled(): Boolean = read(DebugFeatureKeys.splashCarousel) ?: vectorFeatures.isSplashCarouselEnabled()
fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences { fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
if (value == null) { if (value == null) {
it.remove(key) it.remove(key)
@ -66,6 +68,8 @@ class DebugVectorFeatures(
} }
} }
private fun read(key: Preferences.Key<Boolean>): Boolean? = readPreferences()[key]
private fun readPreferences() = runBlocking { dataStore.data.first() } private fun readPreferences() = runBlocking { dataStore.data.first() }
private fun updatePreferences(block: (MutablePreferences) -> Unit) = runBlocking { private fun updatePreferences(block: (MutablePreferences) -> Unit) = runBlocking {
@ -92,6 +96,6 @@ private inline fun <reified T : Enum<T>> enumPreferencesKey() = enumPreferencesK
private fun <T : Enum<T>> enumPreferencesKey(type: KClass<T>) = stringPreferencesKey("enum-${type.simpleName}") private fun <T : Enum<T>> enumPreferencesKey(type: KClass<T>) = stringPreferencesKey("enum-${type.simpleName}")
object DebugFeatureKeys { object DebugFeatureKeys {
val alreadyHaveAnAccount = booleanPreferencesKey("already-have-an-account") val alreadyHaveAnAccount = booleanPreferencesKey("already-have-an-account")
val splashCarousel = booleanPreferencesKey("splash-carousel")
} }

View file

@ -102,6 +102,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordMailConfi
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordSuccessFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordSuccessFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthServerSelectionFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthServerSelectionFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthSignUpSignInSelectionFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthSignUpSignInSelectionFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashCarouselFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment
@ -443,6 +444,11 @@ interface FragmentModule {
@FragmentKey(FtueAuthSplashFragment::class) @FragmentKey(FtueAuthSplashFragment::class)
fun bindFtueAuthSplashFragment(fragment: FtueAuthSplashFragment): Fragment fun bindFtueAuthSplashFragment(fragment: FtueAuthSplashFragment): Fragment
@Binds
@IntoMap
@FragmentKey(FtueAuthSplashCarouselFragment::class)
fun bindFtueAuthSplashCarouselFragment(fragment: FtueAuthSplashCarouselFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(FtueAuthWaitForEmailFragment::class) @FragmentKey(FtueAuthWaitForEmailFragment::class)

View file

@ -24,19 +24,17 @@ interface VectorFeatures {
fun isAlreadyHaveAccountSplashEnabled(): Boolean fun isAlreadyHaveAccountSplashEnabled(): Boolean
fun isSplashCarouselEnabled(): Boolean
enum class OnboardingVariant { enum class OnboardingVariant {
LEGACY, LEGACY,
LOGIN_2, LOGIN_2,
FTUE_AUTH FTUE_AUTH
} }
enum class NotificationSettingsVersion {
V1,
V2
}
} }
class DefaultVectorFeatures : VectorFeatures { class DefaultVectorFeatures : VectorFeatures {
override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT
override fun isAlreadyHaveAccountSplashEnabled() = true override fun isAlreadyHaveAccountSplashEnabled() = true
override fun isSplashCarouselEnabled() = false
} }

View file

@ -36,7 +36,8 @@ class OnboardingVariantFactory @Inject constructor(
views = views, views = views,
onboardingViewModel = onboardingViewModel.value, onboardingViewModel = onboardingViewModel.value,
activity = activity, activity = activity,
supportFragmentManager = activity.supportFragmentManager supportFragmentManager = activity.supportFragmentManager,
vectorFeatures = vectorFeatures
) )
VectorFeatures.OnboardingVariant.LOGIN_2 -> Login2Variant( VectorFeatures.OnboardingVariant.LOGIN_2 -> Login2Variant(
views = views, views = views,

View file

@ -0,0 +1,105 @@
/*
* Copyright (c) 2021 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.onboarding.ftueauth
import android.annotation.SuppressLint
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.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayoutMediator
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.databinding.FragmentFtueSplashCarouselBinding
import im.vector.app.features.VectorFeatures
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingFlow
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.failure.Failure
import java.net.UnknownHostException
import javax.inject.Inject
class FtueAuthSplashCarouselFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val vectorFeatures: VectorFeatures,
private val carouselController: SplashCarouselController
) : AbstractFtueAuthFragment<FragmentFtueSplashCarouselBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueSplashCarouselBinding {
return FragmentFtueSplashCarouselBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupViews()
}
private fun setupViews() {
views.splashCarousel.adapter = carouselController.adapter
TabLayoutMediator(views.carouselIndicator, views.splashCarousel) { _, _ -> }.attach()
carouselController.setData(SplashCarouselState())
views.loginSplashSubmit.debouncedClicks { getStarted() }
views.loginSplashAlreadyHaveAccount.apply {
isVisible = vectorFeatures.isAlreadyHaveAccountSplashEnabled()
debouncedClicks { alreadyHaveAnAccount() }
}
if (BuildConfig.DEBUG || vectorPreferences.developerMode()) {
views.loginSplashVersion.isVisible = true
@SuppressLint("SetTextI18n")
views.loginSplashVersion.text = "Version : ${BuildConfig.VERSION_NAME}#${BuildConfig.BUILD_NUMBER}\n" +
"Branch: ${BuildConfig.GIT_BRANCH_NAME}"
views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) }
}
}
private fun getStarted() {
val getStartedFlow = if (vectorFeatures.isAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow))
}
private fun alreadyHaveAnAccount() {
viewModel.handle(OnboardingAction.OnIAlreadyHaveAnAccount(resetLoginConfig = false, onboardingFlow = OnboardingFlow.SignIn))
}
override fun resetViewModel() {
// Nothing to do
}
override fun onError(throwable: Throwable) {
if (throwable is Failure.NetworkConnection &&
throwable.ioException is UnknownHostException) {
// Invalid homeserver from URL config
val url = viewModel.getInitialHomeServerUrl().orEmpty()
MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.dialog_title_error)
.setMessage(getString(R.string.login_error_homeserver_from_url_not_found, url))
.setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ ->
val flow = withState(viewModel) { it.onboardingFlow } ?: OnboardingFlow.SignInSignUp
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = true, flow))
}
.setNegativeButton(R.string.action_cancel, null)
.show()
} else {
super.onError(throwable)
}
}
}

View file

@ -15,7 +15,6 @@
*/ */
package im.vector.app.features.onboarding.ftueauth package im.vector.app.features.onboarding.ftueauth
import android.content.Intent import android.content.Intent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -34,6 +33,7 @@ import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.databinding.ActivityLoginBinding
import im.vector.app.features.VectorFeatures
import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.HomeActivity
import im.vector.app.features.login.LoginConfig import im.vector.app.features.login.LoginConfig
import im.vector.app.features.login.LoginMode import im.vector.app.features.login.LoginMode
@ -61,7 +61,8 @@ class FtueAuthVariant(
private val views: ActivityLoginBinding, private val views: ActivityLoginBinding,
private val onboardingViewModel: OnboardingViewModel, private val onboardingViewModel: OnboardingViewModel,
private val activity: VectorBaseActivity<ActivityLoginBinding>, private val activity: VectorBaseActivity<ActivityLoginBinding>,
private val supportFragmentManager: FragmentManager private val supportFragmentManager: FragmentManager,
private val vectorFeatures: VectorFeatures
) : OnboardingVariant { ) : OnboardingVariant {
private val enterAnim = R.anim.enter_fade_in private val enterAnim = R.anim.enter_fade_in
@ -108,7 +109,11 @@ class FtueAuthVariant(
} }
private fun addFirstFragment() { private fun addFirstFragment() {
activity.addFragment(views.loginFragmentContainer, FtueAuthSplashFragment::class.java) val splashFragment = when (vectorFeatures.isSplashCarouselEnabled()) {
true -> FtueAuthSplashCarouselFragment::class.java
else -> FtueAuthSplashFragment::class.java
}
activity.addFragment(views.loginFragmentContainer, splashFragment)
} }
private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) { private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021 New Vector Ltd * Copyright 2019 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2021 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.onboarding.ftueauth
import com.airbnb.epoxy.TypedEpoxyController
import javax.inject.Inject
class SplashCarouselController @Inject constructor() : TypedEpoxyController<SplashCarouselState>() {
override fun buildModels(data: SplashCarouselState) {
data.items.forEachIndexed { index, item ->
splashCarouselItem {
id(index)
item(item)
}
}
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2021 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.onboarding.ftueauth
import android.widget.ImageView
import android.widget.TextView
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
@EpoxyModelClass(layout = R.layout.item_splash_carousel)
abstract class SplashCarouselItem : VectorEpoxyModel<SplashCarouselItem.Holder>() {
@EpoxyAttribute
lateinit var item: SplashCarouselState.Item
override fun bind(holder: Holder) {
super.bind(holder)
holder.view.setBackgroundResource(item.pageBackground)
holder.image.setImageResource(item.image)
holder.title.setText(item.title)
holder.body.setText(item.body)
}
class Holder : VectorEpoxyHolder() {
val image by bind<ImageView>(R.id.carousel_item_image)
val title by bind<TextView>(R.id.carousel_item_title)
val body by bind<TextView>(R.id.carousel_item_body)
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2021 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.onboarding.ftueauth
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import im.vector.app.R
data class SplashCarouselState(
val items: List<Item> = listOf(
Item(
R.string.ftue_auth_carousel_1_title,
R.string.ftue_auth_carousel_1_body,
R.drawable.onboarding_carousel_conversations,
R.drawable.bg_carousel_page_1
),
Item(
R.string.ftue_auth_carousel_2_title,
R.string.ftue_auth_carousel_2_body,
R.drawable.onboarding_carousel_ems,
R.drawable.bg_carousel_page_2
),
Item(
R.string.ftue_auth_carousel_3_title,
R.string.ftue_auth_carousel_3_body,
R.drawable.onboarding_carousel_connect,
R.drawable.bg_carousel_page_3
),
Item(
R.string.ftue_auth_carousel_4_title,
R.string.ftue_auth_carousel_4_body,
R.drawable.onboarding_carousel_universal,
R.drawable.bg_carousel_page_4
)
)
) {
data class Item(
@StringRes val title: Int,
@StringRes val body: Int,
@DrawableRes val image: Int,
@DrawableRes val pageBackground: Int
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thickness="4dp"
android:useLevel="false">
<solid android:color="?vctr_content_quaternary" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thickness="4dp"
android:useLevel="false">
<solid android:color="?colorAccent" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/indicator_onboarding_carousel_selected" android:state_selected="true" />
<item android:drawable="@drawable/indicator_onboarding_carousel_inactive" />
</selector>

View file

@ -0,0 +1,226 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground"
android:paddingStart="36dp"
android:paddingTop="@dimen/layout_vertical_margin"
android:paddingEnd="36dp"
android:paddingBottom="@dimen/layout_vertical_margin">
<!-- Strategy: 5 Spaces are used to spread the remaining space, using weight -->
<Space
android:id="@+id/loginSplashSpace1"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/loginSplashLogoContainer"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread_inside"
app:layout_constraintVertical_weight="4" />
<LinearLayout
android:id="@+id/loginSplashLogoContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@id/loginSplashSpace2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginSplashSpace1">
<ImageView
android:id="@+id/loginSplashLogo"
android:layout_width="64dp"
android:layout_height="64dp"
android:importantForAccessibility="no"
android:src="@drawable/element_logo_green"
android:transitionName="loginLogoTransition" />
<ImageView
android:id="@+id/logoType"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="8dp"
android:contentDescription="@string/app_name"
android:src="@drawable/element_logotype"
app:tint="?colorSecondary"
tools:ignore="MissingPrefix" />
</LinearLayout>
<Space
android:id="@+id/loginSplashSpace2"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/loginSplashTitle"
app:layout_constraintTop_toBottomOf="@id/loginSplashLogoContainer"
app:layout_constraintVertical_weight="1" />
<TextView
android:id="@+id/loginSplashTitle"
style="@style/Widget.Vector.TextView.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/login_splash_title"
android:textColor="?vctr_content_primary"
android:transitionName="loginTitleTransition"
app:layout_constraintBottom_toTopOf="@id/loginSplashSpace3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginSplashSpace2" />
<Space
android:id="@+id/loginSplashSpace3"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/loginSplashContent"
app:layout_constraintTop_toBottomOf="@id/loginSplashTitle"
app:layout_constraintVertical_weight="2" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/loginSplashContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/loginSplashSpace4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginSplashSpace3">
<ImageView
android:id="@+id/loginSplashPicto1"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_login_splash_message_circle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/loginSplashText1"
app:tint="?vctr_content_secondary"
tools:ignore="MissingPrefix" />
<TextView
android:id="@+id/loginSplashText1"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:gravity="start"
android:text="@string/login_splash_text1"
android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toTopOf="@id/loginSplashText2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/loginSplashPicto1"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/loginSplashPicto2"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:src="@drawable/ic_login_splash_lock"
app:layout_constraintStart_toStartOf="@id/loginSplashPicto1"
app:layout_constraintTop_toTopOf="@id/loginSplashText2"
app:tint="?vctr_content_secondary"
tools:ignore="MissingPrefix" />
<TextView
android:id="@+id/loginSplashText2"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="start"
android:text="@string/login_splash_text2"
android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toTopOf="@id/loginSplashText3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/loginSplashText1"
app:layout_constraintTop_toBottomOf="@id/loginSplashText1" />
<ImageView
android:id="@+id/loginSplashPicto3"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:src="@drawable/ic_login_splash_sliders"
app:layout_constraintStart_toStartOf="@id/loginSplashPicto1"
app:layout_constraintTop_toTopOf="@id/loginSplashText3"
app:tint="?vctr_content_secondary"
tools:ignore="MissingPrefix" />
<TextView
android:id="@+id/loginSplashText3"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="start"
android:text="@string/login_splash_text3"
android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/loginSplashText1"
app:layout_constraintTop_toBottomOf="@id/loginSplashText2" />
</androidx.constraintlayout.widget.ConstraintLayout>
<Space
android:id="@+id/loginSplashSpace4"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/loginSplashSubmit"
app:layout_constraintTop_toBottomOf="@id/loginSplashContent"
app:layout_constraintVertical_weight="2" />
<Button
android:id="@+id/loginSplashSubmit"
style="@style/Widget.Vector.Button.Login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/login_splash_submit"
android:textAllCaps="true"
android:transitionName="loginSubmitTransition"
app:layout_constraintBottom_toTopOf="@id/loginSplashSpace5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginSplashSpace4" />
<TextView
android:id="@+id/loginSplashAlreadyHaveAccount"
style="@style/Widget.Vector.Button.Text.Login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/login_splash_already_have_account"
android:textAllCaps="true"
android:transitionName="loginSubmitTransition"
app:layout_constraintBottom_toTopOf="@id/loginSplashSpace5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginSplashSubmit" />
<Space
android:id="@+id/loginSplashSpace5"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginSplashSubmit"
app:layout_constraintVertical_weight="4" />
<TextView
android:id="@+id/loginSplashVersion"
style="@style/Widget.Vector.TextView.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?vctr_content_secondary"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@string/settings_version"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/splashCarousel"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/splashCarouselFloatingSection"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.65" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/carouselIndicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
app:layout_constraintBottom_toTopOf="@id/loginSplashButtonsSpace"
app:layout_constraintTop_toBottomOf="@id/splashCarouselFloatingSection"
app:layout_constraintVertical_chainStyle="spread"
app:tabBackground="@drawable/indicator_onboarding_carousel_selector"
app:tabGravity="center"
app:tabIndicatorHeight="0dp"
app:tabPaddingEnd="8dp"
app:tabPaddingStart="8dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/splashCarouselGutterStart"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_start_percent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/splashCarouselGutterEnd"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_end_percent" />
<Space
android:id="@+id/loginSplashButtonsSpace"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/loginSplashSubmit"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.01"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/carouselIndicator" />
<Button
android:id="@+id/loginSplashSubmit"
style="@style/Widget.Vector.Button.Login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/login_splash_submit"
android:textAllCaps="true"
android:transitionName="loginSubmitTransition"
app:layout_constraintBottom_toTopOf="@id/loginSplashAlreadyHaveAccount"
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
app:layout_constraintTop_toBottomOf="@id/loginSplashButtonsSpace" />
<TextView
android:id="@+id/loginSplashAlreadyHaveAccount"
style="@style/Widget.Vector.Button.Text.Login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/login_splash_already_have_account"
android:textAllCaps="true"
android:transitionName="loginSubmitTransition"
app:layout_constraintBottom_toTopOf="@id/loginSplashBottomSpace"
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
app:layout_constraintTop_toBottomOf="@id/loginSplashSubmit" />
<Space
android:id="@+id/loginSplashBottomSpace"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.05"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginSplashAlreadyHaveAccount" />
<TextView
android:id="@+id/loginSplashVersion"
style="@style/Widget.Vector.TextView.Micro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="12dp"
android:textColor="?vctr_content_secondary"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_debug_icon"
app:drawableTint="?colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
tools:text="@string/settings_version"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/splashCarouselGutterStart"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_start_percent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/splashCarouselGutterEnd"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_end_percent" />
<Space
android:id="@+id/carousel_item_top_space"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/carousel_item_image_container"
app:layout_constraintHeight_percent="0.1"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/carousel_item_image_container"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="center"
app:layout_constraintBottom_toTopOf="@id/carousel_item_image_bottom_space"
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
app:layout_constraintTop_toBottomOf="@id/carousel_item_top_space">
<ImageView
android:id="@+id/carousel_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:contentDescription="@null" />
</FrameLayout>
<Space
android:id="@+id/carousel_item_image_bottom_space"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.05"
app:layout_constraintBottom_toTopOf="@id/carousel_item_title"
app:layout_constraintHeight_percent="0.05"
app:layout_constraintTop_toBottomOf="@id/carousel_item_image_container" />
<TextView
android:id="@+id/carousel_item_title"
style="@style/Widget.Vector.TextView.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toTopOf="@id/carousel_item_body"
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
app:layout_constraintTop_toBottomOf="@id/carousel_item_image_bottom_space"
tools:text="Login version" />
<TextView
android:id="@+id/carousel_item_body"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toTopOf="@id/splashCarouselFloatingSection"
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
app:layout_constraintTop_toBottomOf="@id/carousel_item_title"
tools:text="Login version" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/splashCarouselFloatingSection"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.65" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -2516,6 +2516,16 @@
<string name="direct_room_join_rules_invite_by_you">You made this invite only.</string> <string name="direct_room_join_rules_invite_by_you">You made this invite only.</string>
<string name="timeline_unread_messages">Unread messages</string> <string name="timeline_unread_messages">Unread messages</string>
<!-- Onboarding -->
<string name="ftue_auth_carousel_1_title">Own your conversions.</string>
<string name="ftue_auth_carousel_1_body">End-to-end encrypted messaging for secure and independent communication, connected via Matrix.</string>
<string name="ftue_auth_carousel_2_title">You\'re in control.</string>
<string name="ftue_auth_carousel_2_body">Element lets you choose where you messages are stored, keeping you in control of your data.</string>
<string name="ftue_auth_carousel_3_title">Connect with anyone.</string>
<string name="ftue_auth_carousel_3_body">Element works with all Matrix-based apps and can even bridge into proprietary messengers.</string>
<string name="ftue_auth_carousel_4_title">Cut the slack from teams.</string>
<string name="ftue_auth_carousel_4_body">As universal as email, Element is a completely new type of collaboration.</string>
<string name="login_splash_title">It\'s your conversation. Own it.</string> <string name="login_splash_title">It\'s your conversation. Own it.</string>
<string name="login_splash_text1">Chat with people directly or in groups</string> <string name="login_splash_text1">Chat with people directly or in groups</string>
<string name="login_splash_text2">Keep conversations private with encryption</string> <string name="login_splash_text2">Keep conversations private with encryption</string>