1 /*
<lambda>null2  * Copyright (C) 2024 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.floatingsheetcontent
17 
18 import android.content.Context
19 import android.os.Handler
20 import android.util.AttributeSet
21 import android.view.View
22 import android.widget.Button
23 import android.widget.CompoundButton
24 import android.widget.LinearLayout
25 import android.widget.ProgressBar
26 import android.widget.Switch
27 import android.widget.TextView
28 import androidx.annotation.VisibleForTesting
29 import androidx.annotation.VisibleForTesting.Companion.PRIVATE
30 import com.android.wallpaper.R
31 import com.android.wallpaper.effects.EffectsController
32 import com.android.wallpaper.effects.EffectsController.EffectEnumInterface
33 
34 /** A view for displaying wallpaper info. */
35 class WallpaperEffectsView2(context: Context?, attrs: AttributeSet?) :
36     LinearLayout(context, attrs) {
37     private var effectSwitchListener: EffectSwitchListener? = null
38     private var downloadClickListener: EffectDownloadClickListener? = null
39     private var effectSwitch: Switch? = null
40 
41     private var effectTextRes: EffectTextRes =
42         EffectTextRes(
43             effectTitle = "",
44             effectFailedTitle = "",
45             effectSubTitle = "",
46             retryInstruction = "",
47             noEffectInstruction = "",
48         )
49 
50     @VisibleForTesting(otherwise = PRIVATE) var effectTitle: TextView? = null
51     @VisibleForTesting(otherwise = PRIVATE) var description: TextView? = null
52     @VisibleForTesting(otherwise = PRIVATE) var title: TextView? = null
53     @VisibleForTesting(otherwise = PRIVATE) var myPhotosButton: Button? = null
54     @VisibleForTesting(otherwise = PRIVATE) var continueButton: Button? = null
55     @VisibleForTesting(otherwise = PRIVATE) var tryAgainLaterButton: Button? = null
56     @VisibleForTesting(otherwise = PRIVATE) var switchLayout: View? = null
57     @VisibleForTesting(otherwise = PRIVATE) var container: View? = null
58     @VisibleForTesting(otherwise = PRIVATE) var downloadProgression: ProgressBar? = null
59     @VisibleForTesting(otherwise = PRIVATE) var downloadButton: Button? = null
60     @VisibleForTesting(otherwise = PRIVATE) var downloadButtonLayout: View? = null
61 
62     data class EffectTextRes(
63         val effectTitle: String,
64         val effectFailedTitle: String,
65         val effectSubTitle: String,
66         val retryInstruction: String,
67         val noEffectInstruction: String,
68     )
69 
70     enum class Status {
71         IDLE,
72         FAILED,
73         SUCCESS,
74         PROCESSING,
75         SHOW_DOWNLOAD_BUTTON,
76         DOWNLOADING, // downloading of ml models in progress
77     }
78 
79     /**
80      * Adds an Effects switch listener.
81      *
82      * @param listener The effects switch listener.
83      */
84     fun addEffectSwitchListener(listener: EffectSwitchListener?) {
85         effectSwitchListener = listener
86     }
87 
88     /**
89      * Sets an Effects download button listener.
90      *
91      * @param listener The effects download button listener.
92      */
93     fun setEffectDownloadClickListener(listener: EffectDownloadClickListener?) {
94         downloadClickListener = listener
95     }
96 
97     /**
98      * Updates the effect switch status.
99      *
100      * @param checked The status of the switch.
101      */
102     private fun updateEffectSwitchStatus(effect: EffectEnumInterface, checked: Boolean) {
103         effectSwitch?.isChecked = checked
104         effectSwitch?.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
105             switchChanged(effect, isChecked)
106         }
107     }
108 
109     /**
110      * Sets click listener to myPhotos button.
111      *
112      * @param clickListener listener for myPhotos.
113      */
114     fun setMyPhotosClickListener(clickListener: OnClickListener?) {
115         myPhotosButton?.setOnClickListener(clickListener)
116     }
117 
118     /**
119      * Sets click listener to 'try again' and 'continue' button.
120      *
121      * @param clickListener listener for myPhotos.
122      */
123     fun setCollapseFloatingSheetListener(clickListener: OnClickListener?) {
124         tryAgainLaterButton?.setOnClickListener(clickListener)
125         continueButton?.setOnClickListener(clickListener)
126     }
127 
128     fun setEffectResources(effectTextRes: EffectTextRes) {
129         this.effectTextRes = effectTextRes
130         updateDefaultTextIfEmpty(effectTextRes)
131     }
132 
133     private fun updateDefaultTextIfEmpty(effectTextRes: EffectTextRes) {
134         tryAgainLaterButton?.updateTextIfEmpty(effectTextRes.noEffectInstruction)
135         title?.updateTextIfEmpty(effectTextRes.effectTitle)
136         description?.updateTextIfEmpty(effectTextRes.effectSubTitle)
137     }
138 
139     private fun TextView.updateTextIfEmpty(newText: String) {
140         this.text = this.text.ifEmpty { newText }
141     }
142 
143     /**
144      * Updates the wallpaper effect view by status.
145      *
146      * @param status Last status code of wallpaper effect.
147      * @param resultCode the result code to handle different layouts.
148      * @param errorMessage the description of the sheet.
149      */
150     fun updateEffectStatus(
151         effect: EffectEnumInterface,
152         status: Status,
153         resultCode: Int?,
154         errorMessage: String?
155     ) {
156         when (status) {
157             Status.IDLE -> {
158                 showBasicLayout()
159                 updateEffectSwitchStatus(effect, /* checked = */ false)
160                 effectSwitch?.isEnabled = true
161                 switchLayout?.visibility = VISIBLE
162                 container?.visibility = VISIBLE
163             }
164             Status.SUCCESS -> {
165                 showBasicLayout()
166                 updateEffectSwitchStatus(effect, /* checked = */ true)
167                 Handler().postDelayed({ effectSwitch?.isEnabled = true }, 500)
168                 switchLayout?.visibility = VISIBLE
169                 container?.visibility = VISIBLE
170             }
171             Status.PROCESSING -> {
172                 showBasicLayout()
173                 updateEffectSwitchStatus(effect, /* checked = */ true)
174                 effectSwitch?.isEnabled = false
175                 switchLayout?.visibility = VISIBLE
176                 container?.visibility = VISIBLE
177             }
178             Status.FAILED -> showFailedLayout(errorMessage)
179             Status.SHOW_DOWNLOAD_BUTTON -> {
180                 description?.text = effectTextRes.effectSubTitle
181                 switchLayout?.visibility = INVISIBLE
182                 container?.visibility = INVISIBLE
183                 downloadButtonLayout?.visibility = VISIBLE
184                 downloadProgression?.visibility = INVISIBLE
185             }
186             Status.DOWNLOADING -> {
187                 switchLayout?.visibility = INVISIBLE
188                 container?.visibility = INVISIBLE
189                 downloadButtonLayout?.visibility = INVISIBLE
190                 downloadProgression?.visibility = VISIBLE
191             }
192         }
193         controlButtonByCode(
194             myPhotosButton,
195             resultCode,
196             EffectsController.RESULT_ERROR_TRY_ANOTHER_PHOTO
197         )
198         controlButtonByCode(
199             tryAgainLaterButton,
200             resultCode,
201             EffectsController.RESULT_ERROR_TRY_AGAIN_LATER
202         )
203         controlButtonByCode(continueButton, resultCode, EffectsController.RESULT_ERROR_CONTINUE)
204     }
205 
206     private fun showBasicLayout() {
207         title?.text = effectTextRes.effectTitle
208         description?.text = effectTextRes.effectSubTitle
209         switchLayout?.visibility = VISIBLE
210         container?.visibility = INVISIBLE
211         downloadProgression?.visibility = INVISIBLE
212         downloadButtonLayout?.visibility = INVISIBLE
213     }
214 
215     private fun showFailedLayout(errorMessage: String?) {
216         switchLayout?.visibility = INVISIBLE
217         title?.text = effectTextRes.effectFailedTitle
218         description?.text = errorMessage
219         container?.visibility = VISIBLE
220         downloadProgression?.visibility = INVISIBLE
221         downloadButtonLayout?.visibility = INVISIBLE
222     }
223 
224     private fun controlButtonByCode(view: View?, resultCode: Int?, mask: Int) {
225         view?.visibility = if (resultCode == null || resultCode and mask == 0) GONE else VISIBLE
226     }
227 
228     /**
229      * Updates the wallpaper effect switch title.
230      *
231      * @param title The title of the switch.
232      */
233     fun updateEffectTitle(title: String?) {
234         effectTitle!!.text = title
235     }
236 
237     private fun switchChanged(effect: EffectEnumInterface, isChecked: Boolean) {
238         effectSwitchListener?.onEffectSwitchChanged(effect, isChecked)
239     }
240 
241     override fun onFinishInflate() {
242         super.onFinishInflate()
243         effectSwitch = findViewById(R.id.wallpaper_effect_switch)
244         effectTitle = findViewById(R.id.wallpaper_effect_toggle_title)
245         title = findViewById(R.id.wallpaper_effects_title)
246         description = findViewById(R.id.wallpaper_effects_subtitle)
247         myPhotosButton = findViewById(R.id.open_my_photo_button)
248         tryAgainLaterButton = findViewById(R.id.try_again_button)
249         continueButton = findViewById(R.id.continue_button)
250         container = findViewById(R.id.buttons_container)
251         switchLayout = findViewById(R.id.wallpaper_effect_linear_layout)
252         switchLayout?.setOnClickListener {
253             if (effectSwitch?.isEnabled == true) effectSwitch?.toggle()
254         }
255         downloadButton = findViewById(R.id.download_model_button)
256         downloadButton?.setOnClickListener { downloadClickListener?.onEffectDownloadClick() }
257 
258         downloadButtonLayout = findViewById(R.id.button_layout)
259         downloadProgression = findViewById(R.id.action_progress)
260     }
261 
262     /** Implements a listener to know when an effect switch has been selected/unselected. */
263     interface EffectSwitchListener {
264         /**
265          * Called when one of the effect switches has change the checked value.
266          *
267          * @param effect The effect connected to the switch.
268          * @param isChecked if the switch is checked.
269          */
270         fun onEffectSwitchChanged(effect: EffectEnumInterface, isChecked: Boolean)
271     }
272 
273     /** Implements a listener to know when an effect download button has been click. */
274     interface EffectDownloadClickListener {
275         fun onEffectDownloadClick()
276     }
277 }
278