adding barebones splash carousel fragment, based on feature flag

This commit is contained in:
Adam Brown 2021-12-13 14:32:58 +00:00
parent 773d335add
commit 955fb03532
8 changed files with 327 additions and 3 deletions

View file

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

View file

@ -0,0 +1,172 @@
/*
* 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 android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
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
@AndroidEntryPoint
class FtueAuthSplashCarouselFragment : AbstractFtueAuthFragment<FragmentFtueSplashCarouselBinding>() {
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var vectorFeatures: VectorFeatures
@Inject lateinit var carouselController: SplashCarouselController
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(
items = listOf(
SplashCarouselState.Item(
"hello world",
R.drawable.element_logo_green
),
SplashCarouselState.Item(
"2",
R.drawable.element_logo_green
),
SplashCarouselState.Item(
"3",
R.drawable.element_logo_green
),
SplashCarouselState.Item(
"4",
R.drawable.element_logo_green
)
),
currentPage = 0
))
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}\n" +
"Branch: ${BuildConfig.GIT_BRANCH_NAME}\n" +
"Build: ${BuildConfig.BUILD_NUMBER}"
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)
}
}
}
data class SplashCarouselState(
val items: List<Item>,
val currentPage: Int
) {
data class Item(val body: String, @DrawableRes val image: Int)
}
class SplashCarouselController @Inject constructor() : TypedEpoxyController<SplashCarouselState>() {
override fun buildModels(data: SplashCarouselState) {
data.items.forEachIndexed { index, item ->
splashCarouselItem {
id(index)
item(item)
}
}
}
}
@EpoxyModelClass(layout = im.vector.app.R.layout.item_splash_carousel)
abstract class SplashCarouselItem : VectorEpoxyModel<SplashCarouselItem.Holder>() {
@EpoxyAttribute
lateinit var item: SplashCarouselState.Item
override fun bind(holder: Holder) {
holder.image.setImageResource(item.image)
holder.body.text = item.body
}
class Holder : VectorEpoxyHolder() {
val image by bind<ImageView>(im.vector.app.R.id.carousel_item_image)
val body by bind<TextView>(im.vector.app.R.id.carousel_item_body)
}
}

View file

@ -33,6 +33,7 @@ import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLoginBinding
import im.vector.app.features.VectorFeatures
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.login.LoginConfig
import im.vector.app.features.login.LoginMode
@ -60,7 +61,8 @@ class FtueAuthVariant(
private val views: ActivityLoginBinding,
private val onboardingViewModel: OnboardingViewModel,
private val activity: VectorBaseActivity<ActivityLoginBinding>,
private val supportFragmentManager: FragmentManager
private val supportFragmentManager: FragmentManager,
private val vectorFeatures: VectorFeatures
) : OnboardingVariant {
private val enterAnim = R.anim.enter_fade_in
@ -107,7 +109,11 @@ class FtueAuthVariant(
}
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) {

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,88 @@
<?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:paddingTop="@dimen/layout_vertical_margin"
android:paddingBottom="@dimen/layout_vertical_margin">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/splashCarousel"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/loginSplashSubmit"
app:layout_constraintHeight_percent="0.5"
app:layout_constraintTop_toTopOf="parent" />
<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/loginSplashSubmit"
app:layout_constraintTop_toBottomOf="@id/splashCarousel"
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_begin="36dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/splashCarouselGutterEnd"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_end="36dp" />
<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/carouselIndicator" />
<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_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/splashCarouselGutterEnd"
app:layout_constraintStart_toStartOf="@id/splashCarouselGutterStart"
app:layout_constraintTop_toBottomOf="@id/loginSplashSubmit" />
<TextView
android:id="@+id/loginSplashVersion"
style="@style/Widget.Vector.TextView.Caption"
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,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/carousel_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:contentDescription="@null" />
<TextView
android:id="@+id/carousel_item_body"
style="@style/Widget.Vector.TextView.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:textColor="?vctr_content_primary"
tools:text="Login version" />
</LinearLayout>