1 /*
2  * Copyright (C) 2023 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 
18 package com.android.wallpaper.picker.option.ui.adapter
19 
20 import android.view.LayoutInflater
21 import android.view.View
22 import android.view.ViewGroup
23 import androidx.annotation.LayoutRes
24 import androidx.lifecycle.LifecycleOwner
25 import androidx.lifecycle.lifecycleScope
26 import androidx.recyclerview.widget.DiffUtil
27 import androidx.recyclerview.widget.RecyclerView
28 import com.android.wallpaper.R
29 import com.android.wallpaper.picker.option.ui.binder.OptionItemBinder
30 import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
31 import kotlinx.coroutines.CoroutineDispatcher
32 import kotlinx.coroutines.Dispatchers
33 import kotlinx.coroutines.DisposableHandle
34 import kotlinx.coroutines.launch
35 import kotlinx.coroutines.withContext
36 
37 /** Adapts between option items and their views. */
38 class OptionItemAdapter<T>(
39     @LayoutRes private val layoutResourceId: Int,
40     private val lifecycleOwner: LifecycleOwner,
41     private val backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO,
42     private val foregroundTintSpec: OptionItemBinder.TintSpec? = null,
43     private val bindIcon: (View, T) -> Unit,
44 ) : RecyclerView.Adapter<OptionItemAdapter.ViewHolder>() {
45 
46     private val items = mutableListOf<OptionItemViewModel<T>>()
47 
setItemsnull48     fun setItems(items: List<OptionItemViewModel<T>>, callback: (() -> Unit)? = null) {
49         lifecycleOwner.lifecycleScope.launch {
50             val oldItems = this@OptionItemAdapter.items
51             val newItems = items
52             val diffResult =
53                 withContext(backgroundDispatcher) {
54                     DiffUtil.calculateDiff(
55                         object : DiffUtil.Callback() {
56                             override fun getOldListSize(): Int {
57                                 return oldItems.size
58                             }
59 
60                             override fun getNewListSize(): Int {
61                                 return newItems.size
62                             }
63 
64                             override fun areItemsTheSame(
65                                 oldItemPosition: Int,
66                                 newItemPosition: Int
67                             ): Boolean {
68                                 val oldItem = oldItems[oldItemPosition]
69                                 val newItem = newItems[newItemPosition]
70                                 return oldItem.key.value == newItem.key.value
71                             }
72 
73                             override fun areContentsTheSame(
74                                 oldItemPosition: Int,
75                                 newItemPosition: Int
76                             ): Boolean {
77                                 val oldItem = oldItems[oldItemPosition]
78                                 val newItem = newItems[newItemPosition]
79                                 return oldItem == newItem
80                             }
81                         },
82                         /* detectMoves= */ false,
83                     )
84                 }
85 
86             oldItems.clear()
87             oldItems.addAll(items)
88             diffResult.dispatchUpdatesTo(this@OptionItemAdapter)
89             if (callback != null) {
90                 callback()
91             }
92         }
93     }
94 
95     class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
96         var disposableHandle: DisposableHandle? = null
97     }
98 
getItemCountnull99     override fun getItemCount(): Int {
100         return items.size
101     }
102 
onCreateViewHoldernull103     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
104         return ViewHolder(
105             LayoutInflater.from(parent.context)
106                 .inflate(
107                     layoutResourceId,
108                     parent,
109                     false,
110                 )
111         )
112     }
113 
onBindViewHoldernull114     override fun onBindViewHolder(holder: ViewHolder, position: Int) {
115         holder.disposableHandle?.dispose()
116         val item = items[position]
117         item.payload?.let {
118             bindIcon(holder.itemView.requireViewById(R.id.foreground), item.payload)
119         }
120         holder.disposableHandle =
121             OptionItemBinder.bind(
122                 view = holder.itemView,
123                 viewModel = item,
124                 lifecycleOwner = lifecycleOwner,
125                 foregroundTintSpec = foregroundTintSpec,
126             )
127     }
128 }
129