1 /**
<lambda>null2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * ```
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * ```
10  *
11  * Unless required by applicable law or agreed to in writing, software distributed under the License
12  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 package com.android.healthconnect.controller.permissiontypes.prioritylist
17 
18 import android.annotation.SuppressLint
19 import android.graphics.drawable.Drawable
20 import android.view.LayoutInflater
21 import android.view.MotionEvent
22 import android.view.View
23 import android.view.ViewGroup
24 import android.widget.ImageView
25 import android.widget.TextView
26 import androidx.recyclerview.widget.ItemTouchHelper
27 import androidx.recyclerview.widget.RecyclerView
28 import com.android.healthconnect.controller.R
29 import com.android.healthconnect.controller.permissiontypes.HealthPermissionTypesViewModel
30 import com.android.healthconnect.controller.shared.app.AppMetadata
31 import java.text.NumberFormat
32 
33 /** RecyclerView adapter that holds the list of the priority list. */
34 class PriorityListAdapter(
35     appMetadataList: List<AppMetadata>,
36     private val viewModel: HealthPermissionTypesViewModel
37 ) : RecyclerView.Adapter<PriorityListAdapter.PriorityListItemViewHolder?>() {
38 
39     private val POSITION_CHANGED_PAYLOAD = Any()
40 
41     private var listener: ItemTouchHelper? = null
42     private var appMetadataList = appMetadataList.toMutableList()
43 
44     override fun onCreateViewHolder(
45         viewGroup: ViewGroup,
46         viewType: Int
47     ): PriorityListItemViewHolder {
48         return PriorityListItemViewHolder(
49             LayoutInflater.from(viewGroup.context)
50                 .inflate(R.layout.priority_item, viewGroup, false),
51             listener)
52     }
53 
54     override fun onBindViewHolder(viewHolder: PriorityListItemViewHolder, position: Int) {
55         // This method is needed as it's marked as abstract however it's empty as the logic is
56         // handled in the onBindViewHolder method that accepts a payloads list.
57     }
58 
59     override fun onBindViewHolder(
60         viewHolder: PriorityListItemViewHolder,
61         position: Int,
62         payloads: List<Any>
63     ) {
64         viewHolder.bindPosition(position)
65 
66         // Only bind the full view when there's no payload objects to avoid re-binding the icon and
67         // touch listener in the middle of a drag event.
68         if (payloads.isEmpty()) {
69             viewHolder.bind(appMetadataList[position].appName, appMetadataList[position].icon)
70         }
71     }
72 
73     override fun getItemCount(): Int {
74         return appMetadataList.size
75     }
76 
77     fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
78         val movedAppInfo: AppMetadata = appMetadataList.removeAt(fromPosition)
79         appMetadataList.add(
80             if (toPosition > fromPosition + 1) toPosition - 1 else toPosition, movedAppInfo)
81         notifyItemMoved(fromPosition, toPosition)
82         if (toPosition < fromPosition) {
83             notifyItemRangeChanged(
84                 toPosition, fromPosition - toPosition + 1, POSITION_CHANGED_PAYLOAD)
85         } else {
86             notifyItemRangeChanged(
87                 fromPosition, toPosition - fromPosition + 1, POSITION_CHANGED_PAYLOAD)
88         }
89         viewModel.setEditedPriorityList(appMetadataList)
90         return true
91     }
92 
93     fun getPackageNameList(): List<String> {
94         return appMetadataList.stream().map(AppMetadata::packageName).toList()
95     }
96 
97     fun setOnItemDragStartedListener(listener: ItemTouchHelper) {
98         this.listener = listener
99     }
100 
101     fun removeOnItemDragStartedListener() {
102         listener = null
103     }
104 
105     /** Shows a single item of the priority list. */
106     class PriorityListItemViewHolder(itemView: View, onItemDragStartedListener: ItemTouchHelper?) :
107         RecyclerView.ViewHolder(itemView) {
108         private val appPositionView: TextView
109         private val appNameView: TextView
110         private val appIconView: ImageView
111         private val dragIconView: View
112 
113         private val onItemDragStartedListener: ItemTouchHelper?
114 
115         init {
116             appPositionView = itemView.findViewById(R.id.app_position)
117             appNameView = itemView.findViewById(R.id.app_name)
118             appIconView = itemView.findViewById(R.id.app_icon)
119             dragIconView = itemView.findViewById(R.id.action_icon)
120             this.onItemDragStartedListener = onItemDragStartedListener
121         }
122 
123         // These items are not clickable and so onTouch does not need to reimplement click
124         // conditions.
125         // Drag&drop in accessibility mode (talk back) is implemented as custom actions.
126         @SuppressLint("ClickableViewAccessibility")
127         fun bind(appName: String?, appIcon: Drawable?) {
128             appNameView.text = appName
129             appIconView.setImageDrawable(appIcon)
130             dragIconView.setOnTouchListener { _, event ->
131                 if (event.action == MotionEvent.ACTION_DOWN ||
132                     event.action == MotionEvent.ACTION_UP) {
133                     onItemDragStartedListener?.startDrag(this)
134                 }
135                 false
136             }
137         }
138 
139         fun bindPosition(appPosition: Int) {
140             // Adding 1 to position as position starts from 0 but should show to the user starting
141             // from 1.
142             val positionString: String = NumberFormat.getIntegerInstance().format(appPosition + 1)
143             appPositionView.text = positionString
144         }
145     }
146 }
147