Skip to content

Commit

Permalink
Merge pull request #101 from xmtp-labs/ar/group-consent
Browse files Browse the repository at this point in the history
feat: Group Consent Work
  • Loading branch information
alexrisch authored May 29, 2024
2 parents 267dc48 + e2b24e8 commit 628fbd8
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 60 deletions.
2 changes: 1 addition & 1 deletion src/consts/AppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export const AppConfig = {
XMTP_ENV: 'dev' as 'local' | 'dev' | 'production',
MULTI_WALLET: false,
PUSH_NOTIFICATIONS: Platform.OS === 'ios',
GROUP_CONSENT: false,
GROUP_CONSENT: Platform.OS === 'android',
};
60 changes: 60 additions & 0 deletions src/hooks/useGroupConsent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {useCallback, useEffect, useState} from 'react';
import {AppConfig} from '../consts/AppConfig';
import {mmkvStorage} from '../services/mmkvStorage';
import {useClient} from './useClient';
import {useGroup} from './useGroup';

type ConsentState = 'allowed' | 'denied' | 'unknown';

const getInitialConsent = (groupId?: string): ConsentState => {
if (!AppConfig.GROUP_CONSENT || !groupId) {
return 'allowed';
}
return (mmkvStorage.getGroupConsent(groupId) as ConsentState) ?? 'unknown';
};

export const useGroupConsent = (topic: string) => {
const {data: group} = useGroup(topic);
const {client} = useClient();
const [consent, setConsent] = useState<ConsentState>(
getInitialConsent(group?.id),
);

useEffect(() => {
if (!group) {
return;
}
if (!AppConfig.GROUP_CONSENT) {
setConsent('allowed');
return;
}
group.consentState().then(currentConsent => {
setConsent(currentConsent);
mmkvStorage.saveGroupConsent(group.id, currentConsent);
});
}, [group, topic]);

const allow = useCallback(async () => {
if (!group?.id) {
return;
}
await client?.contacts.allowGroups([group.id]);
setConsent('allowed');
mmkvStorage.saveGroupConsent(group.id, 'allowed');
}, [client?.contacts, group?.id]);

const deny = useCallback(async () => {
if (!group?.id) {
return;
}
await client?.contacts.denyGroups([group.id]);
setConsent('denied');
mmkvStorage.saveGroupConsent(group.id, 'denied');
}, [client?.contacts, group?.id]);

return {
consent,
allow,
deny,
};
};
37 changes: 30 additions & 7 deletions src/screens/ConversationListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import {Drawer} from '../components/common/Drawer';
import {Icon} from '../components/common/Icon';
import {Screen} from '../components/common/Screen';
import {Text} from '../components/common/Text';
import {AppConfig} from '../consts/AppConfig';
import {SupportedContentTypes} from '../consts/ContentTypes';
import {TestIds} from '../consts/TestIds';
import {useClient} from '../hooks/useClient';
import {useTypedNavigation} from '../hooks/useTypedNavigation';
import {translate} from '../i18n';
import {ScreenNames} from '../navigation/ScreenNames';
import {useListQuery} from '../queries/useListQuery';
import {mmkvStorage} from '../services/mmkvStorage';
import {colors} from '../theme/colors';

