1 /*
<lambda>null2  * Copyright (C) 2022 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 package com.android.wallpaper.widget
17 
18 import android.annotation.IntDef
19 import android.content.Context
20 import android.transition.AutoTransition
21 import android.transition.TransitionManager
22 import android.util.AttributeSet
23 import android.view.LayoutInflater
24 import android.view.ViewGroup
25 import android.widget.FrameLayout
26 import androidx.appcompat.content.res.AppCompatResources
27 import com.android.wallpaper.R
28 import com.android.wallpaper.util.SizeCalculator
29 import com.android.wallpaper.widget.floatingsheetcontent.FloatingSheetContent
30 import com.google.android.material.bottomsheet.BottomSheetBehavior
31 import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
32 import java.util.function.Consumer
33 
34 /** A `ViewGroup` which provides the specific actions for the user to interact with. */
35 class FloatingSheet(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
36 
37     companion object {
38 
39         @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
40         @IntDef(CUSTOMIZE, INFORMATION, EFFECTS)
41         @Retention(AnnotationRetention.SOURCE)
42         annotation class FloatingSheetContentType
43 
44         const val CUSTOMIZE = 0
45         const val INFORMATION = 1
46         const val EFFECTS = 2
47     }
48 
49     private val floatingSheetView: ViewGroup
50     private val floatingSheetContainer: ViewGroup
51     private val floatingSheetBehavior: BottomSheetBehavior<ViewGroup>
52     private val contentViewMap:
53         MutableMap<@FloatingSheetContentType Int, FloatingSheetContent<*>?> =
54         HashMap()
55 
56     // The system "short" animation time duration, in milliseconds. This
57     // duration is ideal for subtle animations or animations that occur
58     // very frequently.
59     private val shortAnimTimeMillis: Long
60 
61     init {
62         LayoutInflater.from(context).inflate(R.layout.floating_sheet, this, true)
63         floatingSheetView = requireViewById(R.id.floating_sheet_content)
64         SizeCalculator.adjustBackgroundCornerRadius(floatingSheetView)
65         setColor(context)
66         floatingSheetContainer = requireViewById(R.id.floating_sheet_container)
67         floatingSheetBehavior = BottomSheetBehavior.from(floatingSheetContainer)
68         floatingSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
69         shortAnimTimeMillis = resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
70     }
71 
72     /**
73      * Binds the `floatingSheetContent` with an id that can be used identify and switch between
74      * floating sheet content
75      *
76      * @param floatingSheetContent the content object with view being added to the floating sheet
77      */
78     fun putFloatingSheetContent(
79         @FloatingSheetContentType type: Int,
80         floatingSheetContent: FloatingSheetContent<*>
81     ) {
82         floatingSheetContent.initView()
83         contentViewMap[type] = floatingSheetContent
84         floatingSheetView.addView(floatingSheetContent.contentView)
85     }
86 
87     /** Dynamic update color with `Context`. */
88     fun setColor(context: Context) {
89         // Set floating sheet background.
90         floatingSheetView.background =
91             AppCompatResources.getDrawable(context, R.drawable.floating_sheet_background)
92         if (floatingSheetView.childCount > 0) {
93             // Update the bottom sheet content view if any.
94             floatingSheetView.removeAllViews()
95             contentViewMap.values.forEach(
96                 Consumer { floatingSheetContent: FloatingSheetContent<*>? ->
97                     floatingSheetContent?.let {
98                         it.recreateView()
99                         floatingSheetView.addView(it.contentView)
100                     }
101                 }
102             )
103         }
104     }
105 
106     /** Returns `true` if the state of bottom sheet is collapsed. */
107     val isFloatingSheetCollapsed: Boolean
108         get() = floatingSheetBehavior.state == BottomSheetBehavior.STATE_HIDDEN
109 
110     /** Expands [FloatingSheet]. */
111     fun expand() {
112         floatingSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
113     }
114 
115     /** Collapses [FloatingSheet]. */
116     fun collapse() {
117         floatingSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
118         endContentViewAnimation()
119         contentViewMap.forEach { (i: Int, content: FloatingSheetContent<*>?) ->
120             content?.collapse()
121         }
122     }
123 
124     /**
125      * Updates content view of [FloatingSheet] with transition animation
126      *
127      * @param type the integer or enum used to identify the content view
128      */
129     fun updateContentViewWithAnimation(@FloatingSheetContentType type: Int) {
130         val transition = AutoTransition()
131         transition.duration = shortAnimTimeMillis
132         /**
133          * This line records changes you make to its views and applies a transition that animates
134          * the changes when the system redraws the user interface
135          */
136         TransitionManager.beginDelayedTransition(floatingSheetContainer, transition)
137 
138         updateContentView(type)
139     }
140 
141     fun endContentViewAnimation() {
142         TransitionManager.endTransitions(floatingSheetContainer)
143     }
144 
145     /**
146      * Updates content view of [FloatingSheet]
147      *
148      * @param type the integer or enum used to identify the content view
149      */
150     fun updateContentView(@FloatingSheetContentType type: Int) {
151         contentViewMap.forEach { (i: Int, content: FloatingSheetContent<*>?) ->
152             content?.setVisibility(i == type)
153         }
154     }
155 
156     /**
157      * Adds Floating Sheet Callback to connected [BottomSheetBehavior].
158      *
159      * @param callback the callback for floating sheet state changes, has to be in the type of
160      *   [BottomSheetBehavior.BottomSheetCallback] since the floating sheet behavior is currently
161      *   based on [BottomSheetBehavior]
162      */
163     fun addFloatingSheetCallback(callback: BottomSheetCallback) {
164         floatingSheetBehavior.addBottomSheetCallback(callback)
165     }
166 
167     fun removeFloatingSheetCallback(callback: BottomSheetCallback) {
168         floatingSheetBehavior.removeBottomSheetCallback(callback)
169     }
170 }
171