1 /* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mail.ui; 19 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.graphics.Canvas; 23 import android.graphics.LinearGradient; 24 import android.graphics.Paint; 25 import android.graphics.RectF; 26 import android.graphics.Shader; 27 import androidx.core.text.BidiFormatter; 28 29 import com.android.mail.R; 30 import com.android.mail.providers.Conversation; 31 import com.android.mail.providers.Folder; 32 import com.android.mail.providers.UIProvider.FolderType; 33 import com.android.mail.utils.FolderUri; 34 import com.android.mail.utils.LogTag; 35 import com.android.mail.utils.Utils; 36 import com.google.common.collect.Sets; 37 38 import java.util.NavigableSet; 39 import java.util.Set; 40 41 /** 42 * Used to generate folder display information given a raw folders string. 43 * (The raw folders string can be obtained from {@link Conversation#getRawFolders()}.) 44 */ 45 public abstract class FolderDisplayer { 46 public static final String LOG_TAG = LogTag.getLogTag(); 47 protected Context mContext; 48 protected final NavigableSet<Folder> mFoldersSortedSet = Sets.newTreeSet(); 49 protected final FolderDrawableResources mFolderDrawableResources = 50 new FolderDrawableResources(); 51 52 public static class FolderDrawableResources { 53 public int defaultFgColor; 54 public int defaultBgColor; 55 public int folderRoundedCornerRadius; 56 public int overflowGradientPadding; 57 public int folderHorizontalPadding; 58 public int folderInBetweenPadding; 59 public int folderFontSize; 60 public int folderVerticalOffset; 61 } 62 FolderDisplayer(Context context)63 public FolderDisplayer(Context context) { 64 mContext = context; 65 initializeDrawableResources(); 66 } 67 initializeDrawableResources()68 protected void initializeDrawableResources() { 69 // Set default values used across all folder chips 70 final Resources res = mContext.getResources(); 71 mFolderDrawableResources.defaultFgColor = 72 res.getColor(R.color.default_folder_foreground_color); 73 mFolderDrawableResources.defaultBgColor = 74 res.getColor(R.color.default_folder_background_color); 75 mFolderDrawableResources.folderRoundedCornerRadius = 76 res.getDimensionPixelOffset(R.dimen.folder_rounded_corner_radius); 77 mFolderDrawableResources.folderInBetweenPadding = 78 res.getDimensionPixelOffset(R.dimen.folder_start_padding); 79 } 80 81 /** 82 * Configure the FolderDisplayer object by filtering and copying from the list of raw folders. 83 * 84 * @param conv {@link Conversation} containing the folders to display. 85 * @param ignoreFolderUri (optional) folder to omit from the displayed set 86 * @param ignoreFolderType -1, or the {@link FolderType} to omit from the displayed set 87 */ loadConversationFolders(Conversation conv, final FolderUri ignoreFolderUri, final int ignoreFolderType)88 public void loadConversationFolders(Conversation conv, final FolderUri ignoreFolderUri, 89 final int ignoreFolderType) { 90 mFoldersSortedSet.clear(); 91 for (Folder folder : conv.getRawFolders()) { 92 // Skip the ignoreFolderType 93 if (ignoreFolderType >= 0 && folder.isType(ignoreFolderType)) { 94 continue; 95 } 96 // skip the ignoreFolder 97 if (ignoreFolderUri != null && ignoreFolderUri.equals(folder.folderUri)) { 98 continue; 99 } 100 mFoldersSortedSet.add(folder); 101 } 102 } 103 104 /** 105 * Reset this FolderDisplayer so that it can be reused. 106 */ reset()107 public void reset() { 108 mFoldersSortedSet.clear(); 109 } 110 111 /** 112 * Helper function to calculate exactly how much space the displayed folders should take. 113 * @param folders the set of folders to display. 114 * @param maxCellWidth this signifies the absolute max for each folder cell, no exceptions. 115 * @param maxLayoutWidth the view's layout width, aka how much space we have. 116 * @param foldersInBetweenPadding the padding between folder chips. 117 * @param foldersHorizontalPadding the padding between the edge of the chip and the text. 118 * @param maxFolderCount the maximum number of folder chips to display. 119 * @param paint work paint. 120 * @return an array of integers that signifies the length of each folder chip. 121 */ measureFolderDimen(Set<Folder> folders, int maxCellWidth, int maxLayoutWidth, int foldersInBetweenPadding, int foldersHorizontalPadding, int maxFolderCount, Paint paint)122 public static int[] measureFolderDimen(Set<Folder> folders, int maxCellWidth, 123 int maxLayoutWidth, int foldersInBetweenPadding, int foldersHorizontalPadding, 124 int maxFolderCount, Paint paint) { 125 126 final int numDisplayedFolders = Math.min(maxFolderCount, folders.size()); 127 if (numDisplayedFolders == 0) { 128 return new int[0]; 129 } 130 131 // This variable is calculated based on the number of folders we are displaying 132 final int maxAllowedCellSize = Math.min(maxCellWidth, (maxLayoutWidth - 133 (numDisplayedFolders - 1) * foldersInBetweenPadding) / numDisplayedFolders); 134 final int[] measurements = new int[numDisplayedFolders]; 135 136 int count = 0; 137 int missingWidth = 0; 138 int extraWidth = 0; 139 for (Folder f : folders) { 140 if (count > numDisplayedFolders - 1) { 141 break; 142 } 143 144 final String folderString = f.name; 145 final int neededWidth = (int) paint.measureText(folderString) + 146 2 * foldersHorizontalPadding; 147 148 if (neededWidth > maxAllowedCellSize) { 149 // What we can take from others is the minimum of the width we need to borrow 150 // and the width we are allowed to borrow. 151 final int borrowedWidth = Math.min(neededWidth - maxAllowedCellSize, 152 maxCellWidth - maxAllowedCellSize); 153 final int extraWidthLeftover = extraWidth - borrowedWidth; 154 if (extraWidthLeftover >= 0) { 155 measurements[count] = Math.min(neededWidth, maxCellWidth); 156 extraWidth = extraWidthLeftover; 157 } else { 158 measurements[count] = maxAllowedCellSize + extraWidth; 159 extraWidth = 0; 160 } 161 missingWidth = -extraWidthLeftover; 162 } else { 163 extraWidth = maxAllowedCellSize - neededWidth; 164 measurements[count] = neededWidth; 165 if (missingWidth > 0) { 166 if (extraWidth >= missingWidth) { 167 measurements[count - 1] += missingWidth; 168 extraWidth -= missingWidth; 169 } else { 170 measurements[count - 1] += extraWidth; 171 extraWidth = 0; 172 } 173 } 174 missingWidth = 0; 175 } 176 177 count++; 178 } 179 180 return measurements; 181 } 182 drawFolder(Canvas canvas, float x, float y, int width, int height, Folder f, FolderDisplayer.FolderDrawableResources res, BidiFormatter formatter, Paint paint)183 public static void drawFolder(Canvas canvas, float x, float y, int width, int height, 184 Folder f, FolderDisplayer.FolderDrawableResources res, BidiFormatter formatter, 185 Paint paint) { 186 drawFolder(canvas, x, y, width, height, f.name, 187 f.getForegroundColor(res.defaultFgColor), f.getBackgroundColor(res.defaultBgColor), 188 res, formatter, paint); 189 } 190 drawFolder(Canvas canvas, float x, float y, int width, int height, String name, int fgColor, int bgColor, FolderDisplayer.FolderDrawableResources res, BidiFormatter formatter, Paint paint)191 public static void drawFolder(Canvas canvas, float x, float y, int width, int height, 192 String name, int fgColor, int bgColor, FolderDisplayer.FolderDrawableResources res, 193 BidiFormatter formatter, Paint paint) { 194 canvas.save(); 195 canvas.translate(x, y + res.folderVerticalOffset); 196 197 // Draw the box. 198 paint.setColor(bgColor); 199 paint.setStyle(Paint.Style.FILL); 200 final RectF rect = new RectF(0, 0, width, height); 201 canvas.drawRoundRect(rect, res.folderRoundedCornerRadius, res.folderRoundedCornerRadius, 202 paint); 203 204 // Draw the text based on the language locale and layout direction. 205 paint.setColor(fgColor); 206 paint.setStyle(Paint.Style.FILL); 207 208 // Compute the text/gradient indices 209 final int textLength = (int) paint.measureText(name); 210 final int gradientX0; 211 final int gradientX1; 212 final int textX; 213 214 /*************************************************************************************************** 215 * width - the actual folder chip rectangle. * 216 * textLength - the length of the folder's full name (can be longer than * 217 * the actual chip, which is what overflow gradient is for). * 218 * innerPadding - the padding between the text and the chip edge. * 219 * overflowPadding - the padding between start of overflow and the chip edge. * 220 * * 221 * * 222 * text is in a RTL language * 223 * * 224 * index-0 * 225 * |<---------------------------- width ---------------------------->| * 226 * |<-------------------------textLength------------------>| | * 227 * | |<----- overflowPadding ----->| | * 228 * | |<- innerPadding ->|<-------->|<--------->|<- horizontalPadding ->| * 229 * textX gX1 gX0 * 230 * * 231 * * 232 * text is in a LTR language. * 233 * * 234 * index-0 * 235 * |<------------------------------ width ------------------------------->| * 236 * | |<-------------------------textLength-------------------->| * 237 * | |<-------- overflowPadding ------->| * 238 * |<- horizontalPadding ->|<--------->|<-------->|<- horizontalPadding ->| * 239 * textX gX0 gX1 * 240 * * 241 **************************************************************************************************/ 242 if (formatter.isRtl(name)) { 243 gradientX0 = res.overflowGradientPadding; 244 gradientX1 = res.folderHorizontalPadding; 245 textX = width - res.folderHorizontalPadding - textLength; 246 } else { 247 gradientX0 = width - res.overflowGradientPadding; 248 gradientX1 = width - res.folderHorizontalPadding; 249 textX = res.folderHorizontalPadding; 250 } 251 252 // Draw the text and the possible overflow gradient 253 // Overflow happens when the text is longer than the chip width minus side paddings. 254 if (textLength > width - 2 * res.folderHorizontalPadding) { 255 final Shader shader = new LinearGradient(gradientX0, 0, gradientX1, 0, fgColor, 256 Utils.getTransparentColor(fgColor), Shader.TileMode.CLAMP); 257 paint.setShader(shader); 258 } 259 final int textY = height / 2 - (int) (paint.descent() + paint.ascent()) / 2; 260 canvas.drawText(name, textX, textY, paint); 261 paint.setShader(null); 262 263 canvas.restore(); 264 } 265 } 266