MOBILE-2631 message: Allow adding/removing favourites

main
Dani Palou 2018-11-28 11:25:25 +01:00
parent 62a37720da
commit f22f33dad4
7 changed files with 150 additions and 43 deletions

View File

@ -148,6 +148,7 @@
"addon.files.sitefiles": "moodle", "addon.files.sitefiles": "moodle",
"addon.messageoutput_airnotifier.processorsettingsdesc": "local_moodlemobileapp", "addon.messageoutput_airnotifier.processorsettingsdesc": "local_moodlemobileapp",
"addon.messages.addcontact": "message", "addon.messages.addcontact": "message",
"addon.messages.addtofavourites": "message",
"addon.messages.blocknoncontacts": "message", "addon.messages.blocknoncontacts": "message",
"addon.messages.blockuser": "message", "addon.messages.blockuser": "message",
"addon.messages.blockuserconfirm": "message", "addon.messages.blockuserconfirm": "message",
@ -181,6 +182,7 @@
"addon.messages.numparticipants": "message", "addon.messages.numparticipants": "message",
"addon.messages.removecontact": "message", "addon.messages.removecontact": "message",
"addon.messages.removecontactconfirm": "local_moodlemobileapp", "addon.messages.removecontactconfirm": "local_moodlemobileapp",
"addon.messages.removefromfavourites": "message",
"addon.messages.showdeletemessages": "local_moodlemobileapp", "addon.messages.showdeletemessages": "local_moodlemobileapp",
"addon.messages.type_blocked": "local_moodlemobileapp", "addon.messages.type_blocked": "local_moodlemobileapp",
"addon.messages.type_offline": "local_moodlemobileapp", "addon.messages.type_offline": "local_moodlemobileapp",

View File

@ -1,5 +1,6 @@
{ {
"addcontact": "Add contact", "addcontact": "Add contact",
"addtofavourites": "Star",
"blocknoncontacts": "Prevent non-contacts from messaging me", "blocknoncontacts": "Prevent non-contacts from messaging me",
"blockuser": "Block user", "blockuser": "Block user",
"blockuserconfirm": "Are you sure you want to block {{$a}}?", "blockuserconfirm": "Are you sure you want to block {{$a}}?",
@ -33,6 +34,7 @@
"numparticipants": "{{$a}} participants", "numparticipants": "{{$a}} participants",
"removecontact": "Remove contact", "removecontact": "Remove contact",
"removecontactconfirm": "Contact will be removed from your contacts list.", "removecontactconfirm": "Contact will be removed from your contacts list.",
"removefromfavourites": "Unstar",
"showdeletemessages": "Show delete messages", "showdeletemessages": "Show delete messages",
"type_blocked": "Blocked", "type_blocked": "Blocked",
"type_offline": "Offline", "type_offline": "Offline",

View File

@ -3,14 +3,16 @@
<ion-title> <ion-title>
<img *ngIf="conversationImage" class="core-bar-button-image" [src]="conversationImage"> <img *ngIf="conversationImage" class="core-bar-button-image" [src]="conversationImage">
<core-format-text [text]="title"></core-format-text> <core-format-text [text]="title"></core-format-text>
<core-icon *ngIf="conversation && conversation.isfavourite" name="fa-star"></core-icon>
</ion-title> </ion-title>
<ion-buttons end></ion-buttons> <ion-buttons end></ion-buttons>
</ion-navbar> </ion-navbar>
<core-navbar-buttons end> <core-navbar-buttons end>
<core-context-menu> <core-context-menu>
<core-context-menu-item [hidden]="!showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item> <core-context-menu-item [hidden]="!showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item>
<core-context-menu-item [hidden]="!showInfo || !isGroup" [priority]="999" [content]="'addon.messages.groupinfo' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item> <core-context-menu-item [hidden]="!showInfo || !isGroup" [priority]="1000" [content]="'addon.messages.groupinfo' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item>
<core-context-menu-item [hidden]="!canDelete" [priority]="800" [content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()" [iconAction]="'trash'"></core-context-menu-item> <core-context-menu-item [hidden]="!groupMessagingEnabled || !conversation" [priority]="800" [content]="(conversation && conversation.isfavourite ? 'addon.messages.removefromfavourites' : 'addon.messages.addtofavourites') | translate" (action)="changeFavourite($event)" [iconAction]="favouriteIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item [hidden]="!canDelete" [priority]="400" [content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()" [iconAction]="'trash'"></core-context-menu-item>
</core-context-menu> </core-context-menu>
</core-navbar-buttons> </core-navbar-buttons>
</ion-header> </ion-header>

View File

@ -75,6 +75,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
groupMessagingEnabled: boolean; groupMessagingEnabled: boolean;
isGroup = false; isGroup = false;
members: any = {}; // Members that wrote a message, indexed by ID. members: any = {}; // Members that wrote a message, indexed by ID.
favouriteIcon = 'fa-star';
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams, constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider, private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
@ -332,17 +333,52 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @return {Promise<boolean>} Promise resolved with a boolean: whether the conversation exists or not. * @return {Promise<boolean>} Promise resolved with a boolean: whether the conversation exists or not.
*/ */
protected getConversation(conversationId: number, userId: number): Promise<boolean> { protected getConversation(conversationId: number, userId: number): Promise<boolean> {
let promise; let promise,
fallbackConversation;
// Try to get the conversationId if we don't have it.
if (conversationId) { if (conversationId) {
// Retrieve the conversation. Invalidate data first to get the right unreadcount. promise = Promise.resolve(conversationId);
promise = this.messagesProvider.invalidateConversation(conversationId).then(() => {
return this.messagesProvider.getConversation(conversationId);
});
} else { } else {
// We don't have the conversation ID, check if it exists. promise = this.messagesProvider.getConversationBetweenUsers(userId).then((conversation) => {
promise = this.messagesProvider.getConversationBetweenUsers(userId).catch((error) => { fallbackConversation = conversation;
return conversation.id;
});
}
return promise.then((conversationId) => {
// Retrieve the conversation. Invalidate data first to get the right unreadcount.
return this.messagesProvider.invalidateConversation(conversationId).catch(() => {
// Ignore errors.
}).then(() => {
return this.messagesProvider.getConversation(conversationId);
}).catch((error) => {
// Get conversation failed, use the fallback one if we have it.
if (fallbackConversation) {
return fallbackConversation;
}
return Promise.reject(error);
}).then((conversation) => {
this.conversation = conversation;
if (conversation) {
this.conversationId = conversation.id;
this.title = conversation.name;
this.conversationImage = conversation.imageurl;
this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
this.favouriteIcon = conversation.isfavourite ? 'fa-star-o' : 'fa-star';
if (!this.isGroup) {
this.userId = conversation.userid;
}
return true;
} else {
return false;
}
});
}, (error) => {
// Probably conversation does not exist or user is offline. Try to load offline messages. // Probably conversation does not exist or user is offline. Try to load offline messages.
return this.messagesOffline.getMessages(userId).then((messages) => { return this.messagesOffline.getMessages(userId).then((messages) => {
if (messages && messages.length) { if (messages && messages.length) {
@ -361,25 +397,6 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
}); });
} }
return promise.then((conversation) => {
this.conversation = conversation;
if (conversation) {
this.conversationId = conversation.id;
this.title = conversation.name;
this.conversationImage = conversation.imageurl;
this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
if (!this.isGroup) {
this.userId = conversation.userid;
}
return true;
} else {
return false;
}
});
}
/** /**
* Get the messages of the conversation. Used if group messaging is supported. * Get the messages of the conversation. Used if group messaging is supported.
* *
@ -940,6 +957,33 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
} }
} }
/**
* Change the favourite state of the current conversation.
*
* @param {Function} [done] Function to call when done.
*/
changeFavourite(done?: () => void): void {
this.favouriteIcon = 'spinner';
this.messagesProvider.setFavouriteConversation(this.conversation.id, !this.conversation.isfavourite).then(() => {
this.conversation.isfavourite = !this.conversation.isfavourite;
// Get the conversation data so it's cached. Don't block the user for this.
this.messagesProvider.getConversation(this.conversation.id);
this.eventsProvider.trigger(AddonMessagesProvider.UPDATE_CONVERSATION_LIST_EVENT, {
conversationId: this.conversation.id,
action: 'favourite',
value: this.conversation.isfavourite
}, this.siteId);
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Error changing favourite state.');
}).finally(() => {
this.favouriteIcon = this.conversation.isfavourite ? 'fa-star-o' : 'fa-star';
done && done();
});
}
/** /**
* Page destroyed. * Page destroyed.
*/ */

View File

@ -73,6 +73,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
protected readChangedObserver: any; protected readChangedObserver: any;
protected cronObserver: any; protected cronObserver: any;
protected openConversationObserver: any; protected openConversationObserver: any;
protected updateConversationListObserver: any;
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService, constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService,
private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams, private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
@ -115,22 +116,22 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
} }
}, this.siteId); }, this.siteId);
// Update discussions when a message is read. // Update conversations when a message is read.
this.readChangedObserver = eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => { this.readChangedObserver = eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => {
if (data.conversationId) { if (data.conversationId) {
const conversation = this.findConversation(data.conversationId); const conversation = this.findConversation(data.conversationId);
if (typeof conversation != 'undefined') { if (typeof conversation != 'undefined') {
// A discussion has been read reset counter. // A conversation has been read reset counter.
conversation.unreadcount = 0; conversation.unreadcount = 0;
// Discussions changed, invalidate them. // Conversations changed, invalidate them.
this.messagesProvider.invalidateConversations(); this.messagesProvider.invalidateConversations();
} }
} }
}, this.siteId); }, this.siteId);
// Update discussions when cron read is executed. // Update conversations when cron read is executed.
this.cronObserver = eventsProvider.on(AddonMessagesProvider.READ_CRON_EVENT, (data) => { this.cronObserver = eventsProvider.on(AddonMessagesProvider.READ_CRON_EVENT, (data) => {
this.refreshData(); this.refreshData();
}, this.siteId); }, this.siteId);
@ -153,6 +154,11 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
}); });
}); });
// Update conversations if we receive an event to do so.
this.updateConversationListObserver = eventsProvider.on(AddonMessagesProvider.UPDATE_CONVERSATION_LIST_EVENT, () => {
this.refreshData();
}, this.siteId);
// If a message push notification is received, refresh the view. // If a message push notification is received, refresh the view.
this.pushObserver = pushNotificationsDelegate.on('receive').subscribe((notification) => { this.pushObserver = pushNotificationsDelegate.on('receive').subscribe((notification) => {
// New message received. If it's from current site, refresh the data. // New message received. If it's from current site, refresh the data.
@ -551,5 +557,6 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
this.readChangedObserver && this.readChangedObserver.off(); this.readChangedObserver && this.readChangedObserver.off();
this.cronObserver && this.cronObserver.off(); this.cronObserver && this.cronObserver.off();
this.openConversationObserver && this.openConversationObserver.off(); this.openConversationObserver && this.openConversationObserver.off();
this.updateConversationListObserver && this.updateConversationListObserver.off();
} }
} }

