blob: 83a3d0d0457a7a4c606d959ee52ad969a2cce8d7 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.user.ui.binder
import android.content.Context
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.helper.widget.Flow as FlowWidget
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.user.UserSwitcherPopupMenu
import com.android.systemui.user.UserSwitcherRootView
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.util.children
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
/** Binds a user switcher to its view-model. */
object UserSwitcherViewBinder {
private const val USER_VIEW_TAG = "user_view"
/** Binds the given view to the given view-model. */
fun bind(
view: ViewGroup,
viewModel: UserSwitcherViewModel,
lifecycleOwner: LifecycleOwner,
layoutInflater: LayoutInflater,
falsingCollector: FalsingCollector,
onFinish: () -> Unit,
) {
val rootView: UserSwitcherRootView = view.requireViewById(R.id.user_switcher_root)
val flowWidget: FlowWidget = view.requireViewById(R.id.flow)
val addButton: View = view.requireViewById(R.id.add)
val cancelButton: View = view.requireViewById(R.id.cancel)
val popupMenuAdapter = MenuAdapter(layoutInflater)
var popupMenu: UserSwitcherPopupMenu? = null
rootView.touchHandler =
object : Gefingerpoken {
override fun onTouchEvent(ev: MotionEvent?): Boolean {
falsingCollector.onTouchEvent(ev)
return false
}
}
addButton.setOnClickListener { viewModel.onOpenMenuButtonClicked() }
cancelButton.setOnClickListener { viewModel.onCancelButtonClicked() }
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.isFinishRequested
.filter { it }
.collect {
onFinish()
viewModel.onFinished()
}
}
}
}
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { viewModel.isOpenMenuButtonVisible.collect { addButton.isVisible = it } }
launch {
viewModel.isMenuVisible.collect { isVisible ->
if (isVisible && popupMenu?.isShowing != true) {
popupMenu?.dismiss()
// Use post to make sure we show the popup menu *after* the activity is
// ready to show one to avoid a WindowManager$BadTokenException.
view.post {
popupMenu =
createAndShowPopupMenu(
context = view.context,
anchorView = addButton,
adapter = popupMenuAdapter,
onDismissed = viewModel::onMenuClosed,
)
}
} else if (!isVisible && popupMenu?.isShowing == true) {
popupMenu?.dismiss()
popupMenu = null
}
}
}
launch {
viewModel.menu.collect { menuViewModels ->
popupMenuAdapter.setItems(menuViewModels)
}
}
launch {
viewModel.maximumUserColumns.collect { maximumColumns ->
flowWidget.setMaxElementsWrap(maximumColumns)
}
}
launch {
viewModel.users.collect { users ->
val viewPool =
view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList()
viewPool.forEach { view.removeView(it) }
users.forEach { userViewModel ->
val userView =
if (viewPool.isNotEmpty()) {
viewPool.removeAt(0)
} else {
val inflatedView =
layoutInflater.inflate(
R.layout.user_switcher_fullscreen_item,
view,
false,
)
inflatedView.tag = USER_VIEW_TAG
inflatedView
}
userView.id = View.generateViewId()
view.addView(userView)
flowWidget.addView(userView)
UserViewBinder.bind(
view = userView,
viewModel = userViewModel,
)
}
}
}
}
}
}
private fun createAndShowPopupMenu(
context: Context,
anchorView: View,
adapter: MenuAdapter,
onDismissed: () -> Unit,
): UserSwitcherPopupMenu {
return UserSwitcherPopupMenu(context).apply {
this.anchorView = anchorView
setAdapter(adapter)
setOnDismissListener { onDismissed() }
setOnItemClickListener { _, _, position, _ ->
val itemPositionExcludingHeader = position - 1
adapter.getItem(itemPositionExcludingHeader).onClicked()
}
show()
}
}
/** Adapter for the menu that can be opened. */
private class MenuAdapter(
private val layoutInflater: LayoutInflater,
) : BaseAdapter() {
private val items = mutableListOf<UserActionViewModel>()
override fun getCount(): Int {
return items.size
}
override fun getItem(position: Int): UserActionViewModel {
return items[position]
}
override fun getItemId(position: Int): Long {
return getItem(position).viewKey
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view =
convertView
?: layoutInflater.inflate(
R.layout.user_switcher_fullscreen_popup_item,
parent,
false
)
val viewModel = getItem(position)
view.requireViewById<ImageView>(R.id.icon).setImageResource(viewModel.iconResourceId)
view.requireViewById<TextView>(R.id.text).text =
view.resources.getString(viewModel.textResourceId)
return view
}
fun setItems(items: List<UserActionViewModel>) {
this.items.clear()
this.items.addAll(items)
notifyDataSetChanged()
}
}
}