Page MenuHomePhorge

D15348.1765055434.diff
No OneTemporary

Size
21 KB
Referenced Files
None
Subscribers
None

D15348.1765055434.diff

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
@@ -902,7 +902,6 @@
nativeChatThreadListIcon: 'lock',
webChatThreadListIcon: 'lock',
threadAncestorLabel: () => 'Local DM',
- threadSearchHeaderShowsGenesis: false,
protocolIcon: 'lock',
description:
'Comm DMs are end-to-end encrypted and stored locally on your ' +
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
@@ -768,7 +768,6 @@
nativeChatThreadListIcon: 'lock',
webChatThreadListIcon: 'lock',
threadAncestorLabel: () => 'Farcaster DC',
- threadSearchHeaderShowsGenesis: false,
protocolIcon: 'farcaster',
description:
'Farcaster Direct Casts are the native messaging protocol in ' +
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
@@ -704,7 +704,6 @@
nativeChatThreadListIcon: 'server',
webChatThreadListIcon: 'server',
threadAncestorLabel: (ancestorPath: React.Node) => ancestorPath,
- threadSearchHeaderShowsGenesis: true,
protocolIcon: 'server',
description:
"Genesis chats are a legacy chat type hosted on Ashoat's keyserver. " +
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
@@ -477,7 +477,6 @@
+nativeChatThreadListIcon: string,
+webChatThreadListIcon: 'lock' | 'server',
+threadAncestorLabel: (ancestorPath: React.Node) => React.Node,
- +threadSearchHeaderShowsGenesis: boolean,
+protocolIcon: 'lock' | 'server' | 'farcaster',
+description: string,
},
diff --git a/native/chat/chat.react.js b/native/chat/chat.react.js
--- a/native/chat/chat.react.js
+++ b/native/chat/chat.react.js
@@ -25,7 +25,9 @@
import invariant from 'invariant';
import * as React from 'react';
import {
+ Alert,
Platform,
+ TouchableOpacity,
View,
useWindowDimensions,
type MeasureOnSuccessCallback,
@@ -33,9 +35,13 @@
import MessageStorePruner from 'lib/components/message-store-pruner.react.js';
import ThreadDraftUpdater from 'lib/components/thread-draft-updater.react.js';
+import { useProtocolSelection } from 'lib/contexts/protocol-selection-context.js';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
import { threadSettingsNotificationsCopy } from 'lib/shared/thread-settings-notifications-utils.js';
import { threadIsPending, threadIsSidebar } from 'lib/shared/thread-utils.js';
+import { getProtocolByName } from 'lib/shared/threads/protocols/thread-protocols.js';
+import { threadSpecs } from 'lib/shared/threads/thread-specs.js';
+import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js';
import BackgroundChatThreadList from './background-chat-thread-list.react.js';
import ChatHeader from './chat-header.react.js';
@@ -68,6 +74,7 @@
nuxTip,
NUXTipsContext,
} from '../components/nux-tips-context.react.js';
+import ProtocolIcon from '../components/protocol-icon.react.js';
import { ProtocolSelectionProvider } from '../components/protocol-selection-provider.react.js';
import { InputStateContext } from '../input/input-state.js';
import CommunityDrawerButton from '../navigation/community-drawer-button.react.js';
@@ -245,6 +252,67 @@
const headerRightStyle = { flexDirection: 'row' };
+function MessageListHeaderRight({
+ threadInfo,
+ navigation,
+ areSettingsEnabled,
+ isSearching,
+ isSearchEmpty,
+}: {
+ +threadInfo: ThreadInfo,
+ +navigation: ChatNavigationProp<'MessageList'>,
+ +areSettingsEnabled: boolean,
+ +isSearching: boolean,
+ +isSearchEmpty: boolean,
+}) {
+ const { selectedProtocol } = useProtocolSelection();
+
+ const protocolIcon = React.useMemo(() => {
+ if (!isSearching || isSearchEmpty) {
+ return null;
+ }
+
+ const protocol = selectedProtocol
+ ? getProtocolByName(selectedProtocol)
+ : threadSpecs[threadInfo.type].protocol();
+
+ if (!protocol) {
+ return null;
+ }
+
+ const handleProtocolPress = () => {
+ Alert.alert(
+ protocol.protocolName,
+ protocol.presentationDetails.description,
+ );
+ };
+
+ return (
+ <TouchableOpacity onPress={handleProtocolPress}>
+ <ProtocolIcon protocol={protocol.protocolName} size={30} />
+ </TouchableOpacity>
+ );
+ }, [isSearchEmpty, isSearching, selectedProtocol, threadInfo.type]);
+
+ if (areSettingsEnabled) {
+ return (
+ <View style={headerRightStyle}>
+ <SearchMessagesButton
+ threadInfo={threadInfo}
+ navigate={navigation.navigate}
+ />
+ <ThreadSettingsButton
+ threadInfo={threadInfo}
+ navigate={navigation.navigate}
+ />
+ {protocolIcon}
+ </View>
+ );
+ }
+
+ return <View style={headerRightStyle}>{protocolIcon}</View>;
+}
+
const messageListOptions = ({
navigation,
route,
@@ -252,8 +320,9 @@
+navigation: ChatNavigationProp<'MessageList'>,
+route: NavigationRoute<'MessageList'>,
}) => {
+ const isSearching = !!route.params.searching;
const isSearchEmpty =
- !!route.params.searching && route.params.threadInfo.members.length === 1;
+ isSearching && route.params.threadInfo.members.length === 1;
const areSettingsEnabled =
!threadIsPending(route.params.threadInfo.id) && !isSearchEmpty;
@@ -268,20 +337,15 @@
{...props}
/>
),
- headerRight: areSettingsEnabled
- ? () => (
- <View style={headerRightStyle}>
- <SearchMessagesButton
- threadInfo={route.params.threadInfo}
- navigate={navigation.navigate}
- />
- <ThreadSettingsButton
- threadInfo={route.params.threadInfo}
- navigate={navigation.navigate}
- />
- </View>
- )
- : undefined,
+ headerRight: () => (
+ <MessageListHeaderRight
+ threadInfo={route.params.threadInfo}
+ navigation={navigation}
+ areSettingsEnabled={areSettingsEnabled}
+ isSearching={isSearching}
+ isSearchEmpty={isSearchEmpty}
+ />
+ ),
headerBackTitleVisible: false,
headerTitleAlign: isSearchEmpty ? 'center' : 'left',
headerLeftContainerStyle: { width: Platform.OS === 'ios' ? 32 : 40 },
diff --git a/native/chat/message-list-container.react.js b/native/chat/message-list-container.react.js
--- a/native/chat/message-list-container.react.js
+++ b/native/chat/message-list-container.react.js
@@ -18,8 +18,8 @@
} from 'lib/shared/search-utils.js';
import { useExistingThreadInfoFinder } from 'lib/shared/thread-utils.js';
import {
- threadSpecs,
threadTypeIsPersonal,
+ threadSpecs,
} from 'lib/shared/threads/thread-specs.js';
import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js';
import type { AccountUserInfo, UserListItem } from 'lib/types/user-types.js';
@@ -36,9 +36,9 @@
import MessageListThreadSearch from './message-list-thread-search.react.js';
import { MessageListContextProvider } from './message-list-types.js';
import MessageList from './message-list.react.js';
-import ParentThreadHeader from './parent-thread-header.react.js';
import RelationshipPrompt from './relationship-prompt.react.js';
import ContentLoading from '../components/content-loading.react.js';
+import SelectProtocolDropdown from '../components/select-protocol-dropdown.react.js';
import { InputStateContext } from '../input/input-state.js';
import {
OverlayContext,
@@ -172,31 +172,10 @@
let searchComponent = null;
if (searching) {
- const { userInfoInputArray, genesisThreadInfo } = this.props;
- let parentThreadHeader;
- const protocol = threadSpecs[threadInfo.type].protocol();
- const childThreadType = protocol.pendingThreadType(
- userInfoInputArray.length,
- );
- const threadSearchHeaderShowsGenesis =
- protocol.presentationDetails.threadSearchHeaderShowsGenesis;
- if (!threadSearchHeaderShowsGenesis) {
- parentThreadHeader = (
- <ParentThreadHeader childThreadType={childThreadType} />
- );
- } else if (genesisThreadInfo) {
- // It's technically possible for the client to be missing the Genesis
- // ThreadInfo when it first opens up (before the server delivers it)
- parentThreadHeader = (
- <ParentThreadHeader
- parentThreadInfo={genesisThreadInfo}
- childThreadType={childThreadType}
- />
- );
- }
+ const { userInfoInputArray } = this.props;
searchComponent = (
<>
- {parentThreadHeader}
+ <SelectProtocolDropdown />
<MessageListThreadSearch
usernameInputText={this.props.usernameInputText}
updateUsernameInput={this.props.updateUsernameInput}
@@ -383,6 +362,7 @@
const resolveToUser = React.useCallback(
async (user: AccountUserInfo) => {
const newUserInfoInputArray = user.id === viewerID ? [] : [user];
+
const resolvedThreadInfo = existingThreadInfoFinder({
searching: true,
userInfoInputArray: newUserInfoInputArray,
diff --git a/native/components/protocol-icon.react.js b/native/components/protocol-icon.react.js
new file mode 100644
--- /dev/null
+++ b/native/components/protocol-icon.react.js
@@ -0,0 +1,72 @@
+// @flow
+
+import Icon from '@expo/vector-icons/FontAwesome.js';
+import * as React from 'react';
+import { View } from 'react-native';
+
+import { getProtocolByName } from 'lib/shared/threads/protocols/thread-protocols.js';
+import type { ProtocolName } from 'lib/shared/threads/thread-spec.js';
+
+import { useStyles } from '../themes/colors.js';
+import FarcasterLogo from '../vectors/farcaster-logo.react.js';
+
+type Props = {
+ +icon?: React.Node,
+ +protocol?: ProtocolName,
+ +size: number,
+};
+
+function ProtocolIcon(props: Props): React.Node {
+ const styles = useStyles(unboundStyles);
+
+ let iconComponent = null;
+ const protocolIcon = getProtocolByName(props.protocol)?.presentationDetails
+ ?.protocolIcon;
+ const iconSize = props.size * 0.65;
+ let containerStyle = styles.container;
+ if (props.icon) {
+ containerStyle = styles.container;
+ iconComponent = props.icon;
+ } else if (protocolIcon === 'lock') {
+ iconComponent = <Icon name="lock" size={iconSize} style={styles.icon} />;
+ } else if (protocolIcon === 'server') {
+ iconComponent = <Icon name="server" size={iconSize} style={styles.icon} />;
+ } else if (protocolIcon === 'farcaster') {
+ iconComponent = <FarcasterLogo size={iconSize} />;
+ containerStyle = styles.farcasterContainer;
+ }
+
+ const viewStyle = React.useMemo(
+ () => [
+ containerStyle,
+ {
+ width: props.size,
+ height: props.size,
+ borderRadius: props.size,
+ },
+ ],
+ [containerStyle, props.size],
+ );
+
+ return <View style={viewStyle}>{iconComponent}</View>;
+}
+
+const unboundStyles = {
+ container: {
+ backgroundColor: 'panelBackground',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginHorizontal: 5,
+ },
+ farcasterContainer: {
+ backgroundColor: '#855DCD',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginHorizontal: 5,
+ },
+ icon: {
+ color: 'whiteText',
+ },
+};
+
+export default ProtocolIcon;
diff --git a/native/components/select-protocol-dropdown.react.js b/native/components/select-protocol-dropdown.react.js
new file mode 100644
--- /dev/null
+++ b/native/components/select-protocol-dropdown.react.js
@@ -0,0 +1,183 @@
+// @flow
+
+import Icon from '@expo/vector-icons/FontAwesome5.js';
+import * as React from 'react';
+import { View, Text, TouchableOpacity, Alert } from 'react-native';
+
+import { useProtocolSelection } from 'lib/contexts/protocol-selection-context.js';
+import { protocols } from 'lib/shared/threads/protocols/thread-protocols.js';
+import type { ProtocolName } from 'lib/shared/threads/thread-spec';
+import { protocolInfoAlert } from 'lib/utils/alert-utils.js';
+
+import ProtocolIcon from './protocol-icon.react.js';
+import { useStyles } from '../themes/colors.js';
+
+function SelectProtocolDropdown(): React.Node {
+ const { selectedProtocol, setSelectedProtocol, availableProtocols } =
+ useProtocolSelection();
+ const styles = useStyles(unboundStyles);
+
+ const [showOptions, setShowOptions] = React.useState(false);
+
+ const onDropdownPress = React.useCallback(() => {
+ if (availableProtocols.length < 1) {
+ return;
+ }
+ setShowOptions(!showOptions);
+ }, [availableProtocols.length, showOptions]);
+
+ const onInfoPress = React.useCallback(() => {
+ Alert.alert(protocolInfoAlert.title, protocolInfoAlert.message);
+ }, []);
+
+ const onOptionSelection = React.useCallback(
+ (protocolIndex: ProtocolName) => {
+ setSelectedProtocol(protocolIndex);
+ setShowOptions(false);
+ },
+ [setSelectedProtocol],
+ );
+
+ const options = React.useMemo(
+ () =>
+ protocols()
+ .filter(protocol => availableProtocols.includes(protocol.protocolName))
+ .map(protocol => (
+ <TouchableOpacity
+ onPress={() => onOptionSelection(protocol.protocolName)}
+ key={protocol.protocolName}
+ >
+ <View key={protocol.protocolName} style={styles.dropdownOption}>
+ <ProtocolIcon protocol={protocol.protocolName} size={24} />
+ <Text style={styles.protocolName}>{protocol.protocolName}</Text>
+ </View>
+ </TouchableOpacity>
+ )),
+ [
+ availableProtocols,
+ onOptionSelection,
+ styles.dropdownOption,
+ styles.protocolName,
+ ],
+ );
+
+ const dropdownHeader = React.useMemo(() => {
+ if (!selectedProtocol) {
+ return <Text style={styles.text}>Select chat type</Text>;
+ }
+ return (
+ <View key={selectedProtocol} style={styles.selectedOption}>
+ <ProtocolIcon protocol={selectedProtocol} size={24} />
+ <Text style={styles.protocolName}>{selectedProtocol}</Text>
+ </View>
+ );
+ }, [
+ selectedProtocol,
+ styles.protocolName,
+ styles.selectedOption,
+ styles.text,
+ ]);
+
+ const iconName = React.useMemo(
+ () => (availableProtocols.length > 0 ? 'chevron-down' : 'info-circle'),
+ [availableProtocols.length],
+ );
+
+ const optionsComponent = React.useMemo(() => {
+ if (!showOptions) {
+ return null;
+ }
+ return <View style={styles.optionsContainer}>{options}</View>;
+ }, [options, showOptions, styles.optionsContainer]);
+
+ const containerStyle = React.useMemo(
+ () => [
+ styles.container,
+ showOptions ? styles.bordersWithOptions : styles.bordersWithoutOptions,
+ ],
+ [
+ styles.container,
+ styles.bordersWithOptions,
+ styles.bordersWithoutOptions,
+ showOptions,
+ ],
+ );
+
+ return (
+ <View style={containerStyle}>
+ <View style={styles.dropdownHeader}>
+ <TouchableOpacity onPress={onDropdownPress} style={styles.button}>
+ {dropdownHeader}
+ </TouchableOpacity>
+ <TouchableOpacity
+ onPress={
+ availableProtocols.length > 0 ? onDropdownPress : onInfoPress
+ }
+ >
+ <Icon name={iconName} size={14} style={styles.icon} />
+ </TouchableOpacity>
+ </View>
+ {optionsComponent}
+ </View>
+ );
+}
+
+const unboundStyles = {
+ container: {
+ backgroundColor: 'selectProtocolDropdownBackground',
+ zIndex: 4,
+ },
+ button: {
+ flex: 1,
+ },
+ bordersWithOptions: {
+ borderTopLeftRadius: 10,
+ borderTopRightRadius: 10,
+ },
+ bordersWithoutOptions: {
+ borderRadius: 10,
+ },
+ dropdownHeader: {
+ paddingHorizontal: 12,
+ paddingVertical: 8,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ dropdownOption: {
+ paddingHorizontal: 12,
+ paddingVertical: 8,
+ flexDirection: 'row',
+ alignItems: 'center',
+ minHeight: 40,
+ },
+ selectedOption: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ height: 24,
+ },
+ optionsContainer: {
+ backgroundColor: 'selectProtocolDropdownBackground',
+ borderBottomLeftRadius: 10,
+ borderBottomRightRadius: 10,
+ position: 'absolute',
+ top: 43,
+ width: '100%',
+ },
+ text: {
+ fontSize: 16,
+ color: 'panelForegroundLabel',
+ height: 24,
+ textAlignVertical: 'center',
+ },
+ protocolName: {
+ marginLeft: 8,
+ fontSize: 16,
+ color: 'panelForegroundLabel',
+ },
+ icon: {
+ color: 'panelForegroundLabel',
+ },
+};
+
+export default SelectProtocolDropdown;
diff --git a/native/components/user-list-user.react.js b/native/components/user-list-user.react.js
--- a/native/components/user-list-user.react.js
+++ b/native/components/user-list-user.react.js
@@ -3,14 +3,17 @@
import * as React from 'react';
import { Text, Platform } from 'react-native';
+import { protocolNames } from 'lib/shared/protocol-names.js';
import type { UserListItem, AccountUserInfo } from 'lib/types/user-types.js';
import Button from './button.react.js';
+import ProtocolIcon from './protocol-icon.react.js';
import SingleLine from './single-line.react.js';
import UserAvatar from '../avatars/user-avatar.react.js';
import { type Colors, useColors, useStyles } from '../themes/colors.js';
import type { TextStyle } from '../types/styles.js';
import Alert from '../utils/alert.js';
+import CommLogo from '../vectors/comm-logo.react.js';
// eslint-disable-next-line no-unused-vars
const getUserListItemHeight = (item: UserListItem): number => {
@@ -57,6 +60,15 @@
}
const { modalIosHighlightUnderlay: underlayColor } = this.props.colors;
+ let icon = null;
+ if (userInfo.supportedProtocols.includes(protocolNames.COMM_DM)) {
+ icon = <ProtocolIcon icon={<CommLogo />} size={23} />;
+ } else if (
+ userInfo.supportedProtocols.includes(protocolNames.FARCASTER_DC)
+ ) {
+ icon = <ProtocolIcon protocol={protocolNames.FARCASTER_DC} size={23} />;
+ }
+
return (
<Button
onPress={this.onSelect}
@@ -71,6 +83,7 @@
{this.props.userInfo.username}
</SingleLine>
{notice}
+ {icon}
</Button>
);
}
diff --git a/native/themes/colors.js b/native/themes/colors.js
--- a/native/themes/colors.js
+++ b/native/themes/colors.js
@@ -160,6 +160,7 @@
redIndicatorOuter: string,
deletedMessageText: string,
deletedMessageBackground: string,
+ selectProtocolDropdownBackground: string,
}>;
const light: Colors = Object.freeze({
@@ -269,6 +270,7 @@
redIndicatorOuter: designSystemColors.errorDark50,
deletedMessageText: designSystemColors.shadesBlack60,
deletedMessageBackground: designSystemColors.shadesWhite90,
+ selectProtocolDropdownBackground: designSystemColors.shadesWhite60,
});
const dark: Colors = Object.freeze({
@@ -378,6 +380,7 @@
redIndicatorOuter: designSystemColors.errorDark90,
deletedMessageText: designSystemColors.shadesWhite60,
deletedMessageBackground: designSystemColors.shadesBlack90,
+ selectProtocolDropdownBackground: designSystemColors.shadesBlack75,
});
const colors = { light, dark };
diff --git a/native/vectors/comm-logo.react.js b/native/vectors/comm-logo.react.js
new file mode 100644
--- /dev/null
+++ b/native/vectors/comm-logo.react.js
@@ -0,0 +1,32 @@
+// @flow
+
+import * as React from 'react';
+import Svg, { Path, Circle, Rect } from 'react-native-svg';
+
+function CommLogo(): React.Node {
+ return (
+ <Svg
+ width={14}
+ height={14}
+ viewBox="0 0 1177 1177"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <Path
+ d="M272.256.078c-49.987 0-90.474 40.487-90.474 90.474h769.031c74.957 0 135.707 60.754 135.707 135.712v588.082c49.99 0 90.48-40.488 90.48-90.475V90.552c0-49.987-40.49-90.474-90.48-90.474H272.256zM91.308 181.026c-49.987 0-90.474 40.488-90.474 90.475V904.82c0 49.987 40.487 90.474 90.474 90.474H227.02v135.716c0 11.99 4.766 23.5 13.25 31.98a45.211 45.211 0 0031.987 13.25 45.197 45.197 0 0021.578-5.52 45.241 45.241 0 0016.326-15.15l120.338-160.276h475.078c49.987 0 90.474-40.487 90.474-90.474V271.501c0-49.987-40.487-90.475-90.474-90.475H91.308z"
+ fill="#fff"
+ />
+ <Circle cx={499.496} cy={503.222} r={114.408} fill="#121826" />
+ <Rect
+ x={448.211}
+ y={479.549}
+ width={98.6275}
+ height={276.157}
+ rx={49.3137}
+ fill="#121826"
+ />
+ </Svg>
+ );
+}
+
+export default CommLogo;
diff --git a/native/vectors/farcaster-logo.react.js b/native/vectors/farcaster-logo.react.js
--- a/native/vectors/farcaster-logo.react.js
+++ b/native/vectors/farcaster-logo.react.js
@@ -3,12 +3,16 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
-function FarcasterLogo(): React.Node {
- const farcasterLogo = React.useMemo(
+type Props = {
+ +size?: number,
+};
+
+function FarcasterLogo({ size = 200 }: Props): React.Node {
+ return React.useMemo(
() => (
<Svg
- width="200"
- height="200"
+ width={size}
+ height={size}
viewBox="0 0 1000 1000"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@@ -27,10 +31,8 @@
/>
</Svg>
),
- [],
+ [size],
);
-
- return farcasterLogo;
}
export default FarcasterLogo;

File Metadata

Mime Type
text/plain
Expires
Sat, Dec 6, 9:10 PM (19 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5840039
Default Alt Text
D15348.1765055434.diff (21 KB)

Event Timeline