Page MenuHomePhorge

D15410.1765035820.diff
No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None

D15410.1765035820.diff

diff --git a/lib/shared/farcaster/farcaster-hooks.js b/lib/shared/farcaster/farcaster-hooks.js
--- a/lib/shared/farcaster/farcaster-hooks.js
+++ b/lib/shared/farcaster/farcaster-hooks.js
@@ -518,15 +518,20 @@
function useRefreshFarcasterConversation(): (
conversationID: string,
+ messagesLimit?: number,
) => Promise<void> {
const fetchConversationWithMessages = useFetchConversationWithMessages();
const dispatch = useDispatch();
return React.useCallback(
- async (conversationID: string) => {
+ async (conversationID: string, messagesLimit?: number) => {
const batchedUpdates = new BatchedUpdates();
- await fetchConversationWithMessages(conversationID, 20, batchedUpdates);
+ await fetchConversationWithMessages(
+ conversationID,
+ messagesLimit ?? 20,
+ batchedUpdates,
+ );
if (!batchedUpdates.isEmpty()) {
dispatch({
diff --git a/lib/shared/threads/protocols/dm-thread-protocol.js b/lib/shared/threads/protocols/dm-thread-protocol.js
--- a/lib/shared/threads/protocols/dm-thread-protocol.js
+++ b/lib/shared/threads/protocols/dm-thread-protocol.js
@@ -947,6 +947,7 @@
viewerCanUpdateOwnRole: false,
protocolName: protocolNames.COMM_DM,
canReactToRobotext: true,
+ supportsThreadRefreshing: false,
});
function pendingThreadType(numberOfOtherMembers: number) {
diff --git a/lib/shared/threads/protocols/farcaster-thread-protocol.js b/lib/shared/threads/protocols/farcaster-thread-protocol.js
--- a/lib/shared/threads/protocols/farcaster-thread-protocol.js
+++ b/lib/shared/threads/protocols/farcaster-thread-protocol.js
@@ -964,6 +964,7 @@
viewerCanUpdateOwnRole: false,
protocolName: protocolNames.FARCASTER_DC,
canReactToRobotext: false,
+ supportsThreadRefreshing: true,
};
function pendingThreadType(numberOfOtherMembers: number) {
diff --git a/lib/shared/threads/protocols/keyserver-thread-protocol.js b/lib/shared/threads/protocols/keyserver-thread-protocol.js
--- a/lib/shared/threads/protocols/keyserver-thread-protocol.js
+++ b/lib/shared/threads/protocols/keyserver-thread-protocol.js
@@ -751,6 +751,7 @@
viewerCanUpdateOwnRole: true,
protocolName: protocolNames.KEYSERVER,
canReactToRobotext: true,
+ supportsThreadRefreshing: false,
});
function pendingThreadType(numberOfOtherMembers: number) {
diff --git a/lib/shared/threads/thread-spec.js b/lib/shared/threads/thread-spec.js
--- a/lib/shared/threads/thread-spec.js
+++ b/lib/shared/threads/thread-spec.js
@@ -537,6 +537,7 @@
+viewerCanUpdateOwnRole: boolean,
+protocolName: ProtocolName,
+canReactToRobotext: boolean,
+ +supportsThreadRefreshing: boolean,
};
export type ThreadSpec<
diff --git a/native/chat/settings/thread-settings-refresh.react.js b/native/chat/settings/thread-settings-refresh.react.js
new file mode 100644
--- /dev/null
+++ b/native/chat/settings/thread-settings-refresh.react.js
@@ -0,0 +1,85 @@
+// @flow
+
+import Icon from '@expo/vector-icons/Ionicons.js';
+import * as React from 'react';
+import { View, Text, Platform } from 'react-native';
+
+import Button from '../../components/button.react.js';
+import { useStyles } from '../../themes/colors.js';
+
+const unboundStyles = {
+ container: {
+ flex: 1,
+ flexDirection: 'row',
+ paddingHorizontal: 12,
+ paddingVertical: 8,
+ justifyContent: 'center',
+ },
+ icon: {
+ lineHeight: 20,
+ },
+ refreshButton: {
+ paddingTop: Platform.OS === 'ios' ? 4 : 1,
+ },
+ refreshIcon: {
+ color: 'panelForegroundSecondaryLabel',
+ },
+ refreshRow: {
+ backgroundColor: 'panelForeground',
+ paddingHorizontal: 12,
+ },
+ refreshText: {
+ color: 'panelForegroundSecondaryLabel',
+ flex: 1,
+ fontSize: 16,
+ },
+ disabled: {
+ color: 'disabledButtonText',
+ },
+};
+
+type RefreshProps = {
+ +onPress: () => Promise<void>,
+};
+
+function ThreadSettingsRefresh(props: RefreshProps): React.Node {
+ const styles = useStyles(unboundStyles);
+ const [isRefreshing, setIsRefreshing] = React.useState(false);
+
+ const onPressWrapper = React.useCallback(async () => {
+ if (isRefreshing) {
+ return;
+ }
+ setIsRefreshing(true);
+ try {
+ await props.onPress();
+ } finally {
+ setIsRefreshing(false);
+ }
+ }, [isRefreshing, props]);
+
+ const disabledStyle = isRefreshing ? styles.disabled : null;
+
+ return (
+ <View style={styles.refreshRow}>
+ <Button
+ onPress={onPressWrapper}
+ style={styles.refreshButton}
+ disabled={isRefreshing}
+ >
+ <View style={styles.container}>
+ <Text style={[styles.refreshText, disabledStyle]}>
+ {isRefreshing ? 'Refreshing...' : 'Refresh thread'}
+ </Text>
+ <Icon
+ name="refresh"
+ size={20}
+ style={[styles.icon, styles.refreshIcon, disabledStyle]}
+ />
+ </View>
+ </Button>
+ </View>
+ );
+}
+
+export default ThreadSettingsRefresh;
diff --git a/native/chat/settings/thread-settings.react.js b/native/chat/settings/thread-settings.react.js
--- a/native/chat/settings/thread-settings.react.js
+++ b/native/chat/settings/thread-settings.react.js
@@ -31,6 +31,8 @@
childThreadInfos,
threadInfoSelector,
} from 'lib/selectors/thread-selectors.js';
+import { useRefreshFarcasterConversation } from 'lib/shared/farcaster/farcaster-hooks.js';
+import { conversationIDFromFarcasterThreadID } from 'lib/shared/id-utils.js';
import { getAvailableRelationshipButtons } from 'lib/shared/relationship-utils.js';
import {
getSingleOtherUser,
@@ -84,6 +86,7 @@
import ThreadSettingsParent from './thread-settings-parent.react.js';
import ThreadSettingsPromoteSidebar from './thread-settings-promote-sidebar.react.js';
import ThreadSettingsPushNotifs from './thread-settings-push-notifs.react.js';
+import ThreadSettingsRefresh from './thread-settings-refresh.react.js';
import ThreadSettingsVisibility from './thread-settings-visibility.react.js';
import ThreadAncestors from '../../components/thread-ancestors.react.js';
import {
@@ -234,7 +237,7 @@
+verticalBounds: ?VerticalBounds,
}
| {
- +itemType: 'promoteSidebar' | 'leaveThread' | 'deleteThread',
+ +itemType: 'promoteSidebar' | 'leaveThread' | 'deleteThread' | 'refresh',
+key: string,
+threadInfo: ResolvedThreadInfo,
+navigate: ThreadSettingsNavigate,
@@ -296,6 +299,7 @@
+canDeleteThread: boolean,
+canManageInviteLinks: boolean,
+inviteLinkExists: boolean,
+ +refreshFarcasterConversation: (threadID: string) => Promise<void>,
};
type State = {
+numMembersShowing: number,
@@ -758,6 +762,17 @@
) => {
const buttons = [];
+ const supportsThreadRefreshing =
+ threadSpecs[threadInfo.type].protocol().supportsThreadRefreshing;
+ if (supportsThreadRefreshing) {
+ buttons.push({
+ itemType: 'refresh',
+ key: 'refresh',
+ threadInfo,
+ navigate,
+ });
+ }
+
if (this.props.canPromoteSidebar) {
buttons.push({
itemType: 'promoteSidebar',
@@ -1055,6 +1070,8 @@
buttonStyle={item.buttonStyle}
/>
);
+ } else if (item.itemType === 'refresh') {
+ return <ThreadSettingsRefresh onPress={this.onPressRefresh} />;
} else if (item.itemType === 'deleteThread') {
return (
<ThreadSettingsDeleteThread
@@ -1152,6 +1169,11 @@
threadInfo: this.props.threadInfo,
});
};
+
+ onPressRefresh = async () => {
+ const { refreshFarcasterConversation, threadInfo } = this.props;
+ await refreshFarcasterConversation(threadInfo.id);
+ };
}
const threadMembersChangeIsSaving = (
@@ -1347,6 +1369,19 @@
);
}, [callFetchPrimaryLinks, dispatchActionPromise, isCommunityRoot]);
+ const refreshFarcasterConversationHook = useRefreshFarcasterConversation();
+ const refreshFarcasterConversation = React.useCallback(
+ async (farcasterThreadID: string) => {
+ const conversationID =
+ conversationIDFromFarcasterThreadID(farcasterThreadID);
+ await refreshFarcasterConversationHook(
+ conversationID,
+ Number.POSITIVE_INFINITY,
+ );
+ },
+ [refreshFarcasterConversationHook],
+ );
+
return (
<ThreadSettings
{...props}
@@ -1371,6 +1406,7 @@
canDeleteThread={canDeleteThread}
canManageInviteLinks={canManageLinks}
inviteLinkExists={!!inviteLink}
+ refreshFarcasterConversation={refreshFarcasterConversation}
/>
);
},
diff --git a/web/chat/thread-menu.react.js b/web/chat/thread-menu.react.js
--- a/web/chat/thread-menu.react.js
+++ b/web/chat/thread-menu.react.js
@@ -10,6 +10,8 @@
childThreadInfos,
otherUsersButNoOtherAdmins,
} from 'lib/selectors/thread-selectors.js';
+import { useRefreshFarcasterConversation } from 'lib/shared/farcaster/farcaster-hooks.js';
+import { conversationIDFromFarcasterThreadID } from 'lib/shared/id-utils.js';
import {
threadIsChannel,
useThreadHasPermission,
@@ -47,6 +49,43 @@
const { threadInfo } = props;
const { onPromoteSidebar, canPromoteSidebar } = usePromoteSidebar(threadInfo);
+ const [isRefreshing, setIsRefreshing] = React.useState(false);
+
+ const supportsThreadRefreshing =
+ threadSpecs[threadInfo.type].protocol().supportsThreadRefreshing;
+
+ const refreshFarcasterConversationHook = useRefreshFarcasterConversation();
+ const onClickRefresh = React.useCallback(async () => {
+ if (isRefreshing) {
+ return;
+ }
+ setIsRefreshing(true);
+ try {
+ const conversationID = conversationIDFromFarcasterThreadID(threadInfo.id);
+ await refreshFarcasterConversationHook(
+ conversationID,
+ Number.POSITIVE_INFINITY,
+ );
+ } finally {
+ setIsRefreshing(false);
+ }
+ }, [isRefreshing, threadInfo.id, refreshFarcasterConversationHook]);
+
+ const refreshItem = React.useMemo(() => {
+ if (!supportsThreadRefreshing) {
+ return null;
+ }
+ return (
+ <MenuItem
+ key="refresh"
+ text={isRefreshing ? 'Refreshing...' : 'Refresh thread'}
+ icon="refresh"
+ onClick={onClickRefresh}
+ disabled={isRefreshing}
+ />
+ );
+ }, [supportsThreadRefreshing, isRefreshing, onClickRefresh]);
+
const onClickSettings = React.useCallback(
() => pushModal(<ThreadSettingsModal threadID={threadInfo.id} />),
[pushModal, threadInfo.id],
@@ -303,6 +342,7 @@
notificationsItem,
membersItem,
threadMediaGalleryItem,
+ refreshItem,
sidebarItem,
viewSubchannelsItem,
createSubchannelsItem,
@@ -316,6 +356,7 @@
notificationsItem,
membersItem,
threadMediaGalleryItem,
+ refreshItem,
sidebarItem,
viewSubchannelsItem,
promoteSidebar,
diff --git a/web/components/menu-item.react.js b/web/components/menu-item.react.js
--- a/web/components/menu-item.react.js
+++ b/web/components/menu-item.react.js
@@ -14,6 +14,7 @@
+onClick?: () => mixed,
+text: string,
+dangerous?: boolean,
+ +disabled?: boolean,
};
export type MenuItemProps =
| {
@@ -26,7 +27,7 @@
};
function MenuItem(props: MenuItemProps): React.Node {
- const { onClick, icon, iconComponent, text, dangerous } = props;
+ const { onClick, icon, iconComponent, text, dangerous, disabled } = props;
const itemClasses = classNames(css.menuAction, {
[css.menuActionDangerous]: dangerous,
@@ -38,7 +39,7 @@
}
return (
- <Button className={itemClasses} onClick={onClick}>
+ <Button className={itemClasses} onClick={onClick} disabled={disabled}>
<div className={css.menuActionIcon}>{menuItemIcon}</div>
<div>{text}</div>
</Button>

File Metadata

Mime Type
text/plain
Expires
Sat, Dec 6, 3:43 PM (21 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5839313
Default Alt Text
D15410.1765035820.diff (11 KB)

Event Timeline