Skip to content
Snippets Groups Projects
Commit 2879ff50 authored by Alexey Lunin's avatar Alexey Lunin
Browse files

Display email notifications

parent 19c54d7d
Branches
Tags
1 merge request!12Hin messaging
......@@ -42,6 +42,9 @@ const EmailListItem: React.FC<Props> = ({
key={id}
>
<Shadow style={styles.container}>
{!read && (
<View style={styles.unread} />
)}
<View style={styles.info}>
{subject && <Text style={styles.title}>{subject}</Text>}
{sender?.name && <Text style={styles.sender}>{sender.name}</Text>}
......@@ -77,6 +80,16 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
position: 'relative'
},
unread: {
position: 'absolute',
width: 14,
height: 14,
backgroundColor: ColorPallet.baseColors.red,
borderRadius: 7,
top: 6,
right: 6
},
icon: {
backgroundColor: ColorPallet.brand.primary,
......
......@@ -12,7 +12,7 @@ import {
import { StackScreenProps } from '@react-navigation/stack';
import { useNavigation } from '@react-navigation/core';
import { borderRadius, ColorPallet, TextTheme } from 'src/theme/theme';
import useNotifications from 'src/hooks/notifications';
import {useAgentNotifications, useEmailNotifications} from 'src/hooks/notifications';
import SettingsSvg from 'src/assets/svg/settings.svg';
import SearchSvg from 'src/assets/svg/search.svg';
import BellSvg from 'src/assets/svg/bell.svg';
......@@ -27,7 +27,10 @@ const SearchBar: React.FC<Props> = ({ searchPhrase, setSearchPhrase }) => {
const { t } = useTranslation();
const [focused, setFocused] = useState(false);
const navigation = useNavigation<StackScreenProps<SettingStackParams>>();
const notifications = useNotifications();
const agentNotifications = useAgentNotifications();
const emailNotifications = useEmailNotifications();
const count = agentNotifications.length + emailNotifications.length;
const cancel = () => {
Keyboard.dismiss();
......@@ -73,9 +76,9 @@ const SearchBar: React.FC<Props> = ({ searchPhrase, setSearchPhrase }) => {
}}
>
<BellSvg style={styles.bellIcon} />
{notifications.length > 0 && (
{count > 0 && (
<View style={styles.notificationBadge}>
<Text style={styles.badgeText}>{notifications.length < 100 ? notifications.length : 99}</Text>
<Text style={styles.badgeText}>{count < 100 ? count : 99}</Text>
</View>
)}
</TouchableOpacity>
......
......@@ -58,6 +58,7 @@ const TextInput: React.FC<Props> = ({
onBlur={() => setFocused(false)}
{...textInputProps}
placeholderTextColor={ColorPallet.grayscale.lightGrey}
autoCapitalize='none'
/>
</View>
);
......
import React from 'react';
import { Pressable, StyleSheet, View } from 'react-native';
import { format } from "date-fns";
import { Shadow } from 'react-native-shadow-2';
import {borderRadius, ColorPallet, TextTheme} from 'src/theme/theme';
import Text from 'src/components/Text';
import { DATE_TIME_FORMAT } from "src/constants/constants";
import {parseAddress} from "src/utils/email";
import EmailSvg from 'src/assets/svg/verifiable_e-mail2.svg';
interface Props {
id: string;
createdAt: Date;
from: string;
to: string;
cc: string;
bcc: string;
subject: string;
sealed: boolean;
sealUrl: string | null | undefined;
onPress?: () => void;
}
const EmailListItem: React.FC<Props> = ({
id,
createdAt,
from,
to,
cc,
bcc,
subject,
sealed,
sealUrl,
onPress
}) => {
const sender = parseAddress(from);
return (
<Pressable
onPress={onPress}
key={id}
>
<View style={styles.container}>
<View style={styles.avatar}>
<EmailSvg style={styles.svg} />
</View>
<View style={styles.details}>
{subject && <Text style={styles.title}>{subject}</Text>}
{sender?.name && <Text style={styles.sender}>{sender.name}</Text>}
{sender?.email && (
<Text style={styles.senderEmail}>({sender.email})</Text>
)}
<Text style={styles.date}>
{format(createdAt, DATE_TIME_FORMAT)}
</Text>
</View>
</View>
</Pressable>
);
};
export default EmailListItem;
const styles = StyleSheet.create({
container: {
backgroundColor: ColorPallet.grayscale.veryLightGrey,
borderRadius: borderRadius,
padding: 8,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
},
avatar: {
width: 50,
height: 50,
justifyContent: 'center',
alignItems: 'center',
borderRadius: borderRadius,
},
svg: {
width: 50,
height: 50,
fill: ColorPallet.brand.primary,
},
details: {
flex: 1,
flexDirection: 'column',
marginLeft: 10,
flexShrink: 1,
justifyContent: 'flex-start',
},
title: {
fontWeight: 'bold',
color: ColorPallet.brand.primary,
fontSize: 20,
},
sender: {
marginTop: 4,
fontWeight: 'bold',
color: ColorPallet.baseColors.black,
fontSize: 19,
},
senderEmail: {
color: ColorPallet.baseColors.black,
fontSize: 14,
},
date: {
color: ColorPallet.baseColors.black,
fontSize: 14,
},
});
......@@ -3,13 +3,13 @@ import { useTranslation } from 'react-i18next';
import {StyleSheet, View, Text, Pressable} from 'react-native';
import {borderRadius, ColorPallet, TextTheme} from 'src/theme/theme';
import Button, { ButtonType } from 'src/components/Button';
import {Notification} from "src/utils/agentUtils";
import {AgentNotification} from "src/utils/agentUtils";
import rootStore from "src/store/rootStore";
import {hashCode, hashToRGBA, parseCredDef} from "src/utils/helpers";
import NewConnectionSvg from 'src/assets/svg/new_connection.svg';
interface NotificationListItemProps {
notification: Notification;
notification: AgentNotification;
onView?: () => void;
}
......@@ -23,6 +23,7 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
useEffect(() => {
(async () => {
console.log(notification);
if (notification.proofRequest) {
setTitle(t<string>('ProofRequest.ProofRequest'));
setBody(notification.connection?.theirLabel ?? 'Connectionless proof request');
......@@ -38,6 +39,10 @@ const NotificationListItem: React.FC<NotificationListItemProps> = ({
})();
}, [])
// console.log('title');
// console.log(title);
// console.log(body);
return (
<Pressable testID="notification-list-item" onPress={onView}>
......
import { useEffect, useState } from 'react';
import {getNotifications, Notification} from "src/utils/agentUtils";
import {getNotifications, AgentNotification} from "src/utils/agentUtils";
import rootStore from "src/store/rootStore";
import {Results, useQuery} from "@realm/react";
import Email from "../db-models/Email";
const useNotifications = (): Notification[] => {
const [notifications, setNotifications] = useState<Notification[]>([]);
export const useAgentNotifications = (): AgentNotification[] => {
const [notifications, setNotifications] = useState<AgentNotification[]>([]);
const fetchNotifications = async () => {
const data = await getNotifications(rootStore.agentStore.agent);
......@@ -19,4 +21,10 @@ const useNotifications = (): Notification[] => {
return notifications;
};
export default useNotifications;
export const useEmailNotifications = (): Results<Email> => {
let emailQuery = useQuery(Email);
emailQuery = emailQuery.filtered(`read == $0`, false);
emailQuery = emailQuery.sorted('createdAt', true);
return emailQuery;
};
\ No newline at end of file
......@@ -2,54 +2,109 @@ import React from 'react';
import {FlatList, StatusBar, StyleSheet, View} from 'react-native';
import {useNavigation} from "@react-navigation/core";
import {useTranslation} from "react-i18next";
import NotificationListItem from 'src/components/NotificationListItem';
import NotificationListItem from 'src/components/notifications/NotificationListItem';
import NotificationEmailListItem from 'src/components/notifications/NotificationEmailListItem';
import {observer} from "mobx-react";
import {Screens} from "src/type/navigators";
import useNotifications from "src/hooks/notifications";
import {Screens, TabStacks} from "src/type/navigators";
import {useAgentNotifications, useEmailNotifications} from "src/hooks/notifications";
import PageTitle from "src/components/PageTitle";
import NoRecordsAvailable from "src/components/NoRecordsAvailable";
import {ColorPallet} from "src/theme/theme";
import {borderRadius, ColorPallet} from "src/theme/theme";
import {AgentNotification} from "../../utils/agentUtils";
import Email from "src/db-models/Email";
interface CombinedItem {
key: string;
agentNotification: AgentNotification | null;
emailNotification: Email | null;
}
const Notifications = observer(() => {
const { t } = useTranslation();
const navigation = useNavigation();
const notifications = useNotifications();
const agentNotification = useAgentNotifications();
const emailNotifications = useEmailNotifications();
const combinedNotifications = [
...agentNotification.map(p => ({
key: p.key,
agentNotification: p,
emailNotification: null
})),
...emailNotifications.map((p: Email) => ({
key: p._id.toString(),
agentNotification: null,
emailNotification: p
}))
];
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" />
{!!notifications.length && (
{!!combinedNotifications.length && (
<PageTitle>
{t<string>('Notifications.Title', {
count: notifications.length
count: combinedNotifications.length
})}
</PageTitle>
)}
{!notifications.length && (
{!combinedNotifications.length && (
<NoRecordsAvailable text={t<string>('Notifications.NoNewUpdates')} />
)}
{!!notifications.length && (
{!!combinedNotifications.length && (
<FlatList
data={notifications}
data={combinedNotifications}
keyExtractor={item => item.key}
renderItem={({ item, index }) => (
style={styles.list}
renderItem={({ item, index }: { item: CombinedItem, index: number }) => (
<View
key={index}
style={{
marginHorizontal: 10,
marginTop: 15,
marginBottom: index === notifications.length - 1 ? 15 : 0,
marginBottom: index === combinedNotifications.length - 1 ? 15 : 0,
}}
>
<NotificationListItem
notification={item}
onView={() => {
if (item.credentialOffer) {
navigation.navigate(Screens.CredentialOffer, { credentialId: item.credentialOffer.id });
} else if (item.proofRequest) {
navigation.navigate(Screens.ProofRequest, { proofId: item.proofRequest.id });
}
}}
/>
{item.agentNotification && (
<NotificationListItem
notification={item.agentNotification}
onView={() => {
if (item.agentNotification.credentialOffer) {
navigation.navigate(Screens.CredentialOffer, { credentialId: item.agentNotification.credentialOffer.id });
} else if (item.agentNotification.proofRequest) {
navigation.navigate(Screens.ProofRequest, { proofId: item.agentNotification.proofRequest.id });
}
}}
/>
)}
{item.emailNotification && (
<NotificationEmailListItem
id={item.emailNotification._id.toString()}
createdAt={item.emailNotification.createdAt}
from={item.emailNotification.from}
to={item.emailNotification.to}
cc={item.emailNotification.cc}
bcc={item.emailNotification.bcc}
subject={item.emailNotification.subject}
read={item.emailNotification.read}
sealed={item.emailNotification.type === "sealed"}
sealUrl={item.emailNotification.sealUrl}
onPress={() => {
const realmId = item.emailNotification!._id.toString();
if (item.emailNotification!.type === "sealed") {
navigation.getParent().navigate(TabStacks.EmailStack, {
screen: Screens.SealDetailsInfo,
params: { realmId: realmId },
});
} else {
navigation.getParent().navigate(TabStacks.EmailStack, {
screen: Screens.EmailDetails,
params: { realmId: realmId },
});
}
}}
/>
)}
</View>
)}
/>
......@@ -78,4 +133,10 @@ const styles = StyleSheet.create({
marginTop: offset,
marginBottom: 20,
},
list: {
marginTop: 26,
backgroundColor: ColorPallet.grayscale.white,
borderRadius: borderRadius,
flex: 1,
}
});
......@@ -80,6 +80,7 @@ export default ReceivedEmailList;
const styles = StyleSheet.create({
container: {
backgroundColor: ColorPallet.grayscale.white,
margin: 20,
flex: 1,
},
......
......@@ -89,7 +89,7 @@ const useInformation = (
cc: qrCodeData.recipients.cc?.map(formatEmailAddress).join(', '),
bcc: "",
subject: qrCodeData.subject,
read: false,
read: true,
svdxMime: null,
svdxImapSaved: null,
......
import {Agent, CredentialExchangeRecord, CredentialState, ProofExchangeRecord, ProofState} from "@aries-framework/core";
export interface Notification {
export interface AgentNotification {
key: string;
connection?: any;
credentialOffer?: CredentialExchangeRecord;
proofRequest?: ProofExchangeRecord;
}
export const getNotifications = async (agent: Agent): Promise<Notification[]> => {
export const getNotifications = async (agent: Agent): Promise<AgentNotification[]> => {
const newOffers = await agent.credentials.findAllByQuery({ state: CredentialState.OfferReceived });
const newProofRequests = await agent.proofs.findAllByQuery({ state: ProofState.RequestReceived });
......@@ -28,7 +28,7 @@ export const getNotifications = async (agent: Agent): Promise<Notification[]> =>
}
}
const notifications: Notification[] = [];
const notifications: AgentNotification[] = [];
const filteredOffers = newOffers.filter(p => !rejectedIds.some(rejId => rejId === p.id));
for await (const offer of filteredOffers) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment