Skip to content

Commit ceffdbe

Browse files
committed
Merge pull request #146 from lewisl9029/feature-unread-message-notifications
Feature unread message notifications. Fixed #114. 121
2 parents bc4b624 + 3a7af6f commit ceffdbe

File tree

10 files changed

+141
-45
lines changed

10 files changed

+141
-45
lines changed

app/components/channel-list/channel-list.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ <h2>{{channelList.userInfo.displayName}}</h2>
5555
channelList.contacts[channel.channelInfo.contactIds[0]].statusId === undefined
5656
}"></div>
5757
<ion-item class="item item-avatar"
58-
ng-class="{'item-button-right': channel.channelInfo.pendingHandshake}">
58+
ng-class="{'item-button-right': channel.channelInfo.pendingAccept}">
5959
<img src="http://cdn.libravatar.org/avatar/{{channelList.contacts[channel.channelInfo.contactIds[0]].userInfo.id | limitTo:32}}?d=identicon" alt="Avatar for {{channelList.contacts[channel.channelInfo.contactIds[0]].userInfo.email}}" />
6060
<h2>{{channelList.contacts[channel.channelInfo.contactIds[0]].userInfo.displayName}}</h2>
6161
<p>{{channelList.contacts[channel.channelInfo.contactIds[0]].userInfo.id}}</p>
62-
<div class="buttons" ng-if="channel.channelInfo.pendingHandshake">
62+
<div class="buttons" ng-if="channel.channelInfo.pendingAccept">
6363
<!-- FIXME: spinner not showing here -->
6464
<button class="button button-balanced button-clear"
6565
ng-click="channelList.acceptInvite(channel.channelInfo);

app/components/components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
@import 'header/header';
22
@import 'spinner-button/spinner-button';
3+
@import 'message-list/message-list';

app/components/message-list/message-list-directive.js

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import template from './message-list.html!text';
22

