Commit 46910037 authored by atraczyk's avatar atraczyk
Browse files

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, ...@@ -46,7 +46,7 @@ Account::Account(String^ name,
_sipHostname = sipHostname; _sipHostname = sipHostname;
_sipUsername = sipUsername; _sipUsername = sipUsername;
_sipPassword = sipPassword; _sipPassword = sipPassword;
_unreadMessages = 0;
} }
void void
......
...@@ -16,15 +16,17 @@ ...@@ -16,15 +16,17 @@
* You should have received a copy of the GNU General Public License * * You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. * * along with this program. If not, see <http://www.gnu.org/licenses/>. *
**************************************************************************/ **************************************************************************/
#pragma once #pragma once
using namespace Platform; using namespace Platform;
using namespace Windows::UI::Xaml::Data; using namespace Windows::UI::Xaml::Data;
using namespace Platform::Collections; using namespace Platform::Collections;
using namespace Windows::Foundation::Collections;
namespace RingClientUWP namespace RingClientUWP
{ {
ref class Contact;
public ref class Account sealed : public INotifyPropertyChanged public ref class Account sealed : public INotifyPropertyChanged
{ {
public: public:
...@@ -55,11 +57,11 @@ public: ...@@ -55,11 +57,11 @@ public:
property String^ accountType_; // refacto : create a enum accountType property String^ accountType_; // refacto : create a enum accountType
property String^ accountID_; property String^ accountID_;
property String^ _deviceId; property String^ _deviceId;
property Windows::Foundation::Collections::IVector<String^>^ _devicesIdList { property IVector<String^>^ _devicesIdList {
Windows::Foundation::Collections::IVector<String^>^ get() { IVector<String^>^ get() {
return devicesIdList_; return devicesIdList_;
} }
void set(Windows::Foundation::Collections::IVector<String^>^ value) { void set(IVector<String^>^ value) {
devicesIdList_ = value; devicesIdList_ = value;
} }
}; };
...@@ -75,17 +77,40 @@ public: ...@@ -75,17 +77,40 @@ public:
NotifyPropertyChanged("_sipUsername"); 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 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: protected:
void NotifyPropertyChanged(String^ propertyName); void NotifyPropertyChanged(String^ propertyName);
private: private:
Windows::Foundation::Collections::IVector<String^>^ devicesIdList_; IVector<String^>^ devicesIdList_;
IVector<Contact^>^ contactsList_;
String^ alias_; String^ alias_;
String^ ringID__; String^ ringID__;
String^ sipUsername_; String^ sipUsername_;
unsigned unreadMessages_;
}; };
} }
...@@ -51,6 +51,12 @@ void RingClientUWP::ViewModel::AccountListItemsViewModel::OnclearAccountsList() ...@@ -51,6 +51,12 @@ void RingClientUWP::ViewModel::AccountListItemsViewModel::OnclearAccountsList()
itemsList_->Clear(); itemsList_->Clear();
} }
void
AccountListItemsViewModel::updateContactsViewModel()
{
SmartPanelItemsViewModel::instance->update();
}
AccountListItem^ AccountListItem^
RingClientUWP::ViewModel::AccountListItemsViewModel::findItem(String^ accountId) RingClientUWP::ViewModel::AccountListItemsViewModel::findItem(String^ accountId)
{ {
...@@ -71,3 +77,22 @@ void RingClientUWP::ViewModel::AccountListItemsViewModel::removeItem(AccountList ...@@ -71,3 +77,22 @@ void RingClientUWP::ViewModel::AccountListItemsViewModel::removeItem(AccountList
itemsList_->RemoveAt(index); 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; ...@@ -26,10 +26,15 @@ using namespace RingClientUWP::Controls;
namespace RingClientUWP namespace RingClientUWP
{ {
namespace ViewModel { namespace ViewModel
{
public ref class AccountListItemsViewModel sealed public ref class AccountListItemsViewModel sealed
{ {
public:
String^ getSelectedAccountId();
int unreadMessages();
internal: internal:
/* singleton */ /* singleton */
static property AccountListItemsViewModel^ instance static property AccountListItemsViewModel^ instance
...@@ -65,6 +70,7 @@ internal: ...@@ -65,6 +70,7 @@ internal:
if (currentItem_) if (currentItem_)
currentItem_->_isSelected = false; currentItem_->_isSelected = false;
currentItem_ = value; currentItem_ = value;
updateContactsViewModel();
} }
} }
...@@ -75,6 +81,7 @@ private: ...@@ -75,6 +81,7 @@ private:
void OnaccountAdded(RingClientUWP::Account ^account); void OnaccountAdded(RingClientUWP::Account ^account);
void OnclearAccountsList(); void OnclearAccountsList();
void updateContactsViewModel();
}; };
} }
} }
...@@ -23,9 +23,35 @@ ...@@ -23,9 +23,35 @@
using namespace RingClientUWP; using namespace RingClientUWP;
using namespace ViewModel; using namespace ViewModel;
using namespace Windows::UI::Core;
AccountsViewModel::AccountsViewModel() AccountsViewModel::AccountsViewModel()
{ {
accountsList_ = ref new Vector<Account^>(); 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 void
...@@ -43,6 +69,7 @@ AccountsViewModel::addRingAccount(std::string& alias, std::string& ringID, std:: ...@@ -43,6 +69,7 @@ AccountsViewModel::addRingAccount(std::string& alias, std::string& ringID, std::
"" /* sip password not used with ring */ ); "" /* sip password not used with ring */ );
accountsList_->Append(account); accountsList_->Append(account);
contactListModels_->Insert(account->accountID_, ref new ContactListModel(account->accountID_));
updateScrollView(); updateScrollView();
accountAdded(account); accountAdded(account);
} }
...@@ -63,6 +90,7 @@ AccountsViewModel::addSipAccount(std::string& alias, std::string& accountID, std ...@@ -63,6 +90,7 @@ AccountsViewModel::addSipAccount(std::string& alias, std::string& accountID, std
); );
accountsList_->Append(account); accountsList_->Append(account);
contactListModels_->Insert(account->accountID_, ref new ContactListModel(account->accountID_));
updateScrollView(); updateScrollView();
accountAdded(account); accountAdded(account);
} }
...@@ -82,3 +110,89 @@ Account ^ RingClientUWP::ViewModel::AccountsViewModel::findItem(String ^ account ...@@ -82,3 +110,89 @@ Account ^ RingClientUWP::ViewModel::AccountsViewModel::findItem(String ^ account
return nullptr; 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 @@ ...@@ -16,42 +16,45 @@
* You should have received a copy of the GNU General Public License * * You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. * * along with this program. If not, see <http://www.gnu.org/licenses/>. *
**************************************************************************/ **************************************************************************/
#pragma once #pragma once
#include "ContactListModel.h"
using namespace Platform::Collections; using namespace Platform::Collections;
namespace RingClientUWP namespace RingClientUWP
{ {
ref class Contact;
delegate void NewAccountSelected(); delegate void NewAccountSelected();
delegate void NoAccountSelected(); delegate void NoAccountSelected();
delegate void UpdateScrollView(); delegate void UpdateScrollView();
delegate void AccountAdded(Account^ account); delegate void AccountAdded(Account^ account);
delegate void ClearAccountsList(); 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 ref class AccountsViewModel sealed
{ {
public:
void raiseContactAdded(String^ accountId, Contact^ name);
void raiseContactDeleted(String^ accountId, Contact^ name);
void raiseContactDataModified(String^ accountId, Contact^ name);
internal: internal:
/* singleton */ /* properties */
static property AccountsViewModel^ instance static property AccountsViewModel^ instance {
{ AccountsViewModel^ get() {
AccountsViewModel^ get()
{
static AccountsViewModel^ instance_ = ref new AccountsViewModel(); static AccountsViewModel^ instance_ = ref new AccountsViewModel();
return instance_; 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 property Vector<Account^>^ accountsList
{ {
Vector<Account^>^ get() Vector<Account^>^ get()
...@@ -60,17 +63,33 @@ internal: ...@@ -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 */ /* events */
event NewAccountSelected^ newAccountSelected; event NewAccountSelected^ newAccountSelected;
event NoAccountSelected^ noAccountSelected; event NoAccountSelected^ noAccountSelected;
event UpdateScrollView^ updateScrollView; event UpdateScrollView^ updateScrollView;
event AccountAdded^ accountAdded; event AccountAdded^ accountAdded;
event ClearAccountsList^ clearAccountsList; event ClearAccountsList^ clearAccountsList;
event ContactAdded^ contactAdded;
event ContactDeleted^ contactDeleted;
event ContactDataModified^ contactDataModified;
event NewUnreadMessage^ newUnreadMessage;
private: private:
AccountsViewModel(); // singleton AccountsViewModel();
Vector<Account^>^ accountsList_;
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 @@ ...@@ -20,7 +20,7 @@
#include "Contact.h" #include "Contact.h"
#include "ObjBase.h" // for CoCreateGuid #include <ObjBase.h> // for CoCreateGuid
#include "fileutils.h" #include "fileutils.h"
#include "direct.h" #include "direct.h"
...@@ -33,13 +33,14 @@ using namespace Windows::UI::Core; ...@@ -33,13 +33,14 @@ using namespace Windows::UI::Core;
using namespace RingClientUWP; using namespace RingClientUWP;
using namespace ViewModel; using namespace ViewModel;
Contact::Contact(String^ name, Contact::Contact( String^ accountId,
String^ ringID, String^ name,
String^ GUID, String^ ringID,
unsigned int unreadmessages, String^ GUID,
ContactStatus contactStatus) unsigned int unreadmessages,
ContactStatus contactStatus)
{ {
vCard_ = ref new VCardUtils::VCard(this); vCard_ = ref new VCardUtils::VCard(this, accountId);
name_ = name; name_ = name;
ringID_ = ringID; ringID_ = ringID;
...@@ -70,7 +71,7 @@ Contact::Contact(String^ name, ...@@ -70,7 +71,7 @@ Contact::Contact(String^ name,
NotifyPropertyChanged("unreadMessages"); NotifyPropertyChanged("unreadMessages");
} }
_accountIdAssociated = ""; _accountIdAssociated = accountId;
_vcardUID = ""; _vcardUID = "";
_avatarImage = ref new String(L"ms-appx:///Assets/TESTS/contactAvatar.png"); _avatarImage = ref new String(L"ms-appx:///Assets/TESTS/contactAvatar.png");
_displayName = ""; _displayName = "";
...@@ -181,4 +182,10 @@ VCardUtils::VCard^ ...@@ -181,4 +182,10 @@ VCardUtils::VCard^
Contact::getVCard() Contact::getVCard()
{ {
return vCard_; return vCard_;
}
void
Contact::notifyPropertyChanged(String^ propertyName)
{
NotifyPropertyChanged(propertyName);
} }
\ No newline at end of file
...@@ -45,9 +45,16 @@ ref class Conversation; ...@@ -45,9 +45,16 @@ ref class Conversation;
public ref class Contact sealed : public INotifyPropertyChanged public ref class Contact sealed : public INotifyPropertyChanged
{ {
public: 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(); JsonObject^ ToJsonObject();
void notifyPropertyChanged(String^ propertyName);
virtual event PropertyChangedEventHandler^ PropertyChanged; virtual event PropertyChangedEventHandler^ PropertyChanged;
property String^ _name property String^ _name
...@@ -120,7 +127,15 @@ public: ...@@ -120,7 +127,15 @@ public:
NotifyPropertyChanged("_contactBarHeight"); 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^ _vcardUID;
property String^ _displayName property String^ _displayName
{ {
...@@ -181,6 +196,7 @@ private: ...@@ -181,6 +196,7 @@ private:
unsigned int unreadMessages_; unsigned int unreadMessages_;
String^ avatarImage_; String^ avatarImage_;
String^ displayName_; String^ displayName_;
String^ accountIdAssociated_;
Windows::UI::Xaml::GridLength contactBarHeight_ = 0; Windows::UI::Xaml::GridLength contactBarHeight_ = 0;
ContactStatus contactStatus_; ContactStatus contactStatus_;
String^ name_; String^ name_;
......
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
* You should have received a copy of the GNU General Public License * * You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. * * along with this program. If not, see <http://www.gnu.org/licenses/>. *