1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-08-04 06:45:45 +02:00

Feature: unpin location sharing map

- Refactoring
- Unpined map
- handle multiple maps (one map per account)

Change-Id: I2b0abf284ccfe27b986f03915c5942d721403211
Gitlab: #901
This commit is contained in:
Nicolas Vengeon 2022-12-05 15:44:04 -05:00 committed by Sébastien Blin
parent c36ccb5fa9
commit d06902e3b7
22 changed files with 1162 additions and 600 deletions

View file

@ -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>

After

Width:  |  Height:  |  Size: 241 B

View file

@ -54,7 +54,9 @@ extern const QString defaultDownloadPath;
X(WindowGeometry, QRectF(qQNaN(), qQNaN(), 0., 0.)) \ X(WindowGeometry, QRectF(qQNaN(), qQNaN(), 0., 0.)) \
X(WindowState, QWindow::AutomaticVisibility) \ X(WindowState, QWindow::AutomaticVisibility) \
X(EnableExperimentalSwarm, false) \ 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. * A class to expose settings keys in both c++ and QML.

View file

@ -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 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 locationServicesClosedError: qsTr("Your precise location could not be determined. Please check your Internet connection.")
property string stopAllSharings: qsTr("Turn off location sharing"); 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 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 minimizeMapTooltip: qsTr("Minimize");
property string maximizeMapTooltip: qsTr("Maximize"); property string maximizeMapTooltip: qsTr("Maximize");
property string reduceMapTooltip: qsTr("Reduce"); property string reduceMapTooltip: qsTr("Reduce");
@ -318,6 +322,11 @@ Item {
property string dragMapTooltip: qsTr("Drag"); property string dragMapTooltip: qsTr("Drag");
property string centerMapTooltip: qsTr("Center"); property string centerMapTooltip: qsTr("Center");
property string closeMapTooltip: qsTr("Close"); 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 // Chatview header
property string hideChat: qsTr("Hide chat") property string hideChat: qsTr("Hide chat")
@ -694,6 +703,7 @@ Item {
// SmartList // SmartList
property string clearText: qsTr("Clear Text") property string clearText: qsTr("Clear Text")
property string conversations: qsTr("Conversations") property string conversations: qsTr("Conversations")
property string conversation: qsTr("Conversation")
property string searchResults: qsTr("Search Results") property string searchResults: qsTr("Search Results")
// SmartList context menu // SmartList context menu

View file

@ -34,7 +34,7 @@ Rectangle {
id: root id: root
property bool allMessagesLoaded property bool allMessagesLoaded
property var mapPositions: PositionManager.mapStatus
signal needToHideConversationInCall signal needToHideConversationInCall
signal messagesCleared signal messagesCleared
signal messagesLoaded signal messagesLoaded
@ -51,18 +51,28 @@ Rectangle {
addMemberPanel.visible = false addMemberPanel.visible = 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});
if (sprite === null) {
// Error Handling
console.log("Error creating object");
}
}
}
Connections {
target: PositionManager
function onOpenNewMap() {
instanceMapObject()
}
}
color: JamiTheme.chatviewBgColor color: JamiTheme.chatviewBgColor
property string currentConvId: CurrentConversation.id property string currentConvId: CurrentConversation.id
onCurrentConvIdChanged: PositionManager.setMapActive(false);
Loader {
id: mapLoader
active: PositionManager.isMapActive
z: 10
source: WITH_WEBENGINE ? "qrc:/webengine/map/MapPosition.qml" : ""
}
HostPopup { HostPopup {
id: hostPopup id: hostPopup

View file

@ -190,7 +190,7 @@ Rectangle {
} }
onShowMapClicked: { onShowMapClicked: {
PositionManager.setMapActive(true); PositionManager.setMapActive(CurrentAccount.id)
} }
onSendFileButtonClicked: jamiFileDialog.open() onSendFileButtonClicked: jamiFileDialog.open()

View file

@ -65,12 +65,33 @@ ColumnLayout {
spacing: JamiTheme.chatViewFooterRowSpacing spacing: JamiTheme.chatViewFooterRowSpacing
PushButton { PushButton {
id: sendFileButton id: showMapButton
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: marginSize Layout.leftMargin: marginSize
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
Layout.preferredHeight: 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 radius: JamiTheme.chatViewFooterButtonRadius
preferredSize: JamiTheme.chatViewFooterButtonIconSize - 6 preferredSize: JamiTheme.chatViewFooterButtonIconSize - 6
@ -130,27 +151,6 @@ ColumnLayout {
Component.onCompleted: JamiQmlUtils.videoRecordMessageButtonObj = videoRecordMessageButton 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 { MessageBarTextArea {
id: textArea id: textArea

View file

@ -68,8 +68,8 @@ ItemDelegate {
imageId: UID imageId: UID
showPresenceIndicator: Presence !== undefined ? Presence : false showPresenceIndicator: Presence !== undefined ? Presence : false
showSharePositionIndicator: PositionManager.isPositionSharedToConv(UID) showSharePositionIndicator: PositionManager.isPositionSharedToConv(accountId, UID)
showSharedPositionIndicator: PositionManager.isConvSharingPosition(UID) showSharedPositionIndicator: PositionManager.isConvSharingPosition(accountId, UID)
Layout.preferredWidth: JamiTheme.smartListAvatarSize Layout.preferredWidth: JamiTheme.smartListAvatarSize
Layout.preferredHeight: JamiTheme.smartListAvatarSize Layout.preferredHeight: JamiTheme.smartListAvatarSize
@ -77,10 +77,10 @@ ItemDelegate {
Connections { Connections {
target: PositionManager target: PositionManager
function onPositionShareConvIdsCountChanged () { function onPositionShareConvIdsCountChanged () {
avatar.showSharePositionIndicator = PositionManager.isPositionSharedToConv(UID) avatar.showSharePositionIndicator = PositionManager.isPositionSharedToConv(accountId, UID)
} }
function onSharingUrisCountChanged () { function onSharingUrisCountChanged () {
avatar.showSharedPositionIndicator = PositionManager.isConvSharingPosition(UID) avatar.showSharedPositionIndicator = PositionManager.isConvSharingPosition(accountId, UID)
} }
} }

View file

@ -21,14 +21,12 @@
#include <QJsonObject> #include <QJsonObject>
#include <QJsonDocument> #include <QJsonDocument>
Positioning::Positioning(QString uri, QObject* parent) Positioning::Positioning(QObject* parent)
: QObject(parent) : QObject(parent)
, uri_(uri)
{ {
source_ = QGeoPositionInfoSource::createDefaultSource(this); source_ = QGeoPositionInfoSource::createDefaultSource(this);
QTimer* timer = new QTimer(this); timer_ = new QTimer(this);
connect(timer, &QTimer::timeout, this, &Positioning::requestPosition); connect(timer_, &QTimer::timeout, this, &Positioning::requestPosition);
timer->start(5000);
connect(source_, &QGeoPositionInfoSource::errorOccurred, this, &Positioning::slotError); connect(source_, &QGeoPositionInfoSource::errorOccurred, this, &Positioning::slotError);
connect(source_, &QGeoPositionInfoSource::positionUpdated, this, &Positioning::positionUpdated); connect(source_, &QGeoPositionInfoSource::positionUpdated, this, &Positioning::positionUpdated);
// if location services are activated, positioning will be activated automatically // if location services are activated, positioning will be activated automatically
@ -41,6 +39,8 @@ Positioning::Positioning(QString uri, QObject* parent)
void void
Positioning::start() Positioning::start()
{ {
requestPosition();
timer_->start(10000);
if (source_ && !isPositioning) { if (source_ && !isPositioning) {
source_->startUpdates(); source_->startUpdates();
isPositioning = true; isPositioning = true;
@ -53,6 +53,7 @@ Positioning::stop()
if (source_ && isPositioning) if (source_ && isPositioning)
source_->stopUpdates(); source_->stopUpdates();
isPositioning = false; isPositioning = false;
timer_->stop();
} }
QString QString
@ -70,17 +71,11 @@ Positioning::convertToJson(const QGeoPositionInfo& info)
return strJson; return strJson;
} }
void
Positioning::setUri(QString uri)
{
uri_ = uri;
}
void void
Positioning::positionUpdated(const QGeoPositionInfo& info) Positioning::positionUpdated(const QGeoPositionInfo& info)
{ {
Q_EMIT positioningError(""); Q_EMIT positioningError("");
Q_EMIT newPosition("", uri_, convertToJson(info), -1, ""); Q_EMIT newPosition(convertToJson(info));
} }
void void

View file

@ -27,7 +27,7 @@ class Positioning : public QObject
Q_OBJECT Q_OBJECT
public: public:
Positioning(QString uri, QObject* parent = 0); Positioning(QObject* parent = 0);
/** /**
* start to retreive the current position * start to retreive the current position
*/ */
@ -42,8 +42,6 @@ public:
*/ */
QString convertToJson(const QGeoPositionInfo& info); QString convertToJson(const QGeoPositionInfo& info);
void setUri(QString uri);
private Q_SLOTS: private Q_SLOTS:
void slotError(QGeoPositionInfoSource::Error error); void slotError(QGeoPositionInfoSource::Error error);
void positionUpdated(const QGeoPositionInfo& info); void positionUpdated(const QGeoPositionInfo& info);
@ -57,15 +55,12 @@ private Q_SLOTS:
void locationServicesActivated(); void locationServicesActivated();
Q_SIGNALS: Q_SIGNALS:
void newPosition(const QString& unused_AccountId, void newPosition(const QString& body);
const QString& peerId,
const QString& body,
const uint64_t& timestamp,
const QString& daemonId);
void positioningError(const QString error); void positioningError(const QString error);
private: private:
QString uri_; QString uri_;
QGeoPositionInfoSource* source_ = nullptr; QGeoPositionInfoSource* source_ = nullptr;
bool isPositioning = false; bool isPositioning = false;
QTimer* timer_;
}; };

View file

@ -1,7 +1,8 @@
#include "positionmanager.h" #include "positionmanager.h"
#include "qtutils.h" #include "appsettingsmanager.h"
#include "qtutils.h"
#include <QApplication> #include <QApplication>
#include <QBuffer> #include <QBuffer>
#include <QList> #include <QList>
@ -9,31 +10,40 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QImageReader> #include <QImageReader>
PositionManager::PositionManager(SystemTray* systemTray, LRCInstance* instance, QObject* parent) PositionManager::PositionManager(AppSettingsManager* settingsManager,
SystemTray* systemTray,
LRCInstance* instance,
QObject* parent)
: QmlAdapterBase(instance, parent) : QmlAdapterBase(instance, parent)
, systemTray_(systemTray) , systemTray_(systemTray)
, settingsManager_(settingsManager)
{ {
timerTimeLeftSharing_ = new QTimer(this); countdownTimer_ = new QTimer(this);
timerStopSharing_ = new QTimer(this); connect(countdownTimer_, &QTimer::timeout, this, &PositionManager::countdownUpdate);
connect(timerTimeLeftSharing_, &QTimer::timeout, [=] { connect(lrcInstance_,
set_timeSharingRemaining(timerStopSharing_->remainingTime()); &LRCInstance::selectedConvUidChanged,
}); this,
connect(timerStopSharing_, &QTimer::timeout, [=] { stopSharingPosition(); }); &PositionManager::onNewConversation,
connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, [this]() { Qt::UniqueConnection);
set_mapAutoOpening(true); connect(lrcInstance_,
}); &LRCInstance::currentAccountIdChanged,
connect(lrcInstance_, &LRCInstance::currentAccountIdChanged, [this]() { this,
if (!localPositioning_) // Not yet initialized &PositionManager::onNewAccount,
return; Qt::UniqueConnection);
localPositioning_->setUri(lrcInstance_->getCurrentAccountInfo().profileInfo.uri); connect(
}); this,
set_isMapActive(false); &PositionManager::localPositionReceived,
this,
[this](const QString& accountId, const QString& peerId, const QString& body) {
onPositionReceived(accountId, peerId, body, -1, "");
},
Qt::QueuedConnection);
} }
void void
PositionManager::safeInit() PositionManager::safeInit()
{ {
localPositioning_.reset(new Positioning(lrcInstance_->getCurrentAccountInfo().profileInfo.uri)); localPositioning_.reset(new Positioning());
connectAccountModel(); connectAccountModel();
} }
@ -50,41 +60,36 @@ PositionManager::connectAccountModel()
void void
PositionManager::startPositioning() PositionManager::startPositioning()
{ {
currentConvSharingUris_.clear(); if (localPositioning_)
localPositioning_->start(); localPositioning_->start();
connect(localPositioning_.get(),
&Positioning::newPosition,
this,
&PositionManager::onPositionReceived,
Qt::UniqueConnection);
connect(localPositioning_.get(), connect(localPositioning_.get(),
&Positioning::positioningError, &Positioning::positioningError,
this, this,
&PositionManager::onPositionErrorReceived, &PositionManager::onPositionErrorReceived,
Qt::UniqueConnection); Qt::UniqueConnection);
connect(
localPositioning_.get(),
&Positioning::newPosition,
this,
[this](const QString& body) { sendPosition(body, true); },
Qt::UniqueConnection);
} }
void void
PositionManager::stopPositioning() PositionManager::stopPositioning()
{ {
localPositioning_->stop(); if (localPositioning_)
} localPositioning_->stop();
QString
PositionManager::getSelectedConvId()
{
return lrcInstance_->get_selectedConvUid();
} }
bool bool
PositionManager::isConvSharingPosition(const QString& convUri) PositionManager::isConvSharingPosition(const QString& accountId, const QString& convUri)
{ {
const auto& convParticipants = lrcInstance_->getConversationFromConvUid(convUri) const auto& convParticipants = lrcInstance_->getConversationFromConvUid(convUri)
.participantsUris(); .participantsUris();
Q_FOREACH (const auto& id, convParticipants) { Q_FOREACH (const auto& id, convParticipants) {
if (id != lrcInstance_->getCurrentAccountInfo().profileInfo.uri) { if (id != lrcInstance_->getCurrentAccountInfo().profileInfo.uri) {
if (objectListSharingUris_.contains( if (objectListSharingUris_.contains(PositionKey {accountId, id})) {
QPair<QString, QString> {lrcInstance_->get_currentAccountId(), id})) {
return true; return true;
} }
} }
@ -93,36 +98,53 @@ PositionManager::isConvSharingPosition(const QString& convUri)
} }
void void
PositionManager::loadPreviousLocations() PositionManager::loadPreviousLocations(QString& accountId)
{ {
QVariantMap shareInfo; QVariantMap shareInfo;
for (auto it = objectListSharingUris_.begin(); it != objectListSharingUris_.end(); it++) { for (auto it = objectListSharingUris_.begin(); it != objectListSharingUris_.end(); it++) {
QJsonObject jsonObj; if (it.key().first == accountId) {
jsonObj.insert("type", QJsonValue("Position")); QJsonObject jsonObj;
jsonObj.insert("lat", it.value()->getLatitude().toString()); jsonObj.insert("type", QJsonValue("Position"));
jsonObj.insert("long", it.value()->getLongitude().toString()); jsonObj.insert("lat", it.value()->getLatitude().toString());
QJsonDocument doc(jsonObj); jsonObj.insert("long", it.value()->getLongitude().toString());
QString strJson(doc.toJson(QJsonDocument::Compact)); QJsonDocument doc(jsonObj);
onPositionReceived(it.key().first, it.key().second, strJson, -1, ""); 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 bool
PositionManager::isPositionSharedToConv(const QString& convUid) PositionManager::isPositionSharedToConv(const QString& accountId, const QString& convUid)
{ {
if (positionShareConvIds_.length()) { if (positionShareConvIds_.length()) {
auto iter = std::find(positionShareConvIds_.begin(), auto iter = std::find(positionShareConvIds_.begin(),
positionShareConvIds_.end(), positionShareConvIds_.end(),
QPair<QString, QString> {lrcInstance_->get_currentAccountId(), PositionKey {accountId, convUid});
convUid});
return (iter != positionShareConvIds_.end()); return (iter != positionShareConvIds_.end());
} }
return false; return false;
} }
void void
PositionManager::sendPosition(const QString& body) PositionManager::sendPosition(const QString& body, bool triggersLocalPosition)
{ {
// send position to positionShareConvIds_ participants
try { try {
Q_FOREACH (const auto& key, positionShareConvIds_) { Q_FOREACH (const auto& key, positionShareConvIds_) {
const auto& convInfo = lrcInstance_->getConversationFromConvUid(key.second, key.first); const auto& convInfo = lrcInstance_->getConversationFromConvUid(key.second, key.first);
@ -137,6 +159,15 @@ PositionManager::sendPosition(const QString& body)
} catch (const std::exception& e) { } catch (const std::exception& e) {
qDebug() << Q_FUNC_INFO << e.what(); 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 void
@ -149,26 +180,17 @@ PositionManager::onWatchdogTimeout()
if (it != objectListSharingUris_.cend()) { if (it != objectListSharingUris_.cend()) {
QString stopMsg("{\"type\":\"Stop\"}"); QString stopMsg("{\"type\":\"Stop\"}");
onPositionReceived(it.key().first, it.key().second, stopMsg, -1, ""); onPositionReceived(it.key().first, it.key().second, stopMsg, -1, "");
makeVisibleSharingButton(it.key().first);
} }
} }
void 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 { try {
startPositionTimers(maximumTime); if (settingsManager_->getValue(Settings::Key::PositionShareLimit) == true)
const auto convUid = lrcInstance_->get_selectedConvUid(); startPositionTimers(maximumTime);
positionShareConvIds_.append( positionShareConvIds_.append(PositionKey {accountId, convId});
QPair<QString, QString> {lrcInstance_->get_currentAccountId(), convUid});
set_positionShareConvIdsCount(positionShareConvIds_.size()); set_positionShareConvIdsCount(positionShareConvIds_.size());
} catch (...) { } catch (...) {
qDebug() << "Exception during sharePosition:"; qDebug() << "Exception during sharePosition:";
@ -176,17 +198,46 @@ PositionManager::sharePosition(int maximumTime)
} }
void void
PositionManager::stopSharingPosition(const QString convId) PositionManager::stopSharingPosition(QString accountId, const QString convId)
{ {
QString stopMsg; QString stopMsg;
stopMsg = "{\"type\":\"Stop\"}"; stopMsg = "{\"type\":\"Stop\"}";
if (convId == "") { if (accountId == "") {
sendPosition(stopMsg); sendPosition(stopMsg, false);
stopPositionTimers(); stopPositionTimers();
positionShareConvIds_.clear(); positionShareConvIds_.clear();
set_positionShareConvIdsCount(positionShareConvIds_.size());
} else { } 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()) { Q_FOREACH (const QString& uri, convInfo.participantsUris()) {
if (lrcInstance_->getCurrentAccountInfo().profileInfo.uri != uri) { if (lrcInstance_->getCurrentAccountInfo().profileInfo.uri != uri) {
lrcInstance_->getCurrentAccountInfo().contactModel->sendDhtMessage(uri, lrcInstance_->getCurrentAccountInfo().contactModel->sendDhtMessage(uri,
@ -194,22 +245,68 @@ PositionManager::stopSharingPosition(const QString convId)
APPLICATION_GEO); 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));
}
set_positionShareConvIdsCount(positionShareConvIds_.size());
} }
} }
void void
PositionManager::setMapActive(bool state) PositionManager::unPinMap(QString key)
{ {
set_isMapActive(state); QMutexLocker lk(&mapStatusMutex_);
Q_EMIT isMapActiveChanged(); 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);
}
}
}
void
PositionManager::setMapInactive(const QString key)
{
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 QString
@ -233,7 +330,9 @@ PositionManager::getAvatar(const QString& accountId, const QString& uri)
} }
QVariantMap 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()); QJsonDocument temp = QJsonDocument::fromJson(body.toUtf8());
QJsonObject jsonObject = temp.object(); QJsonObject jsonObject = temp.object();
@ -250,6 +349,7 @@ PositionManager::parseJsonPosition(const QString& body, const QString& peerId)
pos["time"] = i.value().toVariant(); pos["time"] = i.value().toVariant();
pos["author"] = peerId; pos["author"] = peerId;
pos["account"] = accountId;
} }
return pos; return pos;
} }
@ -257,17 +357,25 @@ PositionManager::parseJsonPosition(const QString& body, const QString& peerId)
void void
PositionManager::startPositionTimers(int timeSharing) PositionManager::startPositionTimers(int timeSharing)
{ {
set_timeSharingRemaining(timeSharing); mapTimerCountDown_[lrcInstance_->get_currentAccountId()] = timeSharing;
timerTimeLeftSharing_->start(1000); countdownUpdate();
timerStopSharing_->start(timeSharing); countdownTimer_->start(1000);
} }
void void
PositionManager::stopPositionTimers() PositionManager::stopPositionTimers(QString accountId)
{ {
set_timeSharingRemaining(0); // reset all timers
timerTimeLeftSharing_->stop(); if (accountId == nullptr) {
timerStopSharing_->stop(); mapTimerCountDown_.clear();
} else {
auto it = mapTimerCountDown_.find(accountId);
if (it != mapTimerCountDown_.end()) {
mapTimerCountDown_.erase(it);
}
if (!mapTimerCountDown_.size())
countdownTimer_->stop();
}
} }
void void
@ -304,6 +412,151 @@ PositionManager::showNotification(const QString& accountId,
#endif #endif
} }
void
PositionManager::onNewConversation()
{
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++;
}
}
}
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());
}
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;
}
}
}
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);
}
}
}
}
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 {
qWarning() << "error in PositionManager::updatePositionInMemory(), it.value() is null";
}
} else {
qWarning()
<< "Error: A position intented to be updated while not in objectListSharingUris_ ";
}
// 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 void
PositionManager::onPositionReceived(const QString& accountId, PositionManager::onPositionReceived(const QString& accountId,
const QString& peerId, const QString& peerId,
@ -311,106 +564,34 @@ PositionManager::onPositionReceived(const QString& accountId,
const uint64_t& timestamp, const uint64_t& timestamp,
const QString& daemonId) const QString& daemonId)
{ {
// only show shared positions from contacts in the current conversation // handlers variables
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); // parse the position from json
auto getShareInfo = [&](bool update) -> QVariantMap { QVariantMap positionReceived = parseJsonPosition(accountId, peerId, body);
QVariantMap shareInfo;
shareInfo["author"] = peerId;
if (!update) {
shareInfo["avatar"] = getAvatar(accountId, peerId);
}
shareInfo["long"] = newPosition["long"];
shareInfo["lat"] = newPosition["lat"];
return shareInfo;
};
auto endSharing = newPosition["type"] == "Stop";
auto key = QPair<QString, QString> {accountId, peerId}; // is it a message that notify an end of position sharing
auto endSharing = positionReceived["type"] == "Stop";
if (!endSharing) { // key to identify the peer
// open map on position reception auto key = PositionKey {accountId, peerId};
if (!isMapActive_ && mapAutoOpening_ && isPeerIdInConv
&& peerId != lrcInstance_->getCurrentAccountInfo().profileInfo.uri) { // check if the position exists in all shared positions, even if not visible to the screen
set_isMapActive(true); auto findPeerIdinAllPeers = objectListSharingUris_.find(key);
}
// open the map on position reception if needed
if (isNewMessageTriggersMap(endSharing, peerId, accountId)) {
setMapActive(accountId);
} }
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); // if the position already exists
set_sharingUrisCount(objectListSharingUris_.size()); if (findPeerIdinAllPeers != objectListSharingUris_.end()) {
connect(obj, if (endSharing)
&PositionObject::timeout, removePositionFromMemory(key, positionReceived);
this, else
&PositionManager::onWatchdogTimeout, updatePositionInMemory(key, positionReceived);
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);
}
}
// stop sharing position
} else {
auto it = objectListSharingUris_.find(key);
if (it != objectListSharingUris_.end()) {
it.value()->deleteLater();
objectListSharingUris_.erase(it);
set_sharingUrisCount(objectListSharingUris_.size());
}
}
} else { } else {
// Update/remove existing // It is the first time a position is received from this peer
if (endSharing) { addPositionToMemory(key, positionReceived);
// 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
auto it = objectListSharingUris_.find(key);
if (it != objectListSharingUris_.end()) {
it.value()->resetWatchdog();
}
}
} }
} }