3-
export default function tocMessageList(network, $ionicScrollDelegate) {
3+
export default function tocMessageList(network, state, R,
4+
$ionicScrollDelegate) {
45
return {
56
restrict: 'E',
67
template: template,
@@ -21,47 +22,66 @@ export default function tocMessageList(network, $ionicScrollDelegate) {
2122
}
2223

2324
$ionicScrollDelegate.scrollBottom(true);
25+
26+
let messages = messagesCursor.get();
27+
28+
R.pipe(
29+
R.values,
30+
//FIXME: figure out why R.not doesnt work here
31+
// R.filter(R.pipe(R.prop('isRead'), R.not),
32+
R.filter(R.pipe(R.prop('isRead'), (bool) => !bool)),
33+
R.forEach((message) => state.save(
34+
messagesCursor,
35+
[message.messageInfo.id, 'isRead'],
36+
true
37+
))
38+
)(messages);
2439
});
2540
},
2641
controllerAs: 'messageList',
2742
controller: function MessageListController($scope, $state, identity,
28-
contacts, network, R) {
43+
contacts, $interval) {
2944
let channelsCursor = network.NETWORK_CURSORS.synchronized
3045
.select('channels');
46+
3147
let contactsCursor = contacts.CONTACTS_CURSORS.synchronized;
3248
let identityCursor = identity.IDENTITY_CURSORS.synchronized;
3349

34-
let messagesCursor = channelsCursor.select([
35-
$scope.channelId,
36-
'messages'
37-
]);
50+
let messagesCursor = network.NETWORK_CURSORS.synchronized
51+
.select(['channels', $scope.channelId, 'messages']);
3852

3953
this.contacts = contactsCursor.get();
4054
this.userInfo = identityCursor.get(['userInfo']);
4155

4256
let getMessageList = function getMessageList(messages) {
4357
return R.pipe(
4458
R.values,
45-
R.map((message) => message.messageInfo),
4659
R.sort((message1, message2) => {
47-
if (message1.logicalClock === message2.logicalClock) {
48-
return message1.id > message2.id ? 1 : -1;
60+
if (message1.messageInfo.logicalClock ===
61+
message2.messageInfo.logicalClock) {
62+
return message1.messageInfo.id > message2.messageInfo.id ?
63+
1 : -1;
4964
}
5065

51-
return message1.logicalClock > message2.logicalClock ? 1 : -1;
66+
return message1.messageInfo.logicalClock >
67+
message2.messageInfo.logicalClock ?
68+
1 : -1;
5269
}),
5370
R.reduce((groupedMessages, message) => {
71+
let messageVm = message.messageInfo;
72+
messageVm.isRead = message.isRead;
73+
5474
if (groupedMessages.length === 0) {
55-
groupedMessages.push([message]);
75+
groupedMessages.push([messageVm]);
5676
return groupedMessages;
5777
}
5878

5979
let latestGroup = groupedMessages[groupedMessages.length - 1];
6080

61-
if (latestGroup[0].sender === message.sender) {
62-
latestGroup.push(message);
81+
if (latestGroup[0].sender === messageVm.sender) {
82+
latestGroup.push(messageVm);
6383
} else {
64-
groupedMessages.push([message]);
84+
groupedMessages.push([messageVm]);
6585
}
6686

6787
return groupedMessages;
@@ -74,6 +94,42 @@ export default function tocMessageList(network, $ionicScrollDelegate) {
7494
messagesCursor.on('update', () => {
7595
this.groupedMessages = getMessageList(messagesCursor.get());
7696
});
97+
98+
//TODO: write a more performant version of this
99+
$interval(() => {
100+
let scrollView = $ionicScrollDelegate.getScrollView();
101+
102+
if (scrollView.__scrollTop !== scrollView.__maxScrollTop) {
103+
if (!channelsCursor.get([$scope.channelId, 'viewingLatest'])) {
104+
return;
105+
}
106+
107+
return state.save(
108+
channelsCursor,
109+
[$scope.channelId, 'viewingLatest'],
110+
false
111+
);
112+
}
113+
114+
let messages = messagesCursor.get();
115+
116+
R.pipe(
117+
R.values,
118+
//FIXME: figure out why R.not doesnt work here
119+
// R.filter(R.pipe(R.prop('isRead'), R.not),
120+
R.filter(R.pipe(R.prop('isRead'), (bool) => !bool)),
121+
R.forEach((message) => state.save(
122+
messagesCursor,
123+
[message.messageInfo.id, 'isRead'],
124+
true
125+
))
126+
)(messages);
127+
128+
if (channelsCursor.get([$scope.channelId, 'viewingLatest'])) {
129+
return;
130+
}
131+
state.save(channelsCursor, [$scope.channelId, 'viewingLatest'], true);
132+
}, 3000);
77133
}
78134
};
79135
}

app/components/message-list/message-list.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ <h2>{{
2424
messageGroup[0].sentTime : messageGroup[0].receivedTime |
2525
date : 'short'
2626
}}</h2>
27-
<p ng-repeat="message in messageGroup track by message.id">
27+
<p ng-repeat="message in messageGroup track by message.id"
28+
class="toc-message"
29+
ng-class="{'toc-unread-message': !message.isRead}">
2830
{{message.content}}
2931
</p>
3032
</div>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
p.toc-message {
2+
white-space: initial;
3+
overflow: initial;
4+
text-overflow: initial;
5+
&.toc-unread-message {
6+
font-weight: bold;
7+
}
8+
}

app/libraries/angular-toastr/angular-toastr.js

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,4 @@ import angular from 'angular';
33
import 'angular-toastr';
44

55
export default angular.module('toc.libraries.angular-toastr', ['toastr'])
6-
.config((toastrConfig) => {
7-
angular.extend(toastrConfig, {
8-
allowHtml: false,
9-
closeButton: false,
10-
closeHtml: '<button><i class="icon ion-close"></i></button>',
11-
containerId: 'toast-container',
12-
extendedTimeOut: 1000,
13-
iconClasses: {
14-
error: 'toast-error ion-close-circled',
15-
info: 'toast-info ion-ios-information',
16-
success: 'toast-success ion-checkmark-circled',
17-
warning: 'toast-warning ion-android-warning'
18-
},
19-
maxOpened: 0,
20-
messageClass: 'toast-message',
21-
newestOnTop: true,
22-
onHidden: null,
23-
onShown: null,
24-
positionClass: 'toast-top-right',
25-
tapToDismiss: true,
26-
target: 'body',
27-
timeOut: 5000,
28-
titleClass: 'toast-title',
29-
toastClass: 'toast'
30-
});
31-
})
326
.factory('angularToastr', (toastr) => toastr);

app/services/network/network-service.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export default function network($q, $window, $interval, R, state, telehash,
5858

5959
let statusId = 1; //online
6060

61-
channel.pendingHandshake = !existingChannel;
61+
channel.pendingAccept = !existingChannel;
6262

6363
return state.save(
6464
NETWORK_CURSORS.synchronized,
@@ -131,7 +131,24 @@ export default function network($q, $window, $interval, R, state, telehash,
131131
channelCursor,
132132
['messages', messageId, 'messageInfo'],
133133
message
134-
));
134+
))
135+
.then(() => {
136+
let activeChannelId =
137+
NETWORK_CURSORS.synchronized.get(['activeChannelId']);
138+
if (activeChannelId === channelId &&
139+
channelCursor.get(['viewingLatest'])) {
140+
return $q.when();
141+
}
142+
143+
let contactName = state.synchronized.tree.get(
144+
['contacts', contactId, 'userInfo', 'displayName']
145+
);
146+
147+
return notification.success(
148+
message.content,
149+
contactName + ' just said:'
150+
);
151+
});
135152
};
136153

137154
let listen = function listen(channelInfo, session = activeSession) {

app/services/notification/notification.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,30 @@ export default angular.module('toc.services.notification', [
1010
angularToastr.name,
1111
state.name
1212
])
13+
.config((toastrConfig) => {
14+
angular.extend(toastrConfig, {
15+
allowHtml: false,
16+
closeButton: false,
17+
closeHtml: '<button><i class="icon ion-close"></i></button>',
18+
containerId: 'toast-container',
19+
extendedTimeOut: 1000,
20+
iconClasses: {
21+
error: 'toast-error ion-close-circled',
22+
info: 'toast-info ion-ios-information',
23+
success: 'toast-success ion-checkmark-circled',
24+
warning: 'toast-warning ion-android-warning'
25+
},
26+
maxOpened: 0,
27+
messageClass: 'toast-message',
28+
newestOnTop: false,
29+
onHidden: null,
30+
onShown: null,
31+
positionClass: 'toast-top-right',
32+
tapToDismiss: true,
33+
target: 'body',
34+
timeOut: 5000,
35+
titleClass: 'toast-title',
36+
toastClass: 'toast'
37+
});
38+
})
1339
.factory(service.name, service);

app/views/channel/channel-controller.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ export default function ChannelController($q, $stateParams, state, contacts,
77

88
let contactCursor = contacts.CONTACTS_CURSORS.synchronized;
99

10+
state.save(
11+
network.NETWORK_CURSORS.synchronized,
12+
['activeChannelId'],
13+
this.channelId
14+
);
15+
1016
this.contact = contactCursor.get(
1117
channelCursor.get(['channelInfo', 'contactIds'])[0]
1218
);

app/views/home/home-controller.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
export default function HomeController(state, identity, network) {
22
let currentUserCursor = identity.IDENTITY_CURSORS.synchronized;
33

4+
state.save(
5+
network.NETWORK_CURSORS.synchronized,
6+
['activeChannelId'],
7+
'home'
8+
);
9+
410
this.currentUser = currentUserCursor.get();
511

612
currentUserCursor.on('update', () => {

0 commit comments

Comments
 (0)