Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/newsletterSenderInfo/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as NewsletterSenderInfo } from './newsletterSenderInfo';
export { NEWSLETTER_SENDER_INFO_HEIGHT } from './newsletterSenderInfo';
28 changes: 24 additions & 4 deletions src/components/newsletterSenderInfo/newsletterSenderInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { useIntl } from 'react-intl';
import { useNavigation } from '@react-navigation/native';
Expand All @@ -11,8 +11,18 @@ import { useAppDispatch, useAuth } from '../../hooks';
import { toastNotification } from '../../redux/actions/uiAction';
import { IconButton } from '../iconButton';

/**
* Fixed row height, exported so ProfileSummaryView can report it through the
* summary card's moreHeight channel: the CollapsibleCard measures its content
* ONCE, so anything that appears after a query resolves must announce the
* space it takes or it gets clipped (vision-mobile#3522).
*/
export const NEWSLETTER_SENDER_INFO_HEIGHT = 28;

interface Props {
username: string;
/** Reports whether the row occupies space; stable identity expected. */
onVisibilityChange?: (visible: boolean) => void;
}

/**
Expand All @@ -22,7 +32,7 @@ interface Props {
* view is gated to the list owner server-side, so this mounts only on the
* own profile and stays silent while the lookup is unresolved or refused.
*/
const NewsletterSenderInfo = ({ username }: Props) => {
const NewsletterSenderInfo = ({ username, onVisibilityChange }: Props) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const navigation = useNavigation();
Expand All @@ -33,6 +43,15 @@ const NewsletterSenderInfo = ({ username }: Props) => {
);

const subscribers = senderQuery.data?.subscribers;
const visible = !!subscribers;

useEffect(() => {
onVisibilityChange?.(visible);
return () => {
onVisibilityChange?.(false);
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Outdated
};
}, [visible, onVisibilityChange]);

if (!subscribers) {
return null;
}
Expand All @@ -44,7 +63,7 @@ const NewsletterSenderInfo = ({ username }: Props) => {

return (
<View style={styles.row}>
<Text style={styles.countText}>
<Text style={styles.countText} numberOfLines={1}>
{intl.formatMessage(
{ id: 'newsletter.subscriber_count' },
{ weekly: subscribers.weekly ?? 0, monthly: subscribers.monthly ?? 0 },
Expand All @@ -66,13 +85,14 @@ const NewsletterSenderInfo = ({ username }: Props) => {

const styles = EStyleSheet.create({
row: {
height: NEWSLETTER_SENDER_INFO_HEIGHT,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Account for scaled text in the reported row height

With accessibility font scaling enabled, the subscriber count and Manage label can exceed this fixed 28-point height (and the existing IconButton is already 30 points tall). Because the collapsible card is also told to reserve exactly 28 points, large text is clipped or overlaps the following bars; use a measured/minimum height or otherwise include the scaled content's actual height.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in c45ed63: the fixed height is gone. As a sibling outside the measured card the row takes its natural height, so scaled text and the 30pt icon simply make it taller instead of clipping.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Row shorter than icon 📎 Requirement gap ≡ Correctness

NEWSLETTER_SENDER_INFO_HEIGHT fixes the row at 28px, but its IconButton child is fixed at
30×30px. The row therefore does not fully accommodate its own content and can still clip or overlap
vertically.
Agent Prompt
## Issue description
The fixed subscriber row is 28px high while its link `IconButton` is 30px high, so the row does not fully contain its content.

## Issue Context
Compliance rule 1 requires the asynchronously displayed row to be fully accommodated and not clipped. Increase the exported fixed height or reduce the child size, and keep the reported `moreHeight` synchronized through the shared constant.

## Fix Focus Areas
- src/components/newsletterSenderInfo/newsletterSenderInfo.tsx[20-20]
- src/components/newsletterSenderInfo/newsletterSenderInfo.tsx[72-78]
- src/components/newsletterSenderInfo/newsletterSenderInfo.tsx[87-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in c45ed63: no fixed row height any more; the row sits outside the measured card in normal flow and its natural height contains the 30pt icon.

Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Outdated
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 16,
paddingTop: 6,
},
countText: {
flexShrink: 1,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. manage width remains unconstrained 📎 Requirement gap ≡ Correctness

The PR makes the count text shrink, but the Manage action still has no fixed-width container or
width constraint. A long localized action label can therefore consume or exceed the available row
width on small devices, contrary to the required fixed action area.
Agent Prompt
## Issue description
The subscriber count now shrinks, but the `Manage` action remains content-sized rather than occupying a fixed or otherwise bounded action area.

## Issue Context
Compliance rule 1 requires fixed icon and action areas so localized labels cannot push controls off-screen on small devices. Add an explicit bounded wrapper style for the action while preserving a usable single-line label.

## Fix Focus Areas
- src/components/newsletterSenderInfo/newsletterSenderInfo.tsx[79-81]
- src/components/newsletterSenderInfo/newsletterSenderInfo.tsx[94-103]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in c45ed63: the Manage label is single line with maxWidth 120, and the count text keeps flexShrink with one line, so a long localized action cannot push the row content off narrow screens.

fontSize: 13,
color: '$primaryDarkGray',
},
Expand Down
36 changes: 30 additions & 6 deletions src/components/profileSummary/view/profileSummaryView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { makeCountFriendly } from '../../../utils/formatter';
import styles from './profileSummaryStyles';
import getWindowDimensions from '../../../utils/getWindowDimensions';
import { SheetNames } from '../../../navigation/sheets';
import { NewsletterSenderInfo } from '../../newsletterSenderInfo';
import { NEWSLETTER_SENDER_INFO_HEIGHT, NewsletterSenderInfo } from '../../newsletterSenderInfo';

const DEVICE_WIDTH = getWindowDimensions().width;

Expand All @@ -33,9 +33,30 @@ class ProfileSummaryView extends PureComponent<any, any> {
super(props);
this.state = {
isShowPercentText: props.isShowPercentText,
senderInfoVisible: false,
};
}

// The summary card's moreHeight channel is single-valued, so every consumer
// of extra height reports through this ONE combiner: the VP/RC bars toggle
// and the subscriber row would otherwise overwrite each other's height and
// the card would clip whichever reported first (vision-mobile#3522).
_reportMoreHeight = () => {
const { handleUIChange } = this.props;
const { isShowPercentText, senderInfoVisible } = this.state;
if (handleUIChange) {
handleUIChange(
(isShowPercentText ? 30 : 0) + (senderInfoVisible ? NEWSLETTER_SENDER_INFO_HEIGHT : 0),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid double-counting an initially cached sender row

When the own profile is popped and reopened while the sender query remains in the in-memory cache, NewsletterSenderInfo renders its 28-point row during the card's initial layout, so CollapsibleCard already includes that row in contentHeight. The visibility effect then sets senderInfoVisible, and this expression adds the same 28 points again, leaving an extra blank row beneath the summary on repeat visits; only report the delta when the row appeared after the initial measurement, or make the card remeasure its content.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in c45ed63 by removing the arithmetic entirely: the row moved OUT of the CollapsibleCard and renders as a sibling below it in normal layout flow, so nothing is ever added to moreHeight and a cached first render cannot be counted twice. It follows isSummaryOpen, so collapsing the summary hides it too.

);
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
Outdated
}
};

_handleSenderInfoVisibility = (visible: boolean) => {
if (this.state.senderInfoVisible !== visible) {
this.setState({ senderInfoVisible: visible }, this._reportMoreHeight);
}
};

_handleOnPressLink = (url: any) => {
if (url) {
Linking.openURL(url);
Expand Down Expand Up @@ -277,7 +298,7 @@ class ProfileSummaryView extends PureComponent<any, any> {

_renderBars = () => {
const { isShowPercentText } = this.state;
const { handleUIChange, hoursRC, hoursVP, isDarkTheme, percentRC, percentVP } = this.props;
const { hoursRC, hoursVP, isDarkTheme, percentRC, percentVP } = this.props;

const votingPowerHoursText = hoursVP && `• Full in ${hoursVP} hours`;
const votingPowerText = `Voting power: ${percentVP}% ${votingPowerHoursText || ''}`;
Expand All @@ -288,9 +309,7 @@ class ProfileSummaryView extends PureComponent<any, any> {
<TouchableOpacity
style={styles.barsContainer}
onPress={() =>
this.setState({ isShowPercentText: !isShowPercentText }, () => {
handleUIChange(!isShowPercentText ? 30 : 0);
})
this.setState({ isShowPercentText: !isShowPercentText }, this._reportMoreHeight)
}
>
<PercentBar
Expand Down Expand Up @@ -323,7 +342,12 @@ class ProfileSummaryView extends PureComponent<any, any> {
{this._renderIdentity()}
{this._renderMetadata()}
{this._renderFollowerStats()}
{!!isOwnProfile && !!username && <NewsletterSenderInfo username={username} />}
{!!isOwnProfile && !!username && (
<NewsletterSenderInfo
username={username}
onVisibilityChange={this._handleSenderInfoVisibility}
/>
)}
{this._renderBars()}
</Fragment>
);
Expand Down
Loading