Skip to content

Commit 2f94e57

Browse files
authored
Merge pull request #131 from Quivr/feat/schedule-tags
Support for tags
2 parents 14ca02f + 440c153 commit 2f94e57

2 files changed

Lines changed: 179 additions & 1 deletion

File tree

library/src/main/java/com/alamkanak/weekview/WeekView.java

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import android.graphics.*;
66
import android.graphics.drawable.BitmapDrawable;
77
import android.graphics.drawable.Drawable;
8+
import androidx.core.content.res.ResourcesCompat;
9+
import androidx.core.graphics.drawable.DrawableCompat;
810
import android.os.Build;
911

1012
import androidx.annotation.NonNull;
@@ -79,6 +81,47 @@ private enum AutoScrollDirection {
7981
private Paint mTodayHeaderTextPaint;
8082
private Paint mEventBackgroundPaint;
8183
private Paint mNewEventBackgroundPaint;
84+
private TextPaint mTagTextPaint;
85+
private Paint mTagBackgroundPaint;
86+
private Xfermode mXfermode;
87+
private int mTagSize = 96;
88+
private int mTagSpacing = 12;
89+
private int mTagCornerRadius = 12;
90+
private int mTagTextSize = 12;
91+
92+
// Tag icons are resolved from the host app's drawable resources named with the
93+
// prefix `tag_`. E.g. `drawable/tag_meeting`.
94+
95+
public void setTagSize(int sizePx) {
96+
mTagSize = sizePx;
97+
invalidate();
98+
}
99+
public void setTagSpacing(int spacingPx) {
100+
mTagSpacing = spacingPx;
101+
invalidate();
102+
}
103+
public int getTagSize() {
104+
return mTagSize;
105+
}
106+
public int getTagpacing() {
107+
return mTagSpacing;
108+
}
109+
public int getTagCornerRadius() {
110+
return mTagCornerRadius;
111+
}
112+
public int getTagTextSize() {
113+
return mTagTextSize;
114+
}
115+
public void setTagTextSize(int tagTextSize) {
116+
mTagTextSize = tagTextSize;
117+
mTagTextPaint.setTextSize(mTagTextSize);
118+
invalidate();
119+
}
120+
public void setTagCornerRadius(int tagCornerRadius) {
121+
mTagCornerRadius = tagCornerRadius;
122+
invalidate();
123+
}
124+
82125
private float mHeaderColumnWidth;
83126
private List<EventRect> mEventRects;
84127
private List<WeekViewEvent> mEvents;
@@ -561,6 +604,21 @@ private void init() {
561604
// Set default empty event color.
562605
mNewEventColor = Color.parseColor("#3c93d9");
563606

607+
// Initialize paint for tags
608+
mTagBackgroundPaint = new Paint();
609+
mTagBackgroundPaint.setColor(Color.WHITE);
610+
mTagBackgroundPaint.setStyle(Paint.Style.FILL);
611+
612+
mTagTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
613+
mTagTextPaint.setColor(Color.BLACK); // This color will be used to "punch out" the background
614+
mTagTextPaint.setTextSize(mTagTextSize);
615+
mTagTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
616+
// Copy other relevant properties from mEventTextPaint if needed
617+
// mTagTextPaint.setTypeface(mEventTextPaint.getTypeface());
618+
619+
// This Xfermode will create the "punch-out" effect
620+
mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
621+
564622
mScaleDetector = new ScaleGestureDetector(mContext, new WeekViewGestureListener());
565623
}
566624

@@ -1153,7 +1211,14 @@ private void drawEventTitle(WeekViewEvent event, RectF rect, Canvas canvas, floa
11531211
bob.append(event.getLocation());
11541212
}
11551213

1156-
int availableHeight = (int) (rect.bottom - originalTop - mEventPadding * 2);
1214+
// Reserve space for tag icons
1215+
int iconRowHeight = 0;
1216+
List<String> tags = event.getTags();
1217+
if (tags != null && !tags.isEmpty()) {
1218+
iconRowHeight = mTagSize + mTagSpacing;
1219+
}
1220+
1221+
int availableHeight = (int) (rect.bottom - originalTop - mEventPadding * 2 - iconRowHeight);
11571222
int availableWidth = (int) (rect.right - originalLeft - mEventPadding * 2);
11581223

11591224
// Get text color if necessary
@@ -1186,6 +1251,108 @@ private void drawEventTitle(WeekViewEvent event, RectF rect, Canvas canvas, floa
11861251
canvas.restore();
11871252
}
11881253
}
1254+
1255+
// Draw tag icons row
1256+
if (tags != null && !tags.isEmpty()) {
1257+
drawTags(tags, rect, canvas, originalLeft, rect.bottom - mTagSize - mTagSpacing);
1258+
}
1259+
}
1260+
1261+
// Helper to get drawable for a tag. Only looks up host app drawable resources
1262+
// named with the prefix `tag_` (e.g. `drawable/tag_meeting`).
1263+
private Drawable getTagIconDrawable(String tag) {
1264+
if (TextUtils.isEmpty(tag) || tag != tag.toUpperCase(Locale.ROOT)) {
1265+
return null;
1266+
}
1267+
String name = "tag_" + tag.toLowerCase(Locale.ROOT);
1268+
int resId = getResources().getIdentifier(name, "drawable", getContext().getPackageName());
1269+
if (resId != 0) {
1270+
try {
1271+
return ResourcesCompat.getDrawable(getResources(), resId, getContext().getTheme());
1272+
} catch (Exception ignored) {
1273+
}
1274+
}
1275+
return null;
1276+
}
1277+
1278+
// Draw tags
1279+
private void drawTags(List<String> tags, RectF rect, Canvas canvas, float left, float bottomY) {
1280+
canvas.save();
1281+
canvas.clipRect(rect);
1282+
float startX = left + mTagSpacing;
1283+
for (int i = 0; i < tags.size() - 1; i = i + 2) {
1284+
String tag = tags.get(i);
1285+
String color = tags.get(i + 1);
1286+
Drawable icon = getTagIconDrawable(tag);
1287+
if (icon != null) {
1288+
DrawableCompat.setTint(icon, Color.parseColor(color));
1289+
icon.setBounds((int) (startX), (int) bottomY, (int) (startX + mTagSize), (int) (bottomY + mTagSize));
1290+
icon.draw(canvas);
1291+
startX += mTagSize + mTagSpacing;
1292+
} else if (!TextUtils.isEmpty(tag)) {
1293+
// Check if the string is only emojis
1294+
boolean isOnlyEmoji = tag.matches("^[\\p{IsEmoji_Presentation}\\p{IsEmoji_Modifier_Base}\\p{IsEmoji_Component}\\u200d\\uFE0F]+$") && !tag.matches(".*\\d.*");
1295+
1296+
// Measure the text
1297+
float textWidth = mTagTextPaint.measureText(tag);
1298+
float tagPadding = mEventPadding / 2;
1299+
1300+
if (isOnlyEmoji) {
1301+
// --- EMOJI ONLY PATH ---
1302+
// Just draw the emoji in its original colors, no background
1303+
mTagTextPaint.setXfermode(null); // Ensure no Xfermode is active
1304+
1305+
StaticLayout textLayout = StaticLayout.Builder.obtain(tag, 0, tag.length(), mTagTextPaint, (int) Math.ceil(textWidth))
1306+
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
1307+
.setIncludePad(false)
1308+
.build();
1309+
1310+
canvas.save();
1311+
// Align emoji vertically with where the icon/background would be
1312+
float textY = bottomY + (mTagSize - textLayout.getHeight()) / 2;
1313+
canvas.translate(startX, textY);
1314+
textLayout.draw(canvas);
1315+
canvas.restore();
1316+
1317+
startX += textWidth + mTagSpacing;
1318+
1319+
} else {
1320+
// --- STANDARD TEXT PATH (PUNCH-OUT EFFECT) ---
1321+
float backgroundHeight = mTagSize;
1322+
float backgroundWidth = textWidth + tagPadding * 2;
1323+
RectF backgroundRect = new RectF(startX, bottomY, startX + backgroundWidth, bottomY + backgroundHeight);
1324+
1325+
// Save layer for Xfermode composition
1326+
int saveCount = canvas.saveLayer(backgroundRect, null);
1327+
1328+
// 1. Draw the solid background
1329+
mTagBackgroundPaint.setColor(Color.parseColor(color));
1330+
canvas.drawRoundRect(backgroundRect, mTagCornerRadius, mTagCornerRadius, mTagBackgroundPaint);
1331+
1332+
// 2. Set Xfermode to DST_OUT to punch out the text
1333+
mTagTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
1334+
1335+
StaticLayout textLayout = StaticLayout.Builder.obtain(tag, 0, tag.length(), mTagTextPaint, (int) Math.ceil(textWidth))
1336+
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
1337+
.setIncludePad(false)
1338+
.build();
1339+
1340+
canvas.save();
1341+
float textY = backgroundRect.top + (backgroundHeight - textLayout.getHeight()) / 2;
1342+
float textX = backgroundRect.left + tagPadding;
1343+
canvas.translate(textX, textY);
1344+
textLayout.draw(canvas);
1345+
canvas.restore();
1346+
1347+
// 3. Cleanup
1348+
mTagTextPaint.setXfermode(null);
1349+
canvas.restoreToCount(saveCount);
1350+
1351+
startX += backgroundWidth + mTagSpacing;
1352+
}
1353+
}
1354+
}
1355+
canvas.restore();
11891356
}
11901357

11911358
/**

library/src/main/java/com/alamkanak/weekview/WeekViewEvent.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ public class WeekViewEvent {
2424
int mColor;
2525
private boolean mAllDay;
2626
private Shader mShader;
27+
private List<String> mTags = new ArrayList<>();
2728

2829
public WeekViewEvent() {
2930

3031
}
3132

33+
3234
/**
3335
* Initializes the event for week view.
3436
*
@@ -220,6 +222,15 @@ public void setEndTime(Calendar endTime) {
220222
this.mEndTime = endTime;
221223
}
222224

225+
public List<String> getTags() {
226+
return mTags;
227+
}
228+
229+
public void setTags(List<String> tags) {
230+
this.mTags = tags != null ? tags : new ArrayList<>();
231+
}
232+
233+
223234
public String getName() {
224235
return mName;
225236
}

0 commit comments

Comments
 (0)