MOBILE-2729 messages: First implementation of conversations view
parent
31ffc50c01
commit
887ef9416d
|
@ -155,6 +155,7 @@
|
||||||
"addon.messages.contactableprivacy_coursemember": "message",
|
"addon.messages.contactableprivacy_coursemember": "message",
|
||||||
"addon.messages.contactableprivacy_onlycontacts": "message",
|
"addon.messages.contactableprivacy_onlycontacts": "message",
|
||||||
"addon.messages.contactableprivacy_site": "message",
|
"addon.messages.contactableprivacy_site": "message",
|
||||||
|
"addon.messages.contactblocked": "message",
|
||||||
"addon.messages.contactlistempty": "local_moodlemobileapp",
|
"addon.messages.contactlistempty": "local_moodlemobileapp",
|
||||||
"addon.messages.contactname": "local_moodlemobileapp",
|
"addon.messages.contactname": "local_moodlemobileapp",
|
||||||
"addon.messages.contacts": "message",
|
"addon.messages.contacts": "message",
|
||||||
|
@ -164,12 +165,15 @@
|
||||||
"addon.messages.errorwhileretrievingcontacts": "local_moodlemobileapp",
|
"addon.messages.errorwhileretrievingcontacts": "local_moodlemobileapp",
|
||||||
"addon.messages.errorwhileretrievingdiscussions": "local_moodlemobileapp",
|
"addon.messages.errorwhileretrievingdiscussions": "local_moodlemobileapp",
|
||||||
"addon.messages.errorwhileretrievingmessages": "local_moodlemobileapp",
|
"addon.messages.errorwhileretrievingmessages": "local_moodlemobileapp",
|
||||||
|
"addon.messages.groupmessages": "message",
|
||||||
"addon.messages.message": "message",
|
"addon.messages.message": "message",
|
||||||
"addon.messages.messagenotsent": "local_moodlemobileapp",
|
"addon.messages.messagenotsent": "local_moodlemobileapp",
|
||||||
"addon.messages.messagepreferences": "message",
|
"addon.messages.messagepreferences": "message",
|
||||||
"addon.messages.messages": "message",
|
"addon.messages.messages": "message",
|
||||||
"addon.messages.newmessage": "message",
|
"addon.messages.newmessage": "message",
|
||||||
"addon.messages.newmessages": "local_moodlemobileapp",
|
"addon.messages.newmessages": "local_moodlemobileapp",
|
||||||
|
"addon.messages.nofavourites": "message",
|
||||||
|
"addon.messages.nogroupmessages": "message",
|
||||||
"addon.messages.nomessages": "message",
|
"addon.messages.nomessages": "message",
|
||||||
"addon.messages.nousersfound": "local_moodlemobileapp",
|
"addon.messages.nousersfound": "local_moodlemobileapp",
|
||||||
"addon.messages.removecontact": "message",
|
"addon.messages.removecontact": "message",
|
||||||
|
@ -182,6 +186,7 @@
|
||||||
"addon.messages.unblockuser": "message",
|
"addon.messages.unblockuser": "message",
|
||||||
"addon.messages.unblockuserconfirm": "message",
|
"addon.messages.unblockuserconfirm": "message",
|
||||||
"addon.messages.warningmessagenotsent": "local_moodlemobileapp",
|
"addon.messages.warningmessagenotsent": "local_moodlemobileapp",
|
||||||
|
"addon.messages.you": "message",
|
||||||
"addon.mod_assign.acceptsubmissionstatement": "local_moodlemobileapp",
|
"addon.mod_assign.acceptsubmissionstatement": "local_moodlemobileapp",
|
||||||
"addon.mod_assign.addattempt": "assign",
|
"addon.mod_assign.addattempt": "assign",
|
||||||
"addon.mod_assign.addnewattempt": "assign",
|
"addon.mod_assign.addnewattempt": "assign",
|
||||||
|
@ -1271,6 +1276,7 @@
|
||||||
"core.errorsync": "local_moodlemobileapp",
|
"core.errorsync": "local_moodlemobileapp",
|
||||||
"core.errorsyncblocked": "local_moodlemobileapp",
|
"core.errorsyncblocked": "local_moodlemobileapp",
|
||||||
"core.explanationdigitalminor": "moodle",
|
"core.explanationdigitalminor": "moodle",
|
||||||
|
"core.favourites": "moodle",
|
||||||
"core.filename": "repository",
|
"core.filename": "repository",
|
||||||
"core.filenameexist": "local_moodlemobileapp",
|
"core.filenameexist": "local_moodlemobileapp",
|
||||||
"core.fileuploader.addfiletext": "repository",
|
"core.fileuploader.addfiletext": "repository",
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"contactableprivacy_coursemember": "My contacts and anyone in my courses",
|
"contactableprivacy_coursemember": "My contacts and anyone in my courses",
|
||||||
"contactableprivacy_onlycontacts": "My contacts only",
|
"contactableprivacy_onlycontacts": "My contacts only",
|
||||||
"contactableprivacy_site": "Anyone on the site",
|
"contactableprivacy_site": "Anyone on the site",
|
||||||
|
"contactblocked": "Contact blocked",
|
||||||
"contactlistempty": "The contact list is empty",
|
"contactlistempty": "The contact list is empty",
|
||||||
"contactname": "Contact name",
|
"contactname": "Contact name",
|
||||||
"contacts": "Contacts",
|
"contacts": "Contacts",
|
||||||
|
@ -16,12 +17,15 @@
|
||||||
"errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
"errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
||||||
"errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
|
"errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
|
||||||
"errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
"errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
||||||
|
"groupmessages": "Group messages",
|
||||||
"messagenotsent": "The message was not sent. Please try again later.",
|
"messagenotsent": "The message was not sent. Please try again later.",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"messagepreferences": "Message preferences",
|
"messagepreferences": "Message preferences",
|
||||||
"messages": "Messages",
|
"messages": "Messages",
|
||||||
"newmessage": "New message",
|
"newmessage": "New message",
|
||||||
"newmessages": "New messages",
|
"newmessages": "New messages",
|
||||||
|
"nofavourites": "No favourites",
|
||||||
|
"nogroupmessages": "No group messages",
|
||||||
"nomessages": "No messages",
|
"nomessages": "No messages",
|
||||||
"nousersfound": "No users found",
|
"nousersfound": "No users found",
|
||||||
"removecontact": "Remove contact",
|
"removecontact": "Remove contact",
|
||||||
|
@ -33,5 +37,6 @@
|
||||||
"type_strangers": "Others",
|
"type_strangers": "Others",
|
||||||
"unblockuser": "Unblock user",
|
"unblockuser": "Unblock user",
|
||||||
"unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
"unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
||||||
"warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}"
|
"warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
|
||||||
|
"you": "You:"
|
||||||
}
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar core-back-button>
|
||||||
|
<ion-title>{{ 'addon.messages.messages' | translate }}</ion-title>
|
||||||
|
<ion-buttons end>
|
||||||
|
<button ion-button icon-only (click)="gotoContacts($event)" [attr.aria-label]="'addon.messages.contacts' | translate">
|
||||||
|
<ion-icon name="person"></ion-icon> <!-- @todo: Display number of pending requests. -->
|
||||||
|
</button>
|
||||||
|
<button ion-button icon-only (click)="gotoSettings($event)" [attr.aria-label]="'addon.messages.messagepreferences' | translate">
|
||||||
|
<ion-icon name="cog"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-navbar>
|
||||||
|
</ion-header>
|
||||||
|
<core-split-view>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher [enabled]="loaded" (ionRefresh)="refreshData($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
|
||||||
|
<core-search-box *ngIf="search.enabled" (onSubmit)="searchMessage($event)" (onClear)="clearSearch($event)" [placeholder]=" 'addon.messages.message' | translate" autocorrect="off" spellcheck="false" lengthCheck="2" [disabled]="!loaded"></core-search-box>
|
||||||
|
|
||||||
|
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
|
||||||
|
|
||||||
|
<!-- Search results. -->
|
||||||
|
<ion-list *ngIf="search.showResults" no-margin>
|
||||||
|
<ion-item-divider color="light">
|
||||||
|
<h2>{{ 'core.searchresults' | translate }}</h2>
|
||||||
|
<ion-note item-end>{{ search.results.length }}</ion-note>
|
||||||
|
</ion-item-divider>
|
||||||
|
<!-- @todo: Search conversations does not return the conversationid. Check how it is solved in web. -->
|
||||||
|
<a ion-item text-wrap *ngFor="let result of search.results" [title]="result.fullname" (click)="gotoConversation(result.id, result.userid, result.messageid)" [class.core-split-item-selected]="result.id == selectedConversation" detail-none>
|
||||||
|
<ion-avatar item-start>
|
||||||
|
<img src="{{result.profileimageurl}}" [alt]="'core.pictureof' | translate:{$a: result.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'">
|
||||||
|
</ion-avatar>
|
||||||
|
<h2><core-format-text [text]="result.fullname"></core-format-text></h2>
|
||||||
|
<p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage"></core-format-text></p>
|
||||||
|
</a>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<!-- Conversations. -->
|
||||||
|
<ion-list *ngIf="!search.showResults">
|
||||||
|
<!-- Favourite conversations. -->
|
||||||
|
<ion-item-divider color="light" text-wrap *ngIf="favourites.conversations" (click)="toggle(favourites)">
|
||||||
|
<core-icon *ngIf="!favourites.expanded" name="fa-caret-right" item-start></core-icon>
|
||||||
|
<core-icon *ngIf="favourites.expanded" name="fa-caret-down" item-start></core-icon>
|
||||||
|
{{ 'core.favourites' | translate }} ({{ favourites.count }})
|
||||||
|
<!-- @todo: Unread total of favourites (MDL-63913). -->
|
||||||
|
</ion-item-divider>
|
||||||
|
<div *ngIf="favourites.conversations && favourites.expanded">
|
||||||
|
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: favourites.conversations}"></ng-container>
|
||||||
|
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
|
||||||
|
<core-infinite-loading [enabled]="favourites.canLoadMore" (action)="loadMoreConversations(favourites, $event)"></core-infinite-loading>
|
||||||
|
<ion-item text-wrap *ngIf="favourites.conversations.length == 0">
|
||||||
|
<p>{{ 'addon.messages.nofavourites' | translate }}</p>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Group conversations. -->
|
||||||
|
<ion-item-divider color="light" text-wrap *ngIf="group.conversations" (click)="toggle(group)">
|
||||||
|
<core-icon *ngIf="!group.expanded" name="fa-caret-right" item-start></core-icon>
|
||||||
|
<core-icon *ngIf="group.expanded" name="fa-caret-down" item-start></core-icon>
|
||||||
|
{{ 'addon.messages.groupmessages' | translate }} ({{ group.count }})
|
||||||
|
<!-- @todo: Unread total of group conversations (MDL-63913). -->
|
||||||
|
</ion-item-divider>
|
||||||
|
<div *ngIf="group.conversations && group.expanded">
|
||||||
|
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: group.conversations, avatarOptional: true}"></ng-container>
|
||||||
|
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
|
||||||
|
<core-infinite-loading [enabled]="group.canLoadMore" (action)="loadMoreConversations(group, $event)"></core-infinite-loading>
|
||||||
|
<ion-item text-wrap *ngIf="group.conversations.length == 0">
|
||||||
|
<p>{{ 'addon.messages.nogroupmessages' | translate }}</p>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ion-item-divider color="light" text-wrap *ngIf="individual.conversations" (click)="toggle(individual)">
|
||||||
|
<core-icon *ngIf="!individual.expanded" name="fa-caret-right" item-start></core-icon>
|
||||||
|
<core-icon *ngIf="individual.expanded" name="fa-caret-down" item-start></core-icon>
|
||||||
|
{{ 'addon.messages.messages' | translate }} ({{ individual.count }})
|
||||||
|
<!-- @todo: Unread total of individual conversations (MDL-63913). -->
|
||||||
|
</ion-item-divider>
|
||||||
|
<div *ngIf="individual.conversations && individual.expanded">
|
||||||
|
<ng-container *ngTemplateOutlet="conversationsTemplate; context: {conversations: individual.conversations}"></ng-container>
|
||||||
|
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
|
||||||
|
<core-infinite-loading [enabled]="individual.canLoadMore" (action)="loadMoreConversations(individual, $event)"></core-infinite-loading>
|
||||||
|
<ion-item text-wrap *ngIf="individual.conversations.length == 0">
|
||||||
|
<p>{{ 'addon.messages.nomessages' | translate }}</p>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<!-- Search didn't get any result. -->
|
||||||
|
<core-empty-box *ngIf="(!search.results || search.results.length <= 0) && search.showResults" icon="search" [message]="'core.noresults' | translate"></core-empty-box>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
||||||
|
</core-split-view>
|
||||||
|
|
||||||
|
<!-- Template to render a list of conversations. -->
|
||||||
|
<ng-template #conversationsTemplate let-conversations="conversations" let-avatarOptional="avatarOptional">
|
||||||
|
<a ion-item text-wrap *ngFor="let conversation of conversations" [title]="conversation.name" detail-none (click)="gotoConversation(conversation.id, conversation.userid)" [class.core-split-item-selected]="conversation.id == selectedConversation">
|
||||||
|
<ion-avatar item-start *ngIf="conversation.imageurl || !avatarOptional">
|
||||||
|
<img src="{{conversation.imageurl}}" [alt]="conversation.name" core-external-content onError="this.src='assets/img/user-avatar.png'">
|
||||||
|
<!-- @todo: Display connection status.
|
||||||
|
<span *ngIf="conversation.showonlinestatus" class="core-primary-circle" [ngClass]='{"addon-message-contact-online": conversation.isonline}'></span> -->
|
||||||
|
</ion-avatar>
|
||||||
|
<h2>
|
||||||
|
<p>
|
||||||
|
<core-format-text [text]="conversation.name"></core-format-text>
|
||||||
|
<core-icon name="fa-ban" *ngIf="conversation.isblocked" [attr.aria-label]="'addon.messages.contactblocked' | translate"></core-icon>
|
||||||
|
</p>
|
||||||
|
<ion-note *ngIf="conversation.lastmessagedate > 0 || conversation.unreadcount">
|
||||||
|
<ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
|
||||||
|
<span *ngIf="conversation.lastmessagedate > 0">{{conversation.lastmessagedate | coreDateDayOrTime}}</span>
|
||||||
|
</ion-note>
|
||||||
|
</h2>
|
||||||
|
<p><core-format-text *ngIf="conversation.subname" [text]="conversation.subname"></core-format-text></p>
|
||||||
|
<p>
|
||||||
|
<span *ngIf="conversation.sentfromcurrentuser">{{ 'addon.messages.you' | translate }}</span> <core-format-text clean="true" singleLine="true" [text]="conversation.lastmessage" class="addon-message-last-message"></core-format-text>
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
|
@ -0,0 +1,35 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicPageModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AddonMessagesGroupConversationsPage } from './group-conversations';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CorePipesModule } from '@pipes/pipes.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonMessagesGroupConversationsPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CorePipesModule,
|
||||||
|
IonicPageModule.forChild(AddonMessagesGroupConversationsPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonMessagesGroupConversationsPageModule {}
|
|
@ -0,0 +1,17 @@
|
||||||
|
ion-app.app-root page-addon-messages-group-conversations {
|
||||||
|
h2 {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.note {
|
||||||
|
margin: 0;
|
||||||
|
align-self: flex-end;
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core-format-text.addon-message-last-message {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,367 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||||
|
import { IonicPage, Platform, NavParams } from 'ionic-angular';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { AddonMessagesProvider } from '../../providers/messages';
|
||||||
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
|
||||||
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the list of conversations, including group conversations.
|
||||||
|
*/
|
||||||
|
@IonicPage({ segment: 'addon-messages-group-conversations' })
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-messages-group-conversations',
|
||||||
|
templateUrl: 'group-conversations.html',
|
||||||
|
})
|
||||||
|
export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
||||||
|
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
|
||||||
|
|
||||||
|
loaded = false;
|
||||||
|
loadingMessage: string;
|
||||||
|
selectedConversation: number;
|
||||||
|
search = {
|
||||||
|
enabled: false,
|
||||||
|
showResults: false,
|
||||||
|
results: [],
|
||||||
|
loading: '',
|
||||||
|
text: ''
|
||||||
|
};
|
||||||
|
favourites: any = {
|
||||||
|
type: null,
|
||||||
|
favourites: true
|
||||||
|
};
|
||||||
|
group: any = {
|
||||||
|
type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP,
|
||||||
|
favourites: false
|
||||||
|
};
|
||||||
|
individual: any = {
|
||||||
|
type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
|
||||||
|
favourites: false
|
||||||
|
};
|
||||||
|
|
||||||
|
protected loadingString: string;
|
||||||
|
protected siteId: string;
|
||||||
|
protected currentUserId: number;
|
||||||
|
protected conversationId: number;
|
||||||
|
protected newMessagesObserver: any;
|
||||||
|
protected pushObserver: any;
|
||||||
|
protected appResumeSubscription: any;
|
||||||
|
protected readChangedObserver: any;
|
||||||
|
protected cronObserver: any;
|
||||||
|
|
||||||
|
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService,
|
||||||
|
private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
|
||||||
|
private appProvider: CoreAppProvider, platform: Platform, utils: CoreUtilsProvider,
|
||||||
|
pushNotificationsDelegate: AddonPushNotificationsDelegate) {
|
||||||
|
|
||||||
|
this.search.loading = translate.instant('core.searching');
|
||||||
|
this.loadingString = translate.instant('core.loading');
|
||||||
|
this.siteId = sitesProvider.getCurrentSiteId();
|
||||||
|
this.currentUserId = sitesProvider.getCurrentSiteUserId();
|
||||||
|
this.conversationId = navParams.get('conversationId') || false;
|
||||||
|
|
||||||
|
// Update conversations when new message is received.
|
||||||
|
this.newMessagesObserver = eventsProvider.on(AddonMessagesProvider.NEW_MESSAGE_EVENT, (data) => {
|
||||||
|
if (data.conversationId) {
|
||||||
|
// Search the conversation to update.
|
||||||
|
const conversation = this.findConversation(data.conversationId);
|
||||||
|
|
||||||
|
if (typeof conversation == 'undefined') {
|
||||||
|
// Probably a new conversation, refresh the list.
|
||||||
|
this.loaded = false;
|
||||||
|
this.refreshData().finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// An existing conversation has a new message, update the last message.
|
||||||
|
conversation.lastmessage = data.message;
|
||||||
|
conversation.lastmessagedate = data.timecreated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this.siteId);
|
||||||
|
|
||||||
|
// Update discussions when a message is read.
|
||||||
|
this.readChangedObserver = eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => {
|
||||||
|
if (data.conversationId) {
|
||||||
|
const conversation = this.findConversation(data.conversationId);
|
||||||
|
|
||||||
|
if (typeof conversation != 'undefined') {
|
||||||
|
// A discussion has been read reset counter.
|
||||||
|
conversation.unreadcount = 0;
|
||||||
|
|
||||||
|
// Discussions changed, invalidate them.
|
||||||
|
this.messagesProvider.invalidateConversations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, this.siteId);
|
||||||
|
|
||||||
|
// Update discussions when cron read is executed.
|
||||||
|
this.cronObserver = eventsProvider.on(AddonMessagesProvider.READ_CRON_EVENT, (data) => {
|
||||||
|
this.refreshData();
|
||||||
|
}, this.siteId);
|
||||||
|
|
||||||
|
// Refresh the view when the app is resumed.
|
||||||
|
this.appResumeSubscription = platform.resume.subscribe(() => {
|
||||||
|
if (!this.loaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loaded = false;
|
||||||
|
this.refreshData().finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// If a message push notification is received, refresh the view.
|
||||||
|
this.pushObserver = pushNotificationsDelegate.on('receive').subscribe((notification) => {
|
||||||
|
// New message received. If it's from current site, refresh the data.
|
||||||
|
if (utils.isFalseOrZero(notification.notif) && notification.site == this.siteId) {
|
||||||
|
this.refreshData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component loaded.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.conversationId) {
|
||||||
|
// There is a discussion to load, open the discussion in a new state.
|
||||||
|
this.gotoConversation(this.conversationId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetchData().then(() => {
|
||||||
|
if (!this.conversationId && this.splitviewCtrl.isOn()) {
|
||||||
|
// Load the first conversation.
|
||||||
|
let conversation;
|
||||||
|
|
||||||
|
if (this.favourites.expanded) {
|
||||||
|
conversation = this.favourites.conversations[0];
|
||||||
|
} else if (this.group.expanded) {
|
||||||
|
conversation = this.group.conversations[0];
|
||||||
|
} else if (this.individual.expanded) {
|
||||||
|
conversation = this.individual.conversations[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversation) {
|
||||||
|
this.gotoConversation(conversation.id, conversation.userid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch conversations.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchData(): Promise<any> {
|
||||||
|
this.loadingMessage = this.loadingString;
|
||||||
|
this.search.enabled = this.messagesProvider.isSearchMessagesEnabled();
|
||||||
|
|
||||||
|
// Load the first conversations of each type.
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
if (typeof this.favourites.expanded == 'undefined') {
|
||||||
|
// The expanded status hasn't been initialized. Do it now.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true);
|
||||||
|
}).finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
fetchDataForOption(option: any, loadingMore?: boolean): Promise<void> {
|
||||||
|
const limitFrom = loadingMore ? option.conversations.length : 0;
|
||||||
|
|
||||||
|
return this.messagesProvider.getConversations(option.type, option.favourites, limitFrom).then((data) => {
|
||||||
|
if (loadingMore) {
|
||||||
|
option.conversations = option.conversations.concat(data.conversations);
|
||||||
|
} else {
|
||||||
|
option.count = data.canLoadMore ? AddonMessagesProvider.LIMIT_MESSAGES + '+' : data.conversations.length;
|
||||||
|
option.conversations = data.conversations;
|
||||||
|
}
|
||||||
|
|
||||||
|
option.unread = 0; // @todo.
|
||||||
|
option.canLoadMore = data.canLoadMore;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a conversation in the list of loaded conversations.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId The conversation ID to search.
|
||||||
|
* @return {any} Conversation.
|
||||||
|
*/
|
||||||
|
protected findConversation(conversationId: number): any {
|
||||||
|
const conversations = (this.favourites.conversations || []).concat(this.group.conversations || [])
|
||||||
|
.concat(this.individual.conversations || []);
|
||||||
|
|
||||||
|
return conversations.find((conv) => {
|
||||||
|
return conv.id == conversationId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to contacts view.
|
||||||
|
*/
|
||||||
|
gotoContacts(): void {
|
||||||
|
this.splitviewCtrl.getMasterNav().push('AddonMessagesContactsPage');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a particular conversation.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation Id to load.
|
||||||
|
* @param {number} userId User of the conversation. @todo This will probably be removed when group messaging is fully supported.
|
||||||
|
* @param {number} [messageId] Message to scroll after loading the discussion. Used when searching.
|
||||||
|
*/
|
||||||
|
gotoConversation(conversationId: number, userId: number, messageId?: number): void {
|
||||||
|
this.selectedConversation = conversationId;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
userId: userId
|
||||||
|
};
|
||||||
|
if (messageId) {
|
||||||
|
params['message'] = messageId;
|
||||||
|
}
|
||||||
|
this.splitviewCtrl.push('AddonMessagesDiscussionPage', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to message settings.
|
||||||
|
*/
|
||||||
|
gotoSettings(): void {
|
||||||
|
this.splitviewCtrl.push('AddonMessagesSettingsPage');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to load more conversations.
|
||||||
|
*
|
||||||
|
* @param {any} option The option to fetch data for.
|
||||||
|
* @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading.
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
loadMoreConversations(option: any, infiniteComplete?: any): Promise<any> {
|
||||||
|
return this.fetchDataForOption(option, true).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true);
|
||||||
|
option.canLoadMore = false;
|
||||||
|
}).finally(() => {
|
||||||
|
infiniteComplete && infiniteComplete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the data.
|
||||||
|
*
|
||||||
|
* @param {any} [refresher] Refresher.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
refreshData(refresher?: any): Promise<any> {
|
||||||
|
return this.messagesProvider.invalidateConversations().then(() => {
|
||||||
|
return this.fetchData().finally(() => {
|
||||||
|
if (refresher) {
|
||||||
|
// Actions to take if refresh comes from the user.
|
||||||
|
this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, undefined, this.siteId);
|
||||||
|
refresher.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toogle the visibility of an option (expand/collapse).
|
||||||
|
*
|
||||||
|
* @param {any} option The option to expand/collapse.
|
||||||
|
*/
|
||||||
|
toggle(option: any): void {
|
||||||
|
if (option.expanded) {
|
||||||
|
// Already expanded, close it.
|
||||||
|
option.expanded = false;
|
||||||
|
} else {
|
||||||
|
// Collapse all and expand the clicked one.
|
||||||
|
this.favourites.expanded = false;
|
||||||
|
this.group.expanded = false;
|
||||||
|
this.individual.expanded = false;
|
||||||
|
option.expanded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear search and show conversations again.
|
||||||
|
*/
|
||||||
|
clearSearch(): void {
|
||||||
|
this.loaded = false;
|
||||||
|
this.search.showResults = false;
|
||||||
|
this.search.text = ''; // Reset searched string.
|
||||||
|
this.fetchData().finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search messages cotaining text.
|
||||||
|
*
|
||||||
|
* @param {string} query Text to search for.
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
searchMessage(query: string): Promise<any> {
|
||||||
|
this.appProvider.closeKeyboard();
|
||||||
|
this.loaded = false;
|
||||||
|
this.loadingMessage = this.search.loading;
|
||||||
|
|
||||||
|
return this.messagesProvider.searchMessages(query).then((searchResults) => {
|
||||||
|
this.search.showResults = true;
|
||||||
|
this.search.results = searchResults;
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
|
||||||
|
}).finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.newMessagesObserver && this.newMessagesObserver.unsubscribe();
|
||||||
|
this.appResumeSubscription && this.appResumeSubscription.unsubscribe();
|
||||||
|
this.pushObserver && this.pushObserver.unsubscribe();
|
||||||
|
this.readChangedObserver && this.readChangedObserver.off();
|
||||||
|
this.cronObserver && this.cronObserver.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,6 +90,9 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
|
||||||
* @return {CoreMainMenuHandlerToDisplay} Data needed to render the handler.
|
* @return {CoreMainMenuHandlerToDisplay} Data needed to render the handler.
|
||||||
*/
|
*/
|
||||||
getDisplayData(): CoreMainMenuHandlerToDisplay {
|
getDisplayData(): CoreMainMenuHandlerToDisplay {
|
||||||
|
this.handler.page = this.messagesProvider.isGroupMessagingEnabled() ?
|
||||||
|
'AddonMessagesGroupConversationsPage' : 'AddonMessagesIndexPage';
|
||||||
|
|
||||||
if (this.handler.loading) {
|
if (this.handler.loading) {
|
||||||
this.updateBadge();
|
this.updateBadge();
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AddonMessagesProvider {
|
export class AddonMessagesProvider {
|
||||||
protected ROOT_CACHE_KEY = 'mmaMessages:';
|
protected ROOT_CACHE_KEY = 'mmaMessages:';
|
||||||
protected LIMIT_MESSAGES = 50;
|
|
||||||
protected LIMIT_SEARCH_MESSAGES = 50;
|
protected LIMIT_SEARCH_MESSAGES = 50;
|
||||||
|
protected LIMIT_MESSAGES = AddonMessagesProvider.LIMIT_MESSAGES;
|
||||||
static NEW_MESSAGE_EVENT = 'addon_messages_new_message_event';
|
static NEW_MESSAGE_EVENT = 'addon_messages_new_message_event';
|
||||||
static READ_CHANGED_EVENT = 'addon_messages_read_changed_event';
|
static READ_CHANGED_EVENT = 'addon_messages_read_changed_event';
|
||||||
static READ_CRON_EVENT = 'addon_messages_read_cron_event';
|
static READ_CRON_EVENT = 'addon_messages_read_cron_event';
|
||||||
|
@ -40,6 +40,9 @@ export class AddonMessagesProvider {
|
||||||
static MESSAGE_PRIVACY_COURSEMEMBER = 0; // Privacy setting for being messaged by anyone within courses user is member of.
|
static MESSAGE_PRIVACY_COURSEMEMBER = 0; // Privacy setting for being messaged by anyone within courses user is member of.
|
||||||
static MESSAGE_PRIVACY_ONLYCONTACTS = 1; // Privacy setting for being messaged only by contacts.
|
static MESSAGE_PRIVACY_ONLYCONTACTS = 1; // Privacy setting for being messaged only by contacts.
|
||||||
static MESSAGE_PRIVACY_SITE = 2; // Privacy setting for being messaged by anyone on the site.
|
static MESSAGE_PRIVACY_SITE = 2; // Privacy setting for being messaged by anyone on the site.
|
||||||
|
static MESSAGE_CONVERSATION_TYPE_INDIVIDUAL = 1; // An individual conversation.
|
||||||
|
static MESSAGE_CONVERSATION_TYPE_GROUP = 2; // A group conversation.
|
||||||
|
static LIMIT_MESSAGES = 50;
|
||||||
|
|
||||||
protected logger;
|
protected logger;
|
||||||
|
|
||||||
|
@ -184,6 +187,37 @@ export class AddonMessagesProvider {
|
||||||
return this.ROOT_CACHE_KEY + 'discussions';
|
return this.ROOT_CACHE_KEY + 'discussions';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for get conversations.
|
||||||
|
*
|
||||||
|
* @param {number} userId User ID.
|
||||||
|
* @param {number} [type] Filter by type.
|
||||||
|
* @param {boolean} [favourites] Filter favourites.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getCacheKeyForConversations(userId: number, type?: number, favourites?: boolean): string {
|
||||||
|
return this.getCommonCacheKeyForUserConversations(userId) + ':' + type + ':' + favourites;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get common cache key for get user conversations.
|
||||||
|
*
|
||||||
|
* @param {number} userId User ID.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getCommonCacheKeyForUserConversations(userId: number): string {
|
||||||
|
return this.getRootCacheKeyForConversations() + userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get root cache key for get conversations.
|
||||||
|
*
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getRootCacheKeyForConversations(): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'conversations:';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the contacts of the current user.
|
* Get all the contacts of the current user.
|
||||||
*
|
*
|
||||||
|
@ -263,6 +297,78 @@ export class AddonMessagesProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discussions of a certain user. This function is used in Moodle sites higher than 3.6.
|
||||||
|
* If the site is older than 3.6, please use getDiscussions.
|
||||||
|
*
|
||||||
|
* @param {number} [limitFrom=0] The offset to start at.
|
||||||
|
* @param {number} [type] Filter by type.
|
||||||
|
* @param {boolean} [favourites] Whether to restrict the results to contain NO favourite conversations (false), ONLY favourite
|
||||||
|
* conversation (true), or ignore any restriction altogether (undefined or null).
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||||
|
* @param {number} [userId] User ID. If not defined, current user in the site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the conversations.
|
||||||
|
*/
|
||||||
|
getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number)
|
||||||
|
: Promise<{conversations: any[], canLoadMore: boolean}> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getCacheKeyForConversations(userId, type, favourites)
|
||||||
|
},
|
||||||
|
params: any = {
|
||||||
|
userid: userId,
|
||||||
|
limitfrom: limitFrom,
|
||||||
|
limitnum: this.LIMIT_MESSAGES + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof type != 'undefined' && type != null) {
|
||||||
|
params.type = type;
|
||||||
|
}
|
||||||
|
if (typeof favourites != 'undefined' && favourites != null) {
|
||||||
|
params.favourites = favourites ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return site.read('core_message_get_conversations', params, preSets).then((response) => {
|
||||||
|
// Format the conversations, adding some calculated fields.
|
||||||
|
const conversations = response.conversations.map((conversation) => {
|
||||||
|
const numMessages = conversation.messages.length,
|
||||||
|
lastMessage = numMessages ? conversation.messages[numMessages - 1] : null;
|
||||||
|
|
||||||
|
conversation.lastmessage = lastMessage ? lastMessage.text : null;
|
||||||
|
conversation.lastmessagedate = lastMessage ? lastMessage.timecreated : null;
|
||||||
|
conversation.sentfromcurrentuser = lastMessage ? lastMessage.useridfrom == userId : null;
|
||||||
|
|
||||||
|
if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
|
||||||
|
const otherUser = conversation.members.reduce((carry, member) => {
|
||||||
|
if (!carry && member.id != userId) {
|
||||||
|
carry = member;
|
||||||
|
}
|
||||||
|
|
||||||
|
return carry;
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
conversation.name = conversation.name ? conversation.name : otherUser.fullname;
|
||||||
|
conversation.imageurl = conversation.imageurl ? conversation.imageurl : otherUser.profileimageurl;
|
||||||
|
conversation.userid = otherUser.id;
|
||||||
|
conversation.showonlinestatus = otherUser.showonlinestatus;
|
||||||
|
conversation.isonline = otherUser.isonline;
|
||||||
|
conversation.isblocked = otherUser.isblocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversation;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
conversations: conversations,
|
||||||
|
canLoadMore: response.conversations.length > this.LIMIT_MESSAGES
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current user's discussion with another user.
|
* Return the current user's discussion with another user.
|
||||||
*
|
*
|
||||||
|
@ -346,7 +452,8 @@ export class AddonMessagesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the discussions of the current user.
|
* Get the discussions of the current user. This function is used in Moodle sites older than 3.6.
|
||||||
|
* If the site is 3.6 or higher, please use getConversations.
|
||||||
*
|
*
|
||||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
* @return {Promise<any>} Resolved with an object where the keys are the user ID of the other user.
|
* @return {Promise<any>} Resolved with an object where the keys are the user ID of the other user.
|
||||||
|
@ -705,6 +812,21 @@ export class AddonMessagesProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate contacts cache.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @param {number} [userId] User ID. If not defined, current user in the site.
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
invalidateConversations(siteId?: string, userId?: number): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
return site.invalidateWsCacheForKeyStartingWith(this.getCommonCacheKeyForUserConversations(userId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate discussion cache.
|
* Invalidate discussion cache.
|
||||||
*
|
*
|
||||||
|
@ -788,6 +910,16 @@ export class AddonMessagesProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not group messaging is supported.
|
||||||
|
*
|
||||||
|
* @return {boolean} If related WS is avalaible on current site.
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
isGroupMessagingEnabled(): boolean {
|
||||||
|
return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_conversations');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not we can mark all messages as read.
|
* Returns whether or not we can mark all messages as read.
|
||||||
*
|
*
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
"addon.messages.contactableprivacy_coursemember": "My contacts and anyone in my courses",
|
"addon.messages.contactableprivacy_coursemember": "My contacts and anyone in my courses",
|
||||||
"addon.messages.contactableprivacy_onlycontacts": "My contacts only",
|
"addon.messages.contactableprivacy_onlycontacts": "My contacts only",
|
||||||
"addon.messages.contactableprivacy_site": "Anyone on the site",
|
"addon.messages.contactableprivacy_site": "Anyone on the site",
|
||||||
|
"addon.messages.contactblocked": "Contact blocked",
|
||||||
"addon.messages.contactlistempty": "The contact list is empty",
|
"addon.messages.contactlistempty": "The contact list is empty",
|
||||||
"addon.messages.contactname": "Contact name",
|
"addon.messages.contactname": "Contact name",
|
||||||
"addon.messages.contacts": "Contacts",
|
"addon.messages.contacts": "Contacts",
|
||||||
|
@ -164,12 +165,15 @@
|
||||||
"addon.messages.errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
"addon.messages.errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
||||||
"addon.messages.errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
|
"addon.messages.errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
|
||||||
"addon.messages.errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
"addon.messages.errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
||||||
|
"addon.messages.groupmessages": "Group messages",
|
||||||
"addon.messages.message": "Message",
|
"addon.messages.message": "Message",
|
||||||
"addon.messages.messagenotsent": "The message was not sent. Please try again later.",
|
"addon.messages.messagenotsent": "The message was not sent. Please try again later.",
|
||||||
"addon.messages.messagepreferences": "Message preferences",
|
"addon.messages.messagepreferences": "Message preferences",
|
||||||
"addon.messages.messages": "Messages",
|
"addon.messages.messages": "Messages",
|
||||||
"addon.messages.newmessage": "New message",
|
"addon.messages.newmessage": "New message",
|
||||||
"addon.messages.newmessages": "New messages",
|
"addon.messages.newmessages": "New messages",
|
||||||
|
"addon.messages.nofavourites": "No favourites",
|
||||||
|
"addon.messages.nogroupmessages": "No group messages",
|
||||||
"addon.messages.nomessages": "No messages",
|
"addon.messages.nomessages": "No messages",
|
||||||
"addon.messages.nousersfound": "No users found",
|
"addon.messages.nousersfound": "No users found",
|
||||||
"addon.messages.removecontact": "Remove contact",
|
"addon.messages.removecontact": "Remove contact",
|
||||||
|
@ -182,6 +186,7 @@
|
||||||
"addon.messages.unblockuser": "Unblock user",
|
"addon.messages.unblockuser": "Unblock user",
|
||||||
"addon.messages.unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
"addon.messages.unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
||||||
"addon.messages.warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
|
"addon.messages.warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
|
||||||
|
"addon.messages.you": "You:",
|
||||||
"addon.mod_assign.acceptsubmissionstatement": "Please accept the submission statement.",
|
"addon.mod_assign.acceptsubmissionstatement": "Please accept the submission statement.",
|
||||||
"addon.mod_assign.addattempt": "Allow another attempt",
|
"addon.mod_assign.addattempt": "Allow another attempt",
|
||||||
"addon.mod_assign.addnewattempt": "Add a new attempt",
|
"addon.mod_assign.addnewattempt": "Add a new attempt",
|
||||||
|
@ -1271,6 +1276,7 @@
|
||||||
"core.errorsync": "An error occurred while synchronising. Please try again.",
|
"core.errorsync": "An error occurred while synchronising. Please try again.",
|
||||||
"core.errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.",
|
"core.errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.",
|
||||||
"core.explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.",
|
"core.explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.",
|
||||||
|
"core.favourites": "Favourites",
|
||||||
"core.filename": "Filename",
|
"core.filename": "Filename",
|
||||||
"core.filenameexist": "File name already exists: {{$a}}",
|
"core.filenameexist": "File name already exists: {{$a}}",
|
||||||
"core.fileuploader.addfiletext": "Add file",
|
"core.fileuploader.addfiletext": "Add file",
|
||||||
|
|
|
@ -90,6 +90,7 @@
|
||||||
"errorsync": "An error occurred while synchronising. Please try again.",
|
"errorsync": "An error occurred while synchronising. Please try again.",
|
||||||
"errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.",
|
"errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.",
|
||||||
"explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.",
|
"explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.",
|
||||||
|
"favourites": "Favourites",
|
||||||
"filename": "Filename",
|
"filename": "Filename",
|
||||||
"filenameexist": "File name already exists: {{$a}}",
|
"filenameexist": "File name already exists: {{$a}}",
|
||||||
"folder": "Folder",
|
"folder": "Folder",
|
||||||
|
|
Loading…
Reference in New Issue