Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Send images in Comments #183

Merged
merged 6 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
78 changes: 3 additions & 75 deletions packages/mobile/src/components/Chat/Chat-app-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
import {
GestureResponderEvent,
Platform,
Pressable,
StyleProp,
StyleSheet,
TextStyle,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewStyle,
} from 'react-native';
import { Platform, Pressable, StyleSheet, TouchableOpacity, View } from 'react-native';
import { Header, HeaderBackButton } from '@react-navigation/elements';
import { useProfile } from '../../hooks/profile/useProfile';
import { Colors } from '../../app/Colors';
import { useDarkMode } from '../../hooks/useDarkMode';
import { ChatMessageIMessage } from './ChatDetail';
import { memo, ReactNode, useCallback, useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { Copy, EllipsisVertical, Forward, Info, Pencil, Reply, Trash } from '../ui/Icons/icons';
import Toast from 'react-native-toast-message';
import { Avatar, GroupAvatar, OwnerAvatar } from '../ui/Avatars/Avatar';
import { EmbeddedThumb } from '@homebase-id/js-lib/core';
import { ChatDrive } from '../../provider/chat/ConversationProvider';
import { Text } from '../ui/Text/Text';
import { IconButton } from '../ui/Buttons';

export type SelectedMessageProp = {
onReply: () => void;
Expand Down Expand Up @@ -184,63 +172,3 @@ const styles = StyleSheet.create({
borderRadius: 18,
},
});

export const IconButton = memo(
({
icon,
onPress,
touchableProps,
style,
title,
textStyle,
}: {
icon: ReactNode;
onPress?: (e: GestureResponderEvent) => void;
touchableProps?: Omit<TouchableOpacityProps, 'onPress'>;
style?: StyleProp<ViewStyle>;
textStyle?: TextStyle;
title?: string;
}) => {
const defaultActions = useCallback(() => {
Toast.show({
type: 'info',
text1: 'No action provided',
text2: 'Make sure u are passing the props correctly',
});
}, []);
return (
<View
style={
title
? {
flexDirection: 'column',
alignItems: 'center',
gap: 8,
}
: undefined
}
>
<TouchableOpacity
onPress={onPress || defaultActions}
style={[{ padding: 10 }, style]}
{...touchableProps}
>
{icon}
</TouchableOpacity>
{title && (
<Text
style={
textStyle || {
fontSize: 12,
fontWeight: '400',
textAlign: 'center',
}
}
>
{title}
</Text>
)}
</View>
);
}
);
13 changes: 10 additions & 3 deletions packages/mobile/src/components/Feed/Interacts/Comments/Comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { ErrorNotification } from '../../../ui/Alert/ErrorNotification';
import { CommentHead } from './CommentHead';
import { CommentMeta } from './CommentMeta';
import { CommentThread } from './CommentThread';
import { CommentBody } from './CommentBody';
import { ImageSource } from '../../../../provider/image/RNImageProvider';

export interface CommentProps {
context: ReactionContext;
Expand Down Expand Up @@ -59,7 +61,7 @@ export const Comment = memo(
}, [commentData, context]);

const doUpdate = useCallback(
(newBody: string, newAttachment?: File) => {
(newBody: string, newAttachment?: ImageSource) => {
(async () => {
await postComment({
context,
Expand Down Expand Up @@ -103,8 +105,13 @@ export const Comment = memo(
: undefined
}
/>
{/* TODO: Comement Body */}
<Text style={{ flex: 1 }}>{commentContent.body}</Text>
<CommentBody
context={context}
content={commentContent}
previewThumbnail={commentData.fileMetadata.appData.previewThumbnail}
commentFileId={fileId}
commentLastModifed={(commentData as HomebaseFile<ReactionFile>).fileMetadata.updated}
/>
{threadContext.target.fileId && threadContext.target.globalTransitId ? (
<CommentMeta
canReact={canReact}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { EmbeddedThumb, ReactionFile } from '@homebase-id/js-lib/core';
import {
GetTargetDriveFromChannelId,
RawReactionContent,
ReactionContext,
} from '@homebase-id/js-lib/public';
import { Text } from '../../../ui/Text/Text';
import { memo } from 'react';
import { CommentMedia } from './CommentMedia';

export const CommentBody = memo(
({
context,
commentFileId,
commentLastModifed,
content,
previewThumbnail,
}: {
context?: ReactionContext;
commentFileId?: string;
commentLastModifed?: number;
content: RawReactionContent | ReactionFile;
previewThumbnail?: EmbeddedThumb;
}) => {
const { body } = content;
const sourceTargetDrive = context && GetTargetDriveFromChannelId(context.channelId);
return (
<>
<Text style={{ flex: 1 }}>{body}</Text>
{content.mediaPayloadKey && context && (
<CommentMedia
fileId={commentFileId}
postAuthorOdinId={context.authorOdinId}
targetDrive={sourceTargetDrive}
fileKey={content.mediaPayloadKey}
lastModified={commentLastModifed}
previewThumbnail={previewThumbnail}
probablyEncrypted={context.target.isEncrypted}
/>
)}
</>
);
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import { BottomSheetTextInput } from '@gorhom/bottom-sheet';

import { memo, useCallback, useState } from 'react';
import { chatStyles } from '../../../Chat/ChatDetail';
import { Close, SendChat } from '../../../ui/Icons/icons';
import { Close, Images, SendChat } from '../../../ui/Icons/icons';
import { Colors } from '../../../../app/Colors';
import { OwnerAvatar } from '../../../ui/Avatars/Avatar';
import { ErrorNotification } from '../../../ui/Alert/ErrorNotification';
import { CantReactInfo } from '../../CanReactInfo';
import Animated, { Easing, SlideInUp } from 'react-native-reanimated';
import { Text } from '../../../ui/Text/Text';
import { IconButton } from '../../../Chat/Chat-app-bar';

import { Asset, launchImageLibrary } from 'react-native-image-picker';
import { IconButton } from '../../../ui/Buttons';
import { FileOverview } from '../../../Files/FileOverview';

export const CommentComposer = memo(
({
Expand All @@ -39,10 +42,22 @@ export const CommentComposer = memo(
const { isDarkMode } = useDarkMode();
const [message, setMessage] = useState('');
const identity = useDotYouClientContext().getIdentity();
const disabled = postState === 'pending' || message.length === 0;
const [assets, setAssets] = useState<Asset[]>([]);
const disabled = postState === 'pending' || (message.length === 0 && assets.length === 0);

const handleImageIconPress = useCallback(async () => {
const medias = await launchImageLibrary({
mediaType: 'photo',
selectionLimit: 1,
includeExtra: true,
});
if (medias.didCancel) return;
// Keep assets without a type out of it.. We're never sure what it is...
setAssets(medias.assets?.filter((asset) => asset.type) ?? []);
}, [setAssets]);

const onPostComment = useCallback(async () => {
if (postState === 'pending' || !message) return;
if (postState === 'pending' || (!message && !assets)) return;

try {
await postComment({
Expand All @@ -59,7 +74,20 @@ export const CommentComposer = memo(
content: {
authorOdinId: identity,
body: message,
// attachment, //TODO: Add attachment option
attachment: assets.map((value) => {
return {
height: value.height || 0,
width: value.width || 0,
name: value.fileName,
type: value.type && value.type === 'image/jpg' ? 'image/jpeg' : value.type,
uri: value.uri,
filename: value.fileName,
date: Date.parse(value.timestamp || new Date().toUTCString()),
filepath: value.originalPath,
id: value.id,
fileSize: value.fileSize,
};
})?.[0],
},
},
},
Expand All @@ -68,7 +96,7 @@ export const CommentComposer = memo(
} catch (e) {}
setMessage('');
onReplyCancel?.();
}, [context, identity, message, onReplyCancel, postComment, postState, replyThreadId]);
}, [assets, context, identity, message, onReplyCancel, postComment, postState, replyThreadId]);

if (canReact?.canReact === true || canReact?.canReact === 'comment') {
return (
Expand Down Expand Up @@ -134,20 +162,30 @@ export const CommentComposer = memo(
/>
</Animated.View>
)}
<BottomSheetTextInput
value={message}
onChangeText={setMessage}
placeholder={t('Add a comment...')}
{assets && <FileOverview assets={assets} setAssets={setAssets} />}

<View
style={{
flex: 1,
maxHeight: 80,
color: isDarkMode ? Colors.white : Colors.black,
paddingVertical: 8,
flexDirection: 'row',
alignItems: 'center',
}}
multiline
textAlignVertical="center" // Android only
autoCapitalize="sentences"
/>
>
<BottomSheetTextInput
value={message}
onChangeText={setMessage}
placeholder={t('Add a comment...')}
style={{
flex: 1,
maxHeight: 80,
paddingVertical: 8,
color: isDarkMode ? Colors.white : Colors.black,
}}
multiline
textAlignVertical="center" // Android only
autoCapitalize="sentences"
/>
<IconButton icon={<Images />} onPress={handleImageIconPress} />
</View>
</View>
<TouchableOpacity
disabled={disabled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const CommentHead = memo(
style={{
flexDirection: 'row',
gap: 8,
zIndex: 50,
}}
>
<Text
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { EmbeddedThumb, TargetDrive } from '@homebase-id/js-lib/core';
import { OdinImage } from '../../../ui/OdinImage/OdinImage';
import { calculateScaledDimensions } from '../../../../utils/utils';
import { memo } from 'react';

export const CommentMedia = memo(
({
postAuthorOdinId,
targetDrive,
fileId,
fileKey,
lastModified,
previewThumbnail,
probablyEncrypted,
}: {
postAuthorOdinId?: string;
targetDrive?: TargetDrive;
fileId?: string;
fileKey?: string;
lastModified?: number;
previewThumbnail?: EmbeddedThumb;
probablyEncrypted?: boolean;
}) => {
if (!targetDrive) return null;

const aspectRatio = (previewThumbnail?.pixelWidth || 1) / (previewThumbnail?.pixelHeight || 1);

const { width: newWidth, height: newHeight } = calculateScaledDimensions(
previewThumbnail?.pixelWidth || 300,
previewThumbnail?.pixelHeight || 300,
{ width: 250, height: 150 }
);

return (
<OdinImage
odinId={postAuthorOdinId}
fileId={fileId}
targetDrive={targetDrive}
fileKey={fileKey}
lastModified={lastModified}
previewThumbnail={previewThumbnail}
probablyEncrypted={probablyEncrypted}
systemFileType="Comment"
imageSize={{ width: newWidth, height: newHeight }}
style={{
aspectRatio,
}}
fit="contain"
/>
);
}
);
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ApiType, DotYouClient, HomebaseFile } from '@homebase-id/js-lib/core';
import { parseReactionPreview, PostContent, ReactionContext } from '@homebase-id/js-lib/public';
import { memo, useCallback, useMemo, useState } from 'react';
import { IconButton } from '../../Chat/Chat-app-bar';
import { GestureResponderEvent, View } from 'react-native';
import { OpenHeart, Comment, ShareNode, SolidHeart } from '../../ui/Icons/icons';
import {
Expand All @@ -17,6 +16,7 @@ import { ShareContext } from './Share/ShareModal';
import { ErrorNotification } from '../../ui/Alert/ErrorNotification';
import { Colors } from '../../../app/Colors';
import { PostReactionBar } from './Reactions/PostReactionBar';
import { IconButton } from '../../ui/Buttons';

export const PostInteracts = memo(
({
Expand Down
Loading
Loading