Skip to content

Commit

Permalink
Group reactions (#167)
Browse files Browse the repository at this point in the history
* WIP integrating breaking changes;

* Integrated breaking changes

* Add extend permission dialog;

* Version bump;
  • Loading branch information
stef-coenen authored Aug 22, 2024
1 parent f111165 commit d47b88f
Show file tree
Hide file tree
Showing 17 changed files with 493 additions and 128 deletions.
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"react": "18.2.0",
"react-native": "0.73.6",
"patch-package": "8.0.0",
"@youfoundation/js-lib": "0.0.3-alpha.17"
"@youfoundation/js-lib": "0.0.4-alpha.2"
},
"overrides": {
"browserify-sign": "4.2.2",
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './auth/useDotYouClientContext';
export * from './contacts/useAllContacts';
export * from './contacts/useAllConnections';
export * from './contacts/useIsConnected';
export * from './permissions/useMissingPermissions';
107 changes: 107 additions & 0 deletions packages/common/src/hooks/permissions/useMissingPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
stringifyToQueryParams,
getUniqueDrivesWithHighestPermission,
stringGuidsEqual,
} from '@youfoundation/js-lib/helpers';
import { AppPermissionType } from '@youfoundation/js-lib/network';
import { getExtendAppRegistrationParams } from '@youfoundation/js-lib/auth';
import { useDotYouClientContext } from '../auth/useDotYouClientContext';
import { useSecurityContext } from './useSecurityContext';

const getExtendAppRegistrationUrl = (
host: string,
appId: string,
drives: { a: string; t: string; n: string; d: string; p: number }[],
circleDrives: { a: string; t: string; n: string; d: string; p: number }[] | undefined,
permissionKeys: number[],
needsAllConnected: boolean,
returnUrl: string
) => {
const params = getExtendAppRegistrationParams(
appId,
drives,
circleDrives,
permissionKeys,
needsAllConnected,
returnUrl
);

return `${host}/owner/appupdate?${stringifyToQueryParams(params)}`;
};

export const useMissingPermissions = ({
appId,
drives,
circleDrives,
permissions,
needsAllConnected,
}: {
appId: string;
drives: {
a: string;
t: string;
n: string;
d: string;
p: number;
}[];
circleDrives?:
| {
a: string;
t: string;
n: string;
d: string;
p: number;
}[]
| undefined;
permissions: AppPermissionType[];
needsAllConnected?: boolean;
}) => {
const { data: context } = useSecurityContext().fetch;
const host = useDotYouClientContext().getRoot();

if (!context || !host) return;

const driveGrants = context?.permissionContext.permissionGroups.flatMap(
(group) => group.driveGrants
);
const uniqueDriveGrants = driveGrants ? getUniqueDrivesWithHighestPermission(driveGrants) : [];

const permissionKeys = context?.permissionContext.permissionGroups.flatMap(
(group) => group.permissionSet.keys
);

const missingDrives = drives.filter((drive) => {
const matchingGrants = uniqueDriveGrants.filter(
(grant) =>
stringGuidsEqual(grant.permissionedDrive.drive.alias, drive.a) &&
stringGuidsEqual(grant.permissionedDrive.drive.type, drive.t)
);

const hasAccess = matchingGrants.some((grant) => {
const allPermissions = grant.permissionedDrive.permission.reduce((a, b) => a + b, 0);
return allPermissions >= drive.p;
});

return !hasAccess;
});

const missingPermissions = permissions?.filter((key) => permissionKeys?.indexOf(key) === -1);

const hasAllConnectedCircle = context?.caller.isGrantedConnectedIdentitiesSystemCircle;
const missingAllConnectedCircle = (needsAllConnected && !hasAllConnectedCircle) || false;

if (missingDrives.length === 0 && missingPermissions.length === 0 && !missingAllConnectedCircle)
return;

const extendPermissionUrl = getExtendAppRegistrationUrl(
host,
appId,
missingDrives,
circleDrives,
missingPermissions,
missingAllConnectedCircle,
'homebase-fchat://'
);

return extendPermissionUrl;
};
32 changes: 32 additions & 0 deletions packages/common/src/hooks/permissions/useSecurityContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useQuery } from '@tanstack/react-query';
import {
ApiType,
getSecurityContext,
getSecurityContextOverPeer,
} from '@youfoundation/js-lib/core';
import { useDotYouClientContext } from '../auth/useDotYouClientContext';