const EmptyBackground = require('../../assets/images/Bg_asset.svg').default;
Expand All @@ -28,20 +30,35 @@ const useData = () => {
const {data, isLoading, refetch, isRefetching, isError, error} =
useListQuery();

const {listItems, requests} = useMemo(() => {
const {listItems, requests, requestsCount} = useMemo(() => {
const listMessages: Group<SupportedContentTypes>[] = [];
const requestsItems: Group<SupportedContentTypes>[] = [];
let requestCount = 0;
data?.forEach(item => {
// TODO: add a check for isRequest
listMessages.push(item);
if (
!AppConfig.GROUP_CONSENT ||
mmkvStorage.getGroupConsent(item.id) === 'allowed'
) {
listMessages.push(item);
} else {
requestsItems.push(item);
if ((mmkvStorage.getGroupConsent(item.id) ?? 'unknown') === 'unknown') {
requestCount++;
}
}
});
return {listItems: listMessages, requests: requestsItems};
return {
listItems: listMessages,
requests: requestsItems,
requestsCount: requestCount,
};
}, [data]);

console.log('Connected as address:', client?.address);

return {
messageRequests: requests,
messageRequestsCount: requestsCount,
messages: listItems,
isLoading,
refetch,
Expand All @@ -58,8 +75,14 @@ export const ConversationListScreen = () => {
const [showPickerModal, setShowPickerModal] = useState(false);
const [showConsentDrawer, setShowConsentDrawer] = useState(false);
const focused = useIsFocused();
const {messages, messageRequests, isLoading, refetch, isRefetching} =
useData();
const {
messages,
messageRequests,
messageRequestsCount,
isLoading,
refetch,
isRefetching,
} = useData();
const {navigate} = useTypedNavigation();

const showPicker = () => {
Expand Down Expand Up @@ -98,7 +121,7 @@ export const ConversationListScreen = () => {
<ConversationListHeader
showPickerModal={showPicker}
list={list}
messageRequestCount={messageRequests.length}
messageRequestCount={messageRequestsCount}
onShowMessageRequests={() => setList('MESSAGE_REQUESTS')}
/>
}
Expand Down
54 changes: 5 additions & 49 deletions src/screens/GroupScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useFocusEffect, useRoute} from '@react-navigation/native';
import {RemoteAttachmentContent} from '@xmtp/react-native-sdk';
import {Box, FlatList, HStack, VStack} from 'native-base';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import React, {useCallback, useMemo, useState} from 'react';
import {KeyboardAvoidingView, ListRenderItem, Platform} from 'react-native';
import {Asset} from 'react-native-image-picker';
import {ConversationInput} from '../components/ConversationInput';
Expand All @@ -16,10 +16,10 @@ import {GroupInfoModal} from '../components/modals/GroupInfoModal';
import {GroupContext, GroupContextValue} from '../context/GroupContext';
import {useClient} from '../hooks/useClient';
import {useGroup} from '../hooks/useGroup';
import {useGroupConsent} from '../hooks/useGroupConsent';
import {useGroupMessages} from '../hooks/useGroupMessages';
import {translate} from '../i18n';
import {useGroupParticipantsQuery} from '../queries/useGroupParticipantsQuery';
import {mmkvStorage} from '../services/mmkvStorage';
import {AWSHelper} from '../services/s3';
import {colors} from '../theme/colors';

Expand All @@ -43,47 +43,19 @@ const useData = (topic: string) => {
};
};

const getInitialConsentState = (
addresses: string,
groupId: string,
): 'allowed' | 'denied' | 'unknown' => {
const cachedConsent = mmkvStorage.getConsent(addresses, groupId);
// if (cachedConsent === undefined) {
// return 'unknown';
// }
// if (cachedConsent) {
// return 'allowed';
// }
// return 'denied';
return cachedConsent ? 'allowed' : 'allowed';
};

export const GroupScreen = () => {
const {params} = useRoute();
const {topic} = params as {topic: string};
const {myAddress, messages, addresses, group, client, refetch, isRefetching} =
useData(topic);
const [showGroupModal, setShowGroupModal] = useState(false);
const [showAddModal, setShowAddModal] = useState(false);
const [consent, setConsent] = useState<'allowed' | 'denied' | 'unknown'>(
getInitialConsentState(myAddress ?? '', group?.topic ?? ''),
);
const [replyId, setReplyId] = useState<string | null>(null);
const [reactId, setReactId] = useState<string | null>(null);
const {consent, allow, deny} = useGroupConsent(topic);

const {ids, entities, reactionsEntities} = messages ?? {};

useEffect(() => {
if (!group) {
return;
}
// TODO: Update with consent
setConsent('allowed');
// group..then(currentConsent => {
// setConsent(currentConsent);
// });
}, [group]);

useFocusEffect(
useCallback(() => {
refetch();
Expand Down Expand Up @@ -136,22 +108,6 @@ export const GroupScreen = () => {
return <Message message={message} isMe={isMe} reactions={reactions} />;
};

const onConsent = useCallback(() => {
if (addresses) {
client?.contacts.allow(addresses);
}
setConsent('allowed');
mmkvStorage.saveConsent(myAddress ?? '', topic ?? '', true);
}, [addresses, client?.contacts, myAddress, topic]);

const onBlock = useCallback(() => {
if (addresses) {
client?.contacts.deny(addresses);
}
setConsent('denied');
mmkvStorage.saveConsent(myAddress ?? '', topic ?? '', false);
}, [addresses, client?.contacts, topic, myAddress]);

const setReply = useCallback(
(id: string) => {
setReplyId(id);
Expand Down Expand Up @@ -211,14 +167,14 @@ export const GroupScreen = () => {
/>
) : (
<HStack justifyContent={'space-around'} marginX={'40px'}>
<Button onPress={onConsent}>
<Button onPress={allow}>
<Text
typography="text-base/medium"
color={colors.backgroundPrimary}>
{translate('allow')}
</Text>
</Button>
<Button onPress={onBlock}>
<Button onPress={deny}>
<Text
typography="text-base/medium"
color={colors.backgroundPrimary}>
Expand Down
25 changes: 25 additions & 0 deletions src/services/mmkvStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum MMKVKeys {
GROUP_NAME = 'GROUP_NAME',
GROUP_ID_PUSH_SUBSCRIPTION = 'GROUP_ID_PUSH_SUBSCRIPTION',
GROUP_PARTICIPANTS = 'GROUP_PARTICIPANTS',
GROUP_CONSENT = 'GROUP_CONSENT',

GROUP_FIRST_MESSAGE_CONTENT = 'GROUP_FIRST_MESSAGE_CONTENT',
}
Expand Down Expand Up @@ -292,6 +293,30 @@ class MMKVStorage {

//#endregion Group Participants

//#region Group Consent

private getGroupConsentKey = (id: string) => {
return `${MMKVKeys.GROUP_CONSENT}_${id}`;
};

saveGroupConsent = (
id: string,
consent: 'allowed' | 'denied' | 'unknown',
) => {
return this.storage.set(this.getGroupConsentKey(id), consent);
};

getGroupConsent = (id: string) => {
return this.storage.getString(this.getGroupConsentKey(id)) as
| 'allowed'
| 'denied'
| 'unknown';
};

clearGroupConsent = (id: string) => {
return this.storage.delete(this.getGroupConsentKey(id));
};

//#region Group First Message

private getGroupFirstMessageContentKey = (topic: string) => {
Expand Down
7 changes: 4 additions & 3 deletions src/services/pushNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ export class PushNotifications {
constructor(client: Client<SupportedContentTypes>) {
this.client = client;
this.pushClient = new XMTPPush(client);
if (!AppConfig.PUSH_NOTIFICATIONS) {
return;
}
RNPush.configure({
async onRegister(registrationData) {
try {
const token = registrationData.token;
console.log('PUSH NOTIFICATION TOKEN:', token);
if (AppConfig.PUSH_NOTIFICATIONS) {
XMTPPush.register(PUSH_SERVER, token);
}
XMTPPush.register(PUSH_SERVER, token);
} catch (error) {
console.error(
'PUSH NOTIFICATION Failed to register push token:',
Expand Down
7 changes: 7 additions & 0 deletions src/utils/getAllListMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ export const getAllListMessages = async (
const groups = await withRequestLogger(client.conversations.listGroups(), {
name: 'groups',
});
await Promise.allSettled(
groups.map(async group => {
const consent = await group.consentState();
console.log('Consent state', consent);
mmkvStorage.saveGroupConsent(group.id, consent);
}),
);

return groups;
} catch (e) {
Expand Down

0 comments on commit 628fbd8

Please sign in to comment.