View file

@ -24,19 +24,21 @@
#include "positionobject.h" #include "positionobject.h"
#include "systemtray.h" #include "systemtray.h"
#include <QMutex>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
class PositionManager : public QmlAdapterBase class PositionManager : public QmlAdapterBase
{ {
Q_OBJECT Q_OBJECT
QML_RO_PROPERTY(bool, isMapActive) // map of elements : map key and isUnpin
QML_RO_PROPERTY(int, timeSharingRemaining) QML_PROPERTY(QVariantMap, mapStatus)
QML_PROPERTY(bool, mapAutoOpening)
QML_PROPERTY(int, positionShareConvIdsCount) QML_PROPERTY(int, positionShareConvIdsCount)
QML_PROPERTY(int, sharingUrisCount) QML_PROPERTY(int, sharingUrisCount)
QML_PROPERTY(bool, mapAutoOpening)
public: public:
explicit PositionManager(SystemTray* systemTray, explicit PositionManager(AppSettingsManager* settingsManager,
SystemTray* systemTray,
LRCInstance* instance, LRCInstance* instance,
QObject* parent = nullptr); QObject* parent = nullptr);
~PositionManager() = default; ~PositionManager() = default;
@ -45,30 +47,49 @@ Q_SIGNALS:
void positioningError(const QString error); void positioningError(const QString error);
void positionShareAdded(const QVariantMap& shareInfo); void positionShareAdded(const QVariantMap& shareInfo);
void positionShareUpdated(const QVariantMap& posInfo); 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: protected:
void safeInit() override; void safeInit() override;
QString getAvatar(const QString& accountId, const QString& peerId); 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 positionWatchDog();
void startPositionTimers(int timeSharing); 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 connectAccountModel();
Q_INVOKABLE void setMapActive(bool state); Q_INVOKABLE void pinMap(QString key);
Q_INVOKABLE void sharePosition(int maximumTime); Q_INVOKABLE void unPinMap(QString key);
Q_INVOKABLE void stopSharingPosition(const QString convId = ""); 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 startPositioning();
Q_INVOKABLE void stopPositioning(); Q_INVOKABLE void stopPositioning();
Q_INVOKABLE QString getSelectedConvId(); Q_INVOKABLE bool isPositionSharedToConv(const QString& accountId, const QString& convUid);
Q_INVOKABLE bool isPositionSharedToConv(const QString& convUri); Q_INVOKABLE bool isConvSharingPosition(const QString& accountId, const QString& convUri);
Q_INVOKABLE bool isConvSharingPosition(const QString& convUri);
Q_INVOKABLE void loadPreviousLocations(); Q_INVOKABLE void loadPreviousLocations(QString& accountId);
Q_INVOKABLE QString getmapTitle(QString& accountId, QString convId = "");
private Q_SLOTS: private Q_SLOTS:
void onPositionErrorReceived(const QString error); void onPositionErrorReceived(const QString error);
@ -77,16 +98,21 @@ private Q_SLOTS:
const QString& body, const QString& body,
const uint64_t& timestamp, const uint64_t& timestamp,
const QString& daemonId); const QString& daemonId);
void sendPosition(const QString& body); void sendPosition(const QString& body, bool triggersLocalPosition = true);
void onWatchdogTimeout(); void onWatchdogTimeout();
void showNotification(const QString& accountId, const QString& convId, const QString& from); void showNotification(const QString& accountId, const QString& convId, const QString& from);
void onNewConversation();
void onNewAccount();
private: private:
SystemTray* systemTray_; SystemTray* systemTray_;
std::unique_ptr<Positioning> localPositioning_; std::unique_ptr<Positioning> localPositioning_;
QTimer* timerTimeLeftSharing_ = nullptr; QMap<QString, int> mapTimerCountDown_;
QTimer* timerStopSharing_ = nullptr; QTimer* countdownTimer_ = nullptr;
QSet<QPair<QString, QString>> currentConvSharingUris_; // map of all shared position by peers
QMap<QPair<QString, QString>, PositionObject*> objectListSharingUris_; QMap<PositionKey, PositionObject*> objectListSharingUris_;
QList<QPair<QString, QString>> positionShareConvIds_; // list of all the peers the user is sharing position to
QList<PositionKey> positionShareConvIds_;
QMutex mapStatusMutex_;
AppSettingsManager* settingsManager_;
}; };