View File

@ -35,6 +35,7 @@ export class AddonMessagesProvider {
static READ_CRON_EVENT = 'addon_messages_read_cron_event'; static READ_CRON_EVENT = 'addon_messages_read_cron_event';
static OPEN_CONVERSATION_EVENT = 'addon_messages_open_conversation_event'; // Notify that a conversation should be opened. static OPEN_CONVERSATION_EVENT = 'addon_messages_open_conversation_event'; // Notify that a conversation should be opened.
static SPLIT_VIEW_LOAD_EVENT = 'addon_messages_split_view_load_event'; static SPLIT_VIEW_LOAD_EVENT = 'addon_messages_split_view_load_event';
static UPDATE_CONVERSATION_LIST_EVENT = 'addon_messages_update_conversation_list_event';
static POLL_INTERVAL = 10000; static POLL_INTERVAL = 10000;
static PUSH_SIMULATION_COMPONENT = 'AddonMessagesPushSimulation'; static PUSH_SIMULATION_COMPONENT = 'AddonMessagesPushSimulation';
@ -1114,7 +1115,7 @@ export class AddonMessagesProvider {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId(); userId = userId || site.getUserId();
return site.invalidateWsCacheForKey(this.getCacheKeyForConversation(conversationId, userId)); return site.invalidateWsCacheForKey(this.getCacheKeyForConversation(userId, conversationId));
}); });
} }
@ -1697,6 +1698,53 @@ export class AddonMessagesProvider {
}); });
} }
/**
* Set or unset a conversation as favourite.
*
* @param {number} conversationId Conversation ID.
* @param {boolean} set Whether to set or unset it as favourite.
* @param {string} [siteId] Site ID. If not defined, use current site.
* @param {number} [userId] User ID. If not defined, current user in the site.
* @return {Promise<any>} Resolved when done.
*/
setFavouriteConversation(conversationId: number, set: boolean, siteId?: string, userId?: number): Promise<any> {
return this.setFavouriteConversations([conversationId], set, siteId, userId);
}
/**
* Set or unset some conversations as favourites.
*
* @param {number[]} conversations Conversation IDs.
* @param {boolean} set Whether to set or unset them as favourites.
* @param {string} [siteId] Site ID. If not defined, use current site.
* @param {number} [userId] User ID. If not defined, current user in the site.
* @return {Promise<any>} Resolved when done.
*/
setFavouriteConversations(conversations: number[], set: boolean, siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const params = {
userid: userId,
conversations: conversations
},
wsName = set ? 'core_message_set_favourite_conversations' : 'core_message_unset_favourite_conversations';
return site.write(wsName, params).then(() => {
// Invalidate the conversations data.
const promises = [];
conversations.forEach((conversationId) => {
promises.push(this.invalidateConversation(conversationId, site.getId(), userId));
});
return Promise.all(promises).catch(() => {
// Ignore errors.
});
});
});
}
/** /**
* Helper method to sort conversations by last message time. * Helper method to sort conversations by last message time.
* *

View File

@ -148,6 +148,7 @@
"addon.files.sitefiles": "Site files", "addon.files.sitefiles": "Site files",
"addon.messageoutput_airnotifier.processorsettingsdesc": "Configure devices", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configure devices",
"addon.messages.addcontact": "Add contact", "addon.messages.addcontact": "Add contact",
"addon.messages.addtofavourites": "Star",
"addon.messages.blocknoncontacts": "Prevent non-contacts from messaging me", "addon.messages.blocknoncontacts": "Prevent non-contacts from messaging me",
"addon.messages.blockuser": "Block user", "addon.messages.blockuser": "Block user",
"addon.messages.blockuserconfirm": "Are you sure you want to block {{$a}}?", "addon.messages.blockuserconfirm": "Are you sure you want to block {{$a}}?",
@ -181,6 +182,7 @@
"addon.messages.numparticipants": "{{$a}} participants", "addon.messages.numparticipants": "{{$a}} participants",
"addon.messages.removecontact": "Remove contact", "addon.messages.removecontact": "Remove contact",
"addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.", "addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.",
"addon.messages.removefromfavourites": "Unstar",
"addon.messages.showdeletemessages": "Show delete messages", "addon.messages.showdeletemessages": "Show delete messages",
"addon.messages.type_blocked": "Blocked", "addon.messages.type_blocked": "Blocked",
"addon.messages.type_offline": "Offline", "addon.messages.type_offline": "Offline",