Skip to content

Commit 408f6f8

Browse files
authored
Display notifications to the user (4ian#6432)
1 parent c64bac0 commit 408f6f8

29 files changed

Lines changed: 990 additions & 51 deletions

File tree

newIDE/app/scripts/theme-templates/theme.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@
343343
"value": "#494952"
344344
}
345345
}
346+
},
347+
"notification": {
348+
"badge-color": {
349+
"value": "#6BAFFF"
350+
}
346351
}
347352
},
348353
"input": {

newIDE/app/src/MainFrame/EditorContainers/HomePage/BuildSection/ProjectFileListItem.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,14 @@ const ListItemLastModification = ({
156156
{isCurrentProjectOpened ? (
157157
<Trans>Modifying</Trans>
158158
) : (
159-
getRelativeOrAbsoluteDisplayDate(i18n, lastModifiedAt)
159+
getRelativeOrAbsoluteDisplayDate({
160+
i18n,
161+
dateAsNumber: lastModifiedAt,
162+
sameDayFormat: 'todayAndHour',
163+
dayBeforeFormat: 'yesterdayAndHour',
164+
relativeLimit: 'currentWeek',
165+
sameWeekFormat: 'thisWeek',
166+
})
160167
)}
161168
</Text>
162169
</LineStackLayout>

newIDE/app/src/MainFrame/EditorContainers/HomePage/HomePageHeader.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { useResponsiveWindowSize } from '../../../UI/Responsive/ResponsiveWindow
1515
import TextButton from '../../../UI/TextButton';
1616
import IconButton from '../../../UI/IconButton';
1717
import { isNativeMobileApp } from '../../../Utils/Platform';
18+
import NotificationChip from '../../../UI/User/NotificationChip';
1819
const electron = optionalRequire('electron');
1920

2021
type Props = {|
@@ -81,6 +82,7 @@ export const HomePageHeader = ({
8182
/>
8283
)}
8384
<UserChip onOpenProfile={onOpenProfile} />
85+
<NotificationChip />
8486
<TextButton
8587
label={i18n.language.toUpperCase()}
8688
onClick={onOpenLanguageDialog}

newIDE/app/src/Profile/AuthenticatedUserContext.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { type CloudProjectWithUserAccessInfo } from '../Utils/GDevelopServices/P
1414
import { User as FirebaseUser } from 'firebase/auth';
1515
import { type Badge } from '../Utils/GDevelopServices/Badge';
1616
import { type Recommendation } from '../Utils/GDevelopServices/User';
17+
import { type Notification } from '../Utils/GDevelopServices/Notification';
1718
import {
1819
type Limits,
1920
type Usages,
@@ -41,6 +42,7 @@ export type AuthenticatedUser = {|
4142
gameTemplatePurchases: ?Array<Purchase>,
4243
assetPackPurchases: ?Array<Purchase>,
4344
recommendations: ?Array<Recommendation>,
45+
notifications: ?Array<Notification>,
4446
limits: ?Limits,
4547
authenticationError: ?AuthError,
4648
usages: ?Usages,
@@ -71,6 +73,7 @@ export type AuthenticatedUser = {|
7173
onRefreshLimits: () => Promise<void>,
7274
onRefreshGameTemplatePurchases: () => Promise<void>,
7375
onRefreshAssetPackPurchases: () => Promise<void>,
76+
onRefreshNotifications: () => Promise<void>,
7477
onPurchaseSuccessful: () => Promise<void>,
7578
onSendEmailVerification: () => Promise<void>,
7679
onOpenEmailVerificationDialog: ({|
@@ -96,6 +99,7 @@ export const initialAuthenticatedUser = {
9699
gameTemplatePurchases: null,
97100
assetPackPurchases: null,
98101
recommendations: null,
102+
notifications: null,
99103
subscription: null,
100104
usages: null,
101105
limits: null,
@@ -119,6 +123,7 @@ export const initialAuthenticatedUser = {
119123
onRefreshLimits: async () => {},
120124
onRefreshGameTemplatePurchases: async () => {},
121125
onRefreshAssetPackPurchases: async () => {},
126+
onRefreshNotifications: async () => {},
122127
onPurchaseSuccessful: async () => {},
123128
onSendEmailVerification: async () => {},
124129
onOpenEmailVerificationDialog: () => {},

newIDE/app/src/Profile/AuthenticatedUserProvider.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { extractGDevelopApiErrorStatusAndCode } from '../Utils/GDevelopServices/
5858
import { showErrorBox } from '../UI/Messages/MessageBox';
5959
import { userCancellationErrorName } from '../LoginProvider/Utils';
6060
import { listUserPurchases } from '../Utils/GDevelopServices/Shop';
61+
import { listNotifications } from '../Utils/GDevelopServices/Notification';
6162

6263
type Props = {|
6364
authentication: Authentication,
@@ -94,6 +95,7 @@ const cleanUserTracesOnDevice = async () => {
9495
};
9596

9697
const TEN_SECONDS = 10 * 1000;
98+
const ONE_MINUTE = 6 * TEN_SECONDS;
9799

98100
export default class AuthenticatedUserProvider extends React.Component<
99101
Props,
@@ -123,6 +125,7 @@ export default class AuthenticatedUserProvider extends React.Component<
123125
_automaticallyUpdateUserProfile = true;
124126
_hasNotifiedUserAboutEmailVerification = false;
125127
_abortController: ?AbortController = null;
128+
_notificationPollingIntervalId: ?IntervalID = null;
126129

127130
// Cloud projects are requested in 2 different places at app opening.
128131
// - First one comes from user authenticating and automatically fetching
@@ -212,6 +215,7 @@ export default class AuthenticatedUserProvider extends React.Component<
212215
onRefreshLimits: this._fetchUserLimits,
213216
onRefreshGameTemplatePurchases: this._fetchUserGameTemplatePurchases,
214217
onRefreshAssetPackPurchases: this._fetchUserAssetPackPurchases,
218+
onRefreshNotifications: this._fetchUserNotifications,
215219
onPurchaseSuccessful: this._fetchUserProducts,
216220
onSendEmailVerification: this._doSendEmailVerification,
217221
onOpenEmailVerificationDialog: ({
@@ -239,6 +243,10 @@ export default class AuthenticatedUserProvider extends React.Component<
239243
// - When the user logs out.
240244
// - When the user deletes their account.
241245
_markAuthenticatedUserAsLoggedOut() {
246+
if (this._notificationPollingIntervalId) {
247+
clearInterval(this._notificationPollingIntervalId);
248+
this._notificationPollingIntervalId = null;
249+
}
242250
this.setState(({ authenticatedUser }) => ({
243251
authenticatedUser: {
244252
...authenticatedUser,
@@ -267,6 +275,10 @@ export default class AuthenticatedUserProvider extends React.Component<
267275
try {
268276
const firebaseUser = await authentication.getFirebaseUser();
269277
if (!firebaseUser) {
278+
if (this._notificationPollingIntervalId) {
279+
clearInterval(this._notificationPollingIntervalId);
280+
this._notificationPollingIntervalId = null;
281+
}
270282
this.setState(({ authenticatedUser }) => ({
271283
authenticatedUser: {
272284
...authenticatedUser,
@@ -503,6 +515,7 @@ export default class AuthenticatedUserProvider extends React.Component<
503515
}
504516
);
505517
this._fetchUserBadges();
518+
this._fetchUserNotifications();
506519

507520
// Load and wait for the user profile to be fetched.
508521
// (and let the error propagate if any).
@@ -523,6 +536,15 @@ export default class AuthenticatedUserProvider extends React.Component<
523536
}
524537
}
525538

539+
if (!this._notificationPollingIntervalId) {
540+
this._notificationPollingIntervalId = setInterval(() => {
541+
// This property is correctly updated by Electron, browsers and capacitor.
542+
if (document.visibilityState === 'visible') {
543+
this._fetchUserNotifications();
544+
}
545+
}, 10 * ONE_MINUTE);
546+
}
547+
526548
this.setState(
527549
({ authenticatedUser }) => ({
528550
authenticatedUser: {
@@ -561,6 +583,28 @@ export default class AuthenticatedUserProvider extends React.Component<
561583
}
562584
};
563585

586+
_fetchUserNotifications = async () => {
587+
const { authentication } = this.props;
588+
const firebaseUser = this.state.authenticatedUser.firebaseUser;
589+
if (!firebaseUser) return;
590+
591+
try {
592+
const notifications = await listNotifications(
593+
authentication.getAuthorizationHeader,
594+
{ userId: firebaseUser.uid }
595+
);
596+
597+
this.setState(({ authenticatedUser }) => ({
598+
authenticatedUser: {
599+
...authenticatedUser,
600+
notifications,
601+
},
602+
}));
603+
} catch (error) {
604+
console.error('Error while loading user notifications:', error);
605+
}
606+
};
607+
564608
_fetchUserUsages = async () => {
565609
const { authentication } = this.props;
566610
const firebaseUser = this.state.authenticatedUser.firebaseUser;

newIDE/app/src/UI/Badge.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
11
// @flow
22
import * as React from 'react';
33
import MuiBadge from '@material-ui/core/Badge';
4+
import { makeStyles } from '@material-ui/core/styles';
45

56
type Props = {|
67
children: React.Node,
7-
badgeContent: React.Node,
8-
color: 'error' | 'primary' | 'secondary' | 'default',
8+
badgeContent?: React.Node,
9+
color?: 'error' | 'primary' | 'secondary' | 'default',
10+
variant?: 'dot',
11+
forcedColor?: string,
12+
invisible?: boolean,
13+
overlap?: 'circle',
914
|};
1015

11-
const Badge = (props: Props) => <MuiBadge {...props} />;
16+
const Badge = ({ forcedColor, ...otherProps }: Props) => {
17+
const stylesForBadge = React.useMemo(
18+
() =>
19+
forcedColor
20+
? makeStyles({
21+
badge: { backgroundColor: forcedColor },
22+
})
23+
: () => {},
24+
[forcedColor]
25+
);
26+
const classes = stylesForBadge();
27+
return <MuiBadge {...otherProps} classes={classes} />;
28+
};
1229

1330
export default Badge;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import SvgIcon from '@material-ui/core/SvgIcon';
3+
4+
export default React.memo(props => (
5+
<SvgIcon {...props} width="32" height="32" viewBox="0 0 32 32" fill="none">
6+
<path
7+
fillRule="evenodd"
8+
clipRule="evenodd"
9+
d="M9.00004 7.33331C8.07957 7.33331 7.33337 8.0795 7.33337 8.99998V19C7.33337 19.9205 8.07956 20.6666 9.00004 20.6666H12.5C12.7884 20.6666 13.0627 20.7911 13.2526 21.0081L16 24.1481L18.7475 21.0081C18.9374 20.7911 19.2117 20.6666 19.5 20.6666H23C23.9206 20.6666 24.6667 19.9205 24.6667 19V8.99998C24.6667 8.07949 23.9205 7.33331 23 7.33331H9.00004ZM5.33337 8.99998C5.33337 6.97493 6.975 5.33331 9.00004 5.33331H23C25.0251 5.33331 26.6667 6.97494 26.6667 8.99998V19C26.6667 21.0251 25.0251 22.6666 23 22.6666H19.9538L16.7526 26.3252C16.5627 26.5422 16.2884 26.6666 16 26.6666C15.7117 26.6666 15.4374 26.5422 15.2475 26.3252L12.0463 22.6666H9.00004C6.97501 22.6666 5.33337 21.0251 5.33337 19V8.99998Z"
10+
fill="currentColor"
11+
/>
12+
<path
13+
fillRule="evenodd"
14+
clipRule="evenodd"
15+
d="M10.6667 14.6666C10.6667 13.9304 11.2636 13.3333 12 13.3333C12.7365 13.3333 13.3334 13.9304 13.3334 14.6666C13.3334 15.4029 12.7365 16 12 16C11.2636 16 10.6667 15.4029 10.6667 14.6666Z"
16+
fill="currentColor"
17+
/>
18+
<path
19+
fillRule="evenodd"
20+
clipRule="evenodd"
21+
d="M14.6667 14.6666C14.6667 13.9303 15.2637 13.3333 16 13.3333C16.7364 13.3333 17.3334 13.9303 17.3334 14.6666C17.3334 15.403 16.7364 16 16 16C15.2637 16 14.6667 15.403 14.6667 14.6666Z"
22+
fill="currentColor"
23+
/>
24+
<path
25+
fillRule="evenodd"
26+
clipRule="evenodd"
27+
d="M18.6667 14.6666C18.6667 13.9303 19.2637 13.3333 20 13.3333C20.7364 13.3333 21.3334 13.9303 21.3334 14.6666C21.3334 15.403 20.7364 16 20 16C19.2637 16 18.6667 15.403 18.6667 14.6666Z"
28+
fill="currentColor"
29+
/>
30+
</SvgIcon>
31+
));
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import SvgIcon from '@material-ui/core/SvgIcon';
3+
4+
export default React.memo(props => (
5+
<SvgIcon {...props} width="17" height="16" viewBox="0 0 17 16" fill="none">
6+
<path
7+
fillRule="evenodd"
8+
clipRule="evenodd"
9+
d="M4.66707 6.66663C4.66707 4.45749 6.45794 2.66663 8.66707 2.66663C10.8762 2.66663 12.6671 4.45748 12.6671 6.66663V7.88819L13.9528 10.6204C14.0257 10.7753 14.0143 10.9567 13.9226 11.1012C13.8309 11.2457 13.6716 11.3333 13.5004 11.3333H3.83374C3.66257 11.3333 3.50329 11.2457 3.41157 11.1012C3.31986 10.9567 3.30845 10.7753 3.38133 10.6204L4.66707 7.88819V6.66663ZM8.66707 3.66663C7.01022 3.66663 5.66707 5.00978 5.66707 6.66663V7.99996C5.66707 8.07356 5.65082 8.14626 5.61948 8.21286L4.62163 10.3333H12.7125L11.7147 8.21286C11.6833 8.14626 11.6671 8.07356 11.6671 7.99996V6.66663C11.6671 5.00977 10.3239 3.66663 8.66707 3.66663Z"
10+
fill="currentColor"
11+
/>
12+
<path
13+
fillRule="evenodd"
14+
clipRule="evenodd"
15+
d="M7.16706 11.1623C7.16472 10.8881 6.94177 10.6666 6.66707 10.6666C6.39093 10.6666 6.16707 10.8905 6.16707 11.1666H6.66707C6.16707 11.1666 6.16707 11.1663 6.16707 11.1666L6.16708 11.1679L6.16708 11.1693L6.1671 11.1726L6.16723 11.1811C6.16735 11.1876 6.16758 11.1957 6.16796 11.2053C6.16873 11.2246 6.17016 11.2501 6.17281 11.2809C6.17808 11.3424 6.18826 11.4262 6.20803 11.5251C6.24722 11.721 6.32626 11.9871 6.48833 12.2572C6.83186 12.8298 7.49768 13.3333 8.66707 13.3333C9.83646 13.3333 10.5023 12.8298 10.8458 12.2572C11.0079 11.9871 11.0869 11.721 11.1261 11.5251C11.1459 11.4262 11.1561 11.3424 11.1613 11.2809C11.164 11.2501 11.1654 11.2246 11.1662 11.2053C11.1666 11.1957 11.1668 11.1876 11.1669 11.1811L11.167 11.1726L11.1671 11.1693L11.1671 11.1679C11.1671 11.1676 11.1671 11.1666 10.6671 11.1666H11.1671C11.1671 10.8905 10.9432 10.6666 10.6671 10.6666C10.3924 10.6666 10.1694 10.8881 10.1671 11.1623"
16+
fill="currentColor"
17+
/>
18+
</SvgIcon>
19+
));
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import SvgIcon from '@material-ui/core/SvgIcon';
3+
4+
export default React.memo(props => (
5+
<SvgIcon {...props} width="32" height="32" viewBox="0 0 32 32" fill="none">
6+
<path
7+
fillRule="evenodd"
8+
clipRule="evenodd"
9+
d="M5.33337 6.33331C5.33337 5.78103 5.78109 5.33331 6.33337 5.33331H9.33337C9.79224 5.33331 10.1922 5.64561 10.3035 6.09078L11.1142 9.33331H25.6667C25.9722 9.33331 26.261 9.47296 26.4506 9.71244C26.6403 9.95193 26.7101 10.265 26.6401 10.5624L24.4572 19.8398C24.0674 21.496 22.5896 22.6666 20.888 22.6666H15.386C13.7223 22.6666 12.2671 21.5465 11.8414 19.9383L9.36724 10.5914C9.36614 10.5872 9.36507 10.5831 9.36402 10.579C9.36376 10.5779 9.36349 10.5769 9.36323 10.5758L8.5526 7.33331H6.33337C5.78109 7.33331 5.33337 6.8856 5.33337 6.33331ZM11.6325 11.3333L13.7748 19.4265C13.7748 19.4265 13.7748 19.4265 13.7748 19.4265C13.9683 20.1575 14.6298 20.6666 15.386 20.6666H20.888C21.6614 20.6666 22.3332 20.1346 22.5104 19.3817L24.4041 11.3333H11.6325Z"
10+
fill="currentColor"
11+
/>
12+
<path
13+
d="M13.3334 26.6666C14.0698 26.6666 14.6667 26.0697 14.6667 25.3333C14.6667 24.5969 14.0698 24 13.3334 24C12.597 24 12 24.5969 12 25.3333C12 26.0697 12.597 26.6666 13.3334 26.6666Z"
14+
fill="currentColor"
15+
/>
16+
<path
17+
d="M22.6667 26.6666C23.4031 26.6666 24 26.0697 24 25.3333C24 24.5969 23.4031 24 22.6667 24C21.9303 24 21.3334 24.5969 21.3334 25.3333C21.3334 26.0697 21.9303 26.6666 22.6667 26.6666Z"
18+
fill="currentColor"
19+
/>
20+
</SvgIcon>
21+
));

newIDE/app/src/UI/CustomSvgIcons/CoinOutline.js

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)