Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32168923
D15348.1765055434.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
21 KB
Referenced Files
None
Subscribers
None
D15348.1765055434.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D15348: [native] choosing protocol type while creating a thread
Attached
Detach File
Event Timeline
Log In to Comment