View file

@ -2,7 +2,7 @@
PositionObject::PositionObject(QVariant latitude, QVariant longitude, QObject* parent) PositionObject::PositionObject(QVariant latitude, QVariant longitude, QObject* parent)
: QObject(parent) : QObject(parent)
, resetTime(20000) , resetTime(40000)
, longitude_(longitude) , longitude_(longitude)
, latitude_(latitude) , latitude_(latitude)
@ -28,3 +28,10 @@ PositionObject::getLatitude()
{ {
return latitude_; return latitude_;
} }
void
PositionObject::updatePosition(QVariant latitude, QVariant longitude)
{
longitude_ = longitude;
latitude_ = latitude;
}

View file

@ -20,6 +20,8 @@ public:
QVariant getLongitude(); QVariant getLongitude();
QVariant getLatitude(); QVariant getLatitude();
void updatePosition(QVariant latitude, QVariant longitude);
private: private:
QVariant latitude_; QVariant latitude_;
QVariant longitude_; QVariant longitude_;

View file

@ -112,7 +112,7 @@ registerTypes(QQmlEngine* engine,
// setup the adapters (their lifetimes are that of MainApplication) // setup the adapters (their lifetimes are that of MainApplication)
auto callAdapter = new CallAdapter(systemTray, lrcInstance, parent); auto callAdapter = new CallAdapter(systemTray, lrcInstance, parent);
auto messagesAdapter = new MessagesAdapter(settingsManager, previewEngine, 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 conversationsAdapter = new ConversationsAdapter(systemTray, lrcInstance, parent);
auto avAdapter = new AvAdapter(lrcInstance, parent); auto avAdapter = new AvAdapter(lrcInstance, parent);
auto contactAdapter = new ContactAdapter(lrcInstance, parent); auto contactAdapter = new ContactAdapter(lrcInstance, parent);

View file

@ -63,6 +63,16 @@ Rectangle {
itemWidth: preferredColumnWidth 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 // file transfer setting panel
FileTransferSettings { FileTransferSettings {
id: fileTransferSettings id: fileTransferSettings

View file

@ -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
}
}
}
}

View file

@ -15,6 +15,7 @@
* 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 <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@ -23,373 +24,212 @@ import QtWebEngine
import net.jami.Models 1.1 import net.jami.Models 1.1
import net.jami.Adapters 1.1 import net.jami.Adapters 1.1
import net.jami.Enums 1.1
import net.jami.Constants 1.1 import net.jami.Constants 1.1
import "../../commoncomponents" import "../../commoncomponents"
Rectangle { Item {
id: mapPopup id: root
x: xPos property bool isUnpin: false
y: yPos property real maxWidth
width: isFullScreen ? root.width : windowSize property real maxHeight
height: isMinimised property string attachedAccountId
? buttonOverlay.height + buttonsChoseSharing.height + 30 property string currentAccountId: CurrentAccount.id
: isFullScreen ? root.height - yPos : windowSize property string currentConvId: CurrentConversation.id
property bool isSharing: (PositionManager.positionShareConvIdsCount !== 0)
property bool isSharingToCurrentConversation
property bool isFullScreen: false function closeMapPosition() {
property bool isMinimised: false root.destroy()
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 { Connections {
id: webView target: PositionManager
width: parent.width function onPinMapSignal(key) {
height: parent.height if (key === attachedAccountId) {
isUnpin = false
property string mapHtml: ":/webengine/map/map.html" mapObject.state = "pin"
property string olCss: ":/webengine/map/ol.css" windowUnpin.close()
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
}
var scriptOlJs = {
sourceUrl: Qt.resolvedUrl(olJs),
injectionPoint: WebEngineScript.DocumentReady,
worldId: WebEngineScript.MainWorld
}
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 onPositionShareUpdated(shareInfo) {
if(webView.isLoaded) {
var curLong = shareInfo.long
var curLat = shareInfo.lat
webView.runJavaScript("updatePosition([" + curLong + "," + curLat + "], '" + shareInfo.author + "' )" );
}
}
function onPositionShareRemoved(author) {
if(webView.isLoaded) {
webView.runJavaScript("removePosition( '" + author + "' )" );
webView.runJavaScript("zoomTolayersExtent()" );
}
} }
} }
Component.onCompleted: { function onCloseMap(key) {
loadHtml(UtilsAdapter.qStringFromFile(mapHtml), mapHtml) if (key === attachedAccountId )
loadScripts() closeMapPosition()
} }
onLoadingChanged: function (loadingInfo) { function onUnPinMapSignal(key) {
if (loadingInfo.status === WebEngineView.LoadSucceededStatus) { if (key === attachedAccountId ) {
runJavaScript(UtilsAdapter.getStyleSheet("olcss",UtilsAdapter.qStringFromFile(olCss))) isUnpin = true
webView.isLoaded = true mapObject.state = "unpin"
runJavaScript("setMapView([" + 0 + ","+ 0 + "], " + 1 + " );" ); windowUnpin.show()
PositionManager.startPositioning()
//load locations that were received before this conversation was opened
PositionManager.loadPreviousLocations();
} }
} }
} }
ColumnLayout { Window {
id: buttonsChoseSharing id: windowUnpin
anchors.horizontalCenter: mapPopup.horizontalCenter width: parentPin.width
anchors.margins: 10 height: parentPin.height
anchors.bottom: mapPopup.bottom visible: false
title: PositionManager.getmapTitle(attachedAccountId)
property bool shortSharing: true Item {
id: parentUnPin
RowLayout { width: mapObject.width
Layout.alignment: Qt.AlignHCenter height: mapObject.height
MaterialButton {
id: shortSharingButton
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
}
}
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;
}
}
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)
Layout.alignment: Qt.AlignHCenter
property int remainingTimeMs: Math.ceil(PositionManager.timeSharingRemaining / 1000 / 60)
}
}
} }
RowLayout { onClosing: {
id: sharePositionLayout if (isUnpin) {
Layout.alignment: Qt.AlignHCenter PositionManager.setMapInactive(attachedAccountId)
}
}
}
MaterialButton { Item {
id: sharePositionButton id: parentPin
preferredWidth: text.contentWidth width: mapObject.width
textLeftPadding: JamiTheme.buttontextPadding height: mapObject.height
textRightPadding: JamiTheme.buttontextPadding
primary: true Rectangle {
visible: ! PositionManager.isPositionSharedToConv(PositionManager.getSelectedConvId()) id: mapObject
text: JamiStrings.shareLocation
color: isError x: xPos
? JamiTheme.buttonTintedGreyInactive y: yPos
: JamiTheme.buttonTintedBlue width: root.isUnpin
hoveredColor: isError ? windowUnpin.width
? JamiTheme.buttonTintedGreyInactive : isFullScreen ? root.maxWidth : windowSize
: JamiTheme.buttonTintedBlueHovered height: root.isUnpin
pressedColor: isError ? windowUnpin.height
? JamiTheme.buttonTintedGreyInactive : isFullScreen ? root.maxHeight - yPos : windowSize
: JamiTheme.buttonTintedBluePressed
Layout.alignment: Qt.AlignHCenter property bool isFullScreen: false
property bool isHovered: false property real windowSize: windowPreferedSize > JamiTheme.minimumMapWidth
property string positioningError: "default" ? windowPreferedSize
property bool isError: positioningError.length : JamiTheme.minimumMapWidth
function errorString(posError) { property real windowPreferedSize: root.maxWidth > root.maxHeight
if (posError === "locationServicesError") ? root.maxHeight / 3
return JamiStrings.locationServicesError : root.maxWidth / 3
return JamiStrings.locationServicesClosedError 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
onClicked: { WebEngineView {
if (!isError) { id: webView
if( buttonsChoseSharing.shortSharing)
PositionManager.sharePosition(10 * 60 * 1000); layer.enabled: !isFullScreen
else layer.effect: OpacityMask {
PositionManager.sharePosition(60 * 60 * 1000); maskSource:
visible = false Rectangle {
width: webView.width
height: webView.height
radius: 10
} }
} }
onHoveredChanged: { width: parent.width
isHovered = !isHovered height: parent.height
}
MaterialToolTip { property string mapHtml: ":/webengine/map/map.html"
visible: sharePositionButton.isHovered property string olCss: ":/webengine/map/ol.css"
&& sharePositionButton.isError && (sharePositionButton.positioningError !== "default") property string mapJs: "../../webengine/map/map.js"
x: 0 property string olJs: "../../webengine/map/ol.js"
y: 0 property bool isLoaded: false
text: sharePositionButton.errorString(sharePositionButton.positioningError) property var positionList: PositionManager.positionList;
property var avatarPositionList: PositionManager.avatarPositionList;
function loadScripts () {
var scriptMapJs = {
sourceUrl: Qt.resolvedUrl(mapJs),
injectionPoint: WebEngineScript.DocumentReady,
worldId: WebEngineScript.MainWorld
}
var scriptOlJs = {
sourceUrl: Qt.resolvedUrl(olJs),
injectionPoint: WebEngineScript.DocumentReady,
worldId: WebEngineScript.MainWorld
}
userScripts.collection = [ scriptOlJs, scriptMapJs ]
} }
Connections { Connections {
target: PositionManager target: PositionManager
function onPositioningError (err) {
sharePositionButton.positioningError = err;
}
}
}
MaterialButton {
id: stopSharingPositionButton
preferredWidth: text.contentWidth function onPositionShareAdded(shareInfo) {
textLeftPadding: JamiTheme.buttontextPadding if(webView.isLoaded) {
textRightPadding: JamiTheme.buttontextPadding if (shareInfo.account === attachedAccountId) {
primary: true var curLong = shareInfo.long
visible: webView.isSharing var curLat = shareInfo.lat
text: JamiStrings.stopSharingLocation webView.runJavaScript("newPosition([" + curLong + "," + curLat + "], '" + shareInfo.author + "', '" + shareInfo.avatar + "' )" );
color: isError webView.runJavaScript("zoomTolayersExtent()" );
? JamiTheme.buttonTintedGreyInactive }
: JamiTheme.buttonTintedRed }
hoveredColor: isError }
? JamiTheme.buttonTintedGreyInactive
: JamiTheme.buttonTintedRedHovered function onPositionShareUpdated(shareInfo) {
pressedColor: isError if(webView.isLoaded) {
? JamiTheme.buttonTintedGreyInactive if (shareInfo.account === attachedAccountId) {
: JamiTheme.buttonTintedRedPressed var curLong = shareInfo.long
Layout.alignment: Qt.AlignHCenter var curLat = shareInfo.lat
property bool isHovered: false webView.runJavaScript("updatePosition([" + curLong + "," + curLat + "], '" + shareInfo.author + "' )" );
property string positioningError }
property bool isError: positioningError.length }
onClicked: { }
if (!isError) {
if (PositionManager.positionShareConvIdsCount >= 2) { function onPositionShareRemoved(author, accountId) {
stopSharingPositionPopup.open() if(webView.isLoaded) {
} else { if (accountId === attachedAccountId) {
PositionManager.stopSharingPosition(); webView.runJavaScript("removePosition( '" + author + "' )" );
sharePositionButton.visible = true webView.runJavaScript("zoomTolayersExtent()" );
}
} }
} }
} }
}
}
}
StopSharingPositionPopup { Component.onCompleted: {
id: stopSharingPositionPopup loadHtml(UtilsAdapter.qStringFromFile(mapHtml), mapHtml)
loadScripts()
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()" );
} }
}
PushButton { onLoadingChanged: function (loadingInfo) {
id: btnMove if (loadingInfo.status === WebEngineView.LoadSucceededStatus) {
attachedAccountId = CurrentAccount.id
toolTipText: JamiStrings.dragMapTooltip runJavaScript(UtilsAdapter.getStyleSheet("olcss",UtilsAdapter.qStringFromFile(olCss)))
imageColor: JamiTheme.mapButtonColor webView.isLoaded = true
normalColor: JamiTheme.transparentColor runJavaScript("setMapView([" + 0 + ","+ 0 + "], " + 1 + " );" );
source: JamiResources.move_svg PositionManager.startPositioning()
//load locations that were received before this conversation was opened
MouseArea { PositionManager.loadPreviousLocations(attachedAccountId);
anchors.fill: parent
drag.target: mapPopup
drag.minimumX: 0
drag.maximumX: root.width - mapPopup.width
drag.minimumY: 0
drag.maximumY: root.height - mapPopup.height
}
}
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;
}
}
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
} }
isFullScreen = !isFullScreen
isMinimised = false;
} }
} }
PushButton { MapPositionSharingControl {}
id: btnClose
toolTipText: JamiStrings.closeMapTooltip MapPositionOverlay {}
imageColor: JamiTheme.mapButtonColor
normalColor: JamiTheme.transparentColor
source: JamiResources.round_close_24dp_svg
onClicked: { StopSharingPositionPopup {
PositionManager.stopPositioning(); id: stopSharingPositionPopup
PositionManager.setMapActive(false);
PositionManager.mapAutoOpening = false;
} property alias attachedAccountId: root.attachedAccountId
} }
} }
} }

