1 /* <lambda>null2 * Copyright (C) 2025 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 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package com.android.healthconnect.controller.shared.preference 15 16 import android.content.Context 17 import android.icu.text.MessageFormat 18 import android.view.View 19 import android.widget.FrameLayout 20 import android.widget.LinearLayout 21 import android.widget.TextView 22 import androidx.core.view.isVisible 23 import androidx.preference.PreferenceViewHolder 24 import com.android.healthconnect.controller.R 25 import com.android.healthconnect.controller.shared.app.AppMetadata 26 import com.android.healthconnect.controller.shared.app.AppUtils 27 import com.android.settingslib.widget.SettingsThemeHelper 28 import java.text.NumberFormat 29 import java.util.Locale 30 31 /** 32 * A custom `Preference` that represents a ranked item with an action icon. 33 * 34 * This preference is designed for use in lists where items have a specific order (ranking) and 35 * require an action menu for reordering or other operations. It displays: 36 * - A position number indicating its rank, 1-based indexing. 37 * - A label (e.g., app name). 38 * - A clickable action icon for additional options (optional). 39 * 40 * @param context The context in which this preference is used. 41 * @param appMetadata Metadata about the item (e.g., app details). 42 * @param appUtils To determine whether a package name is the default app. 43 * @param position The current position of the item in the ranked list, 0-based indexing. 44 * @param onActionClick A lambda that handles action clicks (e.g., reordering, removing items). 45 * @param shouldShowActionButton Whether the action button should be shown, by default true. 46 */ 47 class RankedActionPreference( 48 context: Context, 49 private val appMetadata: AppMetadata, 50 private val appUtils: AppUtils, 51 private var position: Int, 52 private val onActionClick: (View, Int) -> Unit, 53 private var shouldShowActionButton: Boolean = true, 54 ) : HealthPreference(context, null) { 55 56 private lateinit var rootView: LinearLayout 57 private lateinit var appPositionView: TextView 58 private lateinit var actionView: FrameLayout 59 60 init { 61 layoutResource = 62 if (SettingsThemeHelper.isExpressiveTheme(context)) { 63 R.layout.widget_app_source_layout_expressive 64 } else { 65 R.layout.widget_app_source_layout_legacy 66 } 67 order = position 68 isSelectable = false 69 } 70 71 override fun onBindViewHolder(holder: PreferenceViewHolder) { 72 super.onBindViewHolder(holder) 73 74 rootView = holder.itemView as LinearLayout 75 appPositionView = holder.findViewById(R.id.app_position) as TextView 76 val appNameView = holder.findViewById(R.id.app_name) as TextView 77 val appSummary = holder.findViewById(R.id.app_source_summary) as TextView 78 actionView = holder.findViewById(R.id.action_icon) as FrameLayout 79 80 setPositionString() 81 appNameView.text = appMetadata.appName 82 setAppSummary(appSummary) 83 setActionButton() 84 setRootContentDescription() 85 } 86 87 fun reduceIndex() { 88 position-- 89 setPositionString() 90 } 91 92 fun hideActionButton() { 93 shouldShowActionButton = false 94 actionView.isVisible = false 95 } 96 97 private fun setPositionString() { 98 val positionString: String = NumberFormat.getIntegerInstance().format(position + 1) 99 appPositionView.text = positionString 100 } 101 102 private fun setAppSummary(appSummary: TextView) { 103 if (isDefaultApp()) { 104 appSummary.text = context.getString(R.string.default_app_summary) 105 appSummary.visibility = View.VISIBLE 106 } else { 107 appSummary.visibility = View.GONE 108 } 109 } 110 111 private fun setActionButton() { 112 if (shouldShowActionButton) { 113 actionView.isVisible = true 114 actionView.setOnClickListener { view -> onActionClick(view, position) } 115 actionView.contentDescription = 116 context.getString( 117 R.string.see_actions_button_content_description, 118 appMetadata.appName, 119 ) 120 } else { 121 actionView.isVisible = false 122 } 123 } 124 125 private fun setRootContentDescription() { 126 rootView.let { 127 it.isClickable = false 128 it.isLongClickable = false 129 it.isFocusable = false 130 it.contentDescription = getContentDescription() 131 } 132 } 133 134 private fun getContentDescription(): String { 135 val formatter = MessageFormat("{0,ordinal}", Locale.getDefault()) 136 val ordinal = formatter.format(arrayOf(position + 1)) 137 val description = 138 context.getString( 139 R.string.priority_list_item_content_description, 140 ordinal, 141 appMetadata.appName, 142 ) 143 return if (isDefaultApp()) { 144 description + "\n" + context.getString(R.string.default_app_summary) 145 } else { 146 description 147 } 148 } 149 150 private fun isDefaultApp(): Boolean = appUtils.isDefaultApp(context, appMetadata.packageName) 151 } 152