From a238a9163ef3964041220792cba282a32ba3fb6b Mon Sep 17 00:00:00 2001 From: Willem Vanderhaeghe Date: Mon, 29 Dec 2025 10:56:28 +0100 Subject: [PATCH 1/2] progress: wokring support for tags --- .../java/com/alamkanak/weekview/WeekView.java | 135 +++++++++++++++++- .../com/alamkanak/weekview/WeekViewEvent.java | 11 ++ .../src/main/res/drawable/tag_birthday.xml | 9 ++ .../src/main/res/drawable/tag_deadline.xml | 9 ++ library/src/main/res/drawable/tag_meeting.xml | 9 ++ 5 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 library/src/main/res/drawable/tag_birthday.xml create mode 100644 library/src/main/res/drawable/tag_deadline.xml create mode 100644 library/src/main/res/drawable/tag_meeting.xml diff --git a/library/src/main/java/com/alamkanak/weekview/WeekView.java b/library/src/main/java/com/alamkanak/weekview/WeekView.java index 9882f8f45..4a8ba3734 100755 --- a/library/src/main/java/com/alamkanak/weekview/WeekView.java +++ b/library/src/main/java/com/alamkanak/weekview/WeekView.java @@ -5,6 +5,7 @@ import android.graphics.*; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import androidx.core.graphics.drawable.DrawableCompat; import android.os.Build; import androidx.annotation.NonNull; @@ -79,6 +80,39 @@ private enum AutoScrollDirection { private Paint mTodayHeaderTextPaint; private Paint mEventBackgroundPaint; private Paint mNewEventBackgroundPaint; + private TextPaint mTagTextPaint; + private Paint mTagBackgroundPaint; + private Xfermode mXfermode; + private int mTagIconSize = 96; + private int mTagIconSpacing = 12; + private int mTagCornerRadius = 12; + + private static final Map TAG_ICON_MAP = new HashMap() {{ + put("meeting", R.drawable.tag_meeting); + put("birthday", R.drawable.tag_birthday); + put("deadline", R.drawable.tag_deadline); + }}; + + public void setTagIconSize(int sizePx) { + mTagIconSize = sizePx; + } + public void setTagIconSpacing(int spacingPx) { + mTagIconSpacing = spacingPx; + } + public int getTagIconSize() { + return mTagIconSize; + } + public int getTagIconSpacing() { + return mTagIconSpacing; + } + public int getTagCornerRadius() { + return mTagCornerRadius; + } + public void setTagCornerRadius(int tagCornerRadius) { + mTagCornerRadius = tagCornerRadius; + } + + private float mHeaderColumnWidth; private List mEventRects; private List mEvents; @@ -561,6 +595,21 @@ private void init() { // Set default empty event color. mNewEventColor = Color.parseColor("#3c93d9"); + // Initialize paint for tags + mTagBackgroundPaint = new Paint(); + mTagBackgroundPaint.setColor(Color.WHITE); + mTagBackgroundPaint.setStyle(Paint.Style.FILL); + + mTagTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + mTagTextPaint.setColor(Color.BLACK); // This color will be used to "punch out" the background + mTagTextPaint.setTextSize(mEventTextSize); + mTagTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); + // Copy other relevant properties from mEventTextPaint if needed + // mTagTextPaint.setTypeface(mEventTextPaint.getTypeface()); + + // This Xfermode will create the "punch-out" effect + mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT); + mScaleDetector = new ScaleGestureDetector(mContext, new WeekViewGestureListener()); } @@ -1153,7 +1202,14 @@ private void drawEventTitle(WeekViewEvent event, RectF rect, Canvas canvas, floa bob.append(event.getLocation()); } - int availableHeight = (int) (rect.bottom - originalTop - mEventPadding * 2); + // Reserve space for tag icons + int iconRowHeight = 0; + List tags = event.getTags(); + if (tags != null && !tags.isEmpty()) { + iconRowHeight = mTagIconSize + mTagIconSpacing; + } + + int availableHeight = (int) (rect.bottom - originalTop - mEventPadding * 2 - iconRowHeight); int availableWidth = (int) (rect.right - originalLeft - mEventPadding * 2); // Get text color if necessary @@ -1186,6 +1242,83 @@ private void drawEventTitle(WeekViewEvent event, RectF rect, Canvas canvas, floa canvas.restore(); } } + + // Draw tag icons row + if (tags != null && !tags.isEmpty()) { + drawTags(tags, rect, canvas, originalLeft, rect.bottom - mTagIconSize - mTagIconSpacing); + } + } + + // Helper to get drawable for a tag + private Drawable getTagIconDrawable(String tag) { + Integer resId = TAG_ICON_MAP.get(tag); + if (resId == null) return null; + try { + return getResources().getDrawable(resId); + } catch (Exception e) { + return null; + } + } + + // Draw tags + private void drawTags(List tags, RectF rect, Canvas canvas, float left, float bottomY) { + canvas.save(); + canvas.clipRect(rect); + float startX = left + mTagIconSpacing; + for (int i = 0; i < tags.size() - 1; i = i + 2) { + String tag = tags.get(i); + String color = tags.get(i + 1); + Drawable icon = getTagIconDrawable(tag); + if (icon != null) { + DrawableCompat.setTint(icon, Color.parseColor(color)); + icon.setBounds((int) (startX), (int) bottomY, (int) (startX + mTagIconSize), (int) (bottomY + mTagIconSize)); + icon.draw(canvas); + startX += mTagIconSize + mTagIconSpacing; + } else if (!TextUtils.isEmpty(tag)) { + mTagTextPaint.setXfermode(mXfermode); + // If no icon is found, draw the tag text with a background instead. + + // 1. Define padding for the tag background + float tagPadding = mEventPadding / 2; // Use a small padding + + // 2. Measure the text + float textWidth = mTagTextPaint.measureText(tag); + float textHeight = mTagTextPaint.descent() - mTagTextPaint.ascent(); + + // 3. Define the background bounds + float backgroundHeight = mTagIconSize; // Match the icon height + float backgroundWidth = textWidth + tagPadding * 2; + RectF backgroundRect = new RectF(startX, bottomY, startX + backgroundWidth, bottomY + backgroundHeight); + + int saveCount = canvas.saveLayer(backgroundRect, null); + + mTagBackgroundPaint.setColor(Color.parseColor(color)); + // 2. Draw the solid background + canvas.drawRoundRect(backgroundRect, mTagCornerRadius, mTagCornerRadius, mTagBackgroundPaint); + + // 3. Set the Xfermode to DST_OUT (This "punches out" the destination) + mTagTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + + // 4. Draw the text + StaticLayout textLayout = StaticLayout.Builder.obtain(tag, 0, tag.length(), mTagTextPaint, (int) Math.ceil(textWidth)) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .setIncludePad(false) + .build(); + + canvas.save(); + float textY = backgroundRect.top + (backgroundHeight - textLayout.getHeight()) / 2; + float textX = backgroundRect.left + tagPadding; + canvas.translate(textX, textY); + textLayout.draw(canvas); + canvas.restore(); + + // 5. Clear Xfermode and Restore the layer (composites the result back) + mTagTextPaint.setXfermode(null); + canvas.restoreToCount(saveCount); + startX += backgroundWidth + mTagIconSpacing; + } + } + canvas.restore(); } /** diff --git a/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.java b/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.java index 8fbad5431..0589b228e 100644 --- a/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.java +++ b/library/src/main/java/com/alamkanak/weekview/WeekViewEvent.java @@ -24,11 +24,13 @@ public class WeekViewEvent { int mColor; private boolean mAllDay; private Shader mShader; + private List mTags = new ArrayList<>(); public WeekViewEvent() { } + /** * Initializes the event for week view. * @@ -220,6 +222,15 @@ public void setEndTime(Calendar endTime) { this.mEndTime = endTime; } + public List getTags() { + return mTags; + } + + public void setTags(List tags) { + this.mTags = tags != null ? tags : new ArrayList<>(); + } + + public String getName() { return mName; } diff --git a/library/src/main/res/drawable/tag_birthday.xml b/library/src/main/res/drawable/tag_birthday.xml new file mode 100644 index 000000000..0436cc122 --- /dev/null +++ b/library/src/main/res/drawable/tag_birthday.xml @@ -0,0 +1,9 @@ + + + diff --git a/library/src/main/res/drawable/tag_deadline.xml b/library/src/main/res/drawable/tag_deadline.xml new file mode 100644 index 000000000..0436cc122 --- /dev/null +++ b/library/src/main/res/drawable/tag_deadline.xml @@ -0,0 +1,9 @@ + + + diff --git a/library/src/main/res/drawable/tag_meeting.xml b/library/src/main/res/drawable/tag_meeting.xml new file mode 100644 index 000000000..0436cc122 --- /dev/null +++ b/library/src/main/res/drawable/tag_meeting.xml @@ -0,0 +1,9 @@ + + + From f428da87033495ba6aba19e27cb9e0ece165a57a Mon Sep 17 00:00:00 2001 From: Willem Vanderhaeghe Date: Mon, 29 Dec 2025 20:18:10 +0100 Subject: [PATCH 2/2] feat: support for emoji's --- .../java/com/alamkanak/weekview/WeekView.java | 87 +++++++++++-------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/library/src/main/java/com/alamkanak/weekview/WeekView.java b/library/src/main/java/com/alamkanak/weekview/WeekView.java index 4a8ba3734..e1d3e193e 100755 --- a/library/src/main/java/com/alamkanak/weekview/WeekView.java +++ b/library/src/main/java/com/alamkanak/weekview/WeekView.java @@ -1275,47 +1275,66 @@ private void drawTags(List tags, RectF rect, Canvas canvas, float left, icon.draw(canvas); startX += mTagIconSize + mTagIconSpacing; } else if (!TextUtils.isEmpty(tag)) { - mTagTextPaint.setXfermode(mXfermode); - // If no icon is found, draw the tag text with a background instead. + // Check if the string is only emojis + boolean isOnlyEmoji = tag.matches("^[\\p{IsEmoji_Presentation}\\p{IsEmoji_Modifier_Base}\\p{IsEmoji_Component}\\u200d\\uFE0F]+$") && !tag.matches(".*\\d.*"); - // 1. Define padding for the tag background - float tagPadding = mEventPadding / 2; // Use a small padding - - // 2. Measure the text + // Measure the text float textWidth = mTagTextPaint.measureText(tag); - float textHeight = mTagTextPaint.descent() - mTagTextPaint.ascent(); - - // 3. Define the background bounds - float backgroundHeight = mTagIconSize; // Match the icon height - float backgroundWidth = textWidth + tagPadding * 2; - RectF backgroundRect = new RectF(startX, bottomY, startX + backgroundWidth, bottomY + backgroundHeight); + float tagPadding = mEventPadding / 2; - int saveCount = canvas.saveLayer(backgroundRect, null); + if (isOnlyEmoji) { + // --- EMOJI ONLY PATH --- + // Just draw the emoji in its original colors, no background + mTagTextPaint.setXfermode(null); // Ensure no Xfermode is active - mTagBackgroundPaint.setColor(Color.parseColor(color)); - // 2. Draw the solid background - canvas.drawRoundRect(backgroundRect, mTagCornerRadius, mTagCornerRadius, mTagBackgroundPaint); + StaticLayout textLayout = StaticLayout.Builder.obtain(tag, 0, tag.length(), mTagTextPaint, (int) Math.ceil(textWidth)) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .setIncludePad(false) + .build(); - // 3. Set the Xfermode to DST_OUT (This "punches out" the destination) - mTagTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + canvas.save(); + // Align emoji vertically with where the icon/background would be + float textY = bottomY + (mTagIconSize - textLayout.getHeight()) / 2; + canvas.translate(startX, textY); + textLayout.draw(canvas); + canvas.restore(); - // 4. Draw the text - StaticLayout textLayout = StaticLayout.Builder.obtain(tag, 0, tag.length(), mTagTextPaint, (int) Math.ceil(textWidth)) - .setAlignment(Layout.Alignment.ALIGN_NORMAL) - .setIncludePad(false) - .build(); - - canvas.save(); - float textY = backgroundRect.top + (backgroundHeight - textLayout.getHeight()) / 2; - float textX = backgroundRect.left + tagPadding; - canvas.translate(textX, textY); - textLayout.draw(canvas); - canvas.restore(); + startX += textWidth + mTagIconSpacing; - // 5. Clear Xfermode and Restore the layer (composites the result back) - mTagTextPaint.setXfermode(null); - canvas.restoreToCount(saveCount); - startX += backgroundWidth + mTagIconSpacing; + } else { + // --- STANDARD TEXT PATH (PUNCH-OUT EFFECT) --- + float backgroundHeight = mTagIconSize; + float backgroundWidth = textWidth + tagPadding * 2; + RectF backgroundRect = new RectF(startX, bottomY, startX + backgroundWidth, bottomY + backgroundHeight); + + // Save layer for Xfermode composition + int saveCount = canvas.saveLayer(backgroundRect, null); + + // 1. Draw the solid background + mTagBackgroundPaint.setColor(Color.parseColor(color)); + canvas.drawRoundRect(backgroundRect, mTagCornerRadius, mTagCornerRadius, mTagBackgroundPaint); + + // 2. Set Xfermode to DST_OUT to punch out the text + mTagTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + + StaticLayout textLayout = StaticLayout.Builder.obtain(tag, 0, tag.length(), mTagTextPaint, (int) Math.ceil(textWidth)) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .setIncludePad(false) + .build(); + + canvas.save(); + float textY = backgroundRect.top + (backgroundHeight - textLayout.getHeight()) / 2; + float textX = backgroundRect.left + tagPadding; + canvas.translate(textX, textY); + textLayout.draw(canvas); + canvas.restore(); + + // 3. Cleanup + mTagTextPaint.setXfermode(null); + canvas.restoreToCount(saveCount); + + startX += backgroundWidth + mTagIconSpacing; + } } } canvas.restore();