Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions scripts/langindex.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
"addon.messages.contactname": "local_moodlemobileapp",
"addon.messages.contactrequestsent": "message",
"addon.messages.contacts": "message",
"addon.messages.conversationactions": "message",
"addon.messages.decline": "message",
"addon.messages.deleteallconfirm": "message",
"addon.messages.deleteallselfconfirm": "message",
Expand All @@ -198,6 +199,7 @@
"addon.messages.messagepreferences": "message",
"addon.messages.messages": "message",
"addon.messages.muteconversation": "message",
"addon.messages.mutedconversation": "message",
"addon.messages.newmessage": "message",
"addon.messages.newmessages": "local_moodlemobileapp",
"addon.messages.nocontactrequests": "message",
Expand All @@ -216,9 +218,6 @@
"addon.messages.requests": "moodle",
"addon.messages.requirecontacttomessage": "message",
"addon.messages.searchcombined": "message",
"addon.messages.searchnocontactsfound": "message",
"addon.messages.searchnomessagesfound": "message",
"addon.messages.searchnononcontactsfound": "message",
"addon.messages.selfconversation": "message",
"addon.messages.selfconversationdefaultmessage": "message",
"addon.messages.sendcontactrequest": "message",
Expand Down
5 changes: 2 additions & 3 deletions src/addon/messages/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"contactname": "Contact name",
"contactrequestsent": "Contact request sent",
"contacts": "Contacts",
"conversationactions": "Conversation actions menu",
"decline": "Decline",
"deleteallconfirm": "Are you sure you would like to delete this entire conversation? This will not delete it for other conversation participants.",
"deleteallselfconfirm": "Are you sure you would like to delete this entire personal conversation?",
Expand All @@ -37,6 +38,7 @@
"messagepreferences": "Message preferences",
"messages": "Messages",
"muteconversation": "Mute",
"mutedconversation": "Muted conversation",
"newmessage": "New message",
"newmessages": "New messages",
"nocontactrequests": "No contact requests",
Expand All @@ -55,9 +57,6 @@
"requests": "Requests",
"requirecontacttomessage": "You need to request {{$a}} to add you as a contact to be able to message them.",
"searchcombined": "Search people and messages",
"searchnocontactsfound": "No contacts found",
"searchnomessagesfound": "No messages found",
"searchnononcontactsfound": "No non contacts found",
"selfconversation": "Personal space",
"selfconversationdefaultmessage": "Save draft messages, links, notes etc. to access later.",
"sendcontactrequest": "Send contact request",
Expand Down
11 changes: 6 additions & 5 deletions src/addon/messages/pages/discussion/discussion.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title>
<img *ngIf="loaded && !otherMember && conversationImage" class="core-bar-button-image" [src]="conversationImage" alt="" onError="this.src='assets/img/group-avatar.png'" core-external-content role="presentation" [siteId]="siteId || null">
<img *ngIf="loaded && !otherMember && conversationImage" class="core-bar-button-image" [src]="conversationImage" [alt]="title" onError="this.src='assets/img/group-avatar.png'" core-external-content role="presentation" [siteId]="siteId || null">
<ion-avatar *ngIf="loaded && otherMember" class="core-bar-button-image" core-user-avatar [user]="otherMember" [linkProfile]="false" [checkOnline]="otherMember.showonlinestatus" item-start (click)="showInfo && viewInfo()"></ion-avatar>
<core-format-text [text]="title" (click)="showInfo && !isGroup && viewInfo()"></core-format-text>
<core-icon *ngIf="conversation && conversation.isfavourite" name="fa-star"></core-icon>
<core-icon *ngIf="conversation && conversation.ismuted" name="volume-off"></core-icon>
<core-icon *ngIf="conversation && conversation.isfavourite" name="fa-star" [label]="'core.favourites' | translate"></core-icon>
<core-icon *ngIf="conversation && conversation.ismuted" name="volume-off" [label]="'addon.messages.mutedconversation' | translate"></core-icon>
</ion-title>
<ion-buttons end></ion-buttons>
</ion-navbar>
<core-navbar-buttons end>
<core-context-menu>
<core-context-menu [aria-label]="'addon.messages.conversationactions' | translate">
<core-context-menu-item [hidden]="isSelf || !showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()" iconAction="information-circle"></core-context-menu-item>
<core-context-menu-item [hidden]="isSelf || !showInfo || !isGroup" [priority]="1000" [content]="'addon.messages.groupinfo' | translate" (action)="viewInfo()" iconAction="information-circle"></core-context-menu-item>
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversation" [priority]="800" [content]="(conversation && conversation.isfavourite ? 'addon.messages.removefromfavourites' : 'addon.messages.addtofavourites') | translate" (action)="changeFavourite($event)" [closeOnClick]="false" [iconAction]="favouriteIcon"></core-context-menu-item>
Expand Down Expand Up @@ -47,7 +47,7 @@ <h6 text-center *ngIf="message.showDate" class="addon-messages-date">

<ion-item text-wrap (longPress)="copyMessage(message)" class="addon-message" [class.addon-message-mine]="message.useridfrom == currentUserId" [class.addon-message-not-mine]="message.useridfrom != currentUserId" [class.addon-message-no-user]="!message.showUserData" [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
<!-- User data. -->
<h2 class="addon-message-user" >
<h2 class="addon-message-user">
<ion-avatar item-start core-user-avatar [user]="members[message.useridfrom]" [linkProfile]="false" *ngIf="message.showUserData"></ion-avatar>

<div *ngIf="message.showUserData">{{ members[message.useridfrom].fullname }}</div>
Expand All @@ -64,6 +64,7 @@ <h2 class="addon-message-user" >
<button ion-button icon-only clear="true" *ngIf="!message.sending && showDelete" (click)="deleteMessage(message, index)" class="addon-messages-delete-button" [@coreSlideInOut]="'fromRight'" [attr.aria-label]=" 'addon.messages.deletemessage' | translate">
<ion-icon name="trash" color="danger"></ion-icon>
</button>
<div class="tail" *ngIf="message.showTail"></div>
</ion-item>
</ng-container>
</ion-list>
Expand Down
24 changes: 24 additions & 0 deletions src/addon/messages/pages/discussion/discussion.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ ion-app.app-root page-addon-messages-discussion {
min-height: 0;
position: relative;
@include core-transition(width);
// This is needed to display bubble tails.
overflow: visible;
contain: none;

core-format-text > p:only-child {
display: inline;
Expand Down Expand Up @@ -127,6 +130,15 @@ ion-app.app-root page-addon-messages-discussion {
line-height: initial;
}
}

.tail {
content: '';
width: 0;
height: 0;
border: 0.5rem solid transparent;
position: absolute;
touch-action: none;
}
}

.addon-message.addon-message-mine + .addon-message-no-user.addon-message-mine,
Expand Down Expand Up @@ -158,6 +170,18 @@ ion-app.app-root page-addon-messages-discussion {
height: 16px;
}
}

.tail {
@include position(null, 0, 0, null);
@include margin-horizontal(null, -0.5rem);
border-bottom-color: $item-message-mine-bg;
}
}

.addon-message-not-mine .tail {
@include position(null, null, 0, 0);
@include margin-horizontal(-0.5rem, null);
border-bottom-color: $item-message-bg;
}

.toolbar-title {
Expand Down
12 changes: 12 additions & 0 deletions src/addon/messages/pages/discussion/discussion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.messages.forEach((message, index): any => {
message.showDate = this.showDate(message, this.messages[index - 1]);
message.showUserData = this.showUserData(message, this.messages[index - 1]);
message.showTail = this.showTail(message, this.messages[index + 1]);
});

// Call resize to recalculate the dimensions.
Expand Down Expand Up @@ -1046,6 +1047,17 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
(!prevMessage || prevMessage.useridfrom != message.useridfrom || message.showDate);
}

/**
* Check if a css tail should be shown.
*
* @param {any} message Current message where to show the user info.
* @param {any} [nextMessage] Next message.
* @return {boolean} Whether user data should be shown.
*/
showTail(message: any, nextMessage?: any): boolean {
return !nextMessage || nextMessage.useridfrom != message.useridfrom || nextMessage.showDate;
}