export const useSecurityContext = (odinId?: string, isEnabled?: boolean) => {
const dotYouClient = useDotYouClientContext();

const fetch = async (odinId?: string) => {
if (
!odinId ||
odinId === window.location.hostname ||
(dotYouClient.getType() === ApiType.App && odinId === dotYouClient.getIdentity())
) {
return await getSecurityContext(dotYouClient);
} else return await getSecurityContextOverPeer(dotYouClient, odinId);
};

return {
fetch: useQuery({
queryKey: ['security-context', odinId],
queryFn: () => fetch(odinId),
refetchOnMount: false,
refetchOnWindowFocus: false,
staleTime: 24 * 60 * 60 * 1000, // 24 hours
enabled: isEnabled === undefined ? true : isEnabled,
}),
};
};
2 changes: 1 addition & 1 deletion packages/mobile/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "homebase-feed-mobile",
"version": "0.0.15",
"version": "0.0.16",
"private": true,
"scripts": {
"android": "react-native run-android",
Expand Down
19 changes: 18 additions & 1 deletion packages/mobile/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import {

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import CodePush from 'react-native-code-push';
import { useAuth, useValidTokenCheck } from '../hooks/auth/useAuth';
import {
circleDrives,
drives,
permissions,
useAuth,
useValidTokenCheck,
} from '../hooks/auth/useAuth';
import { DotYouClientProvider } from '../components/Auth/DotYouClientProvider';
import { Platform } from 'react-native';

Expand Down Expand Up @@ -44,6 +50,9 @@ import { ChatStack } from './ChatStack';
import { ProfileStack } from './ProfileStack';
import BootSplash from 'react-native-bootsplash';
import BubbleColorProvider from '../components/BubbleContext/BubbleContext';
import { ExtendPermissionDialog } from '../components/Permissions/ExtendPermissionDialog';
import { t } from 'feed-app-common';
import { CHAT_APP_ID, FEED_CHAT_APP_ID } from './constants';

export type AuthStackParamList = {
Login: undefined;
Expand Down Expand Up @@ -127,6 +136,14 @@ const AuthenticatedRoot = memo(() => {
<WebSocketContextProvider>
<BubbleColorProvider>
<ErrorBoundary>
<ExtendPermissionDialog
appName={t('Homebase Feed & Chat')}
appId={FEED_CHAT_APP_ID}
drives={drives}
circleDrives={circleDrives}
permissions={permissions}
// needsAllConnected={true}
/>
<AppStackScreen />
</ErrorBoundary>
</BubbleColorProvider>
Expand Down
1 change: 1 addition & 0 deletions packages/mobile/src/app/OdinQueryClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const INCLUDED_QUERY_KEYS = [
'conversations-with-recent-message',
'image',
'process-inbox',
'security-context',
];
const persistOptions: Omit<PersistQueryClientOptions, 'queryClient'> = {
buster: '20240524',
Expand Down
61 changes: 41 additions & 20 deletions packages/mobile/src/components/Chat/Chat-Reaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import Animated, {
} from 'react-native-reanimated';
import { useChatReaction } from '../../hooks/chat/useChatReaction';
import { useConversation } from '../../hooks/chat/useConversation';
import { HomebaseFile } from '@youfoundation/js-lib/core';
import { HomebaseFile, ReactionFile } from '@youfoundation/js-lib/core';
import { ChatMessage } from '../../provider/chat/ChatProvider';
import { Colors } from '../../app/Colors';
import { useDarkMode } from '../../hooks/useDarkMode';
import { ErrorNotification } from '../ui/Alert/ErrorNotification';
import { SelectedMessageState } from '../../pages/chat/chat-page';
import { useDotYouClientContext } from 'feed-app-common';

const ChatReaction = memo(
({
Expand Down Expand Up @@ -83,16 +84,20 @@ const ChatReaction = memo(
};
});

const hasReactions =
selectedMessage?.selectedMessage?.fileMetadata.reactionPreview?.reactions &&
Object.keys(selectedMessage?.selectedMessage?.fileMetadata.reactionPreview?.reactions).length;

const { add, get, remove } = useChatReaction({
conversationId: message?.fileMetadata.appData.groupId,
messageId: message?.fileMetadata.appData.uniqueId,
messageFileId: hasReactions ? selectedMessage?.selectedMessage?.fileId : undefined,
messageGlobalTransitId: selectedMessage?.selectedMessage?.fileMetadata.globalTransitId,
});

const identity = useDotYouClientContext().getIdentity();
const { data: reactions } = get;

const filteredReactions = useMemo(
() => reactions?.filter((reaction) => reaction.fileMetadata.senderOdinId === '') || [],
[reactions]
const myReactions = useMemo(
() => reactions?.filter((reaction) => reaction?.authorOdinId === identity) || [],
[identity, reactions]
);

const { mutate: addReaction, error: reactionError } = add;
Expand All @@ -102,7 +107,7 @@ const ChatReaction = memo(
conversationId: message?.fileMetadata.appData.groupId,
}).single.data;

const sendReaction = useCallback(
const toggleReaction = useCallback(
(reaction: string, index: number) => {
if (!selectedMessage) {
return;
Expand All @@ -111,30 +116,31 @@ const ChatReaction = memo(
return;
} else {
if (!conversation) return;
const reactionStrings = filteredReactions.map(
(reac) => reac.fileMetadata.appData.content.message
const matchedReaction = myReactions.find(
(myReaction) => myReaction.body.includes(reaction) || myReaction.body === reaction
);
const matchedReaction = reactionStrings.indexOf(reaction);
if (matchedReaction !== -1) {
if (matchedReaction) {
console.log(matchedReaction);
removeReaction({
conversation,
reaction: filteredReactions[matchedReaction],
reaction: matchedReaction,
message: message as HomebaseFile<ChatMessage>,
});
} else {
addReaction({
conversation: conversation,
message: message as HomebaseFile<ChatMessage>,
reaction,
});
}
addReaction({
conversation: conversation,
message: message as HomebaseFile<ChatMessage>,
reaction,
});
}
afterSendReaction();
},
[
addReaction,
afterSendReaction,
conversation,
filteredReactions,
myReactions,
message,
openEmojiPicker,
removeReaction,
Expand All @@ -159,7 +165,22 @@ const ChatReaction = memo(
]}
>
{initialReactions.map((reaction, index) => (
<TouchableOpacity key={index} onPress={() => sendReaction(reaction, index)}>
<TouchableOpacity
key={index}
onPress={() => toggleReaction(reaction, index)}
style={
myReactions.some(
(myReaction) => myReaction.body.includes(reaction) || myReaction.body === reaction
)
? {
backgroundColor: isDarkMode ? Colors.slate[600] : Colors.slate[300],
borderRadius: 50,
margin: -5,
padding: 5,
}
: {}
}
>
<Animated.Text style={textStyle}>{reaction}</Animated.Text>
</TouchableOpacity>
))}
Expand Down
Loading

0 comments on commit d47b88f

Please sign in to comment.