blob: c0497c2999705d10854a6bf12c3724f88ee5ada2 [file] [log] [blame]
/**
* Copyright (C) 2023 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.healthconnect.controller.datasources.appsources
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.healthconnect.controller.R
import com.android.healthconnect.controller.datasources.DataSourcesViewModel
import com.android.healthconnect.controller.shared.HealthDataCategoryInt
import com.android.healthconnect.controller.shared.app.AppMetadata
import com.android.healthconnect.controller.shared.app.AppUtils
import com.android.healthconnect.controller.utils.AttributeResolver
import java.text.NumberFormat
/** RecyclerView adapter that holds the list of app sources for this [HealthDataCategory]. */
class AppSourcesAdapter(
private val context: Context,
private val appUtils: AppUtils,
priorityList: List<AppMetadata>,
potentialAppSourcesList: List<AppMetadata>,
private val dataSourcesViewModel: DataSourcesViewModel,
private val category: @HealthDataCategoryInt Int,
private val onAppRemovedListener: OnAppRemovedFromPriorityListListener,
private val itemMoveAttachCallbackListener: ItemMoveAttachCallbackListener,
) : RecyclerView.Adapter<AppSourcesAdapter.AppSourcesItemViewHolder?>() {
private var listener: ItemTouchHelper? = null
private var priorityList = priorityList.toMutableList()
private var potentialAppSourcesList = potentialAppSourcesList.toMutableList()
private var isEditMode = false
private val POSITION_CHANGED_PAYLOAD = Any()
interface OnAppRemovedFromPriorityListListener {
fun onAppRemovedFromPriorityList()
}
/**
* Used for re-attaching the onItemMovedCallback to the RecyclerView when we exit the edit mode
*/
interface ItemMoveAttachCallbackListener {
fun attachCallback()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): AppSourcesItemViewHolder {
return AppSourcesItemViewHolder(
LayoutInflater.from(viewGroup.context)
.inflate(R.layout.widget_app_source_layout, viewGroup, false),
listener)
}
override fun onBindViewHolder(viewHolder: AppSourcesItemViewHolder, position: Int) {
viewHolder.bind(position, priorityList[position], isOnlyApp = priorityList.size == 1)
}
override fun getItemCount(): Int {
return priorityList.size
}
fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
val movedAppInfo: AppMetadata = priorityList.removeAt(fromPosition)
priorityList.add(
if (toPosition > fromPosition + 1) toPosition - 1 else toPosition, movedAppInfo)
notifyItemMoved(fromPosition, toPosition)
if (toPosition < fromPosition) {
notifyItemRangeChanged(
toPosition, fromPosition - toPosition + 1, POSITION_CHANGED_PAYLOAD)
} else {
notifyItemRangeChanged(
fromPosition, toPosition - fromPosition + 1, POSITION_CHANGED_PAYLOAD)
}
dataSourcesViewModel.updatePriorityList(priorityList.map { it.packageName }, category)
return true
}
fun setOnItemDragStartedListener(listener: ItemTouchHelper) {
this.listener = listener
}
private fun removeOnItemDragStartedListener() {
listener = null
}
fun toggleEditMode(isEditMode: Boolean) {
this.isEditMode = isEditMode
if (!isEditMode) {
itemMoveAttachCallbackListener.attachCallback()
} else {
removeOnItemDragStartedListener()
}
notifyDataSetChanged()
}
fun removeItem(position: Int) {
priorityList.removeAt(position)
notifyItemRemoved(position)
}
/** Shows a single item of the priority list. */
inner class AppSourcesItemViewHolder(
itemView: View,
private val onItemDragStartedListener: ItemTouchHelper?
) : RecyclerView.ViewHolder(itemView) {
private val appPositionView: TextView
private val appNameView: TextView
private val appSourceSummary: TextView
private val actionView: View
private val actionIconBackground: ImageView
init {
appPositionView = itemView.findViewById(R.id.app_position)
appNameView = itemView.findViewById(R.id.app_name)
actionView = itemView.findViewById(R.id.action_icon)
actionIconBackground = itemView.findViewById(R.id.action_icon_background)
appSourceSummary = itemView.findViewById(R.id.app_source_summary)
}
fun bind(appPosition: Int, appMetadata: AppMetadata, isOnlyApp: Boolean) {
// Adding 1 to position as position starts from 0 but should show to the user starting
// from 1.
val positionString: String = NumberFormat.getIntegerInstance().format(appPosition + 1)
appPositionView.text = positionString
appNameView.text = appMetadata.appName
if (appUtils.isDefaultApp(context, appMetadata.packageName)) {
appSourceSummary.visibility = View.VISIBLE
appSourceSummary.text = context.getString(R.string.default_app_summary)
} else {
appSourceSummary.visibility = View.GONE
}
if (isEditMode) {
setupItemForEditMode(appPosition)
} else {
setupItemForDragMode(isOnlyApp)
}
}
private fun setupItemForEditMode(appPosition: Int) {
actionView.isClickable = true
actionView.visibility = View.VISIBLE
actionIconBackground.background =
AttributeResolver.getDrawable(itemView.context, R.attr.closeIcon)
actionView.setOnTouchListener(null)
actionView.setOnClickListener {
val currentPriority = priorityList.toMutableList()
val removedItem = currentPriority.removeAt(appPosition)
dataSourcesViewModel.setEditedPriorityList(currentPriority)
dataSourcesViewModel.updatePriorityList(
currentPriority.map { it.packageName }, category)
potentialAppSourcesList.add(removedItem)
dataSourcesViewModel.loadPotentialAppSources(category, false)
dataSourcesViewModel.setEditedPotentialAppSources(potentialAppSourcesList)
removeItem(appPosition)
onAppRemovedListener.onAppRemovedFromPriorityList()
}
}
// These items are not clickable and so onTouch does not need to reimplement click
// conditions.
// Drag&drop in accessibility mode (talk back) is implemented as custom actions.
@SuppressLint("ClickableViewAccessibility")
private fun setupItemForDragMode(isOnlyApp: Boolean) {
// Hide drag icon if this is the only app in the list
if (isOnlyApp) {
actionView.visibility = View.INVISIBLE
actionView.isClickable = false
} else {
actionIconBackground.background =
AttributeResolver.getDrawable(itemView.context, R.attr.priorityItemDragIcon)
actionView.setOnClickListener(null)
actionView.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN ||
event.action == MotionEvent.ACTION_UP) {
onItemDragStartedListener?.startDrag(this)
}
false
}
}
}
}
}