/**
* Toggles delete state.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ <h2>{{ 'addon.messages.contacts' | translate }}</h2>
<h2>
<core-format-text [text]="conversation.name"></core-format-text>
<core-icon name="fa-ban" *ngIf="conversation.isblocked" [label]="'addon.messages.contactblocked' | translate"></core-icon>
<core-icon *ngIf="conversation.ismuted" name="volume-off"></core-icon>
<core-icon *ngIf="conversation.ismuted" name="volume-off" [label]="'addon.messages.mutedconversation' | translate"></core-icon>
</h2>
<ion-note *ngIf="conversation.lastmessagedate > 0 || conversation.unreadcount">
<ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
Expand Down
63 changes: 29 additions & 34 deletions src/addon/messages/pages/group-conversations/group-conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,12 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
const promises = [];

promises.push(this.fetchConversationCounts());
promises.push(this.messagesProvider.getContactRequestsCount(this.siteId)); // View updated by the event observer.

// View updated by the events observers.
promises.push(this.messagesProvider.getContactRequestsCount(this.siteId));
if (refreshUnreadCounts) {
promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId));
}

return Promise.all(promises).then(() => {
if (typeof this.favourites.expanded == 'undefined') {
Expand All @@ -276,23 +281,23 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
// We don't know which option it belongs to, so we need to fetch the data for all of them.
const promises = [];

promises.push(this.fetchDataForOption(this.favourites, false, refreshUnreadCounts));
promises.push(this.fetchDataForOption(this.group, false, refreshUnreadCounts));
promises.push(this.fetchDataForOption(this.individual, false, refreshUnreadCounts));
promises.push(this.fetchDataForOption(this.favourites, false));
promises.push(this.fetchDataForOption(this.group, false));
promises.push(this.fetchDataForOption(this.individual, false));

return Promise.all(promises).then(() => {
// All conversations have been loaded, find the one we need to load and expand its option.
const conversation = this.findConversation(this.conversationId, this.discussionUserId);
if (conversation) {
const option = this.getConversationOption(conversation);

return this.expandOption(option, refreshUnreadCounts);
return this.expandOption(option);
} else {
// Conversation not found, just open the default option.
this.calculateExpandedStatus();

// Now load the data for the expanded option.
return this.fetchDataForExpandedOption(refreshUnreadCounts);
return this.fetchDataForExpandedOption();
}
});
}
Expand All @@ -302,7 +307,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
}

// Now load the data for the expanded option.
return this.fetchDataForExpandedOption(refreshUnreadCounts);
return this.fetchDataForExpandedOption();
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true);
}).finally(() => {
Expand All @@ -314,47 +319,37 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* Calculate which option should be expanded initially.
*/
protected calculateExpandedStatus(): void {
this.favourites.expanded = this.favourites.count != 0;
this.group.expanded = this.favourites.count == 0 && this.group.count != 0;
this.individual.expanded = this.favourites.count == 0 && this.group.count == 0;
this.favourites.expanded = this.favourites.count != 0 && !this.group.unread && !this.individual.unread;
this.group.expanded = !this.favourites.expanded && this.group.count != 0 && !this.individual.unread;
this.individual.expanded = !this.favourites.expanded && !this.group.expanded;

this.loadCurrentListElement();
}

/**
* Fetch data for the expanded option.
*
* @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts.
* @return {Promise<any>} Promise resolved when done.
*/
protected fetchDataForExpandedOption(refreshUnreadCounts: boolean = true): Promise<any> {
protected fetchDataForExpandedOption(): Promise<any> {
const expandedOption = this.getExpandedOption();

if (expandedOption) {
return this.fetchDataForOption(expandedOption, false, refreshUnreadCounts);
} else {
// All options are collapsed, update the counts.
const promises = [];

promises.push(this.fetchConversationCounts());
if (refreshUnreadCounts) {
// View updated by event observer.
promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId));
}

return Promise.all(promises);
return this.fetchDataForOption(expandedOption, false);
}

return Promise.resolve();
}

/**
* Fetch data for a certain option.
*
* @param {any} option The option to fetch data for.
* @param {boolean} [loadingMore} Whether we are loading more data or just the first ones.
* @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts.
* @param {booleam} [getCounts] Whether to get counts data.
* @return {Promise<any>} Promise resolved when done.
*/
fetchDataForOption(option: any, loadingMore?: boolean, refreshUnreadCounts: boolean = true): Promise<void> {
fetchDataForOption(option: any, loadingMore?: boolean, getCounts?: boolean): Promise<void> {
option.loadMoreError = false;

const limitFrom = loadingMore ? option.conversations.length : 0,
Expand All @@ -375,12 +370,11 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
promises.push(this.messagesOffline.getAllMessages().then((data) => {
offlineMessages = data;
}));
}

if (getCounts) {
promises.push(this.fetchConversationCounts());
if (refreshUnreadCounts) {
// View updated by the event observer.
promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId));
}
promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId));
}

return Promise.all(promises).then(() => {
Expand Down Expand Up @@ -650,7 +644,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
option.expanded = false;
this.loadCurrentListElement();
} else {
this.expandOption(option).catch((error) => {
// Pass getCounts=true to update the counts everytime the user expands an option.
this.expandOption(option, true).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true);
});
}
Expand All @@ -660,10 +655,10 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* Expand a certain option.
*
* @param {any} option The option to expand.
* @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts.
* @param {booleam} [getCounts] Whether to get counts data.
* @return {Promise<any>} Promise resolved when done.
*/
protected expandOption(option: any, refreshUnreadCounts: boolean = true): Promise<any> {
protected expandOption(option: any, getCounts?: boolean): Promise<any> {
// Collapse all and expand the right one.
this.favourites.expanded = false;
this.group.expanded = false;
Expand All @@ -672,7 +667,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
option.expanded = true;
option.loading = true;

return this.fetchDataForOption(option, false, refreshUnreadCounts).then(() => {
return this.fetchDataForOption(option, false, getCounts).then(() => {
this.loadCurrentListElement();
}).catch((error) => {
option.expanded = false;
Expand Down
Loading