MOBILE-3631 messages: Add contacts page
parent
99448a3054
commit
04c6f4480d
|
@ -16,6 +16,7 @@ import { Injector, NgModule } from '@angular/core';
|
||||||
import { RouterModule, ROUTES, Routes } from '@angular/router';
|
import { RouterModule, ROUTES, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
|
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
|
import { AddonMessagesContactsRoutingModule } from './pages/contacts/messages-contacts-routing.module';
|
||||||
import { AddonMessagesIndexRoutingModule } from './pages/index-35/messages-index-routing.module';
|
import { AddonMessagesIndexRoutingModule } from './pages/index-35/messages-index-routing.module';
|
||||||
|
|
||||||
function buildRoutes(injector: Injector): Routes {
|
function buildRoutes(injector: Injector): Routes {
|
||||||
|
@ -34,6 +35,11 @@ function buildRoutes(injector: Injector): Routes {
|
||||||
loadChildren: () => import('./pages/search/search.module')
|
loadChildren: () => import('./pages/search/search.module')
|
||||||
.then(m => m.AddonMessagesSearchPageModule),
|
.then(m => m.AddonMessagesSearchPageModule),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'contacts', // 3.6 or greater.
|
||||||
|
loadChildren: () => import('./pages/contacts/contacts.module')
|
||||||
|
.then(m => m.AddonMessagesContactsPageModule),
|
||||||
|
},
|
||||||
...buildTabMainRoutes(injector, {
|
...buildTabMainRoutes(injector, {
|
||||||
redirectTo: 'index',
|
redirectTo: 'index',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
|
@ -53,8 +59,25 @@ const indexTabRoutes: Routes = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 3.6 or greater.
|
||||||
|
const contactsTabRoutes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'confirmed',
|
||||||
|
loadChildren: () => import('./pages/contacts-confirmed/contacts-confirmed.module')
|
||||||
|
.then(m => m.AddonMessagesContactsConfirmedPageModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'requests',
|
||||||
|
loadChildren: () => import('./pages/contacts-requests/contacts-requests.module')
|
||||||
|
.then(m => m.AddonMessagesContactsRequestsPageModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [AddonMessagesIndexRoutingModule.forChild({ children: indexTabRoutes })],
|
imports: [
|
||||||
|
AddonMessagesIndexRoutingModule.forChild({ children: indexTabRoutes }),
|
||||||
|
AddonMessagesContactsRoutingModule.forChild({ children: contactsTabRoutes }),
|
||||||
|
],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<ion-list class="ion-no-margin">
|
||||||
|
<ion-item class="ion-text-wrap" *ngFor="let contact of contacts" [title]="contact.fullname"
|
||||||
|
(click)="selectUser(contact.id)"
|
||||||
|
[class.core-split-item-selected]="contact.id == selectedUserId" class="addon-messages-conversation-item">
|
||||||
|
<core-user-avatar slot="start" core-user-avatar [user]="contact" [checkOnline]="contact.showonlinestatus"
|
||||||
|
[linkProfile]="false"></core-user-avatar>
|
||||||
|
<ion-label>
|
||||||
|
<h2>
|
||||||
|
{{ contact.fullname }}
|
||||||
|
<ion-icon *ngIf="contact.isblocked" name="fas-user-slash" slot="end">
|
||||||
|
</ion-icon>
|
||||||
|
</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<core-empty-box *ngIf="!contacts.length" icon="far-address-book"
|
||||||
|
[message]="'addon.messages.nocontactsgetstarted' | translate">
|
||||||
|
</core-empty-box>
|
||||||
|
|
||||||
|
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError"
|
||||||
|
position="bottom">
|
||||||
|
</core-infinite-loading>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// 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 { IonicModule } from '@ionic/angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
|
||||||
|
import { AddonMessagesContactsConfirmedPage } from './contacts-confirmed.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AddonMessagesContactsConfirmedPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreSharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AddonMessagesContactsConfirmedPage,
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class AddonMessagesContactsConfirmedPageModule {}
|
|
@ -0,0 +1,165 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// 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, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import {
|
||||||
|
AddonMessagesProvider,
|
||||||
|
AddonMessagesConversationMember,
|
||||||
|
AddonMessages,
|
||||||
|
AddonMessagesMemberInfoChangedEventData,
|
||||||
|
AddonMessagesSplitViewLoadContactsEventData,
|
||||||
|
} from '../../services/messages';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays the list of confirmed contacts.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-messages-confirmed-contacts',
|
||||||
|
templateUrl: 'contacts-confirmed.html',
|
||||||
|
})
|
||||||
|
export class AddonMessagesContactsConfirmedPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
loaded = false;
|
||||||
|
canLoadMore = false;
|
||||||
|
loadMoreError = false;
|
||||||
|
contacts: AddonMessagesConversationMember[] = [];
|
||||||
|
selectedUserId?: number;
|
||||||
|
|
||||||
|
protected memberInfoObserver: CoreEventObserver;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// this.onUserSelected = new EventEmitter();
|
||||||
|
|
||||||
|
// Update block status of a user.
|
||||||
|
this.memberInfoObserver = CoreEvents.on<AddonMessagesMemberInfoChangedEventData>(
|
||||||
|
AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT,
|
||||||
|
(data) => {
|
||||||
|
if (data.userBlocked || data.userUnblocked) {
|
||||||
|
const user = this.contacts.find((user) => user.id == data.userId);
|
||||||
|
if (user) {
|
||||||
|
user.isblocked = !!data.userBlocked;
|
||||||
|
}
|
||||||
|
} else if (data.contactRemoved) {
|
||||||
|
const index = this.contacts.findIndex((contact) => contact.id == data.userId);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.contacts.splice(index, 1);
|
||||||
|
}
|
||||||
|
} else if (data.contactRequestConfirmed) {
|
||||||
|
this.refreshData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CoreSites.instance.getCurrentSiteId(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component loaded.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.fetchData();
|
||||||
|
if (this.contacts.length) {
|
||||||
|
this.selectUser(this.contacts[0].id, true);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
// Workaround for infinite scrolling.
|
||||||
|
// @todo this.content.resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch contacts.
|
||||||
|
*
|
||||||
|
* @param refresh True if we are refreshing contacts, false if we are loading more.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async fetchData(refresh: boolean = false): Promise<void> {
|
||||||
|
this.loadMoreError = false;
|
||||||
|
|
||||||
|
const limitFrom = refresh ? 0 : this.contacts.length;
|
||||||
|
|
||||||
|
if (limitFrom === 0) {
|
||||||
|
// Always try to get latest data from server.
|
||||||
|
await AddonMessages.instance.invalidateUserContacts();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await AddonMessages.instance.getUserContacts(limitFrom);
|
||||||
|
this.contacts = refresh ? result.contacts : this.contacts.concat(result.contacts);
|
||||||
|
this.canLoadMore = result.canLoadMore;
|
||||||
|
} catch (error) {
|
||||||
|
this.loadMoreError = true;
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh contacts.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async refreshData(refresher?: CustomEvent<IonRefresher>): Promise<void> {
|
||||||
|
// No need to invalidate contacts, we always try to get the latest.
|
||||||
|
await this.fetchData(true).finally(() => {
|
||||||
|
refresher?.detail.complete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load more contacts.
|
||||||
|
*
|
||||||
|
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
|
||||||
|
* @return Resolved when done.
|
||||||
|
*/
|
||||||
|
async loadMore(infiniteComplete?: () => void): Promise<void> {
|
||||||
|
await this.fetchData().finally(() => {
|
||||||
|
infiniteComplete && infiniteComplete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify that a contact has been selected.
|
||||||
|
*
|
||||||
|
* @param userId User id.
|
||||||
|
* @param onInit Whether the contact is selected on initial load.
|
||||||
|
*/
|
||||||
|
selectUser(userId: number, onInit: boolean = false): void {
|
||||||
|
this.selectedUserId = userId;
|
||||||
|
|
||||||
|
const data: AddonMessagesSplitViewLoadContactsEventData = {
|
||||||
|
userId,
|
||||||
|
onInit,
|
||||||
|
};
|
||||||
|
|
||||||
|
CoreEvents.trigger(AddonMessagesProvider.SPLIT_VIEW_LOAD_CONTACTS_EVENT, data);
|
||||||
|
|
||||||
|
// @todo: Check if split view is visible before
|
||||||
|
CoreNavigator.instance.navigateToSitePath('discussion', { params : { userId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.memberInfoObserver?.off();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<ion-list class="ion-no-margin">
|
||||||
|
<ion-item class="ion-text-wrap" *ngFor="let request of requests" [title]="request.fullname"
|
||||||
|
(click)="selectUser(request.id)" [class.core-split-item-selected]="request.id == selectedUserId"
|
||||||
|
class="addon-messages-conversation-item">
|
||||||
|
<core-user-avatar slot="start" [user]="request" [linkProfile]="false"></core-user-avatar>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ request.fullname }}</h2>
|
||||||
|
<p *ngIf="!request.iscontact">
|
||||||
|
{{ 'addon.messages.wouldliketocontactyou' | translate }}
|
||||||
|
</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
<core-empty-box *ngIf="!requests.length" icon="far-address-book" [message]="'addon.messages.nocontactrequests' | translate">
|
||||||
|
</core-empty-box>
|
||||||
|
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError" position="bottom">
|
||||||
|
</core-infinite-loading>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// 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 { IonicModule } from '@ionic/angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
|
||||||
|
import { AddonMessagesContactsRequestsPage } from './contacts-requests.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AddonMessagesContactsRequestsPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreSharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AddonMessagesContactsRequestsPage,
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class AddonMessagesContactsRequestsPageModule {}
|
|
@ -0,0 +1,161 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// 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, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import {
|
||||||
|
AddonMessagesProvider,
|
||||||
|
AddonMessagesConversationMember,
|
||||||
|
AddonMessagesSplitViewLoadContactsEventData,
|
||||||
|
AddonMessages,
|
||||||
|
AddonMessagesMemberInfoChangedEventData,
|
||||||
|
} from '../../services/messages';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays the list of contact requests.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-messages-contact-requests',
|
||||||
|
templateUrl: 'contacts-requests.html',
|
||||||
|
})
|
||||||
|
export class AddonMessagesContactsRequestsPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
loaded = false;
|
||||||
|
canLoadMore = false;
|
||||||
|
loadMoreError = false;
|
||||||
|
requests: AddonMessagesConversationMember[] = [];
|
||||||
|
selectedUserId?: number;
|
||||||
|
|
||||||
|
protected memberInfoObserver: CoreEventObserver;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
// Hide the "Would like to contact you" message when a contact request is confirmed.
|
||||||
|
this.memberInfoObserver = CoreEvents.on<AddonMessagesMemberInfoChangedEventData>(
|
||||||
|
AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT,
|
||||||
|
(data) => {
|
||||||
|
if (data.contactRequestConfirmed || data.contactRequestDeclined) {
|
||||||
|
const index = this.requests.findIndex((request) => request.id == data.userId);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.requests.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
CoreSites.instance.getCurrentSiteId(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component loaded.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.fetchData();
|
||||||
|
if (this.requests.length) {
|
||||||
|
this.selectUser(this.requests[0].id, true);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
// Workaround for infinite scrolling.
|
||||||
|
// @todo this.content.resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch contact requests.
|
||||||
|
*
|
||||||
|
* @param refresh True if we are refreshing contact requests, false if we are loading more.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async fetchData(refresh: boolean = false): Promise<void> {
|
||||||
|
this.loadMoreError = false;
|
||||||
|
|
||||||
|
const limitFrom = refresh ? 0 : this.requests.length;
|
||||||
|
|
||||||
|
if (limitFrom === 0) {
|
||||||
|
// Always try to get latest data from server.
|
||||||
|
await AddonMessages.instance.invalidateContactRequestsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await AddonMessages.instance.getContactRequests(limitFrom);
|
||||||
|
this.requests = refresh ? result.requests : this.requests.concat(result.requests);
|
||||||
|
this.canLoadMore = result.canLoadMore;
|
||||||
|
} catch (error) {
|
||||||
|
this.loadMoreError = true;
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh contact requests.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async refreshData(refresher?: CustomEvent<IonRefresher>): Promise<void> {
|
||||||
|
// Refresh the number of contacts requests to update badges.
|
||||||
|
AddonMessages.instance.refreshContactRequestsCount();
|
||||||
|
|
||||||
|
// No need to invalidate contact requests, we always try to get the latest.
|
||||||
|
await this.fetchData(true).finally(() => {
|
||||||
|
refresher?.detail.complete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load more contact requests.
|
||||||
|
*
|
||||||
|
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
|
||||||
|
* @return Resolved when done.
|
||||||
|
*/
|
||||||
|
async loadMore(infiniteComplete?: () => void): Promise<void> {
|
||||||
|
await this.fetchData().finally(() => {
|
||||||
|
infiniteComplete && infiniteComplete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify that a contact has been selected.
|
||||||
|
*
|
||||||
|
* @param userId User id.
|
||||||
|
* @param onInit Whether the contact is selected on initial load.
|
||||||
|
*/
|
||||||
|
selectUser(userId: number, onInit: boolean = false): void {
|
||||||
|
this.selectedUserId = userId;
|
||||||
|
|
||||||
|
const data: AddonMessagesSplitViewLoadContactsEventData = {
|
||||||
|
userId,
|
||||||
|
onInit,
|
||||||
|
};
|
||||||
|
|
||||||
|
CoreEvents.trigger(AddonMessagesProvider.SPLIT_VIEW_LOAD_CONTACTS_EVENT, data);
|
||||||
|
|
||||||
|
// @todo: Check if split view is visible before
|
||||||
|
CoreNavigator.instance.navigateToSitePath('discussion', { params : { userId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.memberInfoObserver?.off();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>{{ 'addon.messages.contacts' | translate }}</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="gotoSearch()" [attr.aria-label]="'addon.messages.search' | translate">
|
||||||
|
<ion-icon name="fas-search" slot="icon-only"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu disappears in some cases. -->
|
||||||
|
<core-context-menu></core-context-menu>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<!-- @todo <core-split-view>-->
|
||||||
|
<ion-content>
|
||||||
|
<core-tabs [tabs]="tabs" hideUntil="true"></core-tabs>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,64 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// 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 { Injector, NgModule } from '@angular/core';
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterModule, ROUTES, Routes } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
|
||||||
|
import { ADDON_MESSAGES_CONTACTS_ROUTES } from './messages-contacts-routing.module';
|
||||||
|
import { AddonMessagesContactsPage } from './contacts.page';
|
||||||
|
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
|
import { resolveModuleRoutes } from '@/app/app-routing.module';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AddonMessagesContactsPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function buildRoutes(injector: Injector): Routes {
|
||||||
|
const routes = resolveModuleRoutes(injector, ADDON_MESSAGES_CONTACTS_ROUTES);
|
||||||
|
|
||||||
|
return [
|
||||||
|
...buildTabMainRoutes(injector, {
|
||||||
|
path: '',
|
||||||
|
component: AddonMessagesContactsPage,
|
||||||
|
children: routes.children,
|
||||||
|
}),
|
||||||
|
...routes.siblings,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreSharedModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] },
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AddonMessagesContactsPage,
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class AddonMessagesContactsPageModule {}
|
|
@ -0,0 +1,134 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// 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, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import {
|
||||||
|
AddonMessages,
|
||||||
|
AddonMessagesContactRequestCountEventData,
|
||||||
|
AddonMessagesProvider,
|
||||||
|
AddonMessagesSplitViewLoadContactsEventData,
|
||||||
|
} from '../../services/messages';
|
||||||
|
import { CoreTab } from '@components/tabs/tabs';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays contacts and contact requests.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-messages-contacts',
|
||||||
|
templateUrl: 'contacts.html',
|
||||||
|
})
|
||||||
|
export class AddonMessagesContactsPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
// @todo @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
|
||||||
|
|
||||||
|
protected contactsTab: CoreTab = {
|
||||||
|
id: 'contacts-confirmed',
|
||||||
|
class: '',
|
||||||
|
title: 'addon.messages.contacts',
|
||||||
|
icon: 'fas-address-book',
|
||||||
|
enabled: true,
|
||||||
|
page: 'main/messages/contacts/confirmed',
|
||||||
|
};
|
||||||
|
|
||||||
|
protected requestsTab: CoreTab = {
|
||||||
|
id: 'contact-requests',
|
||||||
|
class: '',
|
||||||
|
title: 'addon.messages.requests',
|
||||||
|
icon: 'fas-user-plus',
|
||||||
|
enabled: true,
|
||||||
|
page: 'main/messages/contacts/requests',
|
||||||
|
badge: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
tabs: CoreTab[] = [];
|
||||||
|
|
||||||
|
protected siteId: string;
|
||||||
|
protected contactRequestsCountObserver: CoreEventObserver;
|
||||||
|
protected splitViewObserver: CoreEventObserver;
|
||||||
|
protected selectedUserId?: number; // User id of the conversation opened in the split view.
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
this.tabs = [this.contactsTab, this.requestsTab];
|
||||||
|
|
||||||
|
this.siteId = CoreSites.instance.getCurrentSiteId();
|
||||||
|
|
||||||
|
// Update the contact requests badge.
|
||||||
|
this.contactRequestsCountObserver = CoreEvents.on<AddonMessagesContactRequestCountEventData>(
|
||||||
|
AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT,
|
||||||
|
(data) => {
|
||||||
|
this.requestsTab.badge = data.count > 0 ? String(data.count) : '';
|
||||||
|
},
|
||||||
|
this.siteId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the contact requests badge.
|
||||||
|
this.splitViewObserver = CoreEvents.on<AddonMessagesSplitViewLoadContactsEventData>(
|
||||||
|
AddonMessagesProvider.SPLIT_VIEW_LOAD_CONTACTS_EVENT,
|
||||||
|
(data) => {
|
||||||
|
this.selectUser(data.userId, data.onInit);
|
||||||
|
},
|
||||||
|
this.siteId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
AddonMessages.instance.getContactRequestsCount(this.siteId); // Badge already updated by the observer.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the search page.
|
||||||
|
*/
|
||||||
|
gotoSearch(): void {
|
||||||
|
CoreNavigator.instance.navigateToSitePath('search');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the selected user and open the conversation in the split view if needed.
|
||||||
|
*
|
||||||
|
* @param userId Id of the selected user, undefined to use the last selected user in the tab.
|
||||||
|
* @param onInit Whether the contact was selected on initial load.
|
||||||
|
*/
|
||||||
|
selectUser(userId: number, onInit = false): void {
|
||||||
|
/* @todo if (userId == this.selectedUserId && this.splitviewCtrl.isOn()) {
|
||||||
|
// No user conversation to open or it is already opened.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onInit && !this.splitviewCtrl.isOn()) {
|
||||||
|
// Do not open a conversation by default when split view is not visible.
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
this.selectedUserId = userId;
|
||||||
|
// @todo this.splitviewCtrl.push('AddonMessagesDiscussionPage', { userId });
|
||||||
|
CoreNavigator.instance.navigateToSitePath('discussion', { params : { userId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.contactRequestsCountObserver?.off();
|
||||||
|
this.splitViewObserver?.off();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// 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 { InjectionToken, ModuleWithProviders, NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { ModuleRoutesConfig } from '@/app/app-routing.module';
|
||||||
|
|
||||||
|
export const ADDON_MESSAGES_CONTACTS_ROUTES = new InjectionToken('ADDON_MESSAGES_CONTACTS_ROUTES');
|
||||||
|
|
||||||
|
@NgModule()
|
||||||
|
export class AddonMessagesContactsRoutingModule {
|
||||||
|
|
||||||
|
static forChild(routes: ModuleRoutesConfig): ModuleWithProviders<AddonMessagesContactsRoutingModule> {
|
||||||
|
return {
|
||||||
|
ngModule: AddonMessagesContactsRoutingModule,
|
||||||
|
providers: [
|
||||||
|
{ provide: ADDON_MESSAGES_CONTACTS_ROUTES, multi: true, useValue: routes },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -43,6 +43,7 @@ export class AddonMessagesProvider {
|
||||||
static readonly READ_CHANGED_EVENT = 'addon_messages_read_changed_event';
|
static readonly READ_CHANGED_EVENT = 'addon_messages_read_changed_event';
|
||||||
static readonly OPEN_CONVERSATION_EVENT = 'addon_messages_open_conversation_event'; // Notify a conversation should be opened.
|
static readonly OPEN_CONVERSATION_EVENT = 'addon_messages_open_conversation_event'; // Notify a conversation should be opened.
|
||||||
static readonly SPLIT_VIEW_LOAD_INDEX_EVENT = 'addon_messages_split_view_load_index_event'; // Used on 3.5 or lower.
|
static readonly SPLIT_VIEW_LOAD_INDEX_EVENT = 'addon_messages_split_view_load_index_event'; // Used on 3.5 or lower.
|
||||||
|
static readonly SPLIT_VIEW_LOAD_CONTACTS_EVENT = 'addon_messages_split_view_load_contacts_event'; // Used on 3.6 or greater.
|
||||||
static readonly UPDATE_CONVERSATION_LIST_EVENT = 'addon_messages_update_conversation_list_event';
|
static readonly UPDATE_CONVERSATION_LIST_EVENT = 'addon_messages_update_conversation_list_event';
|
||||||
static readonly MEMBER_INFO_CHANGED_EVENT = 'addon_messages_member_changed_event';
|
static readonly MEMBER_INFO_CHANGED_EVENT = 'addon_messages_member_changed_event';
|
||||||
static readonly UNREAD_CONVERSATION_COUNTS_EVENT = 'addon_messages_unread_conversation_counts_event';
|
static readonly UNREAD_CONVERSATION_COUNTS_EVENT = 'addon_messages_unread_conversation_counts_event';
|
||||||
|
@ -3639,3 +3640,11 @@ export type AddonMessagesSplitViewLoadIndexEventData = {
|
||||||
onlyWithSplitView?: boolean;
|
onlyWithSplitView?: boolean;
|
||||||
message?: number;
|
message?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data sent by SPLIT_VIEW_LOAD_CONTACTS_EVENT event. Used on 3.6 or greater.
|
||||||
|
*/
|
||||||
|
export type AddonMessagesSplitViewLoadContactsEventData = {
|
||||||
|
userId: number;
|
||||||
|
onInit: boolean;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue