diff --git a/src/addon/messages/components/components.module.ts b/src/addon/messages/components/components.module.ts index 929d642f4..1c8091937 100644 --- a/src/addon/messages/components/components.module.ts +++ b/src/addon/messages/components/components.module.ts @@ -20,11 +20,15 @@ import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; import { AddonMessagesDiscussionsComponent } from '../components/discussions/discussions'; +import { AddonMessagesConfirmedContactsComponent } from '../components/confirmed-contacts/confirmed-contacts'; +import { AddonMessagesContactRequestsComponent } from '../components/contact-requests/contact-requests'; import { AddonMessagesContactsComponent } from '../components/contacts/contacts'; @NgModule({ declarations: [ AddonMessagesDiscussionsComponent, + AddonMessagesConfirmedContactsComponent, + AddonMessagesContactRequestsComponent, AddonMessagesContactsComponent ], imports: [ @@ -39,6 +43,8 @@ import { AddonMessagesContactsComponent } from '../components/contacts/contacts' ], exports: [ AddonMessagesDiscussionsComponent, + AddonMessagesConfirmedContactsComponent, + AddonMessagesContactRequestsComponent, AddonMessagesContactsComponent ] }) diff --git a/src/addon/messages/components/confirmed-contacts/addon-messages-confirmed-contacts.html b/src/addon/messages/components/confirmed-contacts/addon-messages-confirmed-contacts.html new file mode 100644 index 000000000..cea86c838 --- /dev/null +++ b/src/addon/messages/components/confirmed-contacts/addon-messages-confirmed-contacts.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts b/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts new file mode 100644 index 000000000..7a4a3d1da --- /dev/null +++ b/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts @@ -0,0 +1,141 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; +import { Content } from 'ionic-angular'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { AddonMessagesProvider } from '../../providers/messages'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; + +/** + * Component that displays the list of confirmed contacts. + */ +@Component({ + selector: 'addon-messages-confirmed-contacts', + templateUrl: 'addon-messages-confirmed-contacts.html', +}) +export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestroy { + @Output() onUserSelected = new EventEmitter<{userId: number, onInit?: boolean}>(); + @ViewChild(Content) content: Content; + + loaded = false; + canLoadMore = false; + loadMoreError = false; + contacts = []; + selectedUserId: number; + + protected memberInfoObserver; + + constructor(private domUtils: CoreDomUtilsProvider, eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, + private messagesProvider: AddonMessagesProvider) { + + this.onUserSelected = new EventEmitter(); + + // Update block status of a user. + this.memberInfoObserver = eventsProvider.on(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); + } + } + }, sitesProvider.getCurrentSiteId()); + } + + /** + * Component loaded. + */ + ngOnInit(): void { + this.fetchData().then(() => { + if (this.contacts.length) { + this.selectUser(this.contacts[0].id, true); + } + }).finally(() => { + this.loaded = true; + }); + + // Workaround for infinite scrolling. + this.content.resize(); + } + + /** + * Fetch contacts. + * + * @param {boolean} [refresh=false] True if we are refreshing contacts, false if we are loading more. + * @return {Promise} Promise resolved when done. + */ + fetchData(refresh: boolean = false): Promise { + this.loadMoreError = false; + + const limitFrom = refresh ? 0 : this.contacts.length; + + return this.messagesProvider.getUserContacts(limitFrom).then((result) => { + this.contacts = refresh ? result.contacts : this.contacts.concat(result.contacts); + this.canLoadMore = result.canLoadMore; + }).catch((error) => { + this.loadMoreError = true; + this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true); + }); + } + + /** + * Refresh contacts. + * + * @param {any} [refresher] Refresher. + * @return {Promise} Promise resolved when done. + */ + refreshData(refresher?: any): Promise { + return this.messagesProvider.invalidateUserContacts().then(() => { + return this.fetchData(true); + }).finally(() => { + refresher && refresher.complete(); + }); + } + + /** + * Load more contacts. + * + * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. + * @return {Promise} Resolved when done. + */ + loadMore(infiniteComplete?: any): Promise { + return this.fetchData().finally(() => { + infiniteComplete && infiniteComplete(); + }); + } + + /** + * Notify that a contact has been selected. + * + * @param {number} userId User id. + * @param {boolean} [onInit=false] Whether the contact is selected on initial load. + */ + selectUser(userId: number, onInit: boolean = false): void { + this.selectedUserId = userId; + this.onUserSelected.emit({userId, onInit}); + } + + /** + * Component destroyed. + */ + ngOnDestroy(): void { + this.memberInfoObserver && this.memberInfoObserver.off(); + } +} diff --git a/src/addon/messages/components/contact-requests/addon-messages-contact-requests.html b/src/addon/messages/components/contact-requests/addon-messages-contact-requests.html new file mode 100644 index 000000000..d55408208 --- /dev/null +++ b/src/addon/messages/components/contact-requests/addon-messages-contact-requests.html @@ -0,0 +1,16 @@ + + + + + + + + + + {{ 'addon.messages.wouldliketocontactyou' | translate }} + + + + + + diff --git a/src/addon/messages/components/contact-requests/contact-requests.ts b/src/addon/messages/components/contact-requests/contact-requests.ts new file mode 100644 index 000000000..854bd90bb --- /dev/null +++ b/src/addon/messages/components/contact-requests/contact-requests.ts @@ -0,0 +1,137 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; +import { Content } from 'ionic-angular'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { AddonMessagesProvider } from '../../providers/messages'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; + +/** + * Component that displays the list of contact requests. + */ +@Component({ + selector: 'addon-messages-contact-requests', + templateUrl: 'addon-messages-contact-requests.html', +}) +export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy { + @Output() onUserSelected = new EventEmitter<{userId: number, onInit?: boolean}>(); + @ViewChild(Content) content: Content; + + loaded = false; + canLoadMore = false; + loadMoreError = false; + requests = []; + selectedUserId: number; + + protected memberInfoObserver; + + constructor(private domUtils: CoreDomUtilsProvider, eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, + private messagesProvider: AddonMessagesProvider) { + + // Hide the "Would like to contact you" message when a contact request is confirmed. + this.memberInfoObserver = eventsProvider.on(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); + } + } + }, sitesProvider.getCurrentSiteId()); + } + + /** + * Component loaded. + */ + ngOnInit(): void { + this.fetchData().then(() => { + if (this.requests.length) { + this.selectUser(this.requests[0].id, true); + } + }).finally(() => { + this.loaded = true; + }); + + // Workaround for infinite scrolling. + this.content.resize(); + } + + /** + * Fetch contact requests. + * + * @param {boolean} [refresh=false] True if we are refreshing contact requests, false if we are loading more. + * @return {Promise} Promise resolved when done. + */ + fetchData(refresh: boolean = false): Promise { + this.loadMoreError = false; + + const limitFrom = refresh ? 0 : this.requests.length; + + return this.messagesProvider.getContactRequests(limitFrom).then((result) => { + this.requests = refresh ? result.requests : this.requests.concat(result.requests); + this.canLoadMore = result.canLoadMore; + }).catch((error) => { + this.loadMoreError = true; + this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true); + }); + } + + /** + * Refresh contact requests. + * + * @param {any} [refresher] Refresher. + * @return {Promise} Promise resolved when done. + */ + refreshData(refresher?: any): Promise { + // Refresh the number of contacts requests to update badges. + this.messagesProvider.refreshContactRequestsCount(); + + return this.messagesProvider.invalidateContactRequestsCache().then(() => { + return this.fetchData(true); + }).finally(() => { + refresher && refresher.complete(); + }); + } + + /** + * Load more contact requests. + * + * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. + * @return {Promise} Resolved when done. + */ + loadMore(infiniteComplete?: any): Promise { + return this.fetchData().finally(() => { + infiniteComplete && infiniteComplete(); + }); + } + + /** + * Notify that a contact has been selected. + * + * @param {number} userId User id. + * @param {boolean} [onInit=false] Whether the contact is selected on initial load. + */ + selectUser(userId: number, onInit: boolean = false): void { + this.selectedUserId = userId; + this.onUserSelected.emit({userId, onInit}); + } + + /** + * Component destroyed. + */ + ngOnDestroy(): void { + this.memberInfoObserver && this.memberInfoObserver.off(); + } +} diff --git a/src/addon/messages/pages/contacts/contacts.html b/src/addon/messages/pages/contacts/contacts.html new file mode 100644 index 000000000..1316bd027 --- /dev/null +++ b/src/addon/messages/pages/contacts/contacts.html @@ -0,0 +1,28 @@ + + + {{ 'addon.messages.contacts' | translate }} + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/addon/messages/pages/contacts/contacts.module.ts b/src/addon/messages/pages/contacts/contacts.module.ts new file mode 100644 index 000000000..a69ec60b7 --- /dev/null +++ b/src/addon/messages/pages/contacts/contacts.module.ts @@ -0,0 +1,37 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonMessagesContactsPage } from './contacts'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; +import { AddonMessagesComponentsModule } from '../../components/components.module'; + +@NgModule({ + declarations: [ + AddonMessagesContactsPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + AddonMessagesComponentsModule, + IonicPageModule.forChild(AddonMessagesContactsPage), + TranslateModule.forChild() + ], +}) +export class AddonMessagesContactsPageModule {} diff --git a/src/addon/messages/pages/contacts/contacts.ts b/src/addon/messages/pages/contacts/contacts.ts new file mode 100644 index 000000000..5e88a8593 --- /dev/null +++ b/src/addon/messages/pages/contacts/contacts.ts @@ -0,0 +1,117 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnDestroy, ViewChild } from '@angular/core'; +import { IonicPage, NavController } from 'ionic-angular'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { AddonMessagesProvider } from '../../providers/messages'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreTabsComponent } from '@components/tabs/tabs'; + +/** + * Page that displays contacts and contact requests. + */ +@IonicPage({ segment: 'addon-messages-contacts' }) +@Component({ + selector: 'page-addon-messages-contacts', + templateUrl: 'contacts.html', +}) +export class AddonMessagesContactsPage implements OnDestroy { + + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + @ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent; + + contactRequestsCount = 0; + + protected loadSplitViewObserver: any; + protected siteId: string; + protected contactRequestsCountObserver: any; + protected conversationUserId: number; // User id of the conversation opened in the split view. + protected selectedUserId = { + contacts: null, // User id of the selected user in the confirmed contacts tab. + requests: null, // User id of the selected user in the contact requests tab. + }; + + constructor(eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, + private navCtrl: NavController, private messagesProvider: AddonMessagesProvider) { + + this.siteId = sitesProvider.getCurrentSiteId(); + + // Update the contact requests badge. + this.contactRequestsCountObserver = eventsProvider.on(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, (data) => { + this.contactRequestsCount = data.count; + }, this.siteId); + } + + /** + * Page being initialized. + */ + ngOnInit(): void { + this.messagesProvider.getContactRequestsCount(this.siteId); // Badge already updated by the observer. + } + + /** + * Navigate to the search page. + */ + gotoSearch(): void { + this.navCtrl.push('AddonMessagesSearchPage'); + } + + /** + * User entered the page. + */ + ionViewDidEnter(): void { + this.tabsComponent && this.tabsComponent.ionViewDidEnter(); + } + + /** + * User left the page. + */ + ionViewDidLeave(): void { + this.tabsComponent && this.tabsComponent.ionViewDidLeave(); + } + + /** + * Set the selected user and open the conversation in the split view if needed. + * + * @param {string} tab Active tab: "contacts" or "requests". + * @param {number} [userId] Id of the selected user, undefined to use the last selected user in the tab. + * @param {boolean} [onInit=false] Whether the contact was selected on initial load. + */ + selectUser(tab: string, userId?: number, onInit: boolean = false): void { + userId = userId || this.selectedUserId[tab]; + + if (!userId || userId == this.conversationUserId) { + // 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.conversationUserId = userId; + this.selectedUserId[tab] = userId; + this.splitviewCtrl.push('AddonMessagesDiscussionPage', { userId }); + } + + /** + * Page destroyed. + */ + ngOnDestroy(): void { + this.contactRequestsCountObserver && this.contactRequestsCountObserver.off(); + } +}
{{ 'addon.messages.wouldliketocontactyou' | translate }}