View file

@ -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
}
}
}
}

View file

@ -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()
}
}
}
}
}
}

View file

@ -19,12 +19,11 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import net.jami.Models 1.1 import net.jami.Models 1.1
import net.jami.Adapters 1.1 import net.jami.Adapters 1.1
import net.jami.Constants 1.1 import net.jami.Constants 1.1
import Qt5Compat.GraphicalEffects
import "../../commoncomponents" import "../../commoncomponents"
@ -101,11 +100,10 @@ Popup {
color: JamiTheme.buttonTintedBlue color: JamiTheme.buttonTintedBlue
hoveredColor: JamiTheme.buttonTintedBlueHovered hoveredColor: JamiTheme.buttonTintedBlueHovered
pressedColor: JamiTheme.buttonTintedBluePressed pressedColor: JamiTheme.buttonTintedBluePressed
text: JamiStrings.stopConvSharing text: JamiStrings.stopConvSharing.arg(PositionManager.getmapTitle(attachedAccountId, CurrentConversation.id))
onClicked: { onClicked: {
PositionManager.stopSharingPosition(PositionManager.getSelectedConvId()) PositionManager.stopSharingPosition(attachedAccountId, CurrentConversation.id)
shareButtonVisibility = true
root.close() root.close()
} }
} }
@ -123,12 +121,10 @@ Popup {
onClicked: { onClicked: {
PositionManager.stopSharingPosition() PositionManager.stopSharingPosition()
shareButtonVisibility = true
root.close() root.close()
} }
} }
} }
} }
} }

