1 /*
<lambda>null2  * 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 
18 package com.android.wallpaper.picker.customization.ui.binder
19 
20 import android.view.LayoutInflater
21 import android.view.View
22 import android.view.ViewGroup
23 import android.widget.TextView
24 import androidx.annotation.DimenRes
25 import androidx.core.view.doOnLayout
26 import androidx.lifecycle.Lifecycle
27 import androidx.lifecycle.LifecycleOwner
28 import androidx.lifecycle.lifecycleScope
29 import androidx.lifecycle.repeatOnLifecycle
30 import com.android.wallpaper.R
31 import com.android.wallpaper.picker.common.text.ui.viewbinder.TextViewBinder
32 import com.android.wallpaper.picker.customization.ui.viewmodel.WallpaperQuickSwitchViewModel
33 import kotlinx.coroutines.launch
34 
35 /** Binds between the view and view-model for the wallpaper quick switch section. */
36 object WallpaperQuickSwitchSectionBinder {
37     fun bind(
38         view: View,
39         viewModel: WallpaperQuickSwitchViewModel,
40         lifecycleOwner: LifecycleOwner,
41         isThumbnailFadeAnimationEnabled: Boolean,
42         onNavigateToFullWallpaperSelector: () -> Unit,
43     ) {
44         val moreWallpapers: TextView = view.requireViewById(R.id.more_wallpapers)
45         moreWallpapers.setOnClickListener { onNavigateToFullWallpaperSelector() }
46 
47         TextViewBinder.bind(moreWallpapers, viewModel.actionText)
48 
49         val optionContainer: ViewGroup = view.requireViewById(R.id.options)
50 
51         if (!viewModel.areRecentsAvailable) {
52             optionContainer.visibility = View.GONE
53         } else {
54             optionContainer.visibility = View.VISIBLE
55             // We have to wait for the container to be laid out before we can bind it because we
56             // need
57             // its size to calculate the sizes of the option items.
58             optionContainer.doOnLayout {
59                 lifecycleOwner.lifecycleScope.launch {
60                     lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
61                         launch {
62                             bindOptions(
63                                 parent = optionContainer,
64                                 viewModel = viewModel,
65                                 lifecycleOwner = lifecycleOwner,
66                                 isThumbnailFadeAnimationEnabled = isThumbnailFadeAnimationEnabled,
67                             )
68                         }
69                     }
70                 }
71             }
72         }
73     }
74 
75     /** Binds the option items to the given parent. */
76     private suspend fun bindOptions(
77         parent: ViewGroup,
78         viewModel: WallpaperQuickSwitchViewModel,
79         lifecycleOwner: LifecycleOwner,
80         isThumbnailFadeAnimationEnabled: Boolean,
81     ) {
82         viewModel.options.collect { options ->
83             // Remove all views from a previous update.
84             parent.removeAllViews()
85 
86             // Calculate the sizes that views should have.
87             val (largeOptionWidth, smallOptionWidth) = calculateSizes(parent, options.size)
88 
89             val titleMap = mutableMapOf<String, Int>()
90 
91             // Create, add, and bind a view for each option.
92             options.forEachIndexed { index, option ->
93                 val optionView =
94                     createOptionView(
95                         parent = parent,
96                     )
97                 parent.addView(optionView)
98                 WallpaperQuickSwitchOptionBinder.bind(
99                     view = optionView,
100                     viewModel = option,
101                     lifecycleOwner = lifecycleOwner,
102                     smallOptionWidthPx = smallOptionWidth,
103                     largeOptionWidthPx = largeOptionWidth,
104                     isThumbnailFadeAnimationEnabled = isThumbnailFadeAnimationEnabled,
105                     position = index,
106                     titleMap = titleMap,
107                 )
108             }
109         }
110     }
111 
112     /**
113      * Returns a pair where the first value is the width that we should use for the large/selected
114      * option and the second value is the width for the small/non-selected options.
115      */
116     private fun calculateSizes(
117         parent: View,
118         optionCount: Int,
119     ): Pair<Int, Int> {
120         // The large/selected option is a square. Its size should be equal to the height of its
121         // container (with padding removed).
122         val largeOptionWidth = parent.height - parent.paddingTop - parent.paddingBottom
123         // We'll use the total (non-padded) width of the container to figure out the widths of the
124         // small/non-selected options.
125         val optionContainerWidthWithoutPadding =
126             parent.width - parent.paddingStart - parent.paddingEnd
127         // First, we will need the total of the widths of all the spacings between the options.
128         val spacingWidth = parent.dimensionResource(R.dimen.spacing_8dp)
129         val totalMarginWidths = (optionCount - 1) * spacingWidth
130 
131         val remainingSpaceForSmallOptions =
132             optionContainerWidthWithoutPadding - largeOptionWidth - totalMarginWidths
133         // One option is always large, the rest are small.
134         val numberOfSmallOptions = optionCount - 1
135         val smallOptionWidth =
136             if (numberOfSmallOptions != 0) {
137                 (remainingSpaceForSmallOptions / numberOfSmallOptions).coerceAtMost(
138                     parent.dimensionResource(R.dimen.wallpaper_quick_switch_max_option_width)
139                 )
140             } else {
141                 0
142             }
143 
144         return Pair(largeOptionWidth, smallOptionWidth)
145     }
146 
147     /** Returns a new [View] for an option, without attaching it to the view-tree. */
148     private fun createOptionView(
149         parent: ViewGroup,
150     ): View {
151         return LayoutInflater.from(parent.context)
152             .inflate(
153                 R.layout.wallpaper_quick_switch_option,
154                 parent,
155                 false,
156             )
157     }
158 
159     /** Compose-inspired convenience alias for getting a dimension in pixels. */
160     private fun View.dimensionResource(
161         @DimenRes res: Int,
162     ): Int {
163         return context.resources.getDimensionPixelSize(res)
164     }
165 }
166