1 /* <lambda>null2 * Copyright (C) 2020 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.deskclock.alarms.dataadapter 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.AnimatorSet 22 import android.animation.ObjectAnimator 23 import android.content.Context 24 import android.graphics.Rect 25 import android.view.LayoutInflater 26 import android.view.View 27 import android.view.ViewGroup 28 import android.widget.TextView 29 import androidx.recyclerview.widget.RecyclerView.ViewHolder 30 31 import com.android.deskclock.AnimatorUtils 32 import com.android.deskclock.ItemAdapter.ItemViewHolder 33 import com.android.deskclock.R 34 import com.android.deskclock.data.DataModel 35 import com.android.deskclock.events.Events 36 import com.android.deskclock.provider.Alarm 37 38 import java.util.Calendar 39 40 /** 41 * A ViewHolder containing views for an alarm item in collapsed stated. 42 */ 43 class CollapsedAlarmViewHolder private constructor(itemView: View) : AlarmItemViewHolder(itemView) { 44 private val alarmLabel: TextView = itemView.findViewById(R.id.label) as TextView 45 val daysOfWeek: TextView = itemView.findViewById(R.id.days_of_week) as TextView 46 private val upcomingInstanceLabel: TextView = 47 itemView.findViewById(R.id.upcoming_instance_label) as TextView 48 private val hairLine: View = itemView.findViewById(R.id.hairline) 49 50 init { 51 // Expand handler 52 itemView.setOnClickListener { _ -> 53 Events.sendAlarmEvent(R.string.action_expand_implied, R.string.label_deskclock) 54 itemHolder?.expand() 55 } 56 alarmLabel.setOnClickListener { _ -> 57 Events.sendAlarmEvent(R.string.action_expand_implied, R.string.label_deskclock) 58 itemHolder?.expand() 59 } 60 arrow.setOnClickListener { _ -> 61 Events.sendAlarmEvent(R.string.action_expand, R.string.label_deskclock) 62 itemHolder?.expand() 63 } 64 // Edit time handler 65 clock.setOnClickListener { _ -> 66 itemHolder!!.alarmTimeClickHandler.onClockClicked(itemHolder!!.item) 67 Events.sendAlarmEvent(R.string.action_expand_implied, R.string.label_deskclock) 68 itemHolder?.expand() 69 } 70 71 itemView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO) 72 } 73 74 override fun onBindItemView(itemHolder: AlarmItemHolder) { 75 super.onBindItemView(itemHolder) 76 val alarm = itemHolder.item 77 val alarmInstance = itemHolder.alarmInstance 78 val context: Context = itemView.getContext() 79 bindRepeatText(context, alarm) 80 bindReadOnlyLabel(context, alarm) 81 bindUpcomingInstance(context, alarm) 82 bindPreemptiveDismissButton(context, alarm, alarmInstance) 83 } 84 85 private fun bindReadOnlyLabel(context: Context, alarm: Alarm) { 86 if (!alarm.label.isNullOrEmpty()) { 87 alarmLabel.text = alarm.label 88 alarmLabel.visibility = View.VISIBLE 89 alarmLabel.setContentDescription(context.getString(R.string.label_description) 90 .toString() + " " + alarm.label) 91 } else { 92 alarmLabel.visibility = View.GONE 93 } 94 } 95 96 private fun bindRepeatText(context: Context, alarm: Alarm) { 97 if (alarm.daysOfWeek.isRepeating) { 98 val weekdayOrder = DataModel.dataModel.weekdayOrder 99 val daysOfWeekText = alarm.daysOfWeek.toString(context, weekdayOrder) 100 daysOfWeek.text = daysOfWeekText 101 102 val string = alarm.daysOfWeek.toAccessibilityString(context, weekdayOrder) 103 daysOfWeek.setContentDescription(string) 104 105 daysOfWeek.visibility = View.VISIBLE 106 } else { 107 daysOfWeek.visibility = View.GONE 108 } 109 } 110 111 private fun bindUpcomingInstance(context: Context, alarm: Alarm) { 112 if (alarm.daysOfWeek.isRepeating) { 113 upcomingInstanceLabel.visibility = View.GONE 114 } else { 115 upcomingInstanceLabel.visibility = View.VISIBLE 116 val labelText: String = if (Alarm.isTomorrow(alarm, Calendar.getInstance())) { 117 context.getString(R.string.alarm_tomorrow) 118 } else { 119 context.getString(R.string.alarm_today) 120 } 121 upcomingInstanceLabel.text = labelText 122 } 123 } 124 125 override fun onAnimateChange( 126 payloads: List<Any>?, 127 fromLeft: Int, 128 fromTop: Int, 129 fromRight: Int, 130 fromBottom: Int, 131 duration: Long 132 ): Animator? { 133 /* There are no possible partial animations for collapsed view holders. */ 134 return null 135 } 136 137 override fun onAnimateChange( 138 oldHolder: ViewHolder, 139 newHolder: ViewHolder, 140 duration: Long 141 ): Animator? { 142 if (oldHolder !is AlarmItemViewHolder || 143 newHolder !is AlarmItemViewHolder) { 144 return null 145 } 146 147 val isCollapsing = this == newHolder 148 setChangingViewsAlpha(if (isCollapsing) 0f else 1f) 149 150 val changeAnimatorSet: Animator = if (isCollapsing) { 151 createCollapsingAnimator(oldHolder, duration) 152 } else { 153 createExpandingAnimator(newHolder, duration) 154 } 155 changeAnimatorSet.addListener(object : AnimatorListenerAdapter() { 156 override fun onAnimationEnd(animator: Animator?) { 157 clock.visibility = View.VISIBLE 158 onOff.visibility = View.VISIBLE 159 arrow.visibility = View.VISIBLE 160 arrow.setTranslationY(0f) 161 setChangingViewsAlpha(1f) 162 arrow.jumpDrawablesToCurrentState() 163 } 164 }) 165 return changeAnimatorSet 166 } 167 168 private fun createExpandingAnimator(newHolder: AlarmItemViewHolder, duration: Long): Animator { 169 clock.visibility = View.INVISIBLE 170 onOff.visibility = View.INVISIBLE 171 arrow.visibility = View.INVISIBLE 172 173 val alphaAnimatorSet = AnimatorSet() 174 alphaAnimatorSet.playTogether( 175 ObjectAnimator.ofFloat(alarmLabel, View.ALPHA, 0f), 176 ObjectAnimator.ofFloat(daysOfWeek, View.ALPHA, 0f), 177 ObjectAnimator.ofFloat(upcomingInstanceLabel, View.ALPHA, 0f), 178 ObjectAnimator.ofFloat(preemptiveDismissButton, View.ALPHA, 0f), 179 ObjectAnimator.ofFloat(hairLine, View.ALPHA, 0f)) 180 alphaAnimatorSet.setDuration((duration * ANIM_SHORT_DURATION_MULTIPLIER).toLong()) 181 182 val oldView: View = itemView 183 val newView: View = newHolder.itemView 184 val boundsAnimator: Animator = AnimatorUtils.getBoundsAnimator(oldView, oldView, newView) 185 .setDuration(duration) 186 boundsAnimator.interpolator = AnimatorUtils.INTERPOLATOR_FAST_OUT_SLOW_IN 187 188 val animatorSet = AnimatorSet() 189 animatorSet.playTogether(alphaAnimatorSet, boundsAnimator) 190 return animatorSet 191 } 192 193 private fun createCollapsingAnimator(oldHolder: AlarmItemViewHolder, duration: Long): Animator { 194 val alphaAnimatorSet = AnimatorSet() 195 alphaAnimatorSet.playTogether( 196 ObjectAnimator.ofFloat(alarmLabel, View.ALPHA, 1f), 197 ObjectAnimator.ofFloat(daysOfWeek, View.ALPHA, 1f), 198 ObjectAnimator.ofFloat(upcomingInstanceLabel, View.ALPHA, 1f), 199 ObjectAnimator.ofFloat(preemptiveDismissButton, View.ALPHA, 1f), 200 ObjectAnimator.ofFloat(hairLine, View.ALPHA, 1f)) 201 val standardDelay = (duration * ANIM_STANDARD_DELAY_MULTIPLIER).toLong() 202 alphaAnimatorSet.setDuration(standardDelay) 203 alphaAnimatorSet.setStartDelay(duration - standardDelay) 204 205 val oldView: View = oldHolder.itemView 206 val newView: View = itemView 207 val boundsAnimator: Animator = AnimatorUtils.getBoundsAnimator(newView, oldView, newView) 208 .setDuration(duration) 209 boundsAnimator.interpolator = AnimatorUtils.INTERPOLATOR_FAST_OUT_SLOW_IN 210 211 val oldArrow: View = oldHolder.arrow 212 val oldArrowRect = Rect(0, 0, oldArrow.getWidth(), oldArrow.getHeight()) 213 val newArrowRect = Rect(0, 0, arrow.getWidth(), arrow.getHeight()) 214 (newView as ViewGroup).offsetDescendantRectToMyCoords(arrow, newArrowRect) 215 (oldView as ViewGroup).offsetDescendantRectToMyCoords(oldArrow, oldArrowRect) 216 val arrowTranslationY: Float = (oldArrowRect.bottom - newArrowRect.bottom).toFloat() 217 arrow.setTranslationY(arrowTranslationY) 218 arrow.visibility = View.VISIBLE 219 clock.visibility = View.VISIBLE 220 onOff.visibility = View.VISIBLE 221 222 val arrowAnimation: Animator = ObjectAnimator.ofFloat(arrow, View.TRANSLATION_Y, 0f) 223 .setDuration(duration) 224 arrowAnimation.interpolator = AnimatorUtils.INTERPOLATOR_FAST_OUT_SLOW_IN 225 226 val animatorSet = AnimatorSet() 227 animatorSet.playTogether(alphaAnimatorSet, boundsAnimator, arrowAnimation) 228 animatorSet.addListener(object : AnimatorListenerAdapter() { 229 override fun onAnimationStart(animator: Animator?) { 230 AnimatorUtils.startDrawableAnimation(arrow) 231 } 232 }) 233 return animatorSet 234 } 235 236 private fun setChangingViewsAlpha(alpha: Float) { 237 alarmLabel.alpha = alpha 238 daysOfWeek.alpha = alpha 239 upcomingInstanceLabel.alpha = alpha 240 hairLine.alpha = alpha 241 preemptiveDismissButton.alpha = alpha 242 } 243 244 class Factory(private val layoutInflater: LayoutInflater) : ItemViewHolder.Factory { 245 override fun createViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder<*> { 246 return CollapsedAlarmViewHolder(layoutInflater.inflate( 247 viewType, parent, false /* attachToRoot */)) 248 } 249 } 250 251 companion object { 252 @JvmField 253 val VIEW_TYPE: Int = R.layout.alarm_time_collapsed 254 } 255 }