From d06902e3b7b6ba9f7eec2931c4dce879d4babdd5 Mon Sep 17 00:00:00 2001 From: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com> Date: Mon, 5 Dec 2022 15:44:04 -0500 Subject: [PATCH] Feature: unpin location sharing map - Refactoring - Unpined map - handle multiple maps (one map per account) Change-Id: I2b0abf284ccfe27b986f03915c5942d721403211 Gitlab: #901 --- resources/icons/unpin.svg | 1 + src/app/appsettingsmanager.h | 4 +- src/app/constant/JamiStrings.qml | 12 +- src/app/mainview/components/ChatView.qml | 30 +- .../mainview/components/ChatViewFooter.qml | 2 +- src/app/mainview/components/MessageBar.qml | 44 +- .../components/SmartListItemDelegate.qml | 8 +- src/app/positioning.cpp | 19 +- src/app/positioning.h | 11 +- src/app/positionmanager.cpp | 541 ++++++++++++------ src/app/positionmanager.h | 66 ++- src/app/positionobject.cpp | 9 +- src/app/positionobject.h | 2 + src/app/qmlregister.cpp | 2 +- .../components/GeneralSettingsPage.qml | 10 + .../components/LocationSharingSettings.qml | 150 +++++ src/app/webengine/map/MapPosition.qml | 478 +++++----------- src/app/webengine/map/MapPositionOverlay.qml | 125 ++++ .../map/MapPositionSharingControl.qml | 203 +++++++ .../map/StopSharingPositionPopup.qml | 10 +- src/app/webengine/map/map.js | 30 +- src/libclient/typedefs.h | 3 + 22 files changed, 1161 insertions(+), 599 deletions(-) create mode 100644 resources/icons/unpin.svg create mode 100644 src/app/settingsview/components/LocationSharingSettings.qml create mode 100644 src/app/webengine/map/MapPositionOverlay.qml create mode 100644 src/app/webengine/map/MapPositionSharingControl.qml diff --git a/resources/icons/unpin.svg b/resources/icons/unpin.svg new file mode 100644 index 000000000..9f8128218 --- /dev/null +++ b/resources/icons/unpin.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M9 42q-1.2 0-2.1-.9Q6 40.2 6 39V9q0-1.2.9-2.1Q7.8 6 9 6h13.95v3H9v30h30V25.05h3V39q0 1.2-.9 2.1-.9.9-2.1.9Zm10.1-10.95L17 28.9 36.9 9H25.95V6H42v16.05h-3v-10.9Z"/></svg> \ No newline at end of file diff --git a/src/app/appsettingsmanager.h b/src/app/appsettingsmanager.h index d69a3acee..30a0573ac 100644 --- a/src/app/appsettingsmanager.h +++ b/src/app/appsettingsmanager.h @@ -54,7 +54,9 @@ extern const QString defaultDownloadPath; X(WindowGeometry, QRectF(qQNaN(), qQNaN(), 0., 0.)) \ X(WindowState, QWindow::AutomaticVisibility) \ X(EnableExperimentalSwarm, false) \ - X(LANG, "SYSTEM") + X(LANG, "SYSTEM") \ + X(PositionShareDuration, 15) \ + X(PositionShareLimit, true) /* * A class to expose settings keys in both c++ and QML. diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml index c8fe3633f..510237180 100644 --- a/src/app/constant/JamiStrings.qml +++ b/src/app/constant/JamiStrings.qml @@ -309,8 +309,12 @@ Item { property string locationServicesError: qsTr("Your precise location could not be determined.\nIn Device Settings, please turn on \"Location Services\".\nOther participants' location can still be received.") property string locationServicesClosedError: qsTr("Your precise location could not be determined. Please check your Internet connection.") property string stopAllSharings: qsTr("Turn off location sharing"); - property string stopConvSharing: qsTr("Stop location sharing in this conversation"); + property string shortStopAllSharings: qsTr("Turn off sharing"); + property string stopConvSharing: qsTr("Stop location sharing in this conversation (%1)"); property string stopSharingPopupBody: qsTr("Location is shared in several conversations"); + property string unpinStopSharingTooltip: qsTr("Pin map to be able to share location or to turn off location in specific conversations"); + property string stopSharingSeveralConversationTooltip: qsTr("Location is shared in several conversations, click to choose how to turn off location sharing") + property string shareLocationToolTip: qsTr("Share location to participants of this conversation (%1)"); property string minimizeMapTooltip: qsTr("Minimize"); property string maximizeMapTooltip: qsTr("Maximize"); property string reduceMapTooltip: qsTr("Reduce"); @@ -318,6 +322,11 @@ Item { property string dragMapTooltip: qsTr("Drag"); property string centerMapTooltip: qsTr("Center"); property string closeMapTooltip: qsTr("Close"); + property string unpin: qsTr("Unpin"); + property string pinWindow: qsTr("Pin"); + property string positionShareDuration: qsTr("Position share duration"); + property string positionShareLimit: qsTr("Limit the duration of location sharing"); + property string locationSharingLabel: qsTr("Location sharing"); // Chatview header property string hideChat: qsTr("Hide chat") @@ -694,6 +703,7 @@ Item { // SmartList property string clearText: qsTr("Clear Text") property string conversations: qsTr("Conversations") + property string conversation: qsTr("Conversation") property string searchResults: qsTr("Search Results") // SmartList context menu diff --git a/src/app/mainview/components/ChatView.qml b/src/app/mainview/components/ChatView.qml index a45d43cf6..1c19ac459 100644 --- a/src/app/mainview/components/ChatView.qml +++ b/src/app/mainview/components/ChatView.qml @@ -34,7 +34,7 @@ Rectangle { id: root property bool allMessagesLoaded - + property var mapPositions: PositionManager.mapStatus signal needToHideConversationInCall signal messagesCleared signal messagesLoaded @@ -51,19 +51,29 @@ Rectangle { addMemberPanel.visible = false } - color: JamiTheme.chatviewBgColor - - property string currentConvId: CurrentConversation.id - onCurrentConvIdChanged: PositionManager.setMapActive(false); + function instanceMapObject() { + if (WITH_WEBENGINE) { + var component = Qt.createComponent("qrc:/webengine/map/MapPosition.qml"); + var sprite = component.createObject(root, {maxWidth: root.width, maxHeight: root.height}); - Loader { - id: mapLoader + if (sprite === null) { + // Error Handling + console.log("Error creating object"); + } + } + } + Connections { + target: PositionManager - active: PositionManager.isMapActive - z: 10 - source: WITH_WEBENGINE ? "qrc:/webengine/map/MapPosition.qml" : "" + function onOpenNewMap() { + instanceMapObject() + } } + color: JamiTheme.chatviewBgColor + + property string currentConvId: CurrentConversation.id + HostPopup { id: hostPopup } diff --git a/src/app/mainview/components/ChatViewFooter.qml b/src/app/mainview/components/ChatViewFooter.qml index a2f161971..2dca139e0 100644 --- a/src/app/mainview/components/ChatViewFooter.qml +++ b/src/app/mainview/components/ChatViewFooter.qml @@ -190,7 +190,7 @@ Rectangle { } onShowMapClicked: { - PositionManager.setMapActive(true); + PositionManager.setMapActive(CurrentAccount.id) } onSendFileButtonClicked: jamiFileDialog.open() diff --git a/src/app/mainview/components/MessageBar.qml b/src/app/mainview/components/MessageBar.qml index 714f6fd94..30c14c817 100644 --- a/src/app/mainview/components/MessageBar.qml +++ b/src/app/mainview/components/MessageBar.qml @@ -65,12 +65,33 @@ ColumnLayout { spacing: JamiTheme.chatViewFooterRowSpacing PushButton { - id: sendFileButton + id: showMapButton Layout.alignment: Qt.AlignVCenter Layout.leftMargin: marginSize Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize + visible: WITH_WEBENGINE && !CurrentConversation.isSip + + radius: JamiTheme.chatViewFooterButtonRadius + preferredSize: JamiTheme.chatViewFooterButtonIconSize + + toolTipText: JamiStrings.shareLocation + + source: JamiResources.share_location_svg + + normalColor: JamiTheme.primaryBackgroundColor + imageColor: JamiTheme.messageWebViewFooterButtonImageColor + + onClicked: root.showMapClicked() + } + + PushButton { + id: sendFileButton + + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize + Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize radius: JamiTheme.chatViewFooterButtonRadius preferredSize: JamiTheme.chatViewFooterButtonIconSize - 6 @@ -130,27 +151,6 @@ ColumnLayout { Component.onCompleted: JamiQmlUtils.videoRecordMessageButtonObj = videoRecordMessageButton } - PushButton { - id: showMapButton - - Layout.alignment: Qt.AlignVCenter - Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize - Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize - visible: WITH_WEBENGINE && !CurrentConversation.isSip - - radius: JamiTheme.chatViewFooterButtonRadius - preferredSize: JamiTheme.chatViewFooterButtonIconSize - - toolTipText: JamiStrings.shareLocation - - source: JamiResources.share_location_svg - - normalColor: JamiTheme.primaryBackgroundColor - imageColor: JamiTheme.messageWebViewFooterButtonImageColor - - onClicked: root.showMapClicked() - } - MessageBarTextArea { id: textArea diff --git a/src/app/mainview/components/SmartListItemDelegate.qml b/src/app/mainview/components/SmartListItemDelegate.qml index 11f173b2f..4888a8291 100644 --- a/src/app/mainview/components/SmartListItemDelegate.qml +++ b/src/app/mainview/components/SmartListItemDelegate.qml @@ -68,8 +68,8 @@ ItemDelegate { imageId: UID showPresenceIndicator: Presence !== undefined ? Presence : false - showSharePositionIndicator: PositionManager.isPositionSharedToConv(UID) - showSharedPositionIndicator: PositionManager.isConvSharingPosition(UID) + showSharePositionIndicator: PositionManager.isPositionSharedToConv(accountId, UID) + showSharedPositionIndicator: PositionManager.isConvSharingPosition(accountId, UID) Layout.preferredWidth: JamiTheme.smartListAvatarSize Layout.preferredHeight: JamiTheme.smartListAvatarSize @@ -77,10 +77,10 @@ ItemDelegate { Connections { target: PositionManager function onPositionShareConvIdsCountChanged () { - avatar.showSharePositionIndicator = PositionManager.isPositionSharedToConv(UID) + avatar.showSharePositionIndicator = PositionManager.isPositionSharedToConv(accountId, UID) } function onSharingUrisCountChanged () { - avatar.showSharedPositionIndicator = PositionManager.isConvSharingPosition(UID) + avatar.showSharedPositionIndicator = PositionManager.isConvSharingPosition(accountId, UID) } } diff --git a/src/app/positioning.cpp b/src/app/positioning.cpp index d8a8bbf07..be0114152 100644 --- a/src/app/positioning.cpp +++ b/src/app/positioning.cpp @@ -21,14 +21,12 @@ #include <QJsonObject> #include <QJsonDocument> -Positioning::Positioning(QString uri, QObject* parent) +Positioning::Positioning(QObject* parent) : QObject(parent) - , uri_(uri) { source_ = QGeoPositionInfoSource::createDefaultSource(this); - QTimer* timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &Positioning::requestPosition); - timer->start(5000); + timer_ = new QTimer(this); + connect(timer_, &QTimer::timeout, this, &Positioning::requestPosition); connect(source_, &QGeoPositionInfoSource::errorOccurred, this, &Positioning::slotError); connect(source_, &QGeoPositionInfoSource::positionUpdated, this, &Positioning::positionUpdated); // if location services are activated, positioning will be activated automatically @@ -41,6 +39,8 @@ Positioning::Positioning(QString uri, QObject* parent) void Positioning::start() { + requestPosition(); + timer_->start(10000); if (source_ && !isPositioning) { source_->startUpdates(); isPositioning = true; @@ -53,6 +53,7 @@ Positioning::stop() if (source_ && isPositioning) source_->stopUpdates(); isPositioning = false; + timer_->stop(); } QString @@ -70,17 +71,11 @@ Positioning::convertToJson(const QGeoPositionInfo& info) return strJson; } -void -Positioning::setUri(QString uri) -{ - uri_ = uri; -} - void Positioning::positionUpdated(const QGeoPositionInfo& info) { Q_EMIT positioningError(""); - Q_EMIT newPosition("", uri_, convertToJson(info), -1, ""); + Q_EMIT newPosition(convertToJson(info)); } void diff --git a/src/app/positioning.h b/src/app/positioning.h index 9b3423c97..e2bc11720 100644 --- a/src/app/positioning.h +++ b/src/app/positioning.h @@ -27,7 +27,7 @@ class Positioning : public QObject Q_OBJECT public: - Positioning(QString uri, QObject* parent = 0); + Positioning(QObject* parent = 0); /** * start to retreive the current position */ @@ -42,8 +42,6 @@ public: */ QString convertToJson(const QGeoPositionInfo& info); - void setUri(QString uri); - private Q_SLOTS: void slotError(QGeoPositionInfoSource::Error error); void positionUpdated(const QGeoPositionInfo& info); @@ -57,15 +55,12 @@ private Q_SLOTS: void locationServicesActivated(); Q_SIGNALS: - void newPosition(const QString& unused_AccountId, - const QString& peerId, - const QString& body, - const uint64_t& timestamp, - const QString& daemonId); + void newPosition(const QString& body); void positioningError(const QString error); private: QString uri_; QGeoPositionInfoSource* source_ = nullptr; bool isPositioning = false; + QTimer* timer_; }; diff --git a/src/app/positionmanager.cpp b/src/app/positionmanager.cpp index 513393f06..c4c230079 100644 --- a/src/app/positionmanager.cpp +++ b/src/app/positionmanager.cpp @@ -1,7 +1,8 @@ #include "positionmanager.h" -#include "qtutils.h" +#include "appsettingsmanager.h" +#include "qtutils.h" #include <QApplication> #include <QBuffer> #include <QList> @@ -9,31 +10,40 @@ #include <QJsonDocument> #include <QImageReader> -PositionManager::PositionManager(SystemTray* systemTray, LRCInstance* instance, QObject* parent) +PositionManager::PositionManager(AppSettingsManager* settingsManager, + SystemTray* systemTray, + LRCInstance* instance, + QObject* parent) : QmlAdapterBase(instance, parent) , systemTray_(systemTray) + , settingsManager_(settingsManager) { - timerTimeLeftSharing_ = new QTimer(this); - timerStopSharing_ = new QTimer(this); - connect(timerTimeLeftSharing_, &QTimer::timeout, [=] { - set_timeSharingRemaining(timerStopSharing_->remainingTime()); - }); - connect(timerStopSharing_, &QTimer::timeout, [=] { stopSharingPosition(); }); - connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, [this]() { - set_mapAutoOpening(true); - }); - connect(lrcInstance_, &LRCInstance::currentAccountIdChanged, [this]() { - if (!localPositioning_) // Not yet initialized - return; - localPositioning_->setUri(lrcInstance_->getCurrentAccountInfo().profileInfo.uri); - }); - set_isMapActive(false); + countdownTimer_ = new QTimer(this); + connect(countdownTimer_, &QTimer::timeout, this, &PositionManager::countdownUpdate); + connect(lrcInstance_, + &LRCInstance::selectedConvUidChanged, + this, + &PositionManager::onNewConversation, + Qt::UniqueConnection); + connect(lrcInstance_, + &LRCInstance::currentAccountIdChanged, + this, + &PositionManager::onNewAccount, + Qt::UniqueConnection); + connect( + this, + &PositionManager::localPositionReceived, + this, + [this](const QString& accountId, const QString& peerId, const QString& body) { + onPositionReceived(accountId, peerId, body, -1, ""); + }, + Qt::QueuedConnection); } void PositionManager::safeInit() { - localPositioning_.reset(new Positioning(lrcInstance_->getCurrentAccountInfo().profileInfo.uri)); + localPositioning_.reset(new Positioning()); connectAccountModel(); } @@ -50,41 +60,36 @@ PositionManager::connectAccountModel() void PositionManager::startPositioning() { - currentConvSharingUris_.clear(); - localPositioning_->start(); - connect(localPositioning_.get(), - &Positioning::newPosition, - this, - &PositionManager::onPositionReceived, - Qt::UniqueConnection); + if (localPositioning_) + localPositioning_->start(); + connect(localPositioning_.get(), &Positioning::positioningError, this, &PositionManager::onPositionErrorReceived, Qt::UniqueConnection); + connect( + localPositioning_.get(), + &Positioning::newPosition, + this, + [this](const QString& body) { sendPosition(body, true); }, + Qt::UniqueConnection); } - void PositionManager::stopPositioning() { - localPositioning_->stop(); -} - -QString -PositionManager::getSelectedConvId() -{ - return lrcInstance_->get_selectedConvUid(); + if (localPositioning_) + localPositioning_->stop(); } bool -PositionManager::isConvSharingPosition(const QString& convUri) +PositionManager::isConvSharingPosition(const QString& accountId, const QString& convUri) { const auto& convParticipants = lrcInstance_->getConversationFromConvUid(convUri) .participantsUris(); Q_FOREACH (const auto& id, convParticipants) { if (id != lrcInstance_->getCurrentAccountInfo().profileInfo.uri) { - if (objectListSharingUris_.contains( - QPair<QString, QString> {lrcInstance_->get_currentAccountId(), id})) { + if (objectListSharingUris_.contains(PositionKey {accountId, id})) { return true; } } @@ -93,36 +98,53 @@ PositionManager::isConvSharingPosition(const QString& convUri) } void -PositionManager::loadPreviousLocations() +PositionManager::loadPreviousLocations(QString& accountId) { QVariantMap shareInfo; for (auto it = objectListSharingUris_.begin(); it != objectListSharingUris_.end(); it++) { - QJsonObject jsonObj; - jsonObj.insert("type", QJsonValue("Position")); - jsonObj.insert("lat", it.value()->getLatitude().toString()); - jsonObj.insert("long", it.value()->getLongitude().toString()); - QJsonDocument doc(jsonObj); - QString strJson(doc.toJson(QJsonDocument::Compact)); - onPositionReceived(it.key().first, it.key().second, strJson, -1, ""); + if (it.key().first == accountId) { + QJsonObject jsonObj; + jsonObj.insert("type", QJsonValue("Position")); + jsonObj.insert("lat", it.value()->getLatitude().toString()); + jsonObj.insert("long", it.value()->getLongitude().toString()); + QJsonDocument doc(jsonObj); + QString strJson(doc.toJson(QJsonDocument::Compact)); + // parse the position from json + QVariantMap positionReceived = parseJsonPosition(it.key().first, + it.key().second, + strJson); + addPositionToMap(it.key(), positionReceived); + } } } +QString +PositionManager::getmapTitle(QString& accountId, QString convId) +{ + if (!convId.isEmpty() && !accountId.isEmpty()) { + return lrcInstance_->getAccountInfo(accountId).conversationModel->title(convId); + } + if (!accountId.isEmpty()) + return lrcInstance_->getAccountInfo(accountId).registeredName; + return {}; +} + bool -PositionManager::isPositionSharedToConv(const QString& convUid) +PositionManager::isPositionSharedToConv(const QString& accountId, const QString& convUid) { if (positionShareConvIds_.length()) { auto iter = std::find(positionShareConvIds_.begin(), positionShareConvIds_.end(), - QPair<QString, QString> {lrcInstance_->get_currentAccountId(), - convUid}); + PositionKey {accountId, convUid}); return (iter != positionShareConvIds_.end()); } return false; } void -PositionManager::sendPosition(const QString& body) +PositionManager::sendPosition(const QString& body, bool triggersLocalPosition) { + // send position to positionShareConvIds_ participants try { Q_FOREACH (const auto& key, positionShareConvIds_) { const auto& convInfo = lrcInstance_->getConversationFromConvUid(key.second, key.first); @@ -137,6 +159,15 @@ PositionManager::sendPosition(const QString& body) } catch (const std::exception& e) { qDebug() << Q_FUNC_INFO << e.what(); } + if (triggersLocalPosition) { + // send own position to every account with an opened map + QMutexLocker lk(&mapStatusMutex_); + for (auto it = mapStatus_.begin(); it != mapStatus_.end(); it++) { + Q_EMIT localPositionReceived(it.key(), + lrcInstance_->getAccountInfo(it.key()).profileInfo.uri, + body); + } + } } void @@ -149,26 +180,17 @@ PositionManager::onWatchdogTimeout() if (it != objectListSharingUris_.cend()) { QString stopMsg("{\"type\":\"Stop\"}"); onPositionReceived(it.key().first, it.key().second, stopMsg, -1, ""); + makeVisibleSharingButton(it.key().first); } } void -PositionManager::sharePosition(int maximumTime) +PositionManager::sharePosition(int maximumTime, QString accountId, QString convId) { - connect( - localPositioning_.get(), - &Positioning::newPosition, - this, - [&](const QString&, const QString&, const QString& body, const uint64_t&, const QString&) { - sendPosition(body); - }, - Qt::UniqueConnection); - try { - startPositionTimers(maximumTime); - const auto convUid = lrcInstance_->get_selectedConvUid(); - positionShareConvIds_.append( - QPair<QString, QString> {lrcInstance_->get_currentAccountId(), convUid}); + if (settingsManager_->getValue(Settings::Key::PositionShareLimit) == true) + startPositionTimers(maximumTime); + positionShareConvIds_.append(PositionKey {accountId, convId}); set_positionShareConvIdsCount(positionShareConvIds_.size()); } catch (...) { qDebug() << "Exception during sharePosition:"; @@ -176,17 +198,46 @@ PositionManager::sharePosition(int maximumTime) } void -PositionManager::stopSharingPosition(const QString convId) +PositionManager::stopSharingPosition(QString accountId, const QString convId) { QString stopMsg; stopMsg = "{\"type\":\"Stop\"}"; - if (convId == "") { - sendPosition(stopMsg); + if (accountId == "") { + sendPosition(stopMsg, false); stopPositionTimers(); positionShareConvIds_.clear(); - set_positionShareConvIdsCount(positionShareConvIds_.size()); } else { - const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId); + if (convId == "") { + stopPositionTimers(accountId); + for (auto it = positionShareConvIds_.begin(); it != positionShareConvIds_.end();) { + if (it->first == accountId) { + sendStopMessage(accountId, it->second); + it = positionShareConvIds_.erase(it); + } else + ++it; + } + } else { + sendStopMessage(accountId, convId); + auto iter = std::find(positionShareConvIds_.begin(), + positionShareConvIds_.end(), + PositionKey {accountId, convId}); + if (iter != positionShareConvIds_.end()) { + positionShareConvIds_.remove(std::distance(positionShareConvIds_.begin(), iter)); + } + } + } + if (!positionShareConvIds_.size()) + countdownTimer_->stop(); + set_positionShareConvIdsCount(positionShareConvIds_.size()); +} + +void +PositionManager::sendStopMessage(QString accountId, const QString convId) +{ + QString stopMsg; + stopMsg = "{\"type\":\"Stop\"}"; + if (accountId != "" && convId != "") { + const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId, accountId); Q_FOREACH (const QString& uri, convInfo.participantsUris()) { if (lrcInstance_->getCurrentAccountInfo().profileInfo.uri != uri) { lrcInstance_->getCurrentAccountInfo().contactModel->sendDhtMessage(uri, @@ -194,22 +245,68 @@ PositionManager::stopSharingPosition(const QString convId) APPLICATION_GEO); } } - auto iter = std::find(positionShareConvIds_.begin(), - positionShareConvIds_.end(), - QPair<QString, QString> {lrcInstance_->get_currentAccountId(), - convId}); - if (iter != positionShareConvIds_.end()) { - positionShareConvIds_.remove(std::distance(positionShareConvIds_.begin(), iter)); + } +} + +void +PositionManager::unPinMap(QString key) +{ + QMutexLocker lk(&mapStatusMutex_); + if (mapStatus_.find(key) != mapStatus_.end()) { + mapStatus_[key] = true; + Q_EMIT mapStatusChanged(); + Q_EMIT unPinMapSignal(key); + } else { + qWarning() << "Error: Can't unpin a map that doesn't exist"; + } +} + +void +PositionManager::pinMap(QString key) +{ + QMutexLocker lk(&mapStatusMutex_); + if (mapStatus_.find(key) != mapStatus_.end()) { + // map can be pined only if it's in the right account + if (key == lrcInstance_->get_currentAccountId()) { + mapStatus_[key] = false; + lk.unlock(); + Q_EMIT mapStatusChanged(); + Q_EMIT pinMapSignal(key); + } else { + lk.unlock(); + setMapInactive(key); } - set_positionShareConvIdsCount(positionShareConvIds_.size()); } } void -PositionManager::setMapActive(bool state) +PositionManager::setMapInactive(const QString key) { - set_isMapActive(state); - Q_EMIT isMapActiveChanged(); + QMutexLocker lk(&mapStatusMutex_); + if (mapStatus_.find(key) != mapStatus_.end()) { + mapStatus_.remove(key); + Q_EMIT mapStatusChanged(); + Q_EMIT closeMap(key); + if (!mapStatus_.size()) { + stopPositioning(); + } + } else { + qWarning() << "Error: Can't set inactive a map that doesn't exists"; + } +} + +void +PositionManager::setMapActive(QString key) +{ + if (mapStatus_.find(key) == mapStatus_.end()) { + mapStatus_.insert(key, false); + Q_EMIT mapStatusChanged(); + // creation on QML + Q_EMIT openNewMap(); + + } else { + pinMap(key); + } } QString @@ -233,7 +330,9 @@ PositionManager::getAvatar(const QString& accountId, const QString& uri) } QVariantMap -PositionManager::parseJsonPosition(const QString& body, const QString& peerId) +PositionManager::parseJsonPosition(const QString& accountId, + const QString& peerId, + const QString& body) { QJsonDocument temp = QJsonDocument::fromJson(body.toUtf8()); QJsonObject jsonObject = temp.object(); @@ -250,6 +349,7 @@ PositionManager::parseJsonPosition(const QString& body, const QString& peerId) pos["time"] = i.value().toVariant(); pos["author"] = peerId; + pos["account"] = accountId; } return pos; } @@ -257,17 +357,25 @@ PositionManager::parseJsonPosition(const QString& body, const QString& peerId) void PositionManager::startPositionTimers(int timeSharing) { - set_timeSharingRemaining(timeSharing); - timerTimeLeftSharing_->start(1000); - timerStopSharing_->start(timeSharing); + mapTimerCountDown_[lrcInstance_->get_currentAccountId()] = timeSharing; + countdownUpdate(); + countdownTimer_->start(1000); } void -PositionManager::stopPositionTimers() +PositionManager::stopPositionTimers(QString accountId) { - set_timeSharingRemaining(0); - timerTimeLeftSharing_->stop(); - timerStopSharing_->stop(); + // reset all timers + if (accountId == nullptr) { + mapTimerCountDown_.clear(); + } else { + auto it = mapTimerCountDown_.find(accountId); + if (it != mapTimerCountDown_.end()) { + mapTimerCountDown_.erase(it); + } + if (!mapTimerCountDown_.size()) + countdownTimer_->stop(); + } } void @@ -305,112 +413,185 @@ PositionManager::showNotification(const QString& accountId, } void -PositionManager::onPositionReceived(const QString& accountId, - const QString& peerId, - const QString& body, - const uint64_t& timestamp, - const QString& daemonId) +PositionManager::onNewConversation() { - // only show shared positions from contacts in the current conversation - const auto& convParticipants = lrcInstance_ - ->getConversationFromConvUid( - lrcInstance_->get_selectedConvUid()) - .participantsUris(); - // to know if the position received is from someone in the current conversation - bool isPeerIdInConv = (std::find(convParticipants.begin(), convParticipants.end(), peerId) - != convParticipants.end()); - - QVariantMap newPosition = parseJsonPosition(body, peerId); - auto getShareInfo = [&](bool update) -> QVariantMap { - QVariantMap shareInfo; - shareInfo["author"] = peerId; - if (!update) { - shareInfo["avatar"] = getAvatar(accountId, peerId); + set_mapAutoOpening(true); +} + +void +PositionManager::onNewAccount() +{ + QMutexLocker lk(&mapStatusMutex_); + for (auto it = mapStatus_.begin(); it != mapStatus_.end();) { + if (it.value() == false) { + Q_EMIT closeMap(it.key()); + it = mapStatus_.erase(it); + Q_EMIT mapStatusChanged(); + } else { + it++; } - shareInfo["long"] = newPosition["long"]; - shareInfo["lat"] = newPosition["lat"]; - return shareInfo; - }; - auto endSharing = newPosition["type"] == "Stop"; + } +} - auto key = QPair<QString, QString> {accountId, peerId}; +bool +PositionManager::isNewMessageTriggersMap(bool endSharing, + const QString& uri, + const QString& accountId) +{ + QMutexLocker lk(&mapStatusMutex_); + return !endSharing && (accountId == lrcInstance_->get_currentAccountId()) && mapAutoOpening_ + && (uri != lrcInstance_->getCurrentAccountInfo().profileInfo.uri) + && (mapStatus_.find(accountId) == mapStatus_.end()); +} - if (!endSharing) { - // open map on position reception - if (!isMapActive_ && mapAutoOpening_ && isPeerIdInConv - && peerId != lrcInstance_->getCurrentAccountInfo().profileInfo.uri) { - set_isMapActive(true); +void +PositionManager::countdownUpdate() +{ + // First removal of timers and shared position + auto end = std::find_if(mapTimerCountDown_.begin(), + mapTimerCountDown_.end(), + [](const auto& end) { return end == 0; }); + if (end != mapTimerCountDown_.end()) { + Q_EMIT sendCountdownUpdate(end.key(), end.value()); + stopSharingPosition(end.key()); + } + // When removals are done, countdown can be updated + for (auto it = mapTimerCountDown_.begin(); it != mapTimerCountDown_.end(); it++) { + if (it.value() != 0) { + Q_EMIT sendCountdownUpdate(it.key(), it.value()); + it.value() -= 1000; } } - auto iter = std::find(currentConvSharingUris_.begin(), currentConvSharingUris_.end(), key); - if (iter == currentConvSharingUris_.end()) { - // New share - if (!endSharing) { - // list to save more information on position + watchdog - auto it = objectListSharingUris_.find(key); - auto isNewSharing = it == objectListSharingUris_.end(); - if (isNewSharing) { - auto obj = new PositionObject(newPosition["lat"], newPosition["long"], this); - - objectListSharingUris_.insert(key, obj); - set_sharingUrisCount(objectListSharingUris_.size()); - connect(obj, - &PositionObject::timeout, - this, - &PositionManager::onWatchdogTimeout, - Qt::DirectConnection); - } +} - if (isPeerIdInConv) { - currentConvSharingUris_.insert(key); - Q_EMIT positionShareAdded(getShareInfo(false)); - } else if (isNewSharing && accountId != "") { - auto& convInfo = lrcInstance_->getConversationFromPeerUri(peerId, accountId); - if (!convInfo.uid.isEmpty()) { - showNotification(accountId, convInfo.uid, peerId); - } +void +PositionManager::addPositionToMap(PositionKey key, QVariantMap position) +{ + // avatar only sent one time to qml, when a new position is added + position["avatar"] = getAvatar(key.first, key.second); + Q_EMIT positionShareAdded(position); +} + +void +PositionManager::addPositionToMemory(PositionKey key, QVariantMap positionReceived) +{ + // add the position to the list + auto obj = new PositionObject(positionReceived["lat"], positionReceived["long"], this); + objectListSharingUris_.insert(key, obj); + + // information for qml + set_sharingUrisCount(objectListSharingUris_.size()); + + // watchdog + connect(obj, + &PositionObject::timeout, + this, + &PositionManager::onWatchdogTimeout, + Qt::DirectConnection); + + auto& accountId = key.first; + auto& uri = key.second; + // Add position to the current map if needed) + addPositionToMap(key, positionReceived); + + // show notification + if (accountId != "") { + QMutexLocker lk(&mapStatusMutex_); + if (mapStatus_.find(accountId) == mapStatus_.end()) { + auto& convInfo = lrcInstance_->getConversationFromPeerUri(uri, accountId); + if (!convInfo.uid.isEmpty()) { + showNotification(accountId, convInfo.uid, uri); } - // stop sharing position + } + } +} + +void +PositionManager::updatePositionInMemory(PositionKey key, QVariantMap positionReceived) +{ + auto it = objectListSharingUris_.find(key); + if (it != objectListSharingUris_.end()) { + if (it.value()) { + // reset watchdog + it.value()->resetWatchdog(); + // update position + it.value()->updatePosition(positionReceived["lat"], positionReceived["long"]); } else { - auto it = objectListSharingUris_.find(key); - if (it != objectListSharingUris_.end()) { - it.value()->deleteLater(); - objectListSharingUris_.erase(it); - set_sharingUrisCount(objectListSharingUris_.size()); - } + qWarning() << "error in PositionManager::updatePositionInMemory(), it.value() is null"; } } else { - // Update/remove existing - if (endSharing) { - // Remove - auto it = objectListSharingUris_.find(key); - if (it != objectListSharingUris_.end()) { - it.value()->deleteLater(); - objectListSharingUris_.erase(it); - set_sharingUrisCount(objectListSharingUris_.size()); - } - if (isPeerIdInConv) { - currentConvSharingUris_.remove(key); - Q_EMIT positionShareRemoved(peerId); - // close the map if you're not sharing and you don't receive position anymore - if (!positionShareConvIds_.length() - && ((sharingUrisCount_ == 1 - && objectListSharingUris_.contains(QPair<QString, QString> { - "", lrcInstance_->getCurrentAccountInfo().profileInfo.uri})) - || sharingUrisCount_ == 0)) { - set_isMapActive(false); - } - } - } else { - // Update - if (isPeerIdInConv) - Q_EMIT positionShareUpdated(getShareInfo(true)); - // reset watchdog + qWarning() + << "Error: A position intented to be updated while not in objectListSharingUris_ "; + } - auto it = objectListSharingUris_.find(key); - if (it != objectListSharingUris_.end()) { - it.value()->resetWatchdog(); - } - } + // update position on the map (if needed) + Q_EMIT positionShareUpdated(positionReceived); +} + +void +PositionManager::removePositionFromMemory(PositionKey key, QVariantMap positionReceived) +{ + // Remove + auto it = objectListSharingUris_.find(key); + if (it != objectListSharingUris_.end()) { + // free memory + it.value()->deleteLater(); + // delete value + objectListSharingUris_.erase(it); + // update list count for qml + set_sharingUrisCount(objectListSharingUris_.size()); + } else { + qWarning() + << "Error: A position intented to be removed while not in objectListSharingUris_ "; + return; + } + // if needed, remove from map + Q_EMIT positionShareRemoved(key.second, positionReceived["account"].toString()); + // close the map if you're not sharing and you don't receive position anymore + if (!positionShareConvIds_.length() + && ((sharingUrisCount_ == 1 + && objectListSharingUris_.begin().key().second + == lrcInstance_->getCurrentAccountInfo().profileInfo.uri) + || sharingUrisCount_ == 0)) { + setMapInactive(lrcInstance_->get_currentAccountId()); + } +} + +void +PositionManager::onPositionReceived(const QString& accountId, + const QString& peerId, + const QString& body, + const uint64_t& timestamp, + const QString& daemonId) +{ + // handlers variables + + // parse the position from json + QVariantMap positionReceived = parseJsonPosition(accountId, peerId, body); + + // is it a message that notify an end of position sharing + auto endSharing = positionReceived["type"] == "Stop"; + + // key to identify the peer + auto key = PositionKey {accountId, peerId}; + + // check if the position exists in all shared positions, even if not visible to the screen + auto findPeerIdinAllPeers = objectListSharingUris_.find(key); + + // open the map on position reception if needed + if (isNewMessageTriggersMap(endSharing, peerId, accountId)) { + setMapActive(accountId); + } + + // if the position already exists + if (findPeerIdinAllPeers != objectListSharingUris_.end()) { + if (endSharing) + removePositionFromMemory(key, positionReceived); + else + updatePositionInMemory(key, positionReceived); + + } else { + // It is the first time a position is received from this peer + addPositionToMemory(key, positionReceived); } } diff --git a/src/app/positionmanager.h b/src/app/positionmanager.h index fc8c53a3c..e75af1eb7 100644 --- a/src/app/positionmanager.h +++ b/src/app/positionmanager.h @@ -24,19 +24,21 @@ #include "positionobject.h" #include "systemtray.h" +#include <QMutex> #include <QObject> #include <QString> class PositionManager : public QmlAdapterBase { Q_OBJECT - QML_RO_PROPERTY(bool, isMapActive) - QML_RO_PROPERTY(int, timeSharingRemaining) + // map of elements : map key and isUnpin + QML_PROPERTY(QVariantMap, mapStatus) + QML_PROPERTY(bool, mapAutoOpening) QML_PROPERTY(int, positionShareConvIdsCount) QML_PROPERTY(int, sharingUrisCount) - QML_PROPERTY(bool, mapAutoOpening) public: - explicit PositionManager(SystemTray* systemTray, + explicit PositionManager(AppSettingsManager* settingsManager, + SystemTray* systemTray, LRCInstance* instance, QObject* parent = nullptr); ~PositionManager() = default; @@ -45,30 +47,49 @@ Q_SIGNALS: void positioningError(const QString error); void positionShareAdded(const QVariantMap& shareInfo); void positionShareUpdated(const QVariantMap& posInfo); - void positionShareRemoved(const QString& uri); + void positionShareRemoved(const QString& uri, const QString& accountId); + void openNewMap(); + void closeMap(const QString& key); + void pinMapSignal(const QString& key); + void unPinMapSignal(const QString& key); + void localPositionReceived(const QString& accountId, const QString& peerId, const QString& body); + void makeVisibleSharingButton(const QString& accountId); + void sendCountdownUpdate(const QString& accountId, const int remainingTime); protected: void safeInit() override; QString getAvatar(const QString& accountId, const QString& peerId); - QVariantMap parseJsonPosition(const QString& body, const QString& peerId); + QVariantMap parseJsonPosition(const QString& accountId, + const QString& peerId, + const QString& body); + void addPositionToMap(PositionKey key, QVariantMap position); + void addPositionToMemory(PositionKey key, QVariantMap positionReceived); + void updatePositionInMemory(PositionKey key, QVariantMap positionReceived); + void removePositionFromMemory(PositionKey key, QVariantMap positionReceived); void positionWatchDog(); void startPositionTimers(int timeSharing); - void stopPositionTimers(); + void stopPositionTimers(QString accountId = {}); + bool isNewMessageTriggersMap(bool endSharing, const QString& uri, const QString& accountId); + void countdownUpdate(); + void sendStopMessage(QString accountId = "", const QString convId = ""); Q_INVOKABLE void connectAccountModel(); - Q_INVOKABLE void setMapActive(bool state); - Q_INVOKABLE void sharePosition(int maximumTime); - Q_INVOKABLE void stopSharingPosition(const QString convId = ""); + Q_INVOKABLE void pinMap(QString key); + Q_INVOKABLE void unPinMap(QString key); + Q_INVOKABLE void setMapActive(QString key); + Q_INVOKABLE void setMapInactive(const QString key); + Q_INVOKABLE void sharePosition(int maximumTime, QString accountId, QString convId); + Q_INVOKABLE void stopSharingPosition(QString accountId = "", const QString convId = ""); Q_INVOKABLE void startPositioning(); Q_INVOKABLE void stopPositioning(); - Q_INVOKABLE QString getSelectedConvId(); - Q_INVOKABLE bool isPositionSharedToConv(const QString& convUri); - Q_INVOKABLE bool isConvSharingPosition(const QString& convUri); + Q_INVOKABLE bool isPositionSharedToConv(const QString& accountId, const QString& convUid); + Q_INVOKABLE bool isConvSharingPosition(const QString& accountId, const QString& convUri); - Q_INVOKABLE void loadPreviousLocations(); + Q_INVOKABLE void loadPreviousLocations(QString& accountId); + Q_INVOKABLE QString getmapTitle(QString& accountId, QString convId = ""); private Q_SLOTS: void onPositionErrorReceived(const QString error); @@ -77,16 +98,21 @@ private Q_SLOTS: const QString& body, const uint64_t& timestamp, const QString& daemonId); - void sendPosition(const QString& body); + void sendPosition(const QString& body, bool triggersLocalPosition = true); void onWatchdogTimeout(); void showNotification(const QString& accountId, const QString& convId, const QString& from); + void onNewConversation(); + void onNewAccount(); private: SystemTray* systemTray_; std::unique_ptr<Positioning> localPositioning_; - QTimer* timerTimeLeftSharing_ = nullptr; - QTimer* timerStopSharing_ = nullptr; - QSet<QPair<QString, QString>> currentConvSharingUris_; - QMap<QPair<QString, QString>, PositionObject*> objectListSharingUris_; - QList<QPair<QString, QString>> positionShareConvIds_; + QMap<QString, int> mapTimerCountDown_; + QTimer* countdownTimer_ = nullptr; + // map of all shared position by peers + QMap<PositionKey, PositionObject*> objectListSharingUris_; + // list of all the peers the user is sharing position to + QList<PositionKey> positionShareConvIds_; + QMutex mapStatusMutex_; + AppSettingsManager* settingsManager_; }; diff --git a/src/app/positionobject.cpp b/src/app/positionobject.cpp index e7a970526..a9e8b0453 100644 --- a/src/app/positionobject.cpp +++ b/src/app/positionobject.cpp @@ -2,7 +2,7 @@ PositionObject::PositionObject(QVariant latitude, QVariant longitude, QObject* parent) : QObject(parent) - , resetTime(20000) + , resetTime(40000) , longitude_(longitude) , latitude_(latitude) @@ -28,3 +28,10 @@ PositionObject::getLatitude() { return latitude_; } + +void +PositionObject::updatePosition(QVariant latitude, QVariant longitude) +{ + longitude_ = longitude; + latitude_ = latitude; +} diff --git a/src/app/positionobject.h b/src/app/positionobject.h index f2edc2ca8..94e60f076 100644 --- a/src/app/positionobject.h +++ b/src/app/positionobject.h @@ -20,6 +20,8 @@ public: QVariant getLongitude(); QVariant getLatitude(); + void updatePosition(QVariant latitude, QVariant longitude); + private: QVariant latitude_; QVariant longitude_; diff --git a/src/app/qmlregister.cpp b/src/app/qmlregister.cpp index 2359c8014..d42c8cc5b 100644 --- a/src/app/qmlregister.cpp +++ b/src/app/qmlregister.cpp @@ -112,7 +112,7 @@ registerTypes(QQmlEngine* engine, // setup the adapters (their lifetimes are that of MainApplication) auto callAdapter = new CallAdapter(systemTray, lrcInstance, parent); auto messagesAdapter = new MessagesAdapter(settingsManager, previewEngine, lrcInstance, parent); - auto positionManager = new PositionManager(systemTray, lrcInstance, parent); + auto positionManager = new PositionManager(settingsManager, systemTray, lrcInstance, parent); auto conversationsAdapter = new ConversationsAdapter(systemTray, lrcInstance, parent); auto avAdapter = new AvAdapter(lrcInstance, parent); auto contactAdapter = new ContactAdapter(lrcInstance, parent); diff --git a/src/app/settingsview/components/GeneralSettingsPage.qml b/src/app/settingsview/components/GeneralSettingsPage.qml index 82f7a93a4..e0c50a2ed 100644 --- a/src/app/settingsview/components/GeneralSettingsPage.qml +++ b/src/app/settingsview/components/GeneralSettingsPage.qml @@ -63,6 +63,16 @@ Rectangle { itemWidth: preferredColumnWidth } + // location sharing setting panel + LocationSharingSettings { + Layout.fillWidth: true + Layout.topMargin: JamiTheme.preferredMarginSize + Layout.leftMargin: JamiTheme.preferredMarginSize + Layout.rightMargin: JamiTheme.preferredMarginSize + + itemWidth: preferredColumnWidth + } + // file transfer setting panel FileTransferSettings { id: fileTransferSettings diff --git a/src/app/settingsview/components/LocationSharingSettings.qml b/src/app/settingsview/components/LocationSharingSettings.qml new file mode 100644 index 000000000..a21009b2d --- /dev/null +++ b/src/app/settingsview/components/LocationSharingSettings.qml @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * Author: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import net.jami.Models 1.1 +import net.jami.Adapters 1.1 +import net.jami.Enums 1.1 +import net.jami.Constants 1.1 + +import "../../commoncomponents" + +ColumnLayout { + id: root + + property int itemWidth + + Label { + Layout.fillWidth: true + + text: JamiStrings.locationSharingLabel + font.pointSize: JamiTheme.headerFontSize + font.kerning: true + color: JamiTheme.textColor + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + ToggleSwitch { + id: isTimeLimit + + visible: WITH_WEBENGINE + + Layout.fillWidth: true + Layout.leftMargin: JamiTheme.preferredMarginSize + + checked: UtilsAdapter.getAppValue(Settings.PositionShareLimit) + + labelText: JamiStrings.positionShareLimit + fontPointSize: JamiTheme.settingsFontSize + + tooltipText: JamiStrings.positionShareLimit + + onSwitchToggled: { + positionSharingLimitation = !UtilsAdapter.getAppValue(Settings.PositionShareLimit) + UtilsAdapter.setAppValue(Settings.PositionShareLimit, + positionSharingLimitation) + + } + property bool positionSharingLimitation: UtilsAdapter.getAppValue(Settings.PositionShareLimit) + } + + RowLayout { + id: timeSharingLocation + + Layout.fillWidth: true + Layout.preferredHeight: JamiTheme.preferredFieldHeight + Layout.leftMargin: JamiTheme.preferredMarginSize + visible: isTimeLimit.positionSharingLimitation + + function standartCountdown(minutes) { + var hour = Math.floor(minutes / 60) + var min = minutes % 60 + if (hour) { + if (min) + return qsTr("%1h%2min").arg(hour).arg(min) + else + return qsTr("%1h").arg(hour) + } + return qsTr("%1min").arg(min) + } + + Text { + Layout.fillWidth: true + Layout.rightMargin: JamiTheme.preferredMarginSize / 2 + + color: JamiTheme.textColor + text: JamiStrings.positionShareDuration + font.pointSize: JamiTheme.settingsFontSize + font.kerning: true + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + Text { + id: timeSharingLocationValueLabel + + Layout.alignment: Qt.AlignRight + Layout.fillHeight: true + Layout.fillWidth: true + Layout.rightMargin: JamiTheme.preferredMarginSize / 2 + + color: JamiTheme.textColor + text: timeSharingLocation.standartCountdown(UtilsAdapter.getAppValue(Settings.PositionShareDuration)) + + font.pointSize: JamiTheme.settingsFontSize + font.kerning: true + + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + } + + Slider { + id: timeSharingSlider + + Layout.maximumWidth: itemWidth + Layout.alignment: Qt.AlignRight + Layout.fillWidth: true + Layout.fillHeight: true + + value: Math.log(UtilsAdapter.getAppValue(Settings.PositionShareDuration)) + + from: 0.5 + to: Math.log(600) + stepSize: 0.05 + + onMoved: { + timeSharingLocationValueLabel.text = timeSharingLocation.standartCountdown(Math.floor(Math.exp(value))) + UtilsAdapter.setAppValue(Settings.PositionShareDuration, Math.floor(Math.exp(value))) + } + + MaterialToolTip { + id: toolTip + + text: JamiStrings.positionShareDuration + visible: parent.hovered + delay: Qt.styleHints.mousePressAndHoldInterval + } + } + } +} diff --git a/src/app/webengine/map/MapPosition.qml b/src/app/webengine/map/MapPosition.qml index 17d9f7232..6f5ea5b05 100644 --- a/src/app/webengine/map/MapPosition.qml +++ b/src/app/webengine/map/MapPosition.qml @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ + import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -23,373 +24,212 @@ import QtWebEngine import net.jami.Models 1.1 import net.jami.Adapters 1.1 +import net.jami.Enums 1.1 import net.jami.Constants 1.1 import "../../commoncomponents" -Rectangle { - id: mapPopup - - x: xPos - y: yPos - width: isFullScreen ? root.width : windowSize - height: isMinimised - ? buttonOverlay.height + buttonsChoseSharing.height + 30 - : isFullScreen ? root.height - yPos : windowSize - - property bool isFullScreen: false - property bool isMinimised: false - property real windowSize: windowPreferedSize > JamiTheme.minimumMapWidth - ? windowPreferedSize - : JamiTheme.minimumMapWidth - property real windowPreferedSize: root.width > root.height - ? root.height / 3 - : root.width / 3 - property real xPos: 0 - property real yPos: JamiTheme.chatViewHeaderPreferredHeight - - WebEngineView { - id: webView - - width: parent.width - height: parent.height - - property string mapHtml: ":/webengine/map/map.html" - property string olCss: ":/webengine/map/ol.css" - property string mapJs: "../../webengine/map/map.js" - property string olJs: "../../webengine/map/ol.js" - property bool isLoaded: false - property var positionList: PositionManager.positionList; - property var avatarPositionList: PositionManager.avatarPositionList; - property bool isSharing: (PositionManager.positionShareConvIdsCount !== 0 ) - - function loadScripts () { - var scriptMapJs = { - sourceUrl: Qt.resolvedUrl(mapJs), - injectionPoint: WebEngineScript.DocumentReady, - worldId: WebEngineScript.MainWorld - } +Item { + id: root - var scriptOlJs = { - sourceUrl: Qt.resolvedUrl(olJs), - injectionPoint: WebEngineScript.DocumentReady, - worldId: WebEngineScript.MainWorld - } + property bool isUnpin: false + property real maxWidth + property real maxHeight + property string attachedAccountId + property string currentAccountId: CurrentAccount.id + property string currentConvId: CurrentConversation.id + property bool isSharing: (PositionManager.positionShareConvIdsCount !== 0) + property bool isSharingToCurrentConversation - userScripts.collection = [ scriptOlJs, scriptMapJs ] - } - Connections { - target: PositionManager - - function onPositionShareAdded(shareInfo) { - if(webView.isLoaded) { - var curLong = shareInfo.long - var curLat = shareInfo.lat - webView.runJavaScript("newPosition([" + curLong + "," + curLat + "], '" + shareInfo.author + "', '" + shareInfo.avatar + "' )" ); - webView.runJavaScript("zoomTolayersExtent()" ); - } - - } + function closeMapPosition() { + root.destroy() + } - function onPositionShareUpdated(shareInfo) { - if(webView.isLoaded) { - var curLong = shareInfo.long - var curLat = shareInfo.lat - webView.runJavaScript("updatePosition([" + curLong + "," + curLat + "], '" + shareInfo.author + "' )" ); - } - } + Connections { + target: PositionManager - function onPositionShareRemoved(author) { - if(webView.isLoaded) { - webView.runJavaScript("removePosition( '" + author + "' )" ); - webView.runJavaScript("zoomTolayersExtent()" ); - } + function onPinMapSignal(key) { + if (key === attachedAccountId) { + isUnpin = false + mapObject.state = "pin" + windowUnpin.close() } } - Component.onCompleted: { - loadHtml(UtilsAdapter.qStringFromFile(mapHtml), mapHtml) - loadScripts() + function onCloseMap(key) { + if (key === attachedAccountId ) + closeMapPosition() } - onLoadingChanged: function (loadingInfo) { - if (loadingInfo.status === WebEngineView.LoadSucceededStatus) { - runJavaScript(UtilsAdapter.getStyleSheet("olcss",UtilsAdapter.qStringFromFile(olCss))) - webView.isLoaded = true - runJavaScript("setMapView([" + 0 + ","+ 0 + "], " + 1 + " );" ); - PositionManager.startPositioning() - //load locations that were received before this conversation was opened - PositionManager.loadPreviousLocations(); + function onUnPinMapSignal(key) { + if (key === attachedAccountId ) { + isUnpin = true + mapObject.state = "unpin" + windowUnpin.show() } } } - ColumnLayout { - id: buttonsChoseSharing + Window { + id: windowUnpin - anchors.horizontalCenter: mapPopup.horizontalCenter - anchors.margins: 10 - anchors.bottom: mapPopup.bottom + width: parentPin.width + height: parentPin.height + visible: false + title: PositionManager.getmapTitle(attachedAccountId) - property bool shortSharing: true + Item { + id: parentUnPin - RowLayout { - Layout.alignment: Qt.AlignHCenter - - MaterialButton { - id: shortSharingButton + width: mapObject.width + height: mapObject.height + } - preferredWidth: text.contentWidth - visible: !webView.isSharing - textLeftPadding: JamiTheme.buttontextPadding - textRightPadding: JamiTheme.buttontextPadding - primary: true - text: JamiStrings.shortSharing - color: buttonsChoseSharing.shortSharing ? JamiTheme.buttonTintedBluePressed : JamiTheme.buttonTintedBlue - fontSize: JamiTheme.timerButtonsFontSize - onClicked: { - buttonsChoseSharing.shortSharing = true - } + onClosing: { + if (isUnpin) { + PositionManager.setMapInactive(attachedAccountId) } + } + } - MaterialButton { - id: longSharingButton - - preferredWidth: text.contentWidth - visible: !webView.isSharing - textLeftPadding: JamiTheme.buttontextPadding - textRightPadding: JamiTheme.buttontextPadding - primary: true - text: JamiStrings.longSharing - color: !buttonsChoseSharing.shortSharing ? JamiTheme.buttonTintedBluePressed : JamiTheme.buttonTintedBlue - fontSize: JamiTheme.timerButtonsFontSize - onClicked: { - buttonsChoseSharing.shortSharing = false; + Item { + id: parentPin + + width: mapObject.width + height: mapObject.height + + Rectangle { + id: mapObject + + x: xPos + y: yPos + width: root.isUnpin + ? windowUnpin.width + : isFullScreen ? root.maxWidth : windowSize + height: root.isUnpin + ? windowUnpin.height + : isFullScreen ? root.maxHeight - yPos : windowSize + + property bool isFullScreen: false + property real windowSize: windowPreferedSize > JamiTheme.minimumMapWidth + ? windowPreferedSize + : JamiTheme.minimumMapWidth + property real windowPreferedSize: root.maxWidth > root.maxHeight + ? root.maxHeight / 3 + : root.maxWidth / 3 + property real xPos: 0 + property real yPos: root.isUnpin ? 0 : JamiTheme.chatViewHeaderPreferredHeight + + states: [ State { + name: "unpin" + ParentChange { target: mapObject; parent: parentUnPin; x:0; y:0 } + }, + State { + name: "pin" + ParentChange { target: mapObject; parent: parentPin; x:xPos; y:JamiTheme.chatViewHeaderPreferredHeight } + } + ] + property alias webView: webView + + WebEngineView { + id: webView + + layer.enabled: !isFullScreen + layer.effect: OpacityMask { + maskSource: + Rectangle { + width: webView.width + height: webView.height + radius: 10 + } } - } - - Rectangle { - - radius: 10 - width: textTimer.width + 15 - height: textTimer.height + 15 - color: JamiTheme.mapButtonsOverlayColor - visible: webView.isSharing && PositionManager.timeSharingRemaining - - Text { - id: textTimer - anchors.centerIn: parent - color: JamiTheme.mapButtonColor - text: remainingTimeMs <= 1 - ? JamiStrings.minuteLeft.arg(remainingTimeMs) - : JamiStrings.minutesLeft.arg(remainingTimeMs) + width: parent.width + height: parent.height - Layout.alignment: Qt.AlignHCenter + property string mapHtml: ":/webengine/map/map.html" + property string olCss: ":/webengine/map/ol.css" + property string mapJs: "../../webengine/map/map.js" + property string olJs: "../../webengine/map/ol.js" + property bool isLoaded: false + property var positionList: PositionManager.positionList; + property var avatarPositionList: PositionManager.avatarPositionList; - property int remainingTimeMs: Math.ceil(PositionManager.timeSharingRemaining / 1000 / 60) - } - } - } - - RowLayout { - id: sharePositionLayout - Layout.alignment: Qt.AlignHCenter - - MaterialButton { - id: sharePositionButton - - preferredWidth: text.contentWidth - textLeftPadding: JamiTheme.buttontextPadding - textRightPadding: JamiTheme.buttontextPadding - primary: true - visible: ! PositionManager.isPositionSharedToConv(PositionManager.getSelectedConvId()) - text: JamiStrings.shareLocation - color: isError - ? JamiTheme.buttonTintedGreyInactive - : JamiTheme.buttonTintedBlue - hoveredColor: isError - ? JamiTheme.buttonTintedGreyInactive - : JamiTheme.buttonTintedBlueHovered - pressedColor: isError - ? JamiTheme.buttonTintedGreyInactive - : JamiTheme.buttonTintedBluePressed - Layout.alignment: Qt.AlignHCenter - property bool isHovered: false - property string positioningError: "default" - property bool isError: positioningError.length - function errorString(posError) { - if (posError === "locationServicesError") - return JamiStrings.locationServicesError - return JamiStrings.locationServicesClosedError - } - - onClicked: { - if (!isError) { - if( buttonsChoseSharing.shortSharing) - PositionManager.sharePosition(10 * 60 * 1000); - else - PositionManager.sharePosition(60 * 60 * 1000); - visible = false + function loadScripts () { + var scriptMapJs = { + sourceUrl: Qt.resolvedUrl(mapJs), + injectionPoint: WebEngineScript.DocumentReady, + worldId: WebEngineScript.MainWorld } - } - onHoveredChanged: { - isHovered = !isHovered - } + var scriptOlJs = { + sourceUrl: Qt.resolvedUrl(olJs), + injectionPoint: WebEngineScript.DocumentReady, + worldId: WebEngineScript.MainWorld + } - MaterialToolTip { - visible: sharePositionButton.isHovered - && sharePositionButton.isError && (sharePositionButton.positioningError !== "default") - x: 0 - y: 0 - text: sharePositionButton.errorString(sharePositionButton.positioningError) + userScripts.collection = [ scriptOlJs, scriptMapJs ] } Connections { target: PositionManager - function onPositioningError (err) { - sharePositionButton.positioningError = err; - } - } - } - MaterialButton { - id: stopSharingPositionButton - - preferredWidth: text.contentWidth - textLeftPadding: JamiTheme.buttontextPadding - textRightPadding: JamiTheme.buttontextPadding - primary: true - visible: webView.isSharing - text: JamiStrings.stopSharingLocation - color: isError - ? JamiTheme.buttonTintedGreyInactive - : JamiTheme.buttonTintedRed - hoveredColor: isError - ? JamiTheme.buttonTintedGreyInactive - : JamiTheme.buttonTintedRedHovered - pressedColor: isError - ? JamiTheme.buttonTintedGreyInactive - : JamiTheme.buttonTintedRedPressed - Layout.alignment: Qt.AlignHCenter - property bool isHovered: false - property string positioningError - property bool isError: positioningError.length - onClicked: { - if (!isError) { - if (PositionManager.positionShareConvIdsCount >= 2) { - stopSharingPositionPopup.open() - } else { - PositionManager.stopSharingPosition(); - sharePositionButton.visible = true + + function onPositionShareAdded(shareInfo) { + if(webView.isLoaded) { + if (shareInfo.account === attachedAccountId) { + var curLong = shareInfo.long + var curLat = shareInfo.lat + webView.runJavaScript("newPosition([" + curLong + "," + curLat + "], '" + shareInfo.author + "', '" + shareInfo.avatar + "' )" ); + webView.runJavaScript("zoomTolayersExtent()" ); + } } } - } - } - } - } - - StopSharingPositionPopup { - id: stopSharingPositionPopup - - property alias shareButtonVisibility: sharePositionButton.visible - } - - Rectangle { - id: buttonOverlay - anchors.right: webView.right - anchors.top: webView.top - anchors.margins: 10 - radius: 10 - width: lay.width + 10 - height: lay.height + 10 - color: JamiTheme.mapButtonsOverlayColor - - RowLayout { - id: lay - - anchors.centerIn: parent - - PushButton { - id: btnCenter - - toolTipText: JamiStrings.centerMapTooltip - imageColor: JamiTheme.mapButtonColor - normalColor: JamiTheme.transparentColor - source: JamiResources.share_location_svg - onClicked: { - webView.runJavaScript("zoomTolayersExtent()" ); - } - } + function onPositionShareUpdated(shareInfo) { + if(webView.isLoaded) { + if (shareInfo.account === attachedAccountId) { + var curLong = shareInfo.long + var curLat = shareInfo.lat + webView.runJavaScript("updatePosition([" + curLong + "," + curLat + "], '" + shareInfo.author + "' )" ); + } + } + } - PushButton { - id: btnMove - - toolTipText: JamiStrings.dragMapTooltip - imageColor: JamiTheme.mapButtonColor - normalColor: JamiTheme.transparentColor - source: JamiResources.move_svg - - MouseArea { - anchors.fill: parent - drag.target: mapPopup - drag.minimumX: 0 - drag.maximumX: root.width - mapPopup.width - drag.minimumY: 0 - drag.maximumY: root.height - mapPopup.height + function onPositionShareRemoved(author, accountId) { + if(webView.isLoaded) { + if (accountId === attachedAccountId) { + webView.runJavaScript("removePosition( '" + author + "' )" ); + webView.runJavaScript("zoomTolayersExtent()" ); + } + } + } } - } - PushButton { - id: btnminimize - - toolTipText: isMinimised - ? JamiStrings.extendMapTooltip - : JamiStrings.minimizeMapTooltip - imageColor: JamiTheme.mapButtonColor - normalColor: JamiTheme.transparentColor - source: isMinimised - ? JamiResources.close_fullscreen_24dp_svg - : JamiResources.minimize_svg - onClicked: { - isMinimised = !isMinimised - isFullScreen = false; + Component.onCompleted: { + loadHtml(UtilsAdapter.qStringFromFile(mapHtml), mapHtml) + loadScripts() } - } - PushButton { - id: btnmaximise - - toolTipText: isFullScreen - ? JamiStrings.reduceMapTooltip - : JamiStrings.maximizeMapTooltip - imageColor: JamiTheme.mapButtonColor - normalColor: JamiTheme.transparentColor - source: isFullScreen? JamiResources.close_fullscreen_24dp_svg : JamiResources.open_in_full_24dp_svg - onClicked: { - if (!isFullScreen && !isMinimised) { - mapPopup.x = mapPopup.xPos - mapPopup.y = mapPopup.yPos + onLoadingChanged: function (loadingInfo) { + if (loadingInfo.status === WebEngineView.LoadSucceededStatus) { + attachedAccountId = CurrentAccount.id + runJavaScript(UtilsAdapter.getStyleSheet("olcss",UtilsAdapter.qStringFromFile(olCss))) + webView.isLoaded = true + runJavaScript("setMapView([" + 0 + ","+ 0 + "], " + 1 + " );" ); + PositionManager.startPositioning() + //load locations that were received before this conversation was opened + PositionManager.loadPreviousLocations(attachedAccountId); } - isFullScreen = !isFullScreen - isMinimised = false; } } - PushButton { - id: btnClose + MapPositionSharingControl {} - toolTipText: JamiStrings.closeMapTooltip - imageColor: JamiTheme.mapButtonColor - normalColor: JamiTheme.transparentColor - source: JamiResources.round_close_24dp_svg + MapPositionOverlay {} - onClicked: { - PositionManager.stopPositioning(); - PositionManager.setMapActive(false); - PositionManager.mapAutoOpening = false; + StopSharingPositionPopup { + id: stopSharingPositionPopup - } + property alias attachedAccountId: root.attachedAccountId } } } diff --git a/src/app/webengine/map/MapPositionOverlay.qml b/src/app/webengine/map/MapPositionOverlay.qml new file mode 100644 index 000000000..8383addc1 --- /dev/null +++ b/src/app/webengine/map/MapPositionOverlay.qml @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * Author: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import QtQuick +import QtQuick.Layouts + +import net.jami.Constants 1.1 +import net.jami.Adapters 1.1 + +import "../../commoncomponents" + +Rectangle { + id: root + + anchors.right: webView.right + anchors.top: webView.top + anchors.margins: 10 + radius: 10 + width: lay.width + 10 + height: lay.height + 10 + color: JamiTheme.mapButtonsOverlayColor + + RowLayout { + id: lay + + anchors.centerIn: parent + + PushButton { + id: btnUnpin + + toolTipText: !isUnpin ? JamiStrings.unpin : JamiStrings.pinWindow + imageColor: JamiTheme.mapButtonColor + normalColor: JamiTheme.transparentColor + source: JamiResources.unpin_svg + onClicked: { + if (!isUnpin) { + PositionManager.unPinMap(attachedAccountId) + } else { + PositionManager.pinMap(attachedAccountId) + } + } + } + + PushButton { + id: btnCenter + + toolTipText: JamiStrings.centerMapTooltip + imageColor: JamiTheme.mapButtonColor + normalColor: JamiTheme.transparentColor + source: JamiResources.share_location_svg + onClicked: { + webView.runJavaScript("zoomTolayersExtent()" ); + } + } + + PushButton { + id: btnMove + + toolTipText: JamiStrings.dragMapTooltip + imageColor: JamiTheme.mapButtonColor + normalColor: JamiTheme.transparentColor + source: JamiResources.move_svg + visible: !isUnpin + + MouseArea { + anchors.fill: parent + drag.target: mapObject + drag.minimumX: 0 + drag.maximumX: maxWidth - mapObject.maxWidth + drag.minimumY: 0 + drag.maximumY: maxHeight - mapObject.maxHeight + } + } + + PushButton { + id: btnMaximise + + visible: !isUnpin + toolTipText: mapObject.isFullScreen + ? JamiStrings.reduceMapTooltip + : JamiStrings.maximizeMapTooltip + imageColor: JamiTheme.mapButtonColor + normalColor: JamiTheme.transparentColor + source: mapObject.isFullScreen? JamiResources.close_fullscreen_24dp_svg : JamiResources.open_in_full_24dp_svg + onClicked: { + if (!mapObject.isFullScreen) { + mapObject.x = mapObject.xPos + mapObject.y = mapObject.yPos + } + + mapObject.isFullScreen = !mapObject.isFullScreen + } + } + + PushButton { + id: btnClose + + toolTipText: JamiStrings.closeMapTooltip + imageColor: JamiTheme.mapButtonColor + normalColor: JamiTheme.transparentColor + source: JamiResources.round_close_24dp_svg + visible: !isUnpin + + onClicked: { + PositionManager.setMapInactive(attachedAccountId) + PositionManager.mapAutoOpening = false + } + } + } +} diff --git a/src/app/webengine/map/MapPositionSharingControl.qml b/src/app/webengine/map/MapPositionSharingControl.qml new file mode 100644 index 000000000..95e20a3ae --- /dev/null +++ b/src/app/webengine/map/MapPositionSharingControl.qml @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * Author: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +import QtQuick +import QtQuick.Layouts + +import net.jami.Constants 1.1 +import net.jami.Adapters 1.1 +import net.jami.Enums 1.1 + +import "../../commoncomponents" + +ColumnLayout { + id: root + + anchors.horizontalCenter: mapObject.horizontalCenter + anchors.margins: 10 + anchors.bottom: mapObject.bottom + + RowLayout { + Layout.alignment: Qt.AlignHCenter + + Rectangle { + radius: 10 + Layout.preferredWidth: textTimer.width + 15 + Layout.preferredHeight: textTimer.height + 15 + color: JamiTheme.mapButtonsOverlayColor + visible: textTimer.remainingTimeMs === 0 + ? false + : isUnpin + ? isSharing + : isSharingToCurrentConversation + + Text { + id: textTimer + + anchors.centerIn: parent + color: JamiTheme.mapButtonColor + text: standartCountdown(Math.floor(remainingTimeMs / 1000)) + + function standartCountdown(seconds) { + var minutes = Math.floor(seconds / 60); + var hour = Math.floor(minutes / 60) + minutes = minutes % 60 + var sec = seconds % 60 + if (hour) { + if (minutes) + return qsTr("%1h%2min").arg(hour).arg(minutes) + else + return qsTr("%1h").arg(hour) + } + if (minutes) { + if (sec) + return qsTr("%1m%2sec").arg(minutes).arg(sec) + else + return qsTr("%1m").arg(minutes) + + } + return qsTr("%1sec").arg(sec) + } + + property int remainingTimeMs: 0 + Connections { + target: PositionManager + function onSendCountdownUpdate(accountId, remainingTime) { + if (accountId === attachedAccountId) { + textTimer.remainingTimeMs = remainingTime + } + } + } + } + } + } + + RowLayout { + id: sharePositionLayout + + Layout.alignment: Qt.AlignHCenter + + MaterialButton { + id: sharePositionButton + + preferredWidth: text.contentWidth + textLeftPadding: JamiTheme.buttontextPadding + textRightPadding: JamiTheme.buttontextPadding + primary: true + visible: !isSharingToCurrentConversation && !isUnpin + text: JamiStrings.shareLocation + color: isError + ? JamiTheme.buttonTintedGreyInactive + : JamiTheme.buttonTintedBlue + hoveredColor: isError + ? JamiTheme.buttonTintedGreyInactive + : JamiTheme.buttonTintedBlueHovered + pressedColor: isError + ? JamiTheme.buttonTintedGreyInactive + : JamiTheme.buttonTintedBluePressed + Layout.alignment: Qt.AlignHCenter + property bool isHovered: false + property string positioningError: "default" + property bool isError: positioningError.length + property int positionShareConvIdsCount: PositionManager.positionShareConvIdsCount + property string currentConvId: CurrentConversation.id + property bool isMapUnpin: isUnpin + + function errorString(posError) { + if (posError === "locationServicesError") + return JamiStrings.locationServicesError + return JamiStrings.locationServicesClosedError + } + + onPositionShareConvIdsCountChanged: { + isSharingToCurrentConversation = PositionManager.isPositionSharedToConv(attachedAccountId, currentConvId) + } + + onCurrentConvIdChanged: { + isSharingToCurrentConversation = PositionManager.isPositionSharedToConv(attachedAccountId, currentConvId) + } + + onIsMapUnpinChanged: { + isSharingToCurrentConversation = PositionManager.isPositionSharedToConv(attachedAccountId, currentConvId) + } + + onClicked: { + var sharingDuration = 60 * 1000 * UtilsAdapter.getAppValue(Settings.PositionShareDuration) + if (!isError && !isUnpin) { + PositionManager.sharePosition(sharingDuration, attachedAccountId, currentConvId); + } + webView.runJavaScript("zoomTolayersExtent()" ); + } + + MaterialToolTip { + property bool isSharingPossible: !(sharePositionButton.isError && (sharePositionButton.positioningError !== "default")) + + visible: sharePositionButton.hovered + text: isSharingPossible + ? JamiStrings.shareLocationToolTip.arg(PositionManager.getmapTitle(attachedAccountId, currentConvId)) + : sharePositionButton.errorString(sharePositionButton.positioningError) + } + Connections { + target: PositionManager + function onPositioningError (err) { + sharePositionButton.positioningError = err; + } + } + } + + MaterialButton { + id: stopSharingPositionButton + + preferredWidth: text.contentWidth + textLeftPadding: JamiTheme.buttontextPadding + textRightPadding: JamiTheme.buttontextPadding + primary: true + visible: isSharing + text: stopAllSharing + ? JamiStrings.shortStopAllSharings + : JamiStrings.stopSharingLocation + color: isError + ? JamiTheme.buttonTintedGreyInactive + : JamiTheme.buttonTintedRed + hoveredColor: isError + ? JamiTheme.buttonTintedGreyInactive + : JamiTheme.buttonTintedRedHovered + pressedColor: isError + ? JamiTheme.buttonTintedGreyInactive + : JamiTheme.buttonTintedRedPressed + Layout.alignment: Qt.AlignHCenter + toolTipText: stopAllSharing + ? isUnpin + ? JamiStrings.unpinStopSharingTooltip + : JamiStrings.stopAllSharings + : JamiStrings.stopSharingSeveralConversationTooltip + property bool isHovered: false + property string positioningError + property bool isError: positioningError.length + property bool stopAllSharing: !(PositionManager.positionShareConvIdsCount >= 2 && !isUnpin && isSharingToCurrentConversation) + onClicked: { + if (!isError) { + if (stopAllSharing) { + PositionManager.stopSharingPosition(); + } else { + stopSharingPositionPopup.open() + } + } + } + } + } +} diff --git a/src/app/webengine/map/StopSharingPositionPopup.qml b/src/app/webengine/map/StopSharingPositionPopup.qml index b59f08866..e1fe2c40b 100644 --- a/src/app/webengine/map/StopSharingPositionPopup.qml +++ b/src/app/webengine/map/StopSharingPositionPopup.qml @@ -19,12 +19,11 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls +import Qt5Compat.GraphicalEffects import net.jami.Models 1.1 import net.jami.Adapters 1.1 import net.jami.Constants 1.1 -import Qt5Compat.GraphicalEffects - import "../../commoncomponents" @@ -101,11 +100,10 @@ Popup { color: JamiTheme.buttonTintedBlue hoveredColor: JamiTheme.buttonTintedBlueHovered pressedColor: JamiTheme.buttonTintedBluePressed - text: JamiStrings.stopConvSharing + text: JamiStrings.stopConvSharing.arg(PositionManager.getmapTitle(attachedAccountId, CurrentConversation.id)) onClicked: { - PositionManager.stopSharingPosition(PositionManager.getSelectedConvId()) - shareButtonVisibility = true + PositionManager.stopSharingPosition(attachedAccountId, CurrentConversation.id) root.close() } } @@ -123,12 +121,10 @@ Popup { onClicked: { PositionManager.stopSharingPosition() - shareButtonVisibility = true root.close() } } } - } } diff --git a/src/app/webengine/map/map.js b/src/app/webengine/map/map.js index 679a269b7..7d53f293f 100644 --- a/src/app/webengine/map/map.js +++ b/src/app/webengine/map/map.js @@ -61,7 +61,7 @@ var proj = new ol.proj.Projection({ extent: extent }) -function setSource (coordos, authorI,imageI) { +function setSource (coordos, avatar) { var coord = ol.proj.fromLonLat(coordos) var pointFeature = new ol.Feature({ geometry: new ol.geom.Point(coord), @@ -69,16 +69,16 @@ function setSource (coordos, authorI,imageI) { }) var preStyle = new ol.style.Icon({ - src: "data:image/png;base64," + imageI}) + src: "data:image/png;base64," + avatar}) - //resize the image to 40 px + //resize the image to 35 px var image = preStyle.getImage() if (!image.width) { image.addEventListener('load', function () { - preStyle.setScale([40 / image.width, 40 / image.height]) + preStyle.setScale([35 / image.width, 35 / image.height]) }) } else { - preStyle.setScale([40 / image.width, 40 / image.height]) + preStyle.setScale([35 / image.width, 35 / image.height]) } var iconStyle = new ol.style.Style({ @@ -93,18 +93,24 @@ function setSource (coordos, authorI,imageI) { return vectorSource } -function newPosition (coordos, authorI, image) { - vectorSource = setSource(coordos, authorI, image) +function newPosition (coordos, authorUri, avatar) { + var layerArray = map.getLayers().getArray(); + for (var i = 0; i < layerArray.length; i++ ){ + if(layerArray[i].layer_type === authorUri) { + return + } + } + vectorSource = setSource(coordos, avatar) var iconLayer = new ol.layer.Vector({source: vectorSource}) - iconLayer.layer_type = authorI + iconLayer.layer_type = authorUri map.addLayer(iconLayer) } -function updatePosition (coordos, authorI) { +function updatePosition (coordos, authorUri) { var coord = ol.proj.fromLonLat(coordos); var layerArray = map.getLayers().getArray(); for (var i = 0; i < layerArray.length; i++ ){ - if(layerArray[i].layer_type === authorI) { + if(layerArray[i].layer_type === authorUri) { layerArray[i].getSource().getFeatures()[0].getGeometry().setCoordinates(coord) return } @@ -123,10 +129,10 @@ function zoomTolayersExtent() { padding: [80 ,80 ,80 ,80]}) } -function removePosition (authorI) { +function removePosition (authorUri) { var layerArray = map.getLayers().getArray(); for (var i = 0; i < layerArray.length; i++ ){ - if(layerArray[i].layer_type === authorI) { + if(layerArray[i].layer_type === authorUri) { map.removeLayer(layerArray[i]) return } diff --git a/src/libclient/typedefs.h b/src/libclient/typedefs.h index b84c47eda..75016c78e 100644 --- a/src/libclient/typedefs.h +++ b/src/libclient/typedefs.h @@ -30,6 +30,7 @@ typedef QMap<QString, QString> MapStringString; typedef QMap<QString, int> MapStringInt; typedef QMap<QString, double> MapStringDouble; +typedef QMap<QPair<QString, QString>, bool> MapPairStrStrBool; typedef QVector<int> VectorInt; typedef QVector<uint> VectorUInt; typedef QVector<qulonglong> VectorULongLong; @@ -41,6 +42,8 @@ typedef QMap<QString, QMap<QString, QStringList>> MapStringMapStringStringList; typedef QMap<QString, QStringList> MapStringStringList; typedef QVector<QByteArray> VectorVectorByte; typedef uint64_t DataTransferId; +// accountId, conversationId +typedef QPair<QString, QString> PositionKey; constexpr static const char* TRUE_STR = "true"; constexpr static const char* TEXT_PLAIN = "text/plain"; -- GitLab