View file

@ -61,7 +61,7 @@ var proj = new ol.proj.Projection({
extent: extent extent: extent
}) })
function setSource (coordos, authorI,imageI) { function setSource (coordos, avatar) {
var coord = ol.proj.fromLonLat(coordos) var coord = ol.proj.fromLonLat(coordos)
var pointFeature = new ol.Feature({ var pointFeature = new ol.Feature({
geometry: new ol.geom.Point(coord), geometry: new ol.geom.Point(coord),
@ -69,16 +69,16 @@ function setSource (coordos, authorI,imageI) {
}) })
var preStyle = new ol.style.Icon({ 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() var image = preStyle.getImage()
if (!image.width) { if (!image.width) {
image.addEventListener('load', function () { image.addEventListener('load', function () {
preStyle.setScale([40 / image.width, 40 / image.height]) preStyle.setScale([35 / image.width, 35 / image.height])
}) })
} else { } else {
preStyle.setScale([40 / image.width, 40 / image.height]) preStyle.setScale([35 / image.width, 35 / image.height])
} }
var iconStyle = new ol.style.Style({ var iconStyle = new ol.style.Style({
@ -93,18 +93,24 @@ function setSource (coordos, authorI,imageI) {
return vectorSource return vectorSource
} }
function newPosition (coordos, authorI, image) { function newPosition (coordos, authorUri, avatar) {
vectorSource = setSource(coordos, authorI, image) 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}) var iconLayer = new ol.layer.Vector({source: vectorSource})
iconLayer.layer_type = authorI iconLayer.layer_type = authorUri
map.addLayer(iconLayer) map.addLayer(iconLayer)
} }
function updatePosition (coordos, authorI) { function updatePosition (coordos, authorUri) {
var coord = ol.proj.fromLonLat(coordos); var coord = ol.proj.fromLonLat(coordos);
var layerArray = map.getLayers().getArray(); var layerArray = map.getLayers().getArray();
for (var i = 0; i < layerArray.length; i++ ){ 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) layerArray[i].getSource().getFeatures()[0].getGeometry().setCoordinates(coord)
return return
} }
@ -123,10 +129,10 @@ function zoomTolayersExtent() {
padding: [80 ,80 ,80 ,80]}) padding: [80 ,80 ,80 ,80]})
} }
function removePosition (authorI) { function removePosition (authorUri) {
var layerArray = map.getLayers().getArray(); var layerArray = map.getLayers().getArray();
for (var i = 0; i < layerArray.length; i++ ){ for (var i = 0; i < layerArray.length; i++ ){
if(layerArray[i].layer_type === authorI) { if(layerArray[i].layer_type === authorUri) {
map.removeLayer(layerArray[i]) map.removeLayer(layerArray[i])
return return
} }

View file

@ -30,6 +30,7 @@
typedef QMap<QString, QString> MapStringString; typedef QMap<QString, QString> MapStringString;
typedef QMap<QString, int> MapStringInt; typedef QMap<QString, int> MapStringInt;
typedef QMap<QString, double> MapStringDouble; typedef QMap<QString, double> MapStringDouble;
typedef QMap<QPair<QString, QString>, bool> MapPairStrStrBool;
typedef QVector<int> VectorInt; typedef QVector<int> VectorInt;
typedef QVector<uint> VectorUInt; typedef QVector<uint> VectorUInt;
typedef QVector<qulonglong> VectorULongLong; typedef QVector<qulonglong> VectorULongLong;
@ -41,6 +42,8 @@ typedef QMap<QString, QMap<QString, QStringList>> MapStringMapStringStringList;
typedef QMap<QString, QStringList> MapStringStringList; typedef QMap<QString, QStringList> MapStringStringList;
typedef QVector<QByteArray> VectorVectorByte; typedef QVector<QByteArray> VectorVectorByte;
typedef uint64_t DataTransferId; typedef uint64_t DataTransferId;
// accountId, conversationId
typedef QPair<QString, QString> PositionKey;
constexpr static const char* TRUE_STR = "true"; constexpr static const char* TRUE_STR = "true";
constexpr static const char* TEXT_PLAIN = "text/plain"; constexpr static const char* TEXT_PLAIN = "text/plain";