Skip to content

Conversation

@cacheung
Copy link
Contributor

@cacheung cacheung commented Oct 2, 2025

Add unread UI implementation
Update the dist files, so a lot of files are changed. You can ignore the dist files.

Description

Example:

Light mode with image icon set - use image and unread background color
lightMode

Dark mode with no image url for icon - default to dot and unread background color
darkMode

Related Issue

Motivation and Context

How Has This Been Tested?

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

Add unread UI implementation
update the unread icon mock example
Use useContainerSettigs hook and update the dist files
update unreadIcon for error handling
: styles.container,
styleOverrides?.container
styleOverrides?.container,
getUnreadBackgroundColor() && { backgroundColor: getUnreadBackgroundColor() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions cannot be memoized by react, so Id either memoize them or just handle them inline, which reads much cleaner

}

// Helper function to convert placement from settings to component position
const convertPlacement = (placement: 'topleft' | 'topright' | 'bottomleft' | 'bottomright'): 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure we really need this, also Id create a type for the placement if you want to keep it


switch (displayPosition) {
case 'top-left':
return { ...baseStyle, top: 6, left: 6 };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since baseStyle is applied everywhere, you can just keep it in the styles of the component its applied to, Id also just add these styles in StyleSheet.create so they can be optimized

}
};

const getDotColor = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesnt need to be a function, you can just do
const dotColor = useMemo(() => colorScheme === 'dark'...

const unreadSettings = settings.content.unread_indicator;

// Use settings from context with fallbacks to props
const displaySize = size;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just reassigns the variable and then it isnt really used differently?

}

// If image failed to load, fallback to dot
if (renderType === 'image' && imageLoadError) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good logic 👍

// If image failed to load, fallback to dot
if (renderType === 'image' && imageLoadError) {
return (
<View
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But here i'd make this a separate component since its the same as below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe <Dot /> ?

}

if (renderType === 'image' && (imageSource || darkImageSource)) {
const finalImageSource = colorScheme === 'dark' && darkImageSource ? darkImageSource : imageSource;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id memoize this above

@dsoffiantini dsoffiantini force-pushed the content-card-containers branch 6 times, most recently from 6997afe to 81521eb Compare October 4, 2025 18:20
Update with review comments
@dsoffiantini dsoffiantini force-pushed the content-card-containers branch from 81521eb to 00e7269 Compare October 6, 2025 16:25
const containerSettings = useContainerSettings();

// Support both controlled and uncontrolled modes
const isRead = isReadProp !== undefined ? isReadProp : internalIsRead;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to support isRead via prop? Or should it be a container settings prop? Something like enableUnread which defaults to true? I don't have strong opinions either way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it to use the container settings Prop to match what Android is doing.

@@ -0,0 +1,325 @@
// /*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should be able to be re-enabled with the latest updates

type?: 'dot' | 'image';
}

const Dot = ({ size, backgroundColor }: { size: number; backgroundColor: string }) => (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you make an interface above for this?

const darkImageSource = unreadSettings?.unread_icon?.image?.darkUrl ?
{ uri: unreadSettings.unread_icon.image.darkUrl } : darkSource;

const getPositionStyle = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of a function, you can do a useMemo to return the styles, and then apply it below. Optimizes it a tiny bit :)


const renderContent = () => {
// Check if we should show dot instead of image based on URL availability
const shouldShowDot =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for dark mode we might also want to check if the default image url is empty?

}

// If image failed to load, fallback to dot
if (renderType === 'image' && imageLoadError) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ love this

getPositionStyle(),
{ minWidth: size, minHeight: size },
containerStyle,
typeof style === 'object' ? style : undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here you can just pass style and not check its type. If its an invalid value it should be stripped.

[colorScheme, darkImageSource, imageSource]
);

const renderContent = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if youd like to do this as a function, id wrap it in a useCallback

update with review comments
@@ -1,4 +1,5 @@
import React, { createContext } from "react";
import { SettingsPlacement } from "../components/UnreadIcon/UnreadIcon";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably invert these, put the SettingsPlacement in this file and consume it inside the component.

}
export class ContentCard extends PropositionItem {
data: ContentCardData['data'];
read: boolean = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets name this isRead

{...DismissButtonProps}
/>
)}
{(containerSettings?.content?.isUnreadEnabled ?? true) && !isRead && (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use isUnreadEnabled since its the same logic

// Mark as read when interacted with (matches Android behavior)
template.read = true;
// Trigger re-render to update unread indicator
forceUpdate({});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm this likely means something isnt being reflected in the state probably, we should look for a fix.

update for the new comments
Update test
Update metro.config and dist files
Update few UI color in the sample app
update the code to automatically refetch the content card
update read status from native
return new ContentTemplate(card, type);
const read = card.data?.read;
const contentTemplate = new ContentTemplate(card, type);
contentTemplate.isRead = read ?? false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you probably want to take this as a parameter inside the constructor for contentTemplate

Add isRead to contentTemplate constructor
@cacheung cacheung merged commit 041318a into adobe:content-card-containers Oct 9, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants