• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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