import { StackActions, useNavigation } from '@react-navigation/native' import React, { useCallback, useMemo, useRef, useState } from 'react' import { Platform, Pressable, StyleSheet, TextInput, View } from 'react-native' import { Banner, ChildNotice, Heading, Text } from '../../../components' import { ActionButton } from '../../../components/display/action_button' import { KeyboardView } from '../../../components/display/keyboard_view' import { AutoFocusAwareTextInput } from '../../../components/display/text_input' import { useApiClient, useCurrentPerson, useSuspenseGet, useTheme } from '../../../hooks' import { useGroupChatConversationPayload } from '../../../hooks/groups/use_group_chat_conversation_payload' import { GroupMembersForNewConversationResult, useGroupMembersForNewConversation, } from '../../../hooks/groups/use_group_members_for_new_conversation' import { useGroupsConversationCreate } from '../../../hooks/groups/use_groups_conversation_create' import { type AvatarUpdatePayload, patchConversationAvatar, } from '../../../hooks/use_conversation_avatar_update' import { useConversationValidate } from '../../../hooks/use_conversation_validate' import { availableFeatures, useFeatures } from '../../../hooks/use_features' import { useMyGender } from '../../../hooks/use_my_gender' import { useUploadClient } from '../../../hooks/use_upload_client' import { GroupsGroupResource } from '../../../types' import { GraphId } from '../../../types/resources/group_resource' import { pluralize } from '../../../utils' import { genderDisplayLabel } from '../../../utils/gender_display_label' import { Haptic } from '../../../utils/native_adapters' import { AvatarSelectionRow } from './avatar_selection_row' import { Divider, FormList } from './form_list' import { GenderFilterToggle } from './gender_filter_toggle' type GroupsFormProps = { groupId: number chat_group_graph_id?: GraphId avatarSelection?: AvatarUpdatePayload } export const GroupsForm = ({ groupId, chat_group_graph_id, avatarSelection }: GroupsFormProps) => { const navigation = useNavigation() const apiClient = useApiClient() const uploadClient = useUploadClient() const [title, setTitle] = useState('') const [genderFilterEnabled, setGenderFilterEnabled] = useState(false) const { featureEnabled } = useFeatures() const customAvatarsEnabled = featureEnabled(availableFeatures.custom_conversation_avatars) const safetyLockEnabled = featureEnabled(availableFeatures.conversation_safety_lock) const { isFeatureEnabled: genderFilterAvailable, genderId, genderValue, isFetching: genderFetching, } = useMyGender() const { data: group } = useSuspenseGet({ url: `/me/groups/${groupId}`, data: { fields: { Group: [], }, }, app: 'groups', }) const activeGenderId = genderFilterEnabled ? genderId : null const activeGenderValue = genderFilterEnabled ? genderValue : null const { payload, isLoading: isLoadingPayload } = useGroupChatConversationPayload({ groupId, enabled: safetyLockEnabled, }) const { warnings, validationPending } = useConversationValidate({ payload, isLoadingPayload, genderId: activeGenderId, enabled: safetyLockEnabled, }) const groupMemberships = useGroupMembersForNewConversation({ id: groupId, gender: activeGenderValue, }) const handleGenderToggle = (enabled: boolean) => { const displayLabel = genderValue ? genderDisplayLabel(genderValue) : '' const defaultTitle = `${group.name} (${displayLabel})` const shouldAutoPopulateTitle = enabled && genderValue && title === '' const shouldClearAutoPopulatedTitle = !enabled && title === defaultTitle setGenderFilterEnabled(enabled) if (shouldAutoPopulateTitle) { setTitle(defaultTitle) } else if (shouldClearAutoPopulatedTitle) { setTitle('') } } const handleCreateSuccess = useCallback( (conversationId: number) => { navigation.dispatch( StackActions.popTo('Conversation', { conversation_id: conversationId, chat_group_graph_id, }) ) Haptic.notificationSuccess() if (avatarSelection && avatarSelection.kind !== 'clear') { patchConversationAvatar(apiClient, uploadClient, conversationId, avatarSelection) } }, [apiClient, avatarSelection, chat_group_graph_id, navigation, uploadClient] ) const { mutate: handleSave, isPending } = useGroupsConversationCreate({ groupId, title, genderId: activeGenderId, onSuccess: handleCreateSuccess, }) return ( } /> 0} title="Start Conversation" onPress={() => handleSave()} infoText="Conversation will be automatically updated if any members are added or removed from this group." warnings={warnings} /> ) } interface FormContentProps { group: GroupsGroupResource title: string setTitle: (title: string) => void genderFilterAvailable: boolean genderFilterEnabled: boolean genderValue: string | null genderFetching: boolean onGenderToggle: (enabled: boolean) => void groupMemberships: GroupMembersForNewConversationResult filteredMemberCount: number avatarSelection?: AvatarUpdatePayload customAvatarsEnabled: boolean } function FormContent({ group, title, setTitle, genderFilterAvailable, genderFilterEnabled, genderValue, genderFetching, onGenderToggle, groupMemberships, filteredMemberCount, avatarSelection, customAvatarsEnabled, }: FormContentProps) { const styles = useStyles() const inputRef = useRef(null) const currentPerson = useCurrentPerson() const { name, membershipsCount } = group const myGroupsMembership = useMemo( () => groupMemberships.data.find(m => m.id === currentPerson.id), [groupMemberships.data, currentPerson.id] ) const isLeader = myGroupsMembership?.role === 'leader' const childMembers = groupMemberships.childMembers const hasChildren = childMembers.length > 0 const displayCount = genderFilterEnabled ? filteredMemberCount : membershipsCount const memberHeaderLabel = `${pluralize(displayCount, 'member')} selected` const showMemberError = groupMemberships.isError const handleTitleSectionPress = useCallback(() => { inputRef.current?.focus() }, []) return ( To: {name} Title * {customAvatarsEnabled && ( <> )} {genderFilterAvailable && ( <> )} {memberHeaderLabel} {hasChildren && ( )} {showMemberError && ( )} ) } const useStyles = () => { const { colors } = useTheme() const sectionPadding = 16 const inputPadding = 8 return StyleSheet.create({ formContent: { paddingBottom: sectionPadding, flex: 1, }, toSection: { padding: sectionPadding, flexDirection: 'row', gap: 8, }, groupName: { fontSize: 18, flex: 1, }, titleSection: { padding: sectionPadding, paddingBottom: Platform.select({ ios: sectionPadding, android: sectionPadding - inputPadding, }), gap: Platform.select({ ios: 8, android: 0, }), }, titleInput: { fontSize: 18, color: colors.textColorDefaultPrimary, }, required: { color: colors.statusErrorText, }, memberSection: { padding: sectionPadding, paddingBottom: 0, }, banner: { marginTop: 16, }, }) }