Commit 46910037 authored by atraczyk's avatar atraczyk

contacts: implement account specific contact lists

- Implements contacts and their respective conversations being
  associated to individual accounts. Prior to this patch, contacts
  were per profile, and stored only an associated account ID for
  conversation history sorting.

- Incoming messages for unselected accounts will trigger visual
  notifications, and the total number of unread messages per
  account is reported in the account selection interface panel.
  Incoming calls for unselected accounts will appear at the top
  of every account's contact list for the duration of the call.

Change-Id: I219ab7b1b2656e5021388fd54e36b21dc81dcd90
Tuleap: #1538
parent 9dc4ce92
......@@ -46,7 +46,7 @@ Account::Account(String^ name,
_sipHostname = sipHostname;
_sipUsername = sipUsername;
_sipPassword = sipPassword;
_unreadMessages = 0;
}
void
......
......@@ -16,15 +16,17 @@
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
**************************************************************************/
#pragma once
using namespace Platform;
using namespace Windows::UI::Xaml::Data;
using namespace Platform::Collections;
using namespace Windows::Foundation::Collections;
namespace RingClientUWP
{
ref class Contact;
public ref class Account sealed : public INotifyPropertyChanged
{
public:
......@@ -55,11 +57,11 @@ public:
property String^ accountType_; // refacto : create a enum accountType
property String^ accountID_;
property String^ _deviceId;
property Windows::Foundation::Collections::IVector<String^>^ _devicesIdList {
Windows::Foundation::Collections::IVector<String^>^ get() {
property IVector<String^>^ _devicesIdList {
IVector<String^>^ get() {
return devicesIdList_;
}
void set(Windows::Foundation::Collections::IVector<String^>^ value) {
void set(IVector<String^>^ value) {
devicesIdList_ = value;
}
};
......@@ -75,17 +77,40 @@ public:
NotifyPropertyChanged("_sipUsername");
}
}
property unsigned _unreadMessages
{
unsigned get() {
return unreadMessages_;
}
void set(unsigned value) {
unreadMessages_ = value;
NotifyPropertyChanged("_unreadMessages");
}
}
property String^ _sipPassword; // refacto : think to encrypt password in memory
property IVector<Contact^>^ _contactsList {
IVector<Contact^>^ get() {
return contactsList_;
}
void set(IVector<Contact^>^ value) {
contactsList_ = value;
}
};
protected:
void NotifyPropertyChanged(String^ propertyName);
private:
Windows::Foundation::Collections::IVector<String^>^ devicesIdList_;
IVector<String^>^ devicesIdList_;
IVector<Contact^>^ contactsList_;
String^ alias_;
String^ ringID__;
String^ sipUsername_;
unsigned unreadMessages_;
};
}
......@@ -51,6 +51,12 @@ void RingClientUWP::ViewModel::AccountListItemsViewModel::OnclearAccountsList()
itemsList_->Clear();
}
void
AccountListItemsViewModel::updateContactsViewModel()
{
SmartPanelItemsViewModel::instance->update();
}
AccountListItem^
RingClientUWP::ViewModel::AccountListItemsViewModel::findItem(String^ accountId)
{
......@@ -71,3 +77,22 @@ void RingClientUWP::ViewModel::AccountListItemsViewModel::removeItem(AccountList
itemsList_->RemoveAt(index);
}
String^
AccountListItemsViewModel::getSelectedAccountId()
{
if (_selectedItem)
return _selectedItem->_account->accountID_;
return nullptr;
}
int
AccountListItemsViewModel::unreadMessages()
{
int messageCount = 0;
for each (auto account in AccountsViewModel::instance->accountsList) {
account->_unreadMessages = AccountsViewModel::instance->unreadMessages(account->accountID_);
messageCount += account->_unreadMessages;
}
return messageCount;
}
......@@ -26,10 +26,15 @@ using namespace RingClientUWP::Controls;
namespace RingClientUWP
{
namespace ViewModel {
namespace ViewModel
{
public ref class AccountListItemsViewModel sealed
{
public:
String^ getSelectedAccountId();
int unreadMessages();
internal:
/* singleton */
static property AccountListItemsViewModel^ instance
......@@ -65,6 +70,7 @@ internal:
if (currentItem_)
currentItem_->_isSelected = false;
currentItem_ = value;
updateContactsViewModel();
}
}
......@@ -75,6 +81,7 @@ private:
void OnaccountAdded(RingClientUWP::Account ^account);
void OnclearAccountsList();
void updateContactsViewModel();
};
}
}
......@@ -23,9 +23,35 @@
using namespace RingClientUWP;
using namespace ViewModel;
using namespace Windows::UI::Core;
AccountsViewModel::AccountsViewModel()
{
accountsList_ = ref new Vector<Account^>();
contactListModels_ = ref new Map<String^, ContactListModel^>();
RingD::instance->incomingAccountMessage +=
ref new RingClientUWP::IncomingAccountMessage(this, &AccountsViewModel::OnincomingAccountMessage);
RingD::instance->incomingMessage +=
ref new RingClientUWP::IncomingMessage(this, &AccountsViewModel::OnincomingMessage);
}
void
AccountsViewModel::raiseContactAdded(String^ accountId, Contact ^ name)
{
contactAdded(accountId, name);
}
void
AccountsViewModel::raiseContactDeleted(String^ accountId, Contact ^ name)
{
contactDeleted(accountId, name);
}
void
AccountsViewModel::raiseContactDataModified(String^ accountId, Contact ^ name)
{
contactDataModified(accountId, name);
}
void
......@@ -43,6 +69,7 @@ AccountsViewModel::addRingAccount(std::string& alias, std::string& ringID, std::
"" /* sip password not used with ring */ );
accountsList_->Append(account);
contactListModels_->Insert(account->accountID_, ref new ContactListModel(account->accountID_));
updateScrollView();
accountAdded(account);
}
......@@ -63,6 +90,7 @@ AccountsViewModel::addSipAccount(std::string& alias, std::string& accountID, std
);
accountsList_->Append(account);
contactListModels_->Insert(account->accountID_, ref new ContactListModel(account->accountID_));
updateScrollView();
accountAdded(account);
}
......@@ -82,3 +110,89 @@ Account ^ RingClientUWP::ViewModel::AccountsViewModel::findItem(String ^ account
return nullptr;
}
ContactListModel^
AccountsViewModel::getContactListModel(std::string& accountId)
{
try {
return contactListModels_->Lookup(Utils::toPlatformString(accountId));
}
catch (Platform::OutOfBoundsException^ e) {
EXC_(e);
return nullptr;
}
}
int
AccountsViewModel::unreadMessages(String ^ accountId)
{
int messageCount = 0;
if (auto contactListModel = getContactListModel(Utils::toString(accountId))) {
for each (auto contact in contactListModel->_contactsList) {
messageCount += contact->_unreadMessages;
}
}
return messageCount;
}
void
AccountsViewModel::OnincomingAccountMessage(String ^ accountId, String ^ fromRingId, String ^ payload)
{
auto contactListModel = getContactListModel(Utils::toString(accountId));
auto contact = contactListModel->findContactByRingId(fromRingId);
if (contact == nullptr)
contact = contactListModel->addNewContact(fromRingId, fromRingId);
auto item = SmartPanelItemsViewModel::instance->_selectedItem;
if (contact == nullptr) {
ERR_("contact not handled!");
return;
}
RingD::instance->lookUpAddress(fromRingId);
contact->_conversation->addMessage(""/* date not yet used*/, MSG_FROM_CONTACT, payload);
/* save contacts conversation to disk */
contact->saveConversationToFile();
auto selectedContact = (item) ? item->_contact : nullptr;
if (contact->ringID_ == fromRingId && contact != selectedContact) {
contact->_unreadMessages++;
newUnreadMessage();
/* saveContactsToFile used to save the notification */
contactListModel->saveContactsToFile();
}
}
void
AccountsViewModel::OnincomingMessage(String ^callId, String ^payload)
{
auto itemlist = SmartPanelItemsViewModel::instance->itemsList;
auto item = SmartPanelItemsViewModel::instance->findItem(callId);
auto contact = item->_contact;
auto contactListModel = getContactListModel(Utils::toString(contact->_accountIdAssociated));
/* the contact HAS TO BE already registered */
if (contact) {
auto item = SmartPanelItemsViewModel::instance->_selectedItem;
contact->_conversation->addMessage(""/* date not yet used*/, MSG_FROM_CONTACT, payload);
/* save contacts conversation to disk */
contact->saveConversationToFile();
auto selectedContact = (item) ? item->_contact : nullptr;
if (contact != selectedContact) {
contact->_unreadMessages++;
newUnreadMessage();
/* saveContactsToFile used to save the notification */
contactListModel->saveContactsToFile();
}
}
}
\ No newline at end of file
......@@ -16,42 +16,45 @@
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
**************************************************************************/
#pragma once
#include "ContactListModel.h"
using namespace Platform::Collections;
namespace RingClientUWP
{
ref class Contact;
delegate void NewAccountSelected();
delegate void NoAccountSelected();
delegate void UpdateScrollView();
delegate void AccountAdded(Account^ account);
delegate void ClearAccountsList();
delegate void ContactAdded(String^, Contact^);
delegate void ContactDeleted(String^, Contact^);
delegate void ContactDataModified(String^, Contact^);
delegate void NewUnreadMessage();
namespace ViewModel {
namespace ViewModel
{
public ref class AccountsViewModel sealed
{
public:
void raiseContactAdded(String^ accountId, Contact^ name);
void raiseContactDeleted(String^ accountId, Contact^ name);
void raiseContactDataModified(String^ accountId, Contact^ name);
internal:
/* singleton */
static property AccountsViewModel^ instance
{
AccountsViewModel^ get()
{
/* properties */
static property AccountsViewModel^ instance {
AccountsViewModel^ get() {
static AccountsViewModel^ instance_ = ref new AccountsViewModel();
return instance_;
}
}
/* functions */
void addRingAccount(std::string& alias, std::string& ringID, std::string& accountID, std::string& deviceId, bool upnpState);
void addSipAccount(std::string& alias, std::string& accountID, std::string& sipHostname, std::string& sipUsername, std::string& sipPassword);
void clearAccountList();
Account^ findItem(String^ accountId);
/* properties */
property Vector<Account^>^ accountsList
{
Vector<Account^>^ get()
......@@ -60,17 +63,33 @@ internal:
}
}
/* functions */
void addRingAccount(std::string& alias, std::string& ringID, std::string& accountID, std::string& deviceId, bool upnpState);
void addSipAccount(std::string& alias, std::string& accountID, std::string& sipHostname, std::string& sipUsername, std::string& sipPassword);
void clearAccountList();
Account^ findItem(String^ accountId);
ContactListModel^ getContactListModel(std::string& accountId);
int unreadMessages(String^ accountId);
/* events */
event NewAccountSelected^ newAccountSelected;
event NoAccountSelected^ noAccountSelected;
event UpdateScrollView^ updateScrollView;
event AccountAdded^ accountAdded;
event ClearAccountsList^ clearAccountsList;
event ContactAdded^ contactAdded;
event ContactDeleted^ contactDeleted;
event ContactDataModified^ contactDataModified;
event NewUnreadMessage^ newUnreadMessage;
private:
AccountsViewModel(); // singleton
Vector<Account^>^ accountsList_;
AccountsViewModel();
void OnincomingAccountMessage(String^ accountId, String^ fromRingId, String^ payload);
void OnincomingMessage(String ^callId, String ^payload);
Vector<Account^>^ accountsList_;
Map<String^, ContactListModel^>^ contactListModels_;
};
}
}
......@@ -20,7 +20,7 @@
#include "Contact.h"
#include "ObjBase.h" // for CoCreateGuid
#include <ObjBase.h> // for CoCreateGuid
#include "fileutils.h"
#include "direct.h"
......@@ -33,13 +33,14 @@ using namespace Windows::UI::Core;
using namespace RingClientUWP;
using namespace ViewModel;
Contact::Contact(String^ name,
String^ ringID,
String^ GUID,
unsigned int unreadmessages,
ContactStatus contactStatus)
Contact::Contact( String^ accountId,
String^ name,
String^ ringID,
String^ GUID,
unsigned int unreadmessages,
ContactStatus contactStatus)
{
vCard_ = ref new VCardUtils::VCard(this);
vCard_ = ref new VCardUtils::VCard(this, accountId);
name_ = name;
ringID_ = ringID;
......@@ -70,7 +71,7 @@ Contact::Contact(String^ name,
NotifyPropertyChanged("unreadMessages");
}
_accountIdAssociated = "";
_accountIdAssociated = accountId;
_vcardUID = "";
_avatarImage = ref new String(L"ms-appx:///Assets/TESTS/contactAvatar.png");
_displayName = "";
......@@ -181,4 +182,10 @@ VCardUtils::VCard^
Contact::getVCard()
{
return vCard_;
}
void
Contact::notifyPropertyChanged(String^ propertyName)
{
NotifyPropertyChanged(propertyName);
}
\ No newline at end of file
......@@ -45,9 +45,16 @@ ref class Conversation;
public ref class Contact sealed : public INotifyPropertyChanged
{
public:
Contact(String^ name, String^ ringID, String^ GUID, unsigned int unreadmessages, ContactStatus contactStatus);
Contact(String^ accountId,
String^ name,
String^ ringID,
String^ GUID,
unsigned int unreadmessages,
ContactStatus contactStatus);
JsonObject^ ToJsonObject();
void notifyPropertyChanged(String^ propertyName);
virtual event PropertyChangedEventHandler^ PropertyChanged;
property String^ _name
......@@ -120,7 +127,15 @@ public:
NotifyPropertyChanged("_contactBarHeight");
}
}
property String^ _accountIdAssociated;
property String^ _accountIdAssociated {
String^ get() {
return accountIdAssociated_;
}
void set(String^ value) {
accountIdAssociated_ = value;
NotifyPropertyChanged("_accountIdAssociated");
}
}
property String^ _vcardUID;
property String^ _displayName
{
......@@ -181,6 +196,7 @@ private:
unsigned int unreadMessages_;
String^ avatarImage_;
String^ displayName_;
String^ accountIdAssociated_;
Windows::UI::Xaml::GridLength contactBarHeight_ = 0;
ContactStatus contactStatus_;
String^ name_;
......
......@@ -16,9 +16,9 @@
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
**************************************************************************/
#include "pch.h"
#include "ContactsViewModel.h"
#include "pch.h"
#include "ContactListModel.h"
#include "fileutils.h"
......@@ -31,51 +31,18 @@ using namespace Windows::UI::Core;
using namespace RingClientUWP;
using namespace ViewModel;
ContactsViewModel::ContactsViewModel()
ContactListModel::ContactListModel(String^ account) : m_Owner(account)
{
contactsList_ = ref new Vector<Contact^>();
openContactsFromFile();
/* connect delegates. */
RingD::instance->incomingAccountMessage += ref new IncomingAccountMessage([&](String^ accountId,
String^ fromRingId, String^ payload) {
auto contact = findContactByRingId(fromRingId);
if (contact == nullptr)
contact = addNewContact(fromRingId, fromRingId); // contact checked inside addNewContact.
auto item = SmartPanelItemsViewModel::instance->_selectedItem;
if (contact == nullptr) {
ERR_("contact not handled!");
return;
}
RingD::instance->lookUpAddress(fromRingId);
contact->_conversation->addMessage(""/* date not yet used*/, MSG_FROM_CONTACT, payload);
/* save contacts conversation to disk */
contact->saveConversationToFile();
auto selectedContact = (item) ? item->_contact : nullptr;
if (contact->ringID_ == fromRingId && contact != selectedContact) {
contact->_unreadMessages++;
/* saveContactsToFile used to save the notification */
saveContactsToFile();
}
});
RingD::instance->incomingMessage +=
ref new RingClientUWP::IncomingMessage(this, &RingClientUWP::ViewModel::ContactsViewModel::OnincomingMessage);
RingD::instance->registeredNameFound += ref new RingClientUWP::RegisteredNameFound(this, &RingClientUWP::ViewModel::ContactsViewModel::OnregisteredNameFound);
RingD::instance->registeredNameFound +=
ref new RingClientUWP::RegisteredNameFound(this, &ContactListModel::OnregisteredNameFound);
}
Contact^ // refacto : remove "byName"
ContactsViewModel::findContactByName(String^ name)
ContactListModel::findContactByName(String^ name)
{
auto trimmedName = Utils::Trim(name);
for each (Contact^ contact in contactsList_)
......@@ -85,7 +52,8 @@ ContactsViewModel::findContactByName(String^ name)
return nullptr;
}
Contact ^ RingClientUWP::ViewModel::ContactsViewModel::findContactByRingId(String^ ringId)
Contact^
ContactListModel::findContactByRingId(String^ ringId)
{
for each (Contact^ contact in contactsList_)
if (contact->ringID_ == ringId)
......@@ -95,15 +63,15 @@ Contact ^ RingClientUWP::ViewModel::ContactsViewModel::findContactByRingId(Strin
}
Contact^
ContactsViewModel::addNewContact(String^ name, String^ ringId, ContactStatus contactStatus)
ContactListModel::addNewContact(String^ name, String^ ringId, ContactStatus contactStatus)
{
auto trimmedName = Utils::Trim(name);
if (contactsList_ && !findContactByName(trimmedName)) {
//if (contactsList_ && !findContactByName(trimmedName) && !findContactByRingId(ringId)) {
Contact^ contact = ref new Contact(trimmedName, ringId, nullptr, 0, contactStatus);
Contact^ contact = ref new Contact(m_Owner, trimmedName, ringId, nullptr, 0, contactStatus);
contactsList_->Append(contact);
saveContactsToFile();
contactAdded(contact);
AccountsViewModel::instance->raiseContactAdded(m_Owner, contact);
return contact;
}
......@@ -111,12 +79,12 @@ ContactsViewModel::addNewContact(String^ name, String^ ringId, ContactStatus con
}
void
ContactsViewModel::saveContactsToFile()
ContactListModel::saveContactsToFile()
{
StorageFolder^ localfolder = ApplicationData::Current->LocalFolder;
String^ contactsFile = localfolder->Path + "\\" + ".profile\\contacts.json";
String^ contactsFile = localfolder->Path + "\\" + ".profile\\" + m_Owner + "\\contacts.json";
if (ring::fileutils::recursive_mkdir(Utils::toString(localfolder->Path + "\\" + ".profile\\").c_str())) {
if (ring::fileutils::recursive_mkdir(Utils::toString(localfolder->Path + "\\" + ".profile\\" + m_Owner).c_str())) {
std::ofstream file(Utils::toString(contactsFile).c_str());
if (file.is_open())
{
......@@ -127,10 +95,10 @@ ContactsViewModel::saveContactsToFile()
}
void
ContactsViewModel::openContactsFromFile()
ContactListModel::openContactsFromFile()
{
StorageFolder^ localfolder = ApplicationData::Current->LocalFolder;
String^ contactsFile = localfolder->Path + "\\" + ".profile\\contacts.json";
String^ contactsFile = localfolder->Path + "\\" + ".profile\\" + m_Owner + "\\contacts.json";
String^ fileContents = Utils::toPlatformString(Utils::getStringFromFile(Utils::toString(contactsFile)));
......@@ -142,7 +110,7 @@ ContactsViewModel::openContactsFromFile()
}
String^
ContactsViewModel::Stringify()
ContactListModel::Stringify()
{
JsonArray^ jsonArray = ref new JsonArray();
......@@ -157,7 +125,7 @@ ContactsViewModel::Stringify()
}
void
ContactsViewModel::Destringify(String^ data)
ContactListModel::Destringify(String^ data)
{
JsonObject^ jsonObject = JsonObject::Parse(data);
String^ name;
......@@ -187,7 +155,7 @@ ContactsViewModel::Destringify(String^ data)
if (contactObject->HasKey(lastTimeKey))
lastTime = contactObject->GetNamedString(lastTimeKey);
}
auto contact = ref new Contact(name, ringid, guid, unreadmessages, ContactStatus::READY);
auto contact = ref new Contact(m_Owner, name, ringid, guid, unreadmessages, ContactStatus::READY);
contact->_displayName = displayname;
contact->_accountIdAssociated = accountIdAssociated;
// contact image
......@@ -201,12 +169,13 @@ ContactsViewModel::Destringify(String^ data)
contact->_avatarImage = Utils::toPlatformString(contactImageFile);
}
contactsList_->Append(contact);
contactAdded(contact);
AccountsViewModel::instance->raiseContactAdded(m_Owner, contact);
}
}
}
void RingClientUWP::ViewModel::ContactsViewModel::deleteContact(Contact ^ contact)
void
ContactListModel::deleteContact(Contact ^ contact)
{
unsigned int index;
auto itemsList = SmartPanelItemsViewModel::instance->itemsList;
......@@ -220,42 +189,14 @@ void RingClientUWP::ViewModel::ContactsViewModel::deleteContact(Contact ^ contac
saveContactsToFile();
}
void RingClientUWP::ViewModel::ContactsViewModel::OnincomingMessage(Platform::String ^callId, Platform::String ^payload)
{
auto itemlist = SmartPanelItemsViewModel::instance->itemsList;
auto item = SmartPanelItemsViewModel::instance->findItem(callId);
auto contact = item->_contact;
/* the contact HAS TO BE already registered */
if (contact) {
auto item = SmartPanelItemsViewModel::instance->_selectedItem;
contact->_conversation->addMessage(""/* date not yet used*/, MSG_FROM_CONTACT, payload);
/* save contacts conversation to disk */
contact->saveConversationToFile();
auto selectedContact = (item) ? item->_contact : nullptr;
if (contact != selectedContact) {
contact->_unreadMessages++;
/* saveContactsToFile used to save the notification */
saveContactsToFile();
}
}
}
void
ContactsViewModel::modifyContact(Contact^ contact)
ContactListModel::modifyContact(Contact^ contact)
{
contactDataModified(contact);
AccountsViewModel::instance->raiseContactDataModified(m_Owner, contact);
}
void RingClientUWP::ViewModel::ContactsViewModel::OnregisteredNameFound(RingClientUWP::LookupStatus status, const std::string &address, const std::string &name)
void
ContactListModel::OnregisteredNameFound(RingClientUWP::LookupStatus status, const std::string &address, const std::string &name)
{
if (status == LookupStatus::SUCCESS) {
for each (Contact^ contact in contactsList_)
......
......@@ -16,7 +16,6 @@
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
**************************************************************************/
#pragma once
using namespace Platform::Collections;
......@@ -24,26 +23,15 @@ using namespace Concurrency;
namespace RingClientUWP
{
namespace ViewModel
{
/* delegates */
delegate void ContactAdded(Contact^);
delegate void ContactDeleted(Contact^);
delegate void ContactDataModified(Contact^);
namespace ViewModel {
public ref class ContactsViewModel sealed
public ref class ContactListModel sealed
{
internal:
/* singleton */
static property ContactsViewModel^ instance
{
ContactsViewModel^ get()
{
static ContactsViewModel^ instance_ = ref new ContactsViewModel();
return instance_;
}
}
public:
ContactListModel(String^ m_Owner);
internal:
/* functions */
Contact^ findContactByName(String^ name);
Contact^ findContactByRingId(String^ ringId);
......@@ -56,26 +44,21 @@ internal:
void modifyContact(Contact^ contact);
/* properties */
property Vector<Contact^>^ contactsList