Skip to content

Commit

Permalink
[Issue-2505] feat: subscribe unread notification count
Browse files Browse the repository at this point in the history
  • Loading branch information
bluezdot committed Sep 28, 2024
1 parent 441b157 commit b44e2ed
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 22 deletions.
12 changes: 5 additions & 7 deletions packages/extension-base/src/background/KoniTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1842,12 +1842,6 @@ export enum NotificationType {
ERROR = 'error',
}

export enum NotificationTransactionType {
SEND = 'send',
RECEIVE = 'receive',
WITHDRAW = 'withdraw',
}

export interface NotificationButton {
title: string;
}
Expand Down Expand Up @@ -2449,8 +2443,12 @@ export interface KoniRequestSignatures {
'pri(swapService.validateSwapProcess)': [ValidateSwapProcessParams, TransactionError[]];
/* Swap */

/* Ledger */
/* Notification Service */
// 'pri(notifications.getSubscription': [null, NotificationInfo[], NotificationInfo[]];
'pri(unreadNotificationCount.getSubscription)': [null, number, number];
/* Notification Service */

/* Ledger */
'pri(ledger.generic.allow)': [null, string[], string[]];
}

Expand Down
49 changes: 49 additions & 0 deletions packages/extension-base/src/koni/background/handlers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4522,6 +4522,48 @@ export default class KoniExtension {
}
/* Swap service */

/* Notification service */
// private subscribeNotification (id: string, port: chrome.runtime.Port): Promise<NotificationInfo[]> {
// const cb = createSubscription<'pri(notifications.getSubscription)'>(id, port);
// const notificationSubscription = this.#koniState.subscribeNotification().subscribe({
// next: (rs) => {
// cb(rs);
// }
// });
//
// this.createUnsubscriptionHandle(id, nftCollectionSubscription.unsubscribe);
//
// port.onDisconnect.addListener((): void => {
// this.cancelSubscription(id);
// });
//
// return this.getNftCollection();
// }

private subscribeUnreadNotificationCount (id: string, port: chrome.runtime.Port): number {
const cb = createSubscription<'pri(unreadNotificationCount.getSubscription)'>(id, port);
let ready = false;

const callback = (rs: number) => {
if (ready) {
cb(rs);
}
};

const subscription = this.#koniState.inappNotificationService.subscribeUnreadNotificationCount(callback);

this.createUnsubscriptionHandle(id, subscription.unsubscribe);

port.onDisconnect.addListener((): void => {
this.cancelSubscription(id);
});

ready = true;

return this.#koniState.inappNotificationService.getUnreadNotificationCount();
}
/* Notification service */

/* Ledger */

private async subscribeLedgerGenericAllowChains (id: string, port: chrome.runtime.Port): Promise<string[]> {
Expand Down Expand Up @@ -5139,6 +5181,13 @@ export default class KoniExtension {
return this.handleSwapStep(request as SwapSubmitParams);
/* Swap service */

/* Notification service */
// case 'pri(notifications.getSubscription)':
// return await this.subscribeNotification(id, port);
case 'pri(unreadNotificationCount.getSubscription)':
return this.subscribeUnreadNotificationCount(id, port);
/* Notification service */

/* Ledger */
case 'pri(ledger.generic.allow)':
return this.subscribeLedgerGenericAllowChains(id, port);
Expand Down
10 changes: 10 additions & 0 deletions packages/extension-base/src/koni/background/handlers/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ export default class KoniState {
data: {}
} as StakingRewardJson;

private unreadNotificationCount = new Subject<number>();

private lazyMap: Record<string, unknown> = {};

readonly notificationService: NotificationService;
Expand Down Expand Up @@ -578,6 +580,14 @@ export default class KoniState {
return this.nftSubject;
}

// public subscribeNotification () {
// return this.notificationSubject;
// }

public subscribeUnreadNotificationCount () {
return this.unreadNotificationCount;
}

public resetStakingReward () {
this.stakingRewardState.data = {};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { DEFAULT_NOTIFICATION_SETTING, NotificationTitleMap } from '@subwallet/e
import { NotificationInfo, NotificationOptions, NotificationSetting, NotificationTimePeriod, NotificationTransactionType } from '@subwallet/extension-base/services/inapp-notification-service/interfaces';
import DatabaseService from '@subwallet/extension-base/services/storage-service/DatabaseService';
import { UnstakingStatus, YieldPoolType } from '@subwallet/extension-base/types';
import { BehaviorSubject } from 'rxjs';

export class InappNotificationService implements CronServiceInterface {
status: ServiceStatus;
private notificationSetting: NotificationSetting = DEFAULT_NOTIFICATION_SETTING;
private refreshTimeout: NodeJS.Timeout | undefined;
private readonly dbService: DatabaseService;
private unreadNotificationCountSubject = new BehaviorSubject<number>(0);

constructor (dbService: DatabaseService) {
this.status = ServiceStatus.NOT_INITIALIZED;
Expand Down Expand Up @@ -80,6 +82,7 @@ export class InappNotificationService implements CronServiceInterface {
this.getWithdrawNotifications()
.then(async (notifications) => {
await this.dbService.upsertNotifications(notifications);
await this.updateUnreadNotificationCount();
})
.catch((e) => {
console.error(e);
Expand All @@ -88,6 +91,22 @@ export class InappNotificationService implements CronServiceInterface {
this.refreshTimeout = setTimeout(this.updateLastestNotifications.bind(this), CRON_UPDATE_NOTIFICATION_INTERVAL);
}

private async updateUnreadNotificationCount () {
const unreadNotificationCount = await this.dbService.getAllUnreadNotifications();

this.unreadNotificationCountSubject.next(unreadNotificationCount);
}

public subscribeUnreadNotificationCount (callback: (data: number) => void) {
return this.unreadNotificationCountSubject.subscribe({
next: callback
});
}

public getUnreadNotificationCount () {
return this.unreadNotificationCountSubject.getValue();
}

async start (): Promise<void> {
if (this.status === ServiceStatus.STARTED) {
return;
Expand All @@ -102,7 +121,7 @@ export class InappNotificationService implements CronServiceInterface {
}
}

startCron (): Promise<void> {
async startCron (): Promise<void> {
this.updateLastestNotifications();

return Promise.resolve();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,10 @@ export default class DatabaseService {
return this.stores.inappNotification.getAll();
}

public getAllUnreadNotifications () {
return this.stores.inappNotification.getAllUnreadNotifications();
}

public upsertNotifications (notifications: NotificationInfo[]) {
return this.stores.inappNotification.bulkUpsert(notifications);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ export default class InappNotificationStore extends BaseStore<NotificationInfo>
async getAll () {
return this.table.toArray();
}

getAllUnreadNotifications () {
return this.table.filter((item) => !item.isRead).count();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2019-2022 @polkadot/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { NotificationTransactionType } from '@subwallet/extension-base/background/KoniTypes';
import { NotificationTimePeriod, NotificationTransactionType } from '@subwallet/extension-base/services/inapp-notification-service/interfaces';
import { PageWrapper, RadioGroup } from '@subwallet/extension-koni-ui/components';
import { useDefaultNavigate } from '@subwallet/extension-koni-ui/hooks';
import { Theme, ThemeProps } from '@subwallet/extension-koni-ui/types';
Expand All @@ -14,15 +14,9 @@ import styled, { useTheme } from 'styled-components';

type Props = ThemeProps;

enum ViewValue {
TODAY = 'today',
THIS_WEEK = 'this_week',
THIS_MONTH = 'this_month',
}

interface ViewOption {
label: string;
value: ViewValue;
value: NotificationTimePeriod;
}

function Component ({ className = '' }: Props): React.ReactElement<Props> {
Expand All @@ -38,22 +32,23 @@ function Component ({ className = '' }: Props): React.ReactElement<Props> {
const notificationOptions = [
{ label: t('Hide send notifications'), value: NotificationTransactionType.SEND },
{ label: t('Hide receive notifications'), value: NotificationTransactionType.RECEIVE },
{ label: t('Hide withdraw notifications'), value: NotificationTransactionType.WITHDRAW }
{ label: t('Hide withdraw notifications'), value: NotificationTransactionType.WITHDRAW },
{ label: t('Hide claim notifications'), value: NotificationTransactionType.CLAIM }
];

const viewOptions = useMemo((): ViewOption[] => {
return [
{
label: t('Today'),
value: ViewValue.TODAY
value: NotificationTimePeriod.TODAY
},
{
label: t('This week'),
value: ViewValue.THIS_WEEK
value: NotificationTimePeriod.THIS_WEEK
},
{
label: t('This month'),
value: ViewValue.THIS_MONTH
value: NotificationTimePeriod.THIS_MONTH
}
];
}, [t]);
Expand Down
5 changes: 4 additions & 1 deletion packages/extension-koni-ui/src/contexts/DataContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { ping } from '@subwallet/extension-koni-ui/messaging';
import { persistor, store, StoreName } from '@subwallet/extension-koni-ui/stores';
import { getMissionPoolData, subscribeAccountsData, subscribeAddressBook, subscribeAssetLogoMaps, subscribeAssetRegistry, subscribeAssetSettings, subscribeAuthorizeRequests, subscribeAuthUrls, subscribeBalance, subscribeBuyServices, subscribeBuyTokens, subscribeCampaignBannerData, subscribeCampaignConfirmationData, subscribeCampaignPopupData, subscribeCampaignPopupVisibility, subscribeChainInfoMap, subscribeChainLogoMaps, subscribeChainStakingMetadata, subscribeChainStateMap, subscribeChainStatusMap, subscribeConfirmationRequests, subscribeConnectWCRequests, subscribeCrowdloan, subscribeKeyringState, subscribeLedgerGenericAllowNetworks, subscribeMantaPayConfig, subscribeMantaPaySyncingState, subscribeMetadataRequests, subscribeMultiChainAssetMap, subscribeNftCollections, subscribeNftItems, subscribePrice, subscribeProcessingCampaign, subscribeRewardHistory, subscribeSigningRequests, subscribeStaking, subscribeStakingNominatorMetadata, subscribeStakingReward, subscribeSwapPairs, subscribeTransactionRequests, subscribeTxHistory, subscribeUiSettings, subscribeWalletConnectSessions, subscribeWCNotSupportRequests, subscribeXcmRefMap, subscribeYieldMinAmountPercent, subscribeYieldPoolInfo, subscribeYieldPositionInfo, subscribeYieldReward } from '@subwallet/extension-koni-ui/stores/utils';
import { getMissionPoolData, subscribeAccountsData, subscribeAddressBook, subscribeAssetLogoMaps, subscribeAssetRegistry, subscribeAssetSettings, subscribeAuthorizeRequests, subscribeAuthUrls, subscribeBalance, subscribeBuyServices, subscribeBuyTokens, subscribeCampaignBannerData, subscribeCampaignConfirmationData, subscribeCampaignPopupData, subscribeCampaignPopupVisibility, subscribeChainInfoMap, subscribeChainLogoMaps, subscribeChainStakingMetadata, subscribeChainStateMap, subscribeChainStatusMap, subscribeConfirmationRequests, subscribeConnectWCRequests, subscribeCrowdloan, subscribeKeyringState, subscribeLedgerGenericAllowNetworks, subscribeMantaPayConfig, subscribeMantaPaySyncingState, subscribeMetadataRequests, subscribeMultiChainAssetMap, subscribeNftCollections, subscribeNftItems, subscribePrice, subscribeProcessingCampaign, subscribeRewardHistory, subscribeSigningRequests, subscribeStaking, subscribeStakingNominatorMetadata, subscribeStakingReward, subscribeSwapPairs, subscribeTransactionRequests, subscribeTxHistory, subscribeUiSettings, subscribeUnreadNotificationCount, subscribeWalletConnectSessions, subscribeWCNotSupportRequests, subscribeXcmRefMap, subscribeYieldMinAmountPercent, subscribeYieldPoolInfo, subscribeYieldPositionInfo, subscribeYieldReward } from '@subwallet/extension-koni-ui/stores/utils';
import Bowser from 'bowser';
import React from 'react';
import { Provider } from 'react-redux';
Expand Down Expand Up @@ -259,6 +259,9 @@ export const DataContextProvider = ({ children }: DataContextProviderProps) => {
// Swap
_DataContext.addHandler({ ...subscribeSwapPairs, name: 'subscribeSwapPairs', relatedStores: ['swap'] });

// Notification
_DataContext.addHandler({ ...subscribeUnreadNotificationCount, name: 'subscribeNotifications', relatedStores: ['notification'], isStartImmediately: true });

return <Provider store={store}>
<PersistGate persistor={persistor}>
<DataContext.Provider value={_DataContext}>
Expand Down
38 changes: 38 additions & 0 deletions packages/extension-koni-ui/src/stores/feature/Notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2019-2022 @polkadot/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { NotificationInfo } from '@subwallet/extension-base/services/inapp-notification-service/interfaces';
import { NotificationStore, ReduxStatus } from '@subwallet/extension-koni-ui/stores/types';

const initialState: NotificationStore = {
notifications: [],
reduxStatus: ReduxStatus.INIT,
unreadNotificationCount: 0
};

const notificationSlice = createSlice({
initialState,
name: 'notification',
reducers: {
updateNotification (state, action: PayloadAction<NotificationInfo[]>): NotificationStore {
const payload = action.payload;

return {
...state,
notifications: payload,
reduxStatus: ReduxStatus.READY
};
},
updateUnreadNotificationCount (state, action: PayloadAction<number>): NotificationStore {
return {
...state,
unreadNotificationCount: action.payload,
reduxStatus: ReduxStatus.READY
};
}
}
});

export const { updateNotification, updateUnreadNotificationCount } = notificationSlice.actions;
export default notificationSlice.reducer;
6 changes: 5 additions & 1 deletion packages/extension-koni-ui/src/stores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import EarningReducer from './feature/Earning';
import MantaPayReducer from './feature/MantaPay';
import MissionPoolReducer from './feature/MissionPool';
import NftReducer from './feature/Nft';
import NotificationReducer from './feature/Notification';
import PriceReducer from './feature/Price';
import StakingReducer from './feature/Staking';
import SwapReducer from './feature/Swap';
Expand Down Expand Up @@ -71,7 +72,10 @@ const rootReducers = combineReducers({
walletConnect: WalletConnectReducer,

// mission pool
missionPool: MissionPoolReducer
missionPool: MissionPoolReducer,

// inapp notification
notification: NotificationReducer
});

const persistedReducer = persistReducer(persistConfig, rootReducers);
Expand Down
6 changes: 6 additions & 0 deletions packages/extension-koni-ui/src/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AuthUrlInfo } from '@subwallet/extension-base/background/handlers/State
import { AddressBookState, AllLogoMap, AssetSetting, CampaignBanner, ChainStakingMetadata, ConfirmationDefinitions, ConfirmationsQueue, ConfirmationType, CrowdloanItem, KeyringState, LanguageType, MantaPayConfig, NftCollection, NftItem, NominatorMetadata, PriceJson, StakingItem, StakingRewardItem, TransactionHistoryItem, UiSettings, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes';
import { AccountJson, AccountsContext, AuthorizeRequest, MetadataRequest, SigningRequest } from '@subwallet/extension-base/background/types';
import { _ChainApiStatus, _ChainState } from '@subwallet/extension-base/services/chain-service/types';
import { NotificationInfo } from '@subwallet/extension-base/services/inapp-notification-service/interfaces';
import { AppBannerData, AppConfirmationData, AppPopupData } from '@subwallet/extension-base/services/mkt-campaign-service/types';
import { SWTransactionResult } from '@subwallet/extension-base/services/transaction-service/types';
import { WalletConnectNotSupportRequest, WalletConnectSessionRequest } from '@subwallet/extension-base/services/wallet-connect-service/types';
Expand Down Expand Up @@ -211,3 +212,8 @@ export interface MissionPoolStore extends BaseReduxStore {
export interface SwapStore extends BaseReduxStore {
swapPairs: SwapPair[];
}

export interface NotificationStore extends BaseReduxStore {
notifications: NotificationInfo[],
unreadNotificationCount: number
}
14 changes: 14 additions & 0 deletions packages/extension-koni-ui/src/stores/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,17 @@ export const updateLedgerGenericAllowNetworks = (data: string[]) => {

export const subscribeLedgerGenericAllowNetworks = lazySubscribeMessage('pri(ledger.generic.allow)', null, updateLedgerGenericAllowNetworks, updateLedgerGenericAllowNetworks);
/* Ledger */

/* Notification service */
export const updateUnreadNotiCount = (data: number) => {
store.dispatch({ type: 'notification/updateUnreadNotificationCount', payload: data });
};

export const subscribeUnreadNotificationCount = lazySubscribeMessage('pri(unreadNotificationCount.getSubscription)', null, updateUnreadNotiCount, updateUnreadNotiCount);

// export const updateNotifications = (data: NotificationInfo[]) => {
// store.dispatch({ type: 'notifications/updateNotifications', payload: data });
// };

// export const subscribeNotifications = lazySubscribeMessage('pri(notifications.getSubscription)', null, updateNotifications, updateNotifications);
/* Notification service */

0 comments on commit b44e2ed

Please sign in to comment.