aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-06-09 17:41:29 -0400
committerGitHub <noreply@github.com>2021-06-09 17:41:29 -0400
commitd13d12980555cb1f659ae5108c52f089a54c3f57 (patch)
tree145098b27611a955d40118046e275a6ae940d969 /src
parent587d08dc8f0508b6efa76cc7d57f21ac2a5fd9fb (diff)
parent93796732be3f5070c0a124d29533396f79736c83 (diff)
Merge pull request #456 from brian-tagg/tma872-purple-indicator
[TMA-872] purple indicator
Diffstat (limited to 'src')
-rw-r--r--src/assets/images/Group 479.jpgbin0 -> 753 bytes
-rw-r--r--src/assets/images/Group 479.svg5
-rw-r--r--src/assets/images/Profile Icon.pngbin0 -> 1504 bytes
-rw-r--r--src/assets/images/pill-icon-1.pngbin0 -> 868 bytes
-rw-r--r--src/assets/images/pill-icon-2.pngbin0 -> 1276 bytes
-rw-r--r--src/assets/images/pill-icon-3.pngbin0 -> 1037 bytes
-rw-r--r--src/assets/images/pill-icon-4.pngbin0 -> 1249 bytes
-rw-r--r--src/assets/images/purple-tip.pngbin0 -> 680 bytes
-rw-r--r--src/components/moments/MomentPostContent.tsx6
-rw-r--r--src/components/notifications/NotificationPill.tsx209
-rw-r--r--src/components/notifications/index.ts1
-rw-r--r--src/constants/api.ts2
-rw-r--r--src/constants/constants.ts3
-rw-r--r--src/routes/tabs/NavigationBar.tsx159
-rw-r--r--src/services/NotificationService.ts63
-rw-r--r--src/types/types.ts4
-rw-r--r--src/utils/common.ts5
17 files changed, 381 insertions, 76 deletions
diff --git a/src/assets/images/Group 479.jpg b/src/assets/images/Group 479.jpg
new file mode 100644
index 00000000..74abad92
--- /dev/null
+++ b/src/assets/images/Group 479.jpg
Binary files differ
diff --git a/src/assets/images/Group 479.svg b/src/assets/images/Group 479.svg
new file mode 100644
index 00000000..4e1eee01
--- /dev/null
+++ b/src/assets/images/Group 479.svg
@@ -0,0 +1,5 @@
+<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<ellipse cx="7.75104" cy="5.91915" rx="2.00104" ry="1.93282" fill="white"/>
+<path d="M7.75195 8.0791C5.67981 8.0791 4 9.6062 4 11.49H11.5039C11.5039 9.6062 9.8241 8.0791 7.75195 8.0791Z" fill="white"/>
+<path d="M7.99349 1C4.13109 1 1 4.13109 1 7.99349C1 11.1633 3.10881 13.8405 6 14.6987L8 17L9.98697 14.6987C12.8782 13.8405 14.987 11.1633 14.987 7.99349C14.987 4.13109 11.8559 1 7.99349 1Z" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>
diff --git a/src/assets/images/Profile Icon.png b/src/assets/images/Profile Icon.png
new file mode 100644
index 00000000..f8eae388
--- /dev/null
+++ b/src/assets/images/Profile Icon.png
Binary files differ
diff --git a/src/assets/images/pill-icon-1.png b/src/assets/images/pill-icon-1.png
new file mode 100644
index 00000000..06956c6a
--- /dev/null
+++ b/src/assets/images/pill-icon-1.png
Binary files differ
diff --git a/src/assets/images/pill-icon-2.png b/src/assets/images/pill-icon-2.png
new file mode 100644
index 00000000..b2370b80
--- /dev/null
+++ b/src/assets/images/pill-icon-2.png
Binary files differ
diff --git a/src/assets/images/pill-icon-3.png b/src/assets/images/pill-icon-3.png
new file mode 100644
index 00000000..6cdf0b15
--- /dev/null
+++ b/src/assets/images/pill-icon-3.png
Binary files differ
diff --git a/src/assets/images/pill-icon-4.png b/src/assets/images/pill-icon-4.png
new file mode 100644
index 00000000..6e132647
--- /dev/null
+++ b/src/assets/images/pill-icon-4.png
Binary files differ
diff --git a/src/assets/images/purple-tip.png b/src/assets/images/purple-tip.png
new file mode 100644
index 00000000..27f5a89a
--- /dev/null
+++ b/src/assets/images/purple-tip.png
Binary files differ
diff --git a/src/components/moments/MomentPostContent.tsx b/src/components/moments/MomentPostContent.tsx
index 4a1f3894..ecbfb3a2 100644
--- a/src/components/moments/MomentPostContent.tsx
+++ b/src/components/moments/MomentPostContent.tsx
@@ -35,12 +35,12 @@ const MomentPostContent: React.FC<MomentPostContentProps> = ({
style,
momentTags,
}) => {
- const state: RootState = useStore().getState();
- const navigation = useNavigation();
- const dispatch = useDispatch();
const [elapsedTime, setElapsedTime] = useState('');
const [comments_count, setCommentsCount] = useState('');
const [tags, setTags] = useState<MomentTagType[]>(momentTags);
+ const state: RootState = useStore().getState();
+ const navigation = useNavigation();
+ const dispatch = useDispatch();
const imageRef = useRef(null);
const [visible, setVisible] = useState(false);
diff --git a/src/components/notifications/NotificationPill.tsx b/src/components/notifications/NotificationPill.tsx
new file mode 100644
index 00000000..01622a6f
--- /dev/null
+++ b/src/components/notifications/NotificationPill.tsx
@@ -0,0 +1,209 @@
+import React, {useEffect, useState, useRef} from 'react';
+import {Image, StyleSheet, Text, View} from 'react-native';
+import LinearGradient from 'react-native-linear-gradient';
+import {SCREEN_WIDTH, isIPhoneX, numberWithCommas} from '../../utils';
+import {
+ NOTIFICATION_ICON_GRADIENT,
+ CHIN_HEIGHT,
+ NAV_BAR_HEIGHT,
+} from '../../constants';
+import {getNotificationsUnreadCount} from '../../services';
+import {normalize} from 'react-native-elements';
+import PillIcon4 from '../../assets/images/Group 479.svg';
+
+interface NotificationPillProps {
+ showIcon: boolean;
+}
+
+export const NotificationPill: React.FC<NotificationPillProps> = ({
+ showIcon,
+}) => {
+ const [iconStart, setIconStart] = useState<number[]>([0, -100]);
+ const [tipStart, setTipStart] = useState<number[]>([0, -100]);
+ const [notificationSets, setNotificationSets] = useState<{
+ CMT?: number;
+ FRD_REQ?: number;
+ P_VIEW?: number;
+ MOM_TAG?: number;
+ }>({});
+ const [timeCount, setTimeCount] = useState<boolean>(false);
+ const [timeOut, setTimeOut] = useState<boolean>(false);
+ const iconRef = useRef(null);
+ const tipRef = useRef(null);
+ const pillTip = require('../../assets/images/purple-tip.png');
+
+ const navBarPos = 20;
+
+ // If there are notifications, determines the size of the pill
+ // and sets points for correct placement
+ useEffect(() => {
+ setTimeout(() => {
+ if (iconRef.current) {
+ iconRef.current.measure(
+ (
+ _fx: number,
+ _fy: number,
+ width: number,
+ height: number,
+ _px: number,
+ _py: number,
+ ) => {
+ if (tipRef.current) {
+ tipRef.current.measure(
+ (
+ __fx: number,
+ __fy: number,
+ width2: number,
+ __height: number,
+ __px: number,
+ __py: number,
+ ) => {
+ const x = SCREEN_WIDTH / 2 - width / 2;
+ const y = isIPhoneX()
+ ? CHIN_HEIGHT + NAV_BAR_HEIGHT + navBarPos
+ : NAV_BAR_HEIGHT + navBarPos;
+ setIconStart([x, y]);
+ setTipStart([width / 2 - width2 / 2, height - 1]);
+ setTimeCount(true);
+ },
+ );
+ }
+ },
+ );
+ } else {
+ }
+ }, 100);
+ }, [notificationSets, iconRef, tipRef]);
+
+ // Used so that pill disappears after 5 seconds
+ useEffect(() => {
+ if (timeCount) {
+ setTimeout(() => {
+ setTimeOut(true);
+ }, 5000);
+ }
+ }, [timeCount]);
+
+ // Gets data from backend to check for unreads
+ useEffect(() => {
+ const getCount = async () => {
+ const data = await getNotificationsUnreadCount();
+ setTimeout(() => {
+ if (data) {
+ setNotificationSets(data);
+ }
+ }, 100);
+ };
+
+ getCount();
+ }, []);
+
+ return (
+ <>
+ {notificationSets &&
+ Object.keys(notificationSets).length !== 0 &&
+ showIcon &&
+ !timeOut && (
+ <View
+ style={[
+ styles.purpleContainer,
+ {bottom: iconStart[1], left: iconStart[0]},
+ ]}
+ ref={iconRef}>
+ <LinearGradient
+ colors={NOTIFICATION_ICON_GRADIENT}
+ style={styles.iconPurple}>
+ {notificationSets.CMT && (
+ <>
+ <Image
+ source={require('../../assets/images/pill-icon-1.png')}
+ style={styles.indicationIcon}
+ />
+ <Text style={styles.text}>
+ {numberWithCommas(notificationSets.CMT)}
+ </Text>
+ </>
+ )}
+ {notificationSets.FRD_REQ && (
+ <>
+ <Image
+ source={require('../../assets/images/pill-icon-2.png')}
+ style={styles.indicationIcon}
+ />
+ <Text style={styles.text}>
+ {numberWithCommas(notificationSets.FRD_REQ)}
+ </Text>
+ </>
+ )}
+ {notificationSets.P_VIEW && (
+ <>
+ <Image
+ source={require('../../assets/images/pill-icon-3.png')}
+ style={styles.indicationIcon}
+ />
+ <Text style={styles.text}>
+ {numberWithCommas(notificationSets.P_VIEW)}
+ </Text>
+ </>
+ )}
+ {notificationSets.MOM_TAG && (
+ <>
+ <PillIcon4 style={styles.indicationIcon} />
+ <Text style={styles.text}>
+ {numberWithCommas(notificationSets.MOM_TAG)}
+ </Text>
+ </>
+ )}
+ </LinearGradient>
+ <Image
+ style={[styles.tip, {top: tipStart[1], left: tipStart[0]}]}
+ source={pillTip}
+ ref={tipRef}
+ />
+ </View>
+ )}
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ purpleContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ position: 'absolute',
+ zIndex: 999,
+ },
+ iconPurple: {
+ padding: 5,
+ borderRadius: 15,
+ flex: 1,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ text: {
+ margin: 2,
+ color: 'white',
+ fontSize: normalize(10),
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginRight: 5,
+ },
+ tip: {
+ position: 'absolute',
+ zIndex: 999,
+ height: 12,
+ flex: 1,
+ resizeMode: 'contain',
+ },
+ indicationIcon: {
+ height: 14,
+ width: 14,
+ margin: 2,
+ marginLeft: 5,
+ },
+ svgIndicationIcon: {
+ height: 14,
+ width: 14,
+ margin: 3,
+ },
+});
diff --git a/src/components/notifications/index.ts b/src/components/notifications/index.ts
index 733b56f1..077c26a4 100644
--- a/src/components/notifications/index.ts
+++ b/src/components/notifications/index.ts
@@ -1,2 +1,3 @@
export {default as Notification} from './Notification';
export {InviteFriendsPrompt} from './NotificationPrompts';
+export {NotificationPill} from './NotificationPill';
diff --git a/src/constants/api.ts b/src/constants/api.ts
index f02ee407..b55489d9 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -45,6 +45,8 @@ export const BLOCK_USER_ENDPOINT: string = API_URL + 'block/';
export const PASSWORD_RESET_ENDPOINT: string = API_URL + 'password-reset/';
export const MOMENT_CATEGORY_ENDPOINT: string = API_URL + 'moment-category/';
export const NOTIFICATIONS_ENDPOINT: string = API_URL + 'notifications/';
+export const NOTIFICATIONS_COUNT_ENDPOINT: string = API_URL + 'notifications/unread_count/';
+export const NOTIFICATIONS_DATE: string = API_URL + 'notifications/seen/';
export const DISCOVER_ENDPOINT: string = API_URL + 'discover/';
export const SEARCH_BUTTONS_ENDPOPINT: string =
DISCOVER_ENDPOINT + 'search_buttons/';
diff --git a/src/constants/constants.ts b/src/constants/constants.ts
index a6d98883..f4ffd750 100644
--- a/src/constants/constants.ts
+++ b/src/constants/constants.ts
@@ -21,6 +21,8 @@ export const AVATAR_GRADIENT_DIM = 50;
export const TAGG_ICON_DIM = 58;
export const TAGG_RING_DIM = normalize(60);
+// default height of the navigation bar, from react native library, unless on ipad
+export const NAV_BAR_HEIGHT = 49;
export const BADGE_LIMIT = 5;
export const INTEGRATED_SOCIAL_LIST: string[] = [
@@ -91,6 +93,7 @@ export const BADGE_GRADIENT_REST = [
'rgba(78, 54, 41, 1)',
'rgba(236, 32, 39, 1)',
];
+export const NOTIFICATION_ICON_GRADIENT = ['#8F01FF', '#7B02DA'];
export const SOCIAL_FONT_COLORS = {
INSTAGRAM: INSTAGRAM_FONT_COLOR,
diff --git a/src/routes/tabs/NavigationBar.tsx b/src/routes/tabs/NavigationBar.tsx
index 000ac614..c3a42739 100644
--- a/src/routes/tabs/NavigationBar.tsx
+++ b/src/routes/tabs/NavigationBar.tsx
@@ -4,9 +4,11 @@ import {useSelector} from 'react-redux';
import {NavigationIcon} from '../../components';
import {NO_NOTIFICATIONS} from '../../store/initialStates';
import {RootState} from '../../store/rootReducer';
+import {setNotificationsReadDate} from '../../services';
import {ScreenType} from '../../types';
import {haveUnreadNotifications} from '../../utils';
import MainStackScreen from '../main/MainStackScreen';
+import {NotificationPill} from '../../components/notifications';
const Tabs = createBottomTabNavigator();
@@ -18,10 +20,14 @@ const NavigationBar: React.FC = () => {
const {notifications: {notifications} = NO_NOTIFICATIONS} = useSelector(
(state: RootState) => state,
);
+ // Triggered if user clicks on Notifications page to close the pill
+ const [showIcon, setShowIcon] = useState<boolean>(true);
const [unreadNotificationsPresent, setUnreadNotificationsPresent] =
useState<boolean>(false);
+ // Prior to pill inclusion, determines if notification bell
+ // should have purple dot
useEffect(() => {
const determine = async () => {
setUnreadNotificationsPresent(
@@ -32,77 +38,88 @@ const NavigationBar: React.FC = () => {
}, [notifications]);
return (
- <Tabs.Navigator
- screenOptions={({route}) => ({
- tabBarIcon: ({focused}) => {
- switch (route.name) {
- case 'Home':
- return <NavigationIcon tab="Home" disabled={!focused} />;
- case 'Search':
- return <NavigationIcon tab="Search" disabled={!focused} />;
- case 'Upload':
- return <NavigationIcon tab="Upload" disabled={!focused} />;
- case 'Notifications':
- return (
- <NavigationIcon
- newIcon={
- newNotificationReceived || unreadNotificationsPresent
- }
- tab="Notifications"
- disabled={!focused}
- />
- );
- case 'Chat':
- return <NavigationIcon tab="Chat" disabled={!focused} />;
- case 'Profile':
- return <NavigationIcon tab="Profile" disabled={!focused} />;
- case 'SuggestedPeople':
- return (
- <NavigationIcon tab="SuggestedPeople" disabled={!focused} />
- );
- default:
- return <Fragment />;
- }
- },
- })}
- initialRouteName={isOnboardedUser ? 'Profile' : 'SuggestedPeople'}
- tabBarOptions={{
- showLabel: false,
- style: {
- backgroundColor: 'transparent',
- position: 'absolute',
- borderTopWidth: 0,
- left: 0,
- right: 0,
- bottom: '1%',
- },
- }}>
- <Tabs.Screen
- name="SuggestedPeople"
- component={MainStackScreen}
- initialParams={{screenType: ScreenType.SuggestedPeople}}
- />
- <Tabs.Screen
- name="Search"
- component={MainStackScreen}
- initialParams={{screenType: ScreenType.Search}}
- />
- <Tabs.Screen
- name="Notifications"
- component={MainStackScreen}
- initialParams={{screenType: ScreenType.Notifications}}
- />
- <Tabs.Screen
- name="Chat"
- component={MainStackScreen}
- initialParams={{screenType: ScreenType.Chat}}
- />
- <Tabs.Screen
- name="Profile"
- component={MainStackScreen}
- initialParams={{screenType: ScreenType.Profile}}
- />
- </Tabs.Navigator>
+ <>
+ <NotificationPill showIcon={showIcon} />
+ <Tabs.Navigator
+ screenOptions={({route}) => ({
+ tabBarIcon: ({focused}) => {
+ switch (route.name) {
+ case 'Home':
+ return <NavigationIcon tab="Home" disabled={!focused} />;
+ case 'Search':
+ return <NavigationIcon tab="Search" disabled={!focused} />;
+ case 'Upload':
+ return <NavigationIcon tab="Upload" disabled={!focused} />;
+ case 'Notifications':
+ return (
+ <NavigationIcon
+ newIcon={
+ newNotificationReceived || unreadNotificationsPresent
+ }
+ tab="Notifications"
+ disabled={!focused}
+ />
+ );
+ case 'Chat':
+ return <NavigationIcon tab="Chat" disabled={!focused} />;
+ case 'Profile':
+ return <NavigationIcon tab="Profile" disabled={!focused} />;
+ case 'SuggestedPeople':
+ return (
+ <NavigationIcon tab="SuggestedPeople" disabled={!focused} />
+ );
+ default:
+ return <Fragment />;
+ }
+ },
+ })}
+ initialRouteName={isOnboardedUser ? 'Profile' : 'SuggestedPeople'}
+ tabBarOptions={{
+ showLabel: false,
+ style: {
+ backgroundColor: 'transparent',
+ position: 'absolute',
+ borderTopWidth: 0,
+ left: 0,
+ right: 0,
+ bottom: '1%',
+ },
+ }}>
+ <Tabs.Screen
+ name="SuggestedPeople"
+ component={MainStackScreen}
+ initialParams={{screenType: ScreenType.SuggestedPeople}}
+ />
+ <Tabs.Screen
+ name="Search"
+ component={MainStackScreen}
+ initialParams={{screenType: ScreenType.Search}}
+ />
+ <Tabs.Screen
+ name="Notifications"
+ component={MainStackScreen}
+ initialParams={{screenType: ScreenType.Notifications}}
+ listeners={{
+ tabPress: (_) => {
+ // Closes the pill once this screen has been opened
+ setShowIcon(false);
+ // Updates backend's date of reading notifications
+ setNotificationsReadDate();
+ },
+ }}
+ />
+ <Tabs.Screen
+ name="Chat"
+ component={MainStackScreen}
+ initialParams={{screenType: ScreenType.Chat}}
+ />
+ <Tabs.Screen
+ name="Profile"
+ component={MainStackScreen}
+ initialParams={{screenType: ScreenType.Profile}}
+ />
+ </Tabs.Navigator>
+ </>
);
};
diff --git a/src/services/NotificationService.ts b/src/services/NotificationService.ts
index c5c843f5..ccaa9135 100644
--- a/src/services/NotificationService.ts
+++ b/src/services/NotificationService.ts
@@ -1,5 +1,9 @@
import AsyncStorage from '@react-native-community/async-storage';
-import {NOTIFICATIONS_ENDPOINT} from '../constants';
+import {
+ NOTIFICATIONS_ENDPOINT,
+ NOTIFICATIONS_COUNT_ENDPOINT,
+ NOTIFICATIONS_DATE,
+} from '../constants';
import {NotificationType} from '../types';
export const getNotificationsData: () => Promise<NotificationType[]> =
@@ -29,3 +33,60 @@ export const getNotificationsData: () => Promise<NotificationType[]> =
return [];
}
};
+
+export const getNotificationsUnreadCount = async () => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ const response = await fetch(NOTIFICATIONS_COUNT_ENDPOINT, {
+ method: 'GET',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ });
+ if (response.status === 200) {
+ const data: any = await response.json();
+ const typedData: {
+ CMT?: number;
+ FRD_REQ?: number;
+ P_VIEW?: number;
+ MOM_TAG?: number;
+ } = {};
+ if (data.CMT) {
+ typedData.CMT = data.CMT;
+ }
+ if (data.FRD_REQ && data.FRD_REQ > 0) {
+ typedData.FRD_REQ = data.FRD_REQ;
+ }
+ if (data.P_VIEW && data.P_VIEW > 0) {
+ typedData.P_VIEW = data.P_VIEW;
+ }
+ if (data.MOM_TAG && data.MOM_TAG > 0) {
+ typedData.MOM_TAG = data.MOM_TAG;
+ }
+ return typedData;
+ }
+ } catch (error) {
+ console.log('Unable to fetch notifications');
+ }
+ return undefined;
+};
+
+export const setNotificationsReadDate: () => Promise<boolean> = async () => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ const response = await fetch(NOTIFICATIONS_DATE, {
+ method: 'POST',
+ headers: {
+ Authorization: 'Token ' + token,
+ },
+ });
+ if (response.status === 204) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (error) {
+ console.log('Unable to fetch notifications');
+ return false;
+ }
+};
diff --git a/src/types/types.ts b/src/types/types.ts
index e54c2201..fd75ab50 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -254,7 +254,9 @@ export type TypeOfNotification =
// notification_object is MomentType
| 'MOM_TAG'
// notification_object is undefined
- | 'SYSTEM_MSG';
+ | 'SYSTEM_MSG'
+ // notification_object is undefined
+ | 'P_VIEW';
export type UniversityBadge = {
id: number;
diff --git a/src/utils/common.ts b/src/utils/common.ts
index cfd9244a..1956e811 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -233,3 +233,8 @@ export const badgesToDisplayBadges = (
img: badgeToImgMap[b.category + b.name],
}));
};
+
+// Documentation: https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
+export const numberWithCommas = (digits: number) => {
+ return digits.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+};