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:
parent
c36ccb5fa9
commit
d06902e3b7
22 changed files with 1162 additions and 600 deletions
1
resources/icons/unpin.svg
Normal file
1
resources/icons/unpin.svg
Normal 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 |
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -34,7 +34,7 @@ Rectangle {
|
|||
id: root
|
||||
|
||||
property bool allMessagesLoaded
|
||||
|
||||
property var mapPositions: PositionManager.mapStatus
|
||||
signal needToHideConversationInCall
|
||||
signal messagesCleared
|
||||
signal messagesLoaded
|
||||
|
@ -51,18 +51,28 @@ Rectangle {
|
|||
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
|
||||
|
||||
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 {
|
||||
id: hostPopup
|
||||
|
|
|
@ -190,7 +190,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
onShowMapClicked: {
|
||||
PositionManager.setMapActive(true);
|
||||
PositionManager.setMapActive(CurrentAccount.id)
|
||||
}
|
||||
|
||||
onSendFileButtonClicked: jamiFileDialog.open()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
if (localPositioning_)
|
||||
localPositioning_->start();
|
||||
connect(localPositioning_.get(),
|
||||
&Positioning::newPosition,
|
||||
this,
|
||||
&PositionManager::onPositionReceived,
|
||||
Qt::UniqueConnection);
|
||||
|
||||
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()
|
||||
{
|
||||
if (localPositioning_)
|
||||
localPositioning_->stop();
|
||||
}
|
||||
|
||||
QString
|
||||
PositionManager::getSelectedConvId()
|
||||
{
|
||||
return lrcInstance_->get_selectedConvUid();
|
||||
}
|
||||
|
||||
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++) {
|
||||
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));
|
||||
onPositionReceived(it.key().first, it.key().second, strJson, -1, "");
|
||||
// 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 {
|
||||
if (settingsManager_->getValue(Settings::Key::PositionShareLimit) == true)
|
||||
startPositionTimers(maximumTime);
|
||||
const auto convUid = lrcInstance_->get_selectedConvUid();
|
||||
positionShareConvIds_.append(
|
||||
QPair<QString, QString> {lrcInstance_->get_currentAccountId(), convUid});
|
||||
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));
|
||||
}
|
||||
set_positionShareConvIdsCount(positionShareConvIds_.size());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::setMapActive(bool state)
|
||||
PositionManager::unPinMap(QString key)
|
||||
{
|
||||
set_isMapActive(state);
|
||||
Q_EMIT isMapActiveChanged();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -304,6 +412,151 @@ PositionManager::showNotification(const QString& accountId,
|
|||
#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
|
||||
PositionManager::onPositionReceived(const QString& accountId,
|
||||
const QString& peerId,
|
||||
|
@ -311,106 +564,34 @@ PositionManager::onPositionReceived(const QString& accountId,
|
|||
const uint64_t& timestamp,
|
||||
const QString& daemonId)
|
||||
{
|
||||
// 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());
|
||||
// handlers variables
|
||||
|
||||
QVariantMap newPosition = parseJsonPosition(body, peerId);
|
||||
auto getShareInfo = [&](bool update) -> QVariantMap {
|
||||
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";
|
||||
// parse the position from json
|
||||
QVariantMap positionReceived = parseJsonPosition(accountId, peerId, body);
|
||||
|
||||
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) {
|
||||
// open map on position reception
|
||||
if (!isMapActive_ && mapAutoOpening_ && isPeerIdInConv
|
||||
&& peerId != lrcInstance_->getCurrentAccountInfo().profileInfo.uri) {
|
||||
set_isMapActive(true);
|
||||
}
|
||||
}
|
||||
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);
|
||||
// key to identify the peer
|
||||
auto key = PositionKey {accountId, peerId};
|
||||
|
||||
objectListSharingUris_.insert(key, obj);
|
||||
set_sharingUrisCount(objectListSharingUris_.size());
|
||||
connect(obj,
|
||||
&PositionObject::timeout,
|
||||
this,
|
||||
&PositionManager::onWatchdogTimeout,
|
||||
Qt::DirectConnection);
|
||||
// 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 (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
|
||||
// if the position already exists
|
||||
if (findPeerIdinAllPeers != objectListSharingUris_.end()) {
|
||||
if (endSharing)
|
||||
removePositionFromMemory(key, positionReceived);
|
||||
else
|
||||
updatePositionInMemory(key, positionReceived);
|
||||
|
||||
} else {
|
||||
auto it = objectListSharingUris_.find(key);
|
||||
if (it != objectListSharingUris_.end()) {
|
||||
it.value()->deleteLater();
|
||||
objectListSharingUris_.erase(it);
|
||||
set_sharingUrisCount(objectListSharingUris_.size());
|
||||
}
|
||||
}
|
||||
} 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
|
||||
|
||||
auto it = objectListSharingUris_.find(key);
|
||||
if (it != objectListSharingUris_.end()) {
|
||||
it.value()->resetWatchdog();
|
||||
}
|
||||
}
|
||||
// It is the first time a position is received from this peer
|
||||
addPositionToMemory(key, positionReceived);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ public:
|
|||
QVariant getLongitude();
|
||||
QVariant getLatitude();
|
||||
|
||||
void updatePosition(QVariant latitude, QVariant longitude);
|
||||
|
||||
private:
|
||||
QVariant latitude_;
|
||||
QVariant longitude_;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
150
src/app/settingsview/components/LocationSharingSettings.qml
Normal file
150
src/app/settingsview/components/LocationSharingSettings.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,34 +24,126 @@ 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
|
||||
Item {
|
||||
id: root
|
||||
|
||||
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
|
||||
|
||||
function closeMapPosition() {
|
||||
root.destroy()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PositionManager
|
||||
|
||||
function onPinMapSignal(key) {
|
||||
if (key === attachedAccountId) {
|
||||
isUnpin = false
|
||||
mapObject.state = "pin"
|
||||
windowUnpin.close()
|
||||
}
|
||||
}
|
||||
|
||||
function onCloseMap(key) {
|
||||
if (key === attachedAccountId )
|
||||
closeMapPosition()
|
||||
}
|
||||
|
||||
function onUnPinMapSignal(key) {
|
||||
if (key === attachedAccountId ) {
|
||||
isUnpin = true
|
||||
mapObject.state = "unpin"
|
||||
windowUnpin.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Window {
|
||||
id: windowUnpin
|
||||
|
||||
width: parentPin.width
|
||||
height: parentPin.height
|
||||
visible: false
|
||||
title: PositionManager.getmapTitle(attachedAccountId)
|
||||
|
||||
Item {
|
||||
id: parentUnPin
|
||||
|
||||
width: mapObject.width
|
||||
height: mapObject.height
|
||||
}
|
||||
|
||||
onClosing: {
|
||||
if (isUnpin) {
|
||||
PositionManager.setMapInactive(attachedAccountId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: parentPin
|
||||
|
||||
width: mapObject.width
|
||||
height: mapObject.height
|
||||
|
||||
Rectangle {
|
||||
id: mapObject
|
||||
|
||||
x: xPos
|
||||
y: yPos
|
||||
width: isFullScreen ? root.width : windowSize
|
||||
height: isMinimised
|
||||
? buttonOverlay.height + buttonsChoseSharing.height + 30
|
||||
: isFullScreen ? root.height - yPos : windowSize
|
||||
width: root.isUnpin
|
||||
? windowUnpin.width
|
||||
: isFullScreen ? root.maxWidth : windowSize
|
||||
height: root.isUnpin
|
||||
? windowUnpin.height
|
||||
: isFullScreen ? root.maxHeight - 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 windowPreferedSize: root.maxWidth > root.maxHeight
|
||||
? root.maxHeight / 3
|
||||
: root.maxWidth / 3
|
||||
property real xPos: 0
|
||||
property real yPos: JamiTheme.chatViewHeaderPreferredHeight
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
|
@ -61,7 +154,6 @@ Rectangle {
|
|||
property bool isLoaded: false
|
||||
property var positionList: PositionManager.positionList;
|
||||
property var avatarPositionList: PositionManager.avatarPositionList;
|
||||
property bool isSharing: (PositionManager.positionShareConvIdsCount !== 0 )
|
||||
|
||||
function loadScripts () {
|
||||
var scriptMapJs = {
|
||||
|
@ -83,29 +175,34 @@ Rectangle {
|
|||
|
||||
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()" );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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 + "' )" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onPositionShareRemoved(author) {
|
||||
function onPositionShareRemoved(author, accountId) {
|
||||
if(webView.isLoaded) {
|
||||
if (accountId === attachedAccountId) {
|
||||
webView.runJavaScript("removePosition( '" + author + "' )" );
|
||||
webView.runJavaScript("zoomTolayersExtent()" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadHtml(UtilsAdapter.qStringFromFile(mapHtml), mapHtml)
|
||||
|
@ -114,282 +211,25 @@ Rectangle {
|
|||
|
||||
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();
|
||||
PositionManager.loadPreviousLocations(attachedAccountId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: buttonsChoseSharing
|
||||
MapPositionSharingControl {}
|
||||
|
||||
anchors.horizontalCenter: mapPopup.horizontalCenter
|
||||
anchors.margins: 10
|
||||
anchors.bottom: mapPopup.bottom
|
||||
|
||||
property bool shortSharing: true
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
onHoveredChanged: {
|
||||
isHovered = !isHovered
|
||||
}
|
||||
|
||||
MaterialToolTip {
|
||||
visible: sharePositionButton.isHovered
|
||||
&& sharePositionButton.isError && (sharePositionButton.positioningError !== "default")
|
||||
x: 0
|
||||
y: 0
|
||||
text: 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: 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MapPositionOverlay {}
|
||||
|
||||
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()" );
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
id: btnClose
|
||||
|
||||
toolTipText: JamiStrings.closeMapTooltip
|
||||
imageColor: JamiTheme.mapButtonColor
|
||||
normalColor: JamiTheme.transparentColor
|
||||
source: JamiResources.round_close_24dp_svg
|
||||
|
||||
onClicked: {
|
||||
PositionManager.stopPositioning();
|
||||
PositionManager.setMapActive(false);
|
||||
PositionManager.mapAutoOpening = false;
|
||||
|
||||
}
|
||||
property alias attachedAccountId: root.attachedAccountId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
125
src/app/webengine/map/MapPositionOverlay.qml
Normal file
125
src/app/webengine/map/MapPositionOverlay.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
203
src/app/webengine/map/MapPositionSharingControl.qml
Normal file
203
src/app/webengine/map/MapPositionSharingControl.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Add table
Reference in a new issue