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 17 package com.android.systemui.statusbar.pipeline.wifi.ui.binder 18 19 import android.content.res.ColorStateList 20 import android.view.View 21 import android.view.ViewGroup 22 import android.widget.ImageView 23 import androidx.core.view.isVisible 24 import androidx.lifecycle.Lifecycle 25 import androidx.lifecycle.repeatOnLifecycle 26 import com.android.systemui.Flags.statusBarStaticInoutIndicators 27 import com.android.systemui.common.ui.binder.IconViewBinder 28 import com.android.systemui.lifecycle.repeatWhenAttached 29 import com.android.systemui.res.R 30 import com.android.systemui.statusbar.StatusBarIconView 31 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN 32 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding 33 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper 34 import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_ACTIVE 35 import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_INACTIVE 36 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon 37 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel 38 import kotlinx.coroutines.InternalCoroutinesApi 39 import kotlinx.coroutines.awaitCancellation 40 import kotlinx.coroutines.flow.MutableStateFlow 41 import kotlinx.coroutines.flow.distinctUntilChanged 42 import kotlinx.coroutines.launch 43 44 /** 45 * Binds a wifi icon in the status bar to its view-model. 46 * 47 * To use this properly, users should maintain a one-to-one relationship between the [View] and the 48 * view-binding, binding each view only once. It is okay and expected for the same instance of the 49 * view-model to be reused for multiple view/view-binder bindings. 50 */ 51 @OptIn(InternalCoroutinesApi::class) 52 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") 53 object WifiViewBinder { 54 55 /** Binds the view to the view-model, continuing to update the former based on the latter. */ 56 @JvmStatic 57 fun bind( 58 view: ViewGroup, 59 viewModel: LocationBasedWifiViewModel, 60 ): ModernStatusBarViewBinding { 61 val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group) 62 val iconView = view.requireViewById<ImageView>(R.id.wifi_signal) 63 val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot) 64 val activityInView = view.requireViewById<ImageView>(R.id.wifi_in) 65 val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out) 66 val activityContainerView = view.requireViewById<View>(R.id.inout_container) 67 val airplaneSpacer = view.requireViewById<View>(R.id.wifi_airplane_spacer) 68 val signalSpacer = view.requireViewById<View>(R.id.wifi_signal_spacer) 69 70 view.isVisible = true 71 iconView.isVisible = true 72 73 // TODO(b/238425913): We should log this visibility state. 74 @StatusBarIconView.VisibleState 75 val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN) 76 77 val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) 78 val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) 79 80 var isCollecting: Boolean = false 81 82 view.repeatWhenAttached { 83 repeatOnLifecycle(Lifecycle.State.STARTED) { 84 isCollecting = true 85 86 launch { 87 visibilityState.collect { visibilityState -> 88 // for b/296864006, we can not hide all the child views if visibilityState 89 // is STATE_HIDDEN. Because hiding all child views would cause the 90 // getWidth() of this view return 0, and that would cause the translation 91 // calculation fails in StatusIconContainer. Therefore, like class 92 // MobileIconBinder, instead of set the child views visibility to View.GONE, 93 // we set their visibility to View.INVISIBLE to make them invisible but 94 // keep the width. 95 ModernStatusBarViewVisibilityHelper.setVisibilityState( 96 visibilityState, 97 groupView, 98 dotView, 99 ) 100 } 101 } 102 103 launch { 104 viewModel.wifiIcon.collect { wifiIcon -> 105 view.isVisible = wifiIcon is WifiIcon.Visible 106 if (wifiIcon is WifiIcon.Visible) { 107 IconViewBinder.bind(wifiIcon.icon, iconView) 108 } 109 } 110 } 111 112 launch { 113 iconTint.collect { tint -> 114 val tintList = ColorStateList.valueOf(tint) 115 iconView.imageTintList = tintList 116 activityInView.imageTintList = tintList 117 activityOutView.imageTintList = tintList 118 dotView.setDecorColor(tint) 119 } 120 } 121 122 launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } } 123 124 if (statusBarStaticInoutIndicators()) { 125 // Set the opacity of the activity indicators 126 launch { 127 viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible 128 -> 129 activityInView.imageAlpha = 130 (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE) 131 } 132 } 133 134 launch { 135 viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible 136 -> 137 activityOutView.imageAlpha = 138 (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE) 139 } 140 } 141 } else { 142 launch { 143 viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible 144 -> 145 activityInView.isVisible = visible 146 } 147 } 148 149 launch { 150 viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible 151 -> 152 activityOutView.isVisible = visible 153 } 154 } 155 } 156 157 launch { 158 viewModel.isActivityContainerVisible.distinctUntilChanged().collect { visible -> 159 activityContainerView.isVisible = visible 160 } 161 } 162 163 launch { 164 viewModel.isAirplaneSpacerVisible.distinctUntilChanged().collect { visible -> 165 airplaneSpacer.isVisible = visible 166 } 167 } 168 169 launch { 170 viewModel.isSignalSpacerVisible.distinctUntilChanged().collect { visible -> 171 signalSpacer.isVisible = visible 172 } 173 } 174 175 try { 176 awaitCancellation() 177 } finally { 178 isCollecting = false 179 } 180 } 181 } 182 183 return object : ModernStatusBarViewBinding { 184 override fun getShouldIconBeVisible(): Boolean { 185 return viewModel.wifiIcon.value is WifiIcon.Visible 186 } 187 188 override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) { 189 visibilityState.value = state 190 } 191 192 override fun onIconTintChanged(newTint: Int, contrastTint: Int /* unused */) { 193 iconTint.value = newTint 194 } 195 196 override fun onDecorTintChanged(newTint: Int) { 197 decorTint.value = newTint 198 } 199 200 override fun isCollecting(): Boolean { 201 return isCollecting 202 } 203 } 204 } 205 } 206