1 /* 2 * Copyright (C) 2023 The Android Open Source Project 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 com.android.settings.remoteauth.introduction 18 19 import android.content.Context 20 import android.util.AttributeSet 21 import android.util.Log 22 import android.view.LayoutInflater 23 import android.view.View 24 import android.view.ViewGroup 25 import android.widget.ImageView 26 import android.widget.TextView 27 28 import androidx.annotation.VisibleForTesting 29 import androidx.constraintlayout.widget.ConstraintLayout 30 import androidx.recyclerview.widget.LinearLayoutManager 31 import androidx.recyclerview.widget.RecyclerView 32 import androidx.viewpager2.widget.MarginPageTransformer 33 import androidx.viewpager2.widget.ViewPager2 34 35 import com.airbnb.lottie.LottieAnimationView 36 37 import com.android.settings.R 38 import com.android.settingslib.widget.LottieColorUtils 39 40 class IntroductionImageCarousel : ConstraintLayout { <lambda>null41 private val carousel: ViewPager2 by lazy { requireViewById<ViewPager2>(R.id.image_carousel) } <lambda>null42 private val progressIndicator: RecyclerView by lazy { 43 requireViewById<RecyclerView>(R.id.carousel_progress_indicator) 44 } <lambda>null45 private val backArrow: ImageView by lazy { 46 requireViewById<ImageView>(R.id.carousel_back_arrow) 47 } <lambda>null48 private val forwardArrow: ImageView by lazy { 49 requireViewById<ImageView>(R.id.carousel_forward_arrow) 50 } 51 private val progressIndicatorAdapter = ProgressIndicatorAdapter() 52 // The index of the current animation we are on 53 private var currentPage = 0 54 set(value) { 55 val pageRange = 0..(ANIMATION_LIST.size - 1) 56 field = value.coerceIn(pageRange) 57 backArrow.isEnabled = field > pageRange.start 58 forwardArrow.isEnabled = field < pageRange.endInclusive 59 carousel.setCurrentItem(field) 60 progressIndicatorAdapter.currentIndex = field 61 } 62 63 private val onPageChangeCallback = 64 object : ViewPager2.OnPageChangeCallback() { onPageSelectednull65 override fun onPageSelected(position: Int) { 66 currentPage = position 67 } 68 } 69 constructor(context: Context) : super(context) 70 constructor(context: Context, attrSet: AttributeSet?) : super(context, attrSet) 71 72 init { 73 LayoutInflater.from(context).inflate(R.layout.remote_auth_introduction_image_carousel, this) 74 <lambda>null75 with(carousel) { 76 setPageTransformer( 77 MarginPageTransformer( 78 context.resources.getDimension(R.dimen.remoteauth_introduction_fragment_padding_horizontal).toInt() 79 ) 80 ) 81 adapter = ImageCarouselAdapter() 82 registerOnPageChangeCallback(onPageChangeCallback) 83 } 84 <lambda>null85 with(progressIndicator) { 86 layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) 87 adapter = progressIndicatorAdapter 88 } 89 <lambda>null90 backArrow.setOnClickListener { currentPage-- } <lambda>null91 forwardArrow.setOnClickListener { currentPage++ } 92 } 93 unregisternull94 fun unregister() { 95 carousel.unregisterOnPageChangeCallback(onPageChangeCallback) 96 } 97 98 private class AnimationViewHolder(val context: Context, itemView: View) : RecyclerView.ViewHolder(itemView) { 99 val animationView = itemView.requireViewById<LottieAnimationView>(R.id.explanation_animation) 100 val descriptionText = itemView.requireViewById<TextView>(R.id.carousel_text) 101 } 102 103 /** Adapter for the onboarding animations. */ 104 private class ImageCarouselAdapter : RecyclerView.Adapter<AnimationViewHolder>() { 105 getItemCountnull106 override fun getItemCount() = ANIMATION_LIST.size 107 108 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 109 AnimationViewHolder(parent.context, LayoutInflater.from(parent.context).inflate(R.layout.remote_auth_introduction_image_carousel_item, parent, false)) 110 111 override fun onBindViewHolder(holder: AnimationViewHolder, position: Int) { 112 with(holder.animationView) { 113 setAnimation(ANIMATION_LIST[position].first) 114 LottieColorUtils.applyDynamicColors(holder.context, this) 115 } 116 holder.descriptionText.setText(ANIMATION_LIST[position].second) 117 with(holder.itemView) { 118 // This makes sure that the proper description text instead of a generic "Page" label is 119 // verbalized by Talkback when switching to a new page on the ViewPager2. 120 contentDescription = context.getString(ANIMATION_LIST[position].second) 121 } 122 } 123 } 124 125 /** Adapter for icons indicating carousel progress. */ 126 private class ProgressIndicatorAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { 127 128 var currentIndex: Int = 0 129 set(value) { 130 val previousIndex = field 131 field = value.coerceIn(0, getItemCount() - 1) 132 notifyItemChanged(previousIndex) 133 notifyItemChanged(field) 134 } 135 getItemCountnull136 override fun getItemCount() = ANIMATION_LIST.size 137 138 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 139 object : 140 RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.remote_auth_introduction_image_carousel_progress_icon, parent, false)) {} 141 onBindViewHoldernull142 override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 143 holder.itemView.isSelected = position == currentIndex 144 } 145 } 146 companion object { 147 @VisibleForTesting 148 val ANIMATION_LIST = 149 listOf( 150 Pair( 151 R.raw.remoteauth_explanation_swipe_animation, 152 R.string.security_settings_remoteauth_enroll_introduction_animation_swipe_up 153 ), 154 Pair( 155 R.raw.remoteauth_explanation_notification_animation, 156 R.string.security_settings_remoteauth_enroll_introduction_animation_tap_notification 157 ), 158 ) 159 const val TAG = "RemoteAuthCarousel" 160 } 161 } 162