MOBILE-3631 messages: Fix split view on pages with tabs
parent
210518d549
commit
44552cfe3b
|
@ -16,8 +16,6 @@ import { Injector, NgModule } from '@angular/core';
|
|||
import { Route, RouterModule, ROUTES, Routes } from '@angular/router';
|
||||
|
||||
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 { AddonMessagesSettingsHandlerService } from './services/handlers/settings';
|
||||
|
||||
export const discussionRoute: Route = {
|
||||
|
@ -30,7 +28,12 @@ function buildRoutes(injector: Injector): Routes {
|
|||
return [
|
||||
{
|
||||
path: 'index', // 3.5 or lower.
|
||||
loadChildren: () => import('./pages/index-35/index.module').then( m => m.AddonMessagesIndex35PageModule),
|
||||
loadChildren: () =>
|
||||
import('./pages/discussions-35/discussions.module').then(m => m.AddonMessagesDiscussions35PageModule),
|
||||
},
|
||||
{
|
||||
path: 'contacts-35', // 3.5 or lower.
|
||||
loadChildren: () => import('./pages/contacts-35/contacts.module').then(m => m.AddonMessagesContacts35PageModule),
|
||||
},
|
||||
{
|
||||
path: 'group-conversations', // 3.6 or greater.
|
||||
|
@ -60,37 +63,7 @@ function buildRoutes(injector: Injector): Routes {
|
|||
];
|
||||
}
|
||||
|
||||
// 3.5 or lower.
|
||||
const indexTabRoutes: Routes = [
|
||||
{
|
||||
path: 'discussions',
|
||||
loadChildren: () => import('./pages/discussions-35/discussions.module').then(m => m.AddonMessagesDiscussions35PageModule),
|
||||
},
|
||||
{
|
||||
path: 'contacts',
|
||||
loadChildren: () => import('./pages/contacts-35/contacts.module').then(m => m.AddonMessagesContacts35PageModule),
|
||||
},
|
||||
];
|
||||
|
||||
// 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({
|
||||
imports: [
|
||||
AddonMessagesIndexRoutingModule.forChild({ children: indexTabRoutes }),
|
||||
AddonMessagesContactsRoutingModule.forChild({ children: contactsTabRoutes }),
|
||||
],
|
||||
exports: [RouterModule],
|
||||
providers: [
|
||||
{
|
||||
|
|
|
@ -1,35 +1,49 @@
|
|||
<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">
|
||||
<!-- 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>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-split-view>
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-search-box (onSubmit)="search($event)" (onClear)="clearSearch($event)"
|
||||
[placeholder]="'addon.messages.contactname' | translate" autocorrect="off" spellcheck="false" lengthCheck="2"
|
||||
[disabled]="!loaded" searchArea="AddonMessagesContacts"></core-search-box>
|
||||
<core-search-box (onSubmit)="search($event)" (onClear)="clearSearch($event)"
|
||||
[placeholder]="'addon.messages.contactname' | translate" autocorrect="off" spellcheck="false" lengthCheck="2"
|
||||
[disabled]="!loaded" searchArea="AddonMessagesContacts"></core-search-box>
|
||||
|
||||
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
|
||||
<core-empty-box *ngIf="!hasContacts && searchString == ''" icon="fas-address-book"
|
||||
[message]="'addon.messages.contactlistempty' | translate"></core-empty-box>
|
||||
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
|
||||
<core-empty-box *ngIf="!hasContacts && searchString == ''" icon="fas-address-book"
|
||||
[message]="'addon.messages.contactlistempty' | translate"></core-empty-box>
|
||||
|
||||
<core-empty-box *ngIf="!hasContacts && searchString != ''" icon="fas-address-book"
|
||||
[message]="'addon.messages.nousersfound' | translate"></core-empty-box>
|
||||
<core-empty-box *ngIf="!hasContacts && searchString != ''" icon="fas-address-book"
|
||||
[message]="'addon.messages.nousersfound' | translate"></core-empty-box>
|
||||
|
||||
<ion-list *ngFor="let contactType of contactTypes" class="ion-no-margin">
|
||||
<ng-container *ngIf="contacts[contactType] && (contacts[contactType].length > 0 || contactType === searchType)">
|
||||
<ion-item-divider>
|
||||
<ion-label><h2>{{ 'addon.messages.type_' + contactType | translate }}</h2></ion-label>
|
||||
<ion-note slot="end" class="ion-padding-end"><ion-badge>{{ contacts[contactType].length }}</ion-badge></ion-note>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngFor="let contact of contacts[contactType]">
|
||||
<!-- Don't show deleted users -->
|
||||
<ion-item class="ion-text-wrap addon-messages-conversation-item" *ngIf="contact.profileimageurl || contact.profileimageurlsmall"
|
||||
[title]="contact.fullname" (click)="gotoDiscussion(contact.id)" detail
|
||||
[class.core-selected-item]="contact.id == discussionUserId">
|
||||
<core-user-avatar [user]="contact" slot="start" [checkOnline]="contact.showonlinestatus"></core-user-avatar>
|
||||
<ion-label><h2>{{ contact.fullname }}</h2></ion-label>
|
||||
</ion-item>
|
||||
<ion-list *ngFor="let contactType of contactTypes" class="ion-no-margin">
|
||||
<ng-container *ngIf="contacts[contactType] && (contacts[contactType].length > 0 || contactType === searchType)">
|
||||
<ion-item-divider>
|
||||
<ion-label><h2>{{ 'addon.messages.type_' + contactType | translate }}</h2></ion-label>
|
||||
<ion-note slot="end" class="ion-padding-end"><ion-badge>{{ contacts[contactType].length }}</ion-badge></ion-note>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngFor="let contact of contacts[contactType]">
|
||||
<!-- Don't show deleted users -->
|
||||
<ion-item class="ion-text-wrap addon-messages-conversation-item" *ngIf="contact.profileimageurl || contact.profileimageurlsmall"
|
||||
[title]="contact.fullname" (click)="gotoDiscussion(contact.id)" detail
|
||||
[class.core-selected-item]="contact.id == discussionUserId">
|
||||
<core-user-avatar [user]="contact" slot="start" [checkOnline]="contact.showonlinestatus"></core-user-avatar>
|
||||
<ion-label><h2>{{ contact.fullname }}</h2></ion-label>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</core-split-view>
|
||||
</ion-content>
|
||||
|
|
|
@ -17,6 +17,9 @@ import { IonicModule } from '@ionic/angular';
|
|||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||
import { discussionRoute } from '@addons/messages/messages-lazy.module';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
||||
|
@ -25,9 +28,21 @@ import { AddonMessagesContacts35Page } from './contacts.page';
|
|||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
matcher: segments => {
|
||||
const matches = CoreScreen.instance.isMobile ? segments.length === 0 : true;
|
||||
|
||||
return matches ? { consumed: [] } : null;
|
||||
},
|
||||
component: AddonMessagesContacts35Page,
|
||||
children: conditionalRoutes([
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
discussionRoute,
|
||||
], () => CoreScreen.instance.isTablet),
|
||||
},
|
||||
...conditionalRoutes([discussionRoute], () => CoreScreen.instance.isMobile),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -21,14 +21,15 @@ import {
|
|||
AddonMessagesSearchContactsContact,
|
||||
AddonMessagesGetContactsContact,
|
||||
AddonMessages,
|
||||
AddonMessagesSplitViewLoadIndexEventData,
|
||||
AddonMessagesMemberInfoChangedEventData,
|
||||
} from '../../services/messages';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
|
||||
/**
|
||||
* Page that displays the list of contacts.
|
||||
|
@ -89,7 +90,15 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy {
|
|||
*/
|
||||
ngOnInit(): void {
|
||||
this.route.queryParams.subscribe(async params => {
|
||||
this.discussionUserId = params['discussionUserId'] || undefined;
|
||||
const discussionUserId = params['discussionUserId']
|
||||
? parseInt(params['discussionUserId'], 10)
|
||||
: (params['userId'] ? parseInt(params['userId'], 10) : undefined);
|
||||
|
||||
if (this.loaded && this.discussionUserId == discussionUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.discussionUserId = discussionUserId;
|
||||
|
||||
if (this.discussionUserId) {
|
||||
// There is a discussion to load, open the discussion in a new state.
|
||||
|
@ -98,7 +107,7 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy {
|
|||
|
||||
try {
|
||||
await this.fetchData();
|
||||
if (!this.discussionUserId && this.hasContacts) {
|
||||
if (!this.discussionUserId && this.hasContacts && CoreScreen.instance.isTablet) {
|
||||
let contact: AddonMessagesGetContactsContact | undefined;
|
||||
for (const x in this.contacts) {
|
||||
if (this.contacts[x].length > 0) {
|
||||
|
@ -109,7 +118,7 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy {
|
|||
|
||||
if (contact) {
|
||||
// Take first and load it.
|
||||
this.gotoDiscussion(contact.id, true);
|
||||
this.gotoDiscussion(contact.id);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
@ -235,16 +244,18 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy {
|
|||
* Navigate to a particular discussion.
|
||||
*
|
||||
* @param discussionUserId Discussion Id to load.
|
||||
* @param onlyWithSplitView Only go to Discussion if split view is on.
|
||||
*/
|
||||
gotoDiscussion(discussionUserId: number, onlyWithSplitView: boolean = false): void {
|
||||
gotoDiscussion(discussionUserId: number): void {
|
||||
this.discussionUserId = discussionUserId;
|
||||
|
||||
const params: AddonMessagesSplitViewLoadIndexEventData = {
|
||||
discussion: discussionUserId,
|
||||
onlyWithSplitView: onlyWithSplitView,
|
||||
const params: Params = {
|
||||
userId: discussionUserId,
|
||||
};
|
||||
CoreEvents.trigger(AddonMessagesProvider.SPLIT_VIEW_LOAD_INDEX_EVENT, params, this.siteId);
|
||||
|
||||
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/contacts-35/discussion');
|
||||
const path = (splitViewLoaded ? '../' : '') + 'discussion';
|
||||
|
||||
CoreNavigator.instance.navigate(path, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
<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 addon-messages-conversation-item" *ngFor="let contact of contacts"
|
||||
[title]="contact.fullname" (click)="selectUser(contact.id)" detail
|
||||
[class.core-selected-item]="contact.id == selectedUserId">
|
||||
<core-user-avatar slot="start" core-user-avatar [user]="contact" [checkOnline]="contact.showonlinestatus"
|
||||
[linkProfile]="false"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="contact.fullname" contextLevel="system" [contextInstanceId]="0"></core-format-text>
|
||||
<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>
|
|
@ -1,45 +0,0 @@
|
|||
// (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 {}
|
|
@ -1,162 +0,0 @@
|
|||
// (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 { CoreScreen } from '@services/screen';
|
||||
|
||||
/**
|
||||
* Component that displays the list of confirmed contacts.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-messages-confirmed-contacts',
|
||||
templateUrl: 'contacts-confirmed.html',
|
||||
styleUrls: ['../../messages-common.scss'],
|
||||
})
|
||||
export class AddonMessagesContactsConfirmedPage implements OnInit, OnDestroy {
|
||||
|
||||
loaded = false;
|
||||
canLoadMore = false;
|
||||
loadMoreError = false;
|
||||
contacts: AddonMessagesConversationMember[] = [];
|
||||
selectedUserId?: number;
|
||||
|
||||
protected memberInfoObserver: CoreEventObserver;
|
||||
|
||||
constructor() {
|
||||
// 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 && CoreScreen.instance.isTablet) {
|
||||
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;
|
||||
|
||||
CoreEvents.trigger<AddonMessagesSplitViewLoadContactsEventData>(
|
||||
AddonMessagesProvider.SPLIT_VIEW_LOAD_CONTACTS_EVENT,
|
||||
{
|
||||
userId,
|
||||
onInit,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.memberInfoObserver?.off();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<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 addon-messages-conversation-item" *ngFor="let request of requests"
|
||||
[title]="request.fullname" (click)="selectUser(request.id)"
|
||||
[class.core-selected-item]="request.id == selectedUserId" detail>
|
||||
<core-user-avatar slot="start" [user]="request" [linkProfile]="false"></core-user-avatar>
|
||||
<ion-label>
|
||||
<core-format-text [text]="request.fullname" contextLevel="system" [contextInstanceId]="0"></core-format-text>
|
||||
<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>
|
|
@ -1,45 +0,0 @@
|
|||
// (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 {}
|
|
@ -1,160 +0,0 @@
|
|||
// (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';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
|
||||
/**
|
||||
* Component that displays the list of contact requests.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-messages-contact-requests',
|
||||
templateUrl: 'contacts-requests.html',
|
||||
styleUrls: ['../../messages-common.scss'],
|
||||
})
|
||||
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 && CoreScreen.instance.isTablet) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.memberInfoObserver?.off();
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,77 @@
|
|||
</ion-header>
|
||||
<ion-content>
|
||||
<core-split-view>
|
||||
<core-tabs [tabs]="tabs" hideUntil="true"></core-tabs>
|
||||
<ion-tab-bar class="core-tabs-bar">
|
||||
<ion-row>
|
||||
<ion-col class="tab-slide" [attr.aria-selected]="selected == 'confirmed'" (click)="selectTab('confirmed', $event)">
|
||||
<ion-label>{{ 'addon.messages.contacts' | translate}}</ion-label>
|
||||
</ion-col>
|
||||
<ion-col class="tab-slide" [attr.aria-selected]="selected != 'confirmed'" (click)="selectTab('requests', $event)">
|
||||
<ion-label>
|
||||
{{ 'addon.messages.requests' | translate}}
|
||||
<ion-badge *ngIf="requestsBadge">{{ requestsBadge }}</ion-badge>
|
||||
</ion-label>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-tab-bar>
|
||||
<div *ngIf="selected == 'confirmed'">
|
||||
<ion-refresher slot="fixed" [disabled]="!confirmedLoaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="confirmedLoaded" class="core-loading-center">
|
||||
<ion-list class="ion-no-margin">
|
||||
<ion-item class="ion-text-wrap addon-messages-conversation-item" *ngFor="let contact of confirmedContacts"
|
||||
[title]="contact.fullname" (click)="selectUser(contact.id)" detail
|
||||
[class.core-selected-item]="contact.id == selectedUserId">
|
||||
<core-user-avatar slot="start" core-user-avatar [user]="contact" [checkOnline]="contact.showonlinestatus"
|
||||
[linkProfile]="false"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="contact.fullname" contextLevel="system" [contextInstanceId]="0">
|
||||
</core-format-text>
|
||||
<ion-icon *ngIf="contact.isblocked" name="fas-user-slash" slot="end">
|
||||
</ion-icon>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<core-empty-box *ngIf="!confirmedContacts.length" icon="far-address-book"
|
||||
[message]="'addon.messages.nocontactsgetstarted' | translate">
|
||||
</core-empty-box>
|
||||
|
||||
<core-infinite-loading [enabled]="confirmedCanLoadMore" (action)="loadMore($event)" [error]="confirmedLoadMoreError"
|
||||
position="bottom">
|
||||
</core-infinite-loading>
|
||||
</core-loading>
|
||||
</div>
|
||||
<div *ngIf="selected != 'confirmed'">
|
||||
<ion-refresher slot="fixed" [disabled]="!requestsLoaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="requestsLoaded" class="core-loading-center">
|
||||
<ion-list class="ion-no-margin">
|
||||
<ion-item class="ion-text-wrap addon-messages-conversation-item" *ngFor="let request of requests"
|
||||
[title]="request.fullname" (click)="selectUser(request.id)"
|
||||
[class.core-selected-item]="request.id == selectedUserId" detail>
|
||||
<core-user-avatar slot="start" [user]="request" [linkProfile]="false"></core-user-avatar>
|
||||
<ion-label>
|
||||
<core-format-text [text]="request.fullname" contextLevel="system" [contextInstanceId]="0">
|
||||
</core-format-text>
|
||||
<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]="requestsCanLoadMore" (action)="loadMore($event)" [error]="requestsLoadMoreError"
|
||||
position="bottom">
|
||||
</core-infinite-loading>
|
||||
</core-loading>
|
||||
</div>
|
||||
|
||||
</core-split-view>
|
||||
</ion-content>
|
||||
|
|
|
@ -12,22 +12,19 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injector, NgModule } from '@angular/core';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterModule, ROUTES, Routes } from '@angular/router';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { conditionalRoutes, resolveModuleRoutes } from '@/app/app-routing.module';
|
||||
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||
import { discussionRoute } from '@addons/messages/messages-lazy.module';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
|
||||
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';
|
||||
|
||||
// @todo mix both routes to get messages/contacts/requests/discussion and messages/contacts/confirmed/discussion working
|
||||
const routes: Routes = [
|
||||
{
|
||||
matcher: segments => {
|
||||
|
@ -47,19 +44,6 @@ const routes: Routes = [
|
|||
...conditionalRoutes([discussionRoute], () => CoreScreen.instance.isMobile),
|
||||
];
|
||||
|
||||
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),
|
||||
|
@ -68,9 +52,6 @@ function buildRoutes(injector: Injector): Routes {
|
|||
TranslateModule.forChild(),
|
||||
CoreSharedModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] },
|
||||
],
|
||||
declarations: [
|
||||
AddonMessagesContactsPage,
|
||||
],
|
||||
|
|
|
@ -18,12 +18,14 @@ import { CoreSites } from '@services/sites';
|
|||
import {
|
||||
AddonMessages,
|
||||
AddonMessagesContactRequestCountEventData,
|
||||
AddonMessagesConversationMember,
|
||||
AddonMessagesMemberInfoChangedEventData,
|
||||
AddonMessagesProvider,
|
||||
AddonMessagesSplitViewLoadContactsEventData,
|
||||
} from '../../services/messages';
|
||||
import { CoreTab } from '@components/tabs/tabs';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
|
||||
/**
|
||||
* Page that displays contacts and contact requests.
|
||||
|
@ -31,56 +33,71 @@ import { CoreScreen } from '@services/screen';
|
|||
@Component({
|
||||
selector: 'page-addon-messages-contacts',
|
||||
templateUrl: 'contacts.html',
|
||||
styleUrls: [
|
||||
'tabs.scss',
|
||||
'../../messages-common.scss',
|
||||
],
|
||||
})
|
||||
export class AddonMessagesContactsPage implements OnInit, OnDestroy {
|
||||
|
||||
protected contactsTab: CoreTab = {
|
||||
id: 'contacts-confirmed',
|
||||
class: '',
|
||||
title: 'addon.messages.contacts',
|
||||
icon: 'fas-address-book',
|
||||
enabled: true,
|
||||
page: '/main/messages/contacts/confirmed',
|
||||
};
|
||||
selected = 'confirmed';
|
||||
requestsBadge = '';
|
||||
selectedUserId?: number; // User id of the conversation opened in the split view.
|
||||
|
||||
protected requestsTab: CoreTab = {
|
||||
id: 'contact-requests',
|
||||
class: '',
|
||||
title: 'addon.messages.requests',
|
||||
icon: 'fas-user-plus',
|
||||
enabled: true,
|
||||
page: '/main/messages/contacts/requests',
|
||||
badge: '',
|
||||
};
|
||||
confirmedLoaded = false;
|
||||
confirmedCanLoadMore = false;
|
||||
confirmedLoadMoreError = false;
|
||||
confirmedContacts: AddonMessagesConversationMember[] = [];
|
||||
|
||||
tabs: CoreTab[] = [];
|
||||
requestsLoaded = false;
|
||||
requestsCanLoadMore = false;
|
||||
requestsLoadMoreError = false;
|
||||
requests: AddonMessagesConversationMember[] = [];
|
||||
|
||||
protected siteId: string;
|
||||
protected contactRequestsCountObserver: CoreEventObserver;
|
||||
protected splitViewObserver: CoreEventObserver;
|
||||
protected selectedUserId?: number; // User id of the conversation opened in the split view.
|
||||
protected memberInfoObserver: CoreEventObserver;
|
||||
|
||||
|
||||
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.requestsBadge = data.count > 0 ? String(data.count) : '';
|
||||
},
|
||||
this.siteId,
|
||||
);
|
||||
|
||||
// Update the contact requests badge.
|
||||
this.splitViewObserver = CoreEvents.on<AddonMessagesSplitViewLoadContactsEventData>(
|
||||
AddonMessagesProvider.SPLIT_VIEW_LOAD_CONTACTS_EVENT,
|
||||
// Update block status of a user.
|
||||
this.memberInfoObserver = CoreEvents.on<AddonMessagesMemberInfoChangedEventData>(
|
||||
AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT,
|
||||
(data) => {
|
||||
this.selectUser(data.userId, data.onInit);
|
||||
if (data.userBlocked || data.userUnblocked) {
|
||||
const user = this.confirmedContacts.find((user) => user.id == data.userId);
|
||||
if (user) {
|
||||
user.isblocked = !!data.userBlocked;
|
||||
}
|
||||
} else if (data.contactRemoved) {
|
||||
const index = this.confirmedContacts.findIndex((contact) => contact.id == data.userId);
|
||||
if (index >= 0) {
|
||||
this.confirmedContacts.splice(index, 1);
|
||||
}
|
||||
} else if (data.contactRequestConfirmed) {
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
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(),
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -88,8 +105,123 @@ export class AddonMessagesContactsPage implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Page being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
async ngOnInit(): Promise<void> {
|
||||
AddonMessages.instance.getContactRequestsCount(this.siteId); // Badge already updated by the observer.
|
||||
|
||||
if (this.selected == 'confirmed') {
|
||||
try {
|
||||
|
||||
await this.confirmedFetchData();
|
||||
if (this.confirmedContacts.length && CoreScreen.instance.isTablet) {
|
||||
this.selectUser(this.confirmedContacts[0].id, true);
|
||||
}
|
||||
} finally {
|
||||
this.confirmedLoaded = true;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await this.requestsFetchData();
|
||||
if (this.requests.length && CoreScreen.instance.isTablet) {
|
||||
this.selectUser(this.requests[0].id, true);
|
||||
}
|
||||
} finally {
|
||||
this.requestsLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch contacts.
|
||||
*
|
||||
* @param refresh True if we are refreshing contacts, false if we are loading more.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async confirmedFetchData(refresh: boolean = false): Promise<void> {
|
||||
this.confirmedLoadMoreError = false;
|
||||
|
||||
const limitFrom = refresh ? 0 : this.confirmedContacts.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.confirmedContacts = refresh ? result.contacts : this.confirmedContacts.concat(result.contacts);
|
||||
this.confirmedCanLoadMore = result.canLoadMore;
|
||||
} catch (error) {
|
||||
this.confirmedLoadMoreError = true;
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch contact requests.
|
||||
*
|
||||
* @param refresh True if we are refreshing contact requests, false if we are loading more.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async requestsFetchData(refresh: boolean = false): Promise<void> {
|
||||
this.requestsLoadMoreError = 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.requestsCanLoadMore = result.canLoadMore;
|
||||
} catch (error) {
|
||||
this.requestsLoadMoreError = true;
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh contacts or requests.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async refreshData(refresher?: CustomEvent<IonRefresher>): Promise<void> {
|
||||
try {
|
||||
if (this.selected == 'confirmed') {
|
||||
// No need to invalidate contacts, we always try to get the latest.
|
||||
await this.confirmedFetchData(true);
|
||||
} else {
|
||||
// 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.requestsFetchData(true);
|
||||
}
|
||||
} finally {
|
||||
refresher?.detail.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load more contacts or requests.
|
||||
*
|
||||
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
async loadMore(infiniteComplete?: () => void): Promise<void> {
|
||||
try {
|
||||
if (this.selected == 'confirmed') {
|
||||
// No need to invalidate contacts, we always try to get the latest.
|
||||
await this.confirmedFetchData();
|
||||
} else {
|
||||
await this.requestsFetchData();
|
||||
}
|
||||
} finally {
|
||||
infiniteComplete && infiniteComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,6 +231,14 @@ export class AddonMessagesContactsPage implements OnInit, OnDestroy {
|
|||
CoreNavigator.instance.navigateToSitePath('search');
|
||||
}
|
||||
|
||||
selectTab(selected: string): void {
|
||||
this.selected = selected;
|
||||
|
||||
if ((this.selected == 'confirmed' && !this.confirmedLoaded) || (this.selected != 'confirmed' && !this.requestsLoaded)) {
|
||||
this.ngOnInit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selected user and open the conversation in the split view if needed.
|
||||
*
|
||||
|
@ -118,14 +258,8 @@ export class AddonMessagesContactsPage implements OnInit, OnDestroy {
|
|||
|
||||
this.selectedUserId = userId;
|
||||
|
||||
// @todo it does not seem to work load anything.
|
||||
let path = 'discussion';
|
||||
if (CoreScreen.instance.isMobile) {
|
||||
path = '../../' + path;
|
||||
} else {
|
||||
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/contacts/**/discussion');
|
||||
path = (splitViewLoaded ? '../' : '') + path;
|
||||
}
|
||||
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/contacts/discussion');
|
||||
const path = (splitViewLoaded ? '../' : '') + 'discussion';
|
||||
|
||||
CoreNavigator.instance.navigate(path, { params : { userId } });
|
||||
}
|
||||
|
@ -135,7 +269,6 @@ export class AddonMessagesContactsPage implements OnInit, OnDestroy {
|
|||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.contactRequestsCountObserver?.off();
|
||||
this.splitViewObserver?.off();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
// (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 },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
:host {
|
||||
ion-tab-bar.core-tabs-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: var(--core-tabs-background);
|
||||
color: var(--core-tab-color);
|
||||
-webkit-filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow)));
|
||||
filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow)));
|
||||
border: 0;
|
||||
|
||||
ion-row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tab-slide {
|
||||
border-bottom: 2px solid transparent;
|
||||
min-width: 100px;
|
||||
min-height: 56px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
margin-bottom: 1px;
|
||||
|
||||
ion-label {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
line-height: 1.2em;
|
||||
margin: 16px auto;
|
||||
}
|
||||
|
||||
&[aria-selected=true] {
|
||||
color: var(--core-tab-border-color-active);
|
||||
border-bottom-color: var(--core-tab-color-active);
|
||||
ion-tab-button {
|
||||
color: var(--core-tab-border-color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +1,78 @@
|
|||
<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.messages' | translate }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<!-- 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>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-split-view>
|
||||
<ion-refresher slot="fixed" [disabled]="!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" searchArea="AddonMessagesDiscussions"></core-search-box>
|
||||
<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" searchArea="AddonMessagesDiscussions"></core-search-box>
|
||||
|
||||
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
|
||||
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
|
||||
|
||||
<ion-list *ngIf="search.showResults" class="ion-no-margin">
|
||||
<ion-item-divider>
|
||||
<ion-label>
|
||||
<h2>{{ 'core.searchresults' | translate }}</h2>
|
||||
</ion-label>
|
||||
<ion-note slot="end" class="ion-padding-end"><ion-badge>{{ search.results.length }}</ion-badge></ion-note>
|
||||
</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let result of search.results" [title]="result.fullname"
|
||||
(click)="gotoDiscussion(result.userid, result.messageid)"
|
||||
[class.core-selected-item]="result.userid == discussionUserId" class="addon-message-discussion">
|
||||
<core-user-avatar [user]="result" slot="start" [checkOnline]="result.showonlinestatus"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{ result.fullname }}</h2>
|
||||
<p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage" contextLevel="system"
|
||||
[contextInstanceId]="0"></core-format-text></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-list class="ion-no-margin">
|
||||
|
||||
<ion-list *ngIf="!search.showResults" class="ion-no-margin">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let discussion of discussions" [title]="discussion.fullname"
|
||||
(click)="gotoDiscussion(discussion.message!.user)"
|
||||
[class.core-selected-item]="discussion.message!.user == discussionUserId" class="addon-message-discussion">
|
||||
<core-user-avatar [user]="discussion" slot="start" checkOnline="false"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{ discussion.fullname }}</h2>
|
||||
<ion-note *ngIf="discussion.message!.timecreated > 0 || discussion.unread">
|
||||
<span *ngIf="discussion.unread" class="core-primary-circle"></span>
|
||||
<span *ngIf="discussion.message!.timecreated > 0">{{discussion.message!.timecreated / 1000 | coreDateDayOrTime}}</span>
|
||||
</ion-note>
|
||||
<p>
|
||||
<core-format-text clean="true" singleLine="true" [text]="discussion.message!.message" contextLevel="system" [contextInstanceId]="0">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts($event)"
|
||||
[attr.aria-label]="'addon.messages.contacts' | translate" detail>
|
||||
<ion-icon name="fas-address-book" slot="start"></ion-icon>
|
||||
<ion-label><h2>{{ 'addon.messages.contacts' | translate }}</h2></ion-label>
|
||||
</ion-item>
|
||||
|
||||
<core-empty-box *ngIf="(!discussions || discussions.length <= 0) && !search.showResults" icon="far-comments"
|
||||
[message]="'addon.messages.nomessagesfound' | translate"></core-empty-box>
|
||||
<ng-container *ngIf="search.showResults">
|
||||
<ion-item-divider>
|
||||
<ion-label>
|
||||
<h2>{{ 'core.searchresults' | translate }}</h2>
|
||||
</ion-label>
|
||||
<ion-note slot="end" class="ion-padding-end"><ion-badge>{{ search.results.length }}</ion-badge></ion-note>
|
||||
</ion-item-divider>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let result of search.results" [title]="result.fullname"
|
||||
(click)="gotoDiscussion(result.userid, result.messageid)"
|
||||
[class.core-selected-item]="result.userid == discussionUserId" class="addon-message-discussion">
|
||||
<core-user-avatar [user]="result" slot="start" [checkOnline]="result.showonlinestatus"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{ result.fullname }}</h2>
|
||||
<p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage" contextLevel="system"
|
||||
[contextInstanceId]="0"></core-format-text></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!search.showResults">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let discussion of discussions" [title]="discussion.fullname"
|
||||
(click)="gotoDiscussion(discussion.message!.user)"
|
||||
[class.core-selected-item]="discussion.message!.user == discussionUserId" class="addon-message-discussion">
|
||||
<core-user-avatar [user]="discussion" slot="start" checkOnline="false"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{ discussion.fullname }}</h2>
|
||||
<ion-note *ngIf="discussion.message!.timecreated > 0 || discussion.unread">
|
||||
<span *ngIf="discussion.unread" class="core-primary-circle"></span>
|
||||
<span *ngIf="discussion.message!.timecreated > 0">{{discussion.message!.timecreated / 1000 | coreDateDayOrTime}}</span>
|
||||
</ion-note>
|
||||
<p>
|
||||
<core-format-text clean="true" singleLine="true" [text]="discussion.message!.message" contextLevel="system" [contextInstanceId]="0">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
|
||||
<core-empty-box *ngIf="(!search.results || search.results.length <= 0) && search.showResults" icon="search"
|
||||
[message]="'core.noresults' | translate"></core-empty-box>
|
||||
</core-loading>
|
||||
<core-empty-box *ngIf="(!discussions || discussions.length <= 0) && !search.showResults" icon="far-comments"
|
||||
[message]="'addon.messages.nomessagesfound' | translate"></core-empty-box>
|
||||
|
||||
<core-empty-box *ngIf="(!search.results || search.results.length <= 0) && search.showResults" icon="search"
|
||||
[message]="'core.noresults' | translate"></core-empty-box>
|
||||
</core-loading>
|
||||
</core-split-view>
|
||||
</ion-content>
|
||||
|
|
|
@ -17,6 +17,9 @@ import { IonicModule } from '@ionic/angular';
|
|||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||
import { discussionRoute } from '@addons/messages/messages-lazy.module';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
||||
|
@ -25,9 +28,21 @@ import { AddonMessagesDiscussions35Page } from './discussions.page';
|
|||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
matcher: segments => {
|
||||
const matches = CoreScreen.instance.isMobile ? segments.length === 0 : true;
|
||||
|
||||
return matches ? { consumed: [] } : null;
|
||||
},
|
||||
component: AddonMessagesDiscussions35Page,
|
||||
children: conditionalRoutes([
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
discussionRoute,
|
||||
], () => CoreScreen.instance.isTablet),
|
||||
},
|
||||
...conditionalRoutes([discussionRoute], () => CoreScreen.instance.isMobile),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -22,17 +22,18 @@ import {
|
|||
AddonMessagesNewMessagedEventData,
|
||||
AddonMessagesProvider,
|
||||
AddonMessagesReadChangedEventData,
|
||||
AddonMessagesSplitViewLoadIndexEventData,
|
||||
} from '../../services/messages';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
|
||||
import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Translate, Platform } from '@singletons';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
|
||||
/**
|
||||
* Page that displays the list of discussions.
|
||||
|
@ -46,7 +47,6 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
|
||||
protected newMessagesObserver: CoreEventObserver;
|
||||
protected readChangedObserver: CoreEventObserver;
|
||||
protected cronObserver: CoreEventObserver;
|
||||
protected appResumeSubscription: Subscription;
|
||||
protected pushObserver: Subscription;
|
||||
protected loadingMessages: string;
|
||||
|
@ -121,11 +121,6 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
this.siteId,
|
||||
);
|
||||
|
||||
// Update unread conversation counts.
|
||||
this.cronObserver = CoreEvents.on(AddonMessagesProvider.UNREAD_CONVERSATION_COUNTS_EVENT, () => {
|
||||
AddonMessages.instance.refreshUnreadConversationCounts(this.siteId);
|
||||
}, this.siteId);
|
||||
|
||||
// Refresh the view when the app is resumed.
|
||||
this.appResumeSubscription = Platform.instance.resume.subscribe(() => {
|
||||
if (!this.loaded) {
|
||||
|
@ -152,7 +147,15 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
*/
|
||||
ngOnInit(): void {
|
||||
this.route.queryParams.subscribe(async params => {
|
||||
this.discussionUserId = params['discussionUserId'] || undefined;
|
||||
const discussionUserId = params['discussionUserId']
|
||||
? parseInt(params['discussionUserId'], 10)
|
||||
: (params['userId'] ? parseInt(params['userId'], 10) : undefined);
|
||||
|
||||
if (this.loaded && this.discussionUserId == discussionUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.discussionUserId = discussionUserId;
|
||||
|
||||
if (this.discussionUserId) {
|
||||
// There is a discussion to load, open the discussion in a new state.
|
||||
|
@ -161,9 +164,9 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
|
||||
await this.fetchData();
|
||||
|
||||
if (!this.discussionUserId && this.discussions.length > 0) {
|
||||
if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.instance.isTablet) {
|
||||
// Take first and load it.
|
||||
this.gotoDiscussion(this.discussions[0].message!.user, undefined, true);
|
||||
this.gotoDiscussion(this.discussions[0].message!.user);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -267,17 +270,34 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
* @param messageId Message to scroll after loading the discussion. Used when searching.
|
||||
* @param onlyWithSplitView Only go to Discussion if split view is on.
|
||||
*/
|
||||
gotoDiscussion(discussionUserId: number, messageId?: number, onlyWithSplitView: boolean = false): void {
|
||||
gotoDiscussion(discussionUserId: number, messageId?: number): void {
|
||||
this.discussionUserId = discussionUserId;
|
||||
|
||||
const params: AddonMessagesSplitViewLoadIndexEventData = {
|
||||
discussion: discussionUserId,
|
||||
onlyWithSplitView: onlyWithSplitView,
|
||||
const params: Params = {
|
||||
userId: discussionUserId,
|
||||
};
|
||||
|
||||
if (messageId) {
|
||||
params.message = messageId;
|
||||
}
|
||||
CoreEvents.trigger(AddonMessagesProvider.SPLIT_VIEW_LOAD_INDEX_EVENT, params, this.siteId);
|
||||
|
||||
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/index/discussion');
|
||||
const path = (splitViewLoaded ? '../' : '') + 'discussion';
|
||||
|
||||
CoreNavigator.instance.navigate(path, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to contacts view.
|
||||
*/
|
||||
gotoContacts(): void {
|
||||
const params: Params = {};
|
||||
|
||||
if (CoreScreen.instance.isTablet && this.discussionUserId) {
|
||||
params.discussionUserId = this.discussionUserId;
|
||||
}
|
||||
|
||||
CoreNavigator.instance.navigateToSitePath('contacts-35', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -286,7 +306,6 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
ngOnDestroy(): void {
|
||||
this.newMessagesObserver?.off();
|
||||
this.readChangedObserver?.off();
|
||||
this.cronObserver?.off();
|
||||
this.appResumeSubscription?.unsubscribe();
|
||||
this.pushObserver?.unsubscribe();
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
|
||||
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
|
||||
<ion-list>
|
||||
<ion-item class="ion-text-wrap" (click)="gotoContacts($event)" [attr.aria-label]="'addon.messages.contacts' | translate"
|
||||
class="addon-message-discussion">
|
||||
<ion-item class="ion-text-wrap addon-message-discussion" (click)="gotoContacts($event)"
|
||||
[attr.aria-label]="'addon.messages.contacts' | translate" detail>
|
||||
<ion-icon name="fas-address-book" slot="start"></ion-icon>
|
||||
<ion-label><h2>{{ 'addon.messages.contacts' | translate }}</h2></ion-label>
|
||||
<ion-badge *ngIf="contactRequestsCount > 0" slot="end">{{contactRequestsCount}}</ion-badge>
|
||||
|
|
|
@ -511,7 +511,6 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
*/
|
||||
gotoContacts(): void {
|
||||
CoreNavigator.instance.navigateToSitePath('contacts');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
<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.messages' | translate }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<!-- 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>
|
||||
<ion-content>
|
||||
<core-split-view>
|
||||
<core-tabs [tabs]="tabs" hideUntil="true"></core-tabs>
|
||||
</core-split-view>
|
||||
</ion-content>
|
|
@ -1,79 +0,0 @@
|
|||
// (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 { conditionalRoutes, resolveModuleRoutes } from '@/app/app-routing.module';
|
||||
import { discussionRoute } from '@addons/messages/messages-lazy.module';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
|
||||
import { AddonMessagesIndex35Page } from './index.page';
|
||||
import { ADDON_MESSAGES_INDEX_ROUTES } from './messages-index-routing.module';
|
||||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
|
||||
// @todo mix both routes to get messages/index/discussions/discussion and messages/index/contacts/discussion working
|
||||
const routes: Routes = [
|
||||
{
|
||||
matcher: segments => {
|
||||
const matches = CoreScreen.instance.isMobile ? segments.length === 0 : true;
|
||||
|
||||
return matches ? { consumed: [] } : null;
|
||||
},
|
||||
component: AddonMessagesIndex35Page,
|
||||
children: conditionalRoutes([
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
discussionRoute,
|
||||
], () => CoreScreen.instance.isTablet),
|
||||
},
|
||||
...conditionalRoutes([discussionRoute], () => CoreScreen.instance.isMobile),
|
||||
];
|
||||
|
||||
function buildRoutes(injector: Injector): Routes {
|
||||
const routes = resolveModuleRoutes(injector, ADDON_MESSAGES_INDEX_ROUTES);
|
||||
|
||||
return [
|
||||
...buildTabMainRoutes(injector, {
|
||||
path: '',
|
||||
component: AddonMessagesIndex35Page,
|
||||
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: [
|
||||
AddonMessagesIndex35Page,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AddonMessagesIndex35PageModule {}
|
|
@ -1,104 +0,0 @@
|
|||
// (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 } from '@angular/core';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreTab } from '@components/tabs/tabs';
|
||||
import { Params } from '@angular/router';
|
||||
import { AddonMessagesProvider, AddonMessagesSplitViewLoadIndexEventData } from '../../services/messages';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
|
||||
/**
|
||||
* Page that displays the messages index page.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-messages-index',
|
||||
templateUrl: 'index.html',
|
||||
})
|
||||
export class AddonMessagesIndex35Page implements OnDestroy {
|
||||
|
||||
tabs: CoreTab[] = [
|
||||
{
|
||||
id: 'discussions-35',
|
||||
class: '',
|
||||
title: 'addon.messages.messages',
|
||||
icon: 'fas-comments',
|
||||
enabled: true,
|
||||
page: 'main/messages/index/discussions',
|
||||
},
|
||||
{
|
||||
id: 'contacts-35',
|
||||
class: '',
|
||||
title: 'addon.messages.contacts',
|
||||
icon: 'fas-address-book',
|
||||
enabled: true,
|
||||
page: 'main/messages/index/contacts',
|
||||
},
|
||||
];
|
||||
|
||||
protected loadSplitViewObserver?: CoreEventObserver;
|
||||
protected siteId: string;
|
||||
|
||||
constructor() {
|
||||
this.siteId = CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
// Update split view or navigate.
|
||||
this.loadSplitViewObserver = CoreEvents.on<AddonMessagesSplitViewLoadIndexEventData>(
|
||||
AddonMessagesProvider.SPLIT_VIEW_LOAD_INDEX_EVENT,
|
||||
(data) => {
|
||||
if (data.discussion && (CoreScreen.instance.isTablet || !data.onlyWithSplitView)) {
|
||||
this.gotoDiscussion(data.discussion, data.message);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a particular discussion.
|
||||
*
|
||||
* @param discussionUserId Discussion Id to load.
|
||||
* @param messageId Message to scroll after loading the discussion. Used when searching.
|
||||
*/
|
||||
gotoDiscussion(discussionUserId: number, messageId?: number): void {
|
||||
const params: Params = {
|
||||
userId: discussionUserId,
|
||||
};
|
||||
|
||||
if (messageId) {
|
||||
params.message = messageId;
|
||||
}
|
||||
|
||||
let path = 'discussion';
|
||||
if (CoreScreen.instance.isMobile) {
|
||||
path = '../../' + path;
|
||||
} else {
|
||||
const splitViewLoaded = CoreNavigator.instance.isSplitViewOutletLoaded('**/messages/index/**/discussion');
|
||||
path = (splitViewLoaded ? '../' : '') + path;
|
||||
}
|
||||
|
||||
CoreNavigator.instance.navigate(path, { params });
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.loadSplitViewObserver?.off();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// (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_INDEX_ROUTES = new InjectionToken('ADDON_MESSAGES_INDEX_ROUTES');
|
||||
|
||||
@NgModule()
|
||||
export class AddonMessagesIndexRoutingModule {
|
||||
|
||||
static forChild(routes: ModuleRoutesConfig): ModuleWithProviders<AddonMessagesIndexRoutingModule> {
|
||||
return {
|
||||
ngModule: AddonMessagesIndexRoutingModule,
|
||||
providers: [
|
||||
{ provide: ADDON_MESSAGES_INDEX_ROUTES, multi: true, useValue: routes },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -42,8 +42,6 @@ export class AddonMessagesProvider {
|
|||
static readonly NEW_MESSAGE_EVENT = 'addon_messages_new_message_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 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 MEMBER_INFO_CHANGED_EVENT = 'addon_messages_member_changed_event';
|
||||
static readonly UNREAD_CONVERSATION_COUNTS_EVENT = 'addon_messages_unread_conversation_counts_event';
|
||||
|
@ -3696,23 +3694,6 @@ export type AddonMessagesMemberInfoChangedEventData = {
|
|||
contactRemoved?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data sent by SPLIT_VIEW_LOAD_INDEX_EVENT event. Used on 3.5 or lower.
|
||||
*/
|
||||
export type AddonMessagesSplitViewLoadIndexEventData = {
|
||||
discussion: number;
|
||||
onlyWithSplitView?: boolean;
|
||||
message?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data sent by SPLIT_VIEW_LOAD_CONTACTS_EVENT event. Used on 3.6 or greater.
|
||||
*/
|
||||
export type AddonMessagesSplitViewLoadContactsEventData = {
|
||||
userId: number;
|
||||
onInit: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data sent by READ_CHANGED_EVENT event.
|
||||
*/
|
||||
|
|
|
@ -135,14 +135,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
--core-tabs-background: var(--custom-tabs-background, var(--white));
|
||||
--core-tab-background: var(--custom-tab-background, var(--core-tabs-background));
|
||||
--core-tab-color: var(--custom-tab-color, var(--gray-dark));
|
||||
--core-tab-border-color: var(--custom-tab-border-color, var(--gray));
|
||||
--core-tab-color-active: var(--custom-tab-color-active, var(--core-color));
|
||||
--core-tab-border-color-active: var(--custom-tab-border-color-active, var(--core-color));
|
||||
|
||||
core-tabs {
|
||||
--background: var(--custom-tabs-background, var(--white));
|
||||
--background: var(--core-tabs-background);
|
||||
ion-slide {
|
||||
--background: var(--custom-tab-background, var(--white));
|
||||
--color: var(--custom-tab-background, var(--gray-dark));
|
||||
--border-color: var(--custom-tab-border-color, var(--gray));
|
||||
--color-active: var(--custom-tab-color-active, var(--color));
|
||||
--border-color-active: var(--custom-tab-border-color-active, var(--core-color));
|
||||
--background: var(--core-tab-background);
|
||||
--color: var(--core-tab-color);
|
||||
--border-color: var(--core-tab-border-colo);
|
||||
--color-active: var(--core-tab-color-active);
|
||||
--border-color-active: var(--core-tab-border-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,14 +283,10 @@
|
|||
--contrast-background: var(--ion-background-color);
|
||||
}
|
||||
|
||||
core-tabs {
|
||||
--background: var(--custom-tabs-background, #3a3a3a);
|
||||
ion-slide {
|
||||
--background: var(--custom-tab-background, #3a3a3a);
|
||||
--color: var(--custom-tab-background, var(--white));
|
||||
--border-color: var(--custom-tab-border-color, var(--gray-light));
|
||||
}
|
||||
}
|
||||
--core-tabs-background: var(--custom-tabs-background, #3a3a3a);
|
||||
--core-tab-background: var(--custom-tab-background, #3a3a3a);
|
||||
--core-tab-color: var(--custom-tab-color, var(--white));
|
||||
--core-tab-border-color: var(--custom-tab-border-color, var(--gray-light));
|
||||
|
||||
core-progress-bar {
|
||||
--text-color: var(--custom-progress-text-color, var(--gray-lighter));
|
||||
|
|
Loading…
Reference in New Issue