unread count 관련 처리.

This commit is contained in:
leejh 2019-10-30 16:22:49 +09:00
parent d099bcb035
commit 33e6566eff
11 changed files with 181 additions and 55 deletions

View File

@ -137,7 +137,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked {
this.userInfoListSubscription = this.store this.userInfoListSubscription = this.store
.pipe( .pipe(
select(AppStore.MessengerSelector.RoomSelector.userInfoList), select(AppStore.MessengerSelector.RoomSelector.selectUserinfolist),
tap(userInfoList => { tap(userInfoList => {
this.userInfoList = userInfoList; this.userInfoList = userInfoList;
}) })

View File

@ -154,11 +154,7 @@ export class AppNotificationService {
'Notification::eventProtocolService::ReadNotification', 'Notification::eventProtocolService::ReadNotification',
noti noti
); );
this.store.dispatch( this.store.dispatch(EventStore.readNotification(noti));
EventStore.readNotification({
noti
})
);
} }
break; break;
case SSVC_TYPE_EVENT_CANCEL_NOTI: case SSVC_TYPE_EVENT_CANCEL_NOTI:

View File

@ -133,7 +133,7 @@ export const readFailure = createAction(
export const readNotification = createAction( export const readNotification = createAction(
'[Messenger::Event] Read Notification', '[Messenger::Event] Read Notification',
props<{ noti: ReadNotification }>() props<ReadNotification>()
); );
/** 대화 회수 */ /** 대화 회수 */

View File

@ -32,10 +32,14 @@ import {
ReadResponse, ReadResponse,
DelResponse, DelResponse,
CancelResponse, CancelResponse,
EventType EventType,
ReadNotification,
SSVC_TYPE_EVENT_SEND_RES,
SSVC_TYPE_EVENT_SEND_NOTI
} from '@ucap-webmessenger/protocol-event'; } from '@ucap-webmessenger/protocol-event';
import * as ChatStore from '@app/store/messenger/chat'; import * as ChatStore from '@app/store/messenger/chat';
import * as RoomStore from '@app/store/messenger/room';
import * as SyncStore from '@app/store/messenger/sync'; import * as SyncStore from '@app/store/messenger/sync';
import { import {
@ -149,7 +153,7 @@ export class Effects {
exhaustMap(req => exhaustMap(req =>
this.eventProtocolService.read(req).pipe( this.eventProtocolService.read(req).pipe(
map((res: ReadResponse) => { map((res: ReadResponse) => {
return readNotification({ noti: res }); return readNotification(res as ReadNotification);
}), }),
catchError(error => of(readFailure({ error }))) catchError(error => of(readFailure({ error })))
) )
@ -161,14 +165,33 @@ export class Effects {
() => { () => {
return this.actions$.pipe( return this.actions$.pipe(
ofType(readNotification), ofType(readNotification),
map(action => action.noti), withLatestFrom(
tap(noti => { this.store.pipe(
this.store.dispatch( select((state: any) => state.messenger.room.roomInfo as RoomInfo)
SyncStore.updateUnreadCount({ ),
roomSeq: noti.roomSeq, this.store.pipe(
noReadCnt: 0 select(
}) (state: any) =>
); state.account.authentication.loginRes as LoginResponse
)
)
),
// map(([action, roomInfo]) => ([action.noti, roomInfo])),
tap(([action, roomInfo, loginRes]) => {
// 현재 오픈된 방에 대한 read noti/res 가 유입될 경우 roomUserList 의 lastReadSeq 를 갱신한다.
if (roomInfo.roomSeq === action.roomSeq) {
this.store.dispatch(RoomStore.updateRoomUserLastReadSeq(action));
}
// noti/res 를 일으킨 주체가 본인이라면 현재 오픈된 방 여부에 상관없이 해당방에 대한 noReadCnt 를 초기화 한다.
if (action.SENDER_SEQ === loginRes.userSeq) {
this.store.dispatch(
SyncStore.updateUnreadCount({
roomSeq: action.roomSeq,
noReadCnt: 0
})
);
}
}) })
); );
}, },
@ -376,12 +399,24 @@ export class Effects {
// opened room :: event add // opened room :: event add
if (!!roomInfo && roomInfo.roomSeq === action.roomSeq) { if (!!roomInfo && roomInfo.roomSeq === action.roomSeq) {
this.store.dispatch(appendInfoList({ info: action.info })); this.store.dispatch(appendInfoList({ info: action.info }));
if (
action.SVC_TYPE === SVC_TYPE_EVENT &&
action.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI
) {
this.store.dispatch(
read({
roomSeq: action.roomSeq,
lastReadSeq: action.info.seq
})
);
}
} }
// not opened room :: unread count increased // not opened room :: unread count increased
if ( if (
action.SVC_TYPE === SVC_TYPE_EVENT && action.SVC_TYPE === SVC_TYPE_EVENT &&
action.SSVC_TYPE === SSVC_TYPE_EVENT_INFO_RES action.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_RES
) { ) {
/** /**
* RES noti . * RES noti .

View File

@ -15,6 +15,7 @@ import {
ExitRequest, ExitRequest,
ExitResponse ExitResponse
} from '@ucap-webmessenger/protocol-room'; } from '@ucap-webmessenger/protocol-room';
import { ReadNotification } from '@ucap-webmessenger/protocol-event';
export const info = createAction( export const info = createAction(
'[Messenger::Room] Info', '[Messenger::Room] Info',
@ -60,6 +61,11 @@ export const updateOnlyAlarm = createAction(
props<{ roomInfo: RoomInfo }>() props<{ roomInfo: RoomInfo }>()
); );
export const updateRoomUserLastReadSeq = createAction(
'[Messenger::Room] Update Room User Only LastReadSeq',
props<ReadNotification>()
);
export const update = createAction( export const update = createAction(
'[Messenger::Room] Update', '[Messenger::Room] Update',
props<{ req: UpdateRequest }>() props<{ req: UpdateRequest }>()

View File

@ -1,8 +1,13 @@
import { createReducer, on } from '@ngrx/store'; import { createReducer, on } from '@ngrx/store';
import { initialState } from './state'; import { initialState, adapterUserInfo, adapterUserInfoShort } from './state';
import { infoSuccess, updateSuccess } from './actions'; import {
infoSuccess,
updateSuccess,
updateRoomUserLastReadSeq
} from './actions';
import * as AuthenticationStore from '@app/store/account/authentication'; import * as AuthenticationStore from '@app/store/account/authentication';
import { UserInfo } from '@ucap-webmessenger/protocol-room';
export const reducer = createReducer( export const reducer = createReducer(
initialState, initialState,
@ -10,8 +15,12 @@ export const reducer = createReducer(
return { return {
...state, ...state,
roomInfo: action.roomInfo, roomInfo: action.roomInfo,
userInfoList: action.userInfoList, userInfoList: adapterUserInfo.addAll(action.userInfoList, {
userInfoShortList: action.userInfoShortList ...state.userInfoList
}),
userInfoShortList: adapterUserInfoShort.addAll(action.userInfoShortList, {
...state.userInfoShortList
})
}; };
}), }),
@ -26,6 +35,23 @@ export const reducer = createReducer(
}; };
}), }),
on(updateRoomUserLastReadSeq, (state, action) => {
const userSeq = action.SENDER_SEQ;
const userInfo: UserInfo = {
...state.userInfoList.entities[userSeq],
lastReadEventSeq: action.lastReadSeq
};
return {
...state,
userInfoList: adapterUserInfo.updateOne(
{ id: userSeq, changes: userInfo },
{ ...state.userInfoList }
)
};
}),
on(AuthenticationStore.logout, (state, action) => { on(AuthenticationStore.logout, (state, action) => {
return { return {
...initialState ...initialState

View File

@ -1,3 +1,4 @@
import { EntityState, createEntityAdapter } from '@ngrx/entity';
import { Selector, createSelector } from '@ngrx/store'; import { Selector, createSelector } from '@ngrx/store';
import { import {
InfoRequest, InfoRequest,
@ -6,31 +7,71 @@ import {
UserInfo UserInfo
} from '@ucap-webmessenger/protocol-room'; } from '@ucap-webmessenger/protocol-room';
export interface UserInfoShortListState extends EntityState<UserInfoShort> {}
export interface UserInfoListState extends EntityState<UserInfo> {}
export interface State { export interface State {
roomInfo: RoomInfo | null; roomInfo: RoomInfo | null;
userInfoShortList: UserInfoShort[] | null; userInfoShortList: UserInfoShortListState;
userInfoList: UserInfo[] | null; userInfoList: UserInfoListState;
} }
export const adapterUserInfoShort = createEntityAdapter<UserInfoShort>({
selectId: userInfo => userInfo.seq
});
export const adapterUserInfo = createEntityAdapter<UserInfo>({
selectId: userInfo => userInfo.seq
});
const userInfoShortListInitialState: UserInfoShortListState = adapterUserInfoShort.getInitialState(
{}
);
const userInfoListInitialState: UserInfoListState = adapterUserInfo.getInitialState(
{}
);
export const initialState: State = { export const initialState: State = {
roomInfo: null, roomInfo: null,
userInfoShortList: null, userInfoShortList: userInfoShortListInitialState,
userInfoList: null userInfoList: userInfoListInitialState
}; };
const {
selectAll: ngeSelectAllUserInfoShort,
selectEntities: ngeSelectEntitiesUserInfoShort,
selectIds: ngeSelectIdsUserInfoShort,
selectTotal: ngeSelectTotalUserInfoShort
} = adapterUserInfoShort.getSelectors();
const {
selectAll: ngeSelectAllUserInfo,
selectEntities: ngeSelectEntitiesUserInfo,
selectIds: ngeSelectIdsUserInfo,
selectTotal: ngeSelectTotalUserInfo
} = adapterUserInfo.getSelectors();
export function selectors<S>(selector: Selector<any, State>) { export function selectors<S>(selector: Selector<any, State>) {
const selectUserInfoShortList = createSelector(
selector,
(state: State) => state.userInfoShortList
);
const selectUserInfoList = createSelector(
selector,
(state: State) => state.userInfoList
);
return { return {
roomInfo: createSelector( roomInfo: createSelector(
selector, selector,
(state: State) => state.roomInfo (state: State) => state.roomInfo
), ),
userInfoShortList: createSelector( selectUserInfoShortList: createSelector(
selector, selectUserInfoShortList,
(state: State) => state.userInfoShortList ngeSelectAllUserInfoShort
), ),
userInfoList: createSelector( selectUserinfolist: createSelector(
selector, selectUserInfoList,
(state: State) => state.userInfoList ngeSelectAllUserInfo
) )
}; };
} }

View File

@ -8,7 +8,6 @@ import {
} from '@ucap-webmessenger/protocol-sync'; } from '@ucap-webmessenger/protocol-sync';
import { import {
RoomInfo, RoomInfo,
UserInfoShort,
UserInfo as RoomUserInfo UserInfo as RoomUserInfo
} from '@ucap-webmessenger/protocol-room'; } from '@ucap-webmessenger/protocol-room';

View File

@ -146,7 +146,12 @@
</div> </div>
<div class="time secondary-text"> <div class="time secondary-text">
{{ message.sendDate | date: 'a hh:mm' }} <ul>
<li>{{ getUnreadCount(message) }}</li>
<li>
{{ message.sendDate | date: 'a hh:mm' }}
</li>
</ul>
</div> </div>
</ng-template> </ng-template>
</div> </div>

View File

@ -140,23 +140,23 @@ $meBox-bg: #ffffff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.information-msg{ .information-msg {
width:100%; width: 100%;
height:100%; height: 100%;
text-align:center; text-align: center;
background-color:#cccccc; background-color: #cccccc;
padding:10px; padding: 10px;
margin:10px 0; margin: 10px 0;
} }
.message-row { .message-row {
margin-bottom: 30px; margin-bottom: 30px;
.date-splitter{ .date-splitter {
display:block; display: block;
width:100%; width: 100%;
margin-bottom:10px; margin-bottom: 10px;
} }
.chat-row{ .chat-row {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -165,14 +165,14 @@ $meBox-bg: #ffffff;
} }
} }
&.me { &.me {
.chat-row{ .chat-row {
flex-direction: row-reverse; flex-direction: row-reverse;
.profile-info { .profile-info {
flex-direction: row-reverse; flex-direction: row-reverse;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
} }
} }
} }
.message-main { .message-main {
@ -219,10 +219,15 @@ $meBox-bg: #ffffff;
.secondary-text { .secondary-text {
align-self: flex-end; align-self: flex-end;
font-size: 11px; font-size: 11px;
color:#666666; color: #666666;
word-wrap: break-word; word-wrap: break-word;
white-space: nowrap; white-space: nowrap;
} }
&.me {
.secondary-text {
text-align: end;
}
}
} }
.message-row.me > .bubble { .message-row.me > .bubble {
@ -267,4 +272,4 @@ $meBox-bg: #ffffff;
top: 5px; top: 5px;
} }
} }
} }

View File

@ -12,10 +12,8 @@ import {
EventType, EventType,
InfoResponse InfoResponse
} from '@ucap-webmessenger/protocol-event'; } from '@ucap-webmessenger/protocol-event';
import { import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
LoginResponse, import { UserInfo } from '@ucap-webmessenger/protocol-room';
UserInfo
} from '@ucap-webmessenger/protocol-authentication';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import { FileInfo } from '../models/file-info.json'; import { FileInfo } from '../models/file-info.json';
@ -61,6 +59,9 @@ export class MessagesComponent implements OnInit {
this.profileImageRoot || this.sessionVerInfo.profileRoot; this.profileImageRoot || this.sessionVerInfo.profileRoot;
} }
/**
* UserInfo getter
*/
getUserName(seq: number): string { getUserName(seq: number): string {
if (!this.userInfos) { if (!this.userInfos) {
return ''; return '';
@ -88,6 +89,17 @@ export class MessagesComponent implements OnInit {
return ''; return '';
} }
getUnreadCount(message: Info): string | number {
const unreadCnt = this.userInfos.filter(user => {
if (message.senderSeq === user.seq) {
// 본인 글은 unreadCount 에 포함하지 않는다.
return false;
}
return user.lastReadEventSeq < message.seq;
}).length;
return unreadCnt === 0 ? '' : unreadCnt;
}
/** /**
* Event . * Event .
* @description event , . * @description event , .
@ -106,7 +118,7 @@ export class MessagesComponent implements OnInit {
return false; return false;
} }
/** Date Splitter check */ /** Date Splitter show check */
getDateSplitter(curIndex: number): boolean { getDateSplitter(curIndex: number): boolean {
if (curIndex === 0) { if (curIndex === 0) {
return true; return true;
@ -141,6 +153,7 @@ export class MessagesComponent implements OnInit {
this.save.emit(value); this.save.emit(value);
} }
/** [Event] Context Menu */
onContextMenuMessage(event: MouseEvent, message: Info) { onContextMenuMessage(event: MouseEvent, message: Info) {
this.contextMenu.emit({ event, message }); this.contextMenu.emit({ event, message });
} }