mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-08-01 05:15:44 +02:00
feature: show and share user location
copyright OpenLayers v7.1.0: ol.css copyright OpenLayers v7.1.0: ol.js GitLab: #867 Change-Id: I4e01f6d9727d56541d1b44023f26959ebe4fbe26 Signed-off-by: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com>
This commit is contained in:
parent
9bccc3805a
commit
e5b54ad787
34 changed files with 1602 additions and 18 deletions
|
@ -85,7 +85,8 @@ set(QT_MODULES
|
|||
Core
|
||||
Core5Compat
|
||||
Multimedia
|
||||
Widgets)
|
||||
Widgets
|
||||
Positioning)
|
||||
|
||||
if(NOT DEFINED WITH_WEBENGINE)
|
||||
set(WITH_WEBENGINE true)
|
||||
|
@ -96,7 +97,8 @@ if(WITH_WEBENGINE)
|
|||
WebEngineCore
|
||||
WebEngineQuick
|
||||
WebChannel
|
||||
WebEngineWidgets)
|
||||
WebEngineWidgets
|
||||
)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS
|
||||
|
@ -176,6 +178,7 @@ set(COMMON_SOURCES
|
|||
${APP_SRC_DIR}/utils.cpp
|
||||
${APP_SRC_DIR}/mainapplication.cpp
|
||||
${APP_SRC_DIR}/messagesadapter.cpp
|
||||
${APP_SRC_DIR}/positionmanager.cpp
|
||||
${APP_SRC_DIR}/accountadapter.cpp
|
||||
${APP_SRC_DIR}/calladapter.cpp
|
||||
${APP_SRC_DIR}/conversationsadapter.cpp
|
||||
|
@ -211,7 +214,9 @@ set(COMMON_SOURCES
|
|||
${APP_SRC_DIR}/videodevices.cpp
|
||||
${APP_SRC_DIR}/videoprovider.cpp
|
||||
${APP_SRC_DIR}/callparticipantsmodel.cpp
|
||||
${APP_SRC_DIR}/tipsmodel.cpp)
|
||||
${APP_SRC_DIR}/tipsmodel.cpp
|
||||
${APP_SRC_DIR}/positioning.cpp
|
||||
)
|
||||
|
||||
set(COMMON_HEADERS
|
||||
${APP_SRC_DIR}/avatarimageprovider.h
|
||||
|
@ -228,6 +233,7 @@ set(COMMON_HEADERS
|
|||
${APP_SRC_DIR}/mainapplication.h
|
||||
${APP_SRC_DIR}/qrimageprovider.h
|
||||
${APP_SRC_DIR}/messagesadapter.h
|
||||
${APP_SRC_DIR}/positionmanager.h
|
||||
${APP_SRC_DIR}/accountadapter.h
|
||||
${APP_SRC_DIR}/calladapter.h
|
||||
${APP_SRC_DIR}/conversationsadapter.h
|
||||
|
@ -267,7 +273,8 @@ set(COMMON_HEADERS
|
|||
${APP_SRC_DIR}/videodevices.h
|
||||
${APP_SRC_DIR}/videoprovider.h
|
||||
${APP_SRC_DIR}/callparticipantsmodel.h
|
||||
${APP_SRC_DIR}/tipsmodel.h)
|
||||
${APP_SRC_DIR}/tipsmodel.h
|
||||
${APP_SRC_DIR}/positioning.h)
|
||||
|
||||
if(WITH_WEBENGINE)
|
||||
list(APPEND COMMON_SOURCES
|
||||
|
|
1
resources/icons/minimize.svg
Normal file
1
resources/icons/minimize.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M12 41.5v-3h24.05v3Z"/></svg>
|
After Width: | Height: | Size: 101 B |
1
resources/icons/move.svg
Normal file
1
resources/icons/move.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="m24 44-8.15-8.15 2.2-2.2 4.45 4.45v-9.45h3v9.45l4.45-4.45 2.2 2.2ZM11.9 31.9 4 24l7.95-7.95 2.2 2.2L9.9 22.5h9.45v3H9.9l4.2 4.2Zm24.2 0-2.2-2.2 4.2-4.2h-9.4v-3h9.4l-4.2-4.2 2.2-2.2L44 24ZM22.5 19.3V9.9l-4.2 4.2-2.2-2.2L24 4l7.9 7.9-2.2 2.2-4.2-4.2v9.4Z"/></svg>
|
After Width: | Height: | Size: 333 B |
1
resources/icons/my_location.svg
Normal file
1
resources/icons/my_location.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M22.5 45.9v-3.75q-6.85-.7-11.4-5.25-4.55-4.55-5.25-11.4H2.1v-3h3.75q.7-6.85 5.25-11.4 4.55-4.55 11.4-5.25V2.1h3v3.75q6.85.7 11.4 5.25 4.55 4.55 5.25 11.4h3.75v3h-3.75q-.7 6.85-5.25 11.4-4.55 4.55-11.4 5.25v3.75Zm1.5-6.7q6.25 0 10.725-4.475T39.2 24q0-6.25-4.475-10.725T24 8.8q-6.25 0-10.725 4.475T8.8 24q0 6.25 4.475 10.725T24 39.2Zm0-7.7q-3.15 0-5.325-2.175Q16.5 27.15 16.5 24q0-3.15 2.175-5.325Q20.85 16.5 24 16.5q3.15 0 5.325 2.175Q31.5 20.85 31.5 24q0 3.15-2.175 5.325Q27.15 31.5 24 31.5Zm0-3q1.9 0 3.2-1.3 1.3-1.3 1.3-3.2 0-1.9-1.3-3.2-1.3-1.3-3.2-1.3-1.9 0-3.2 1.3-1.3 1.3-1.3 3.2 0 1.9 1.3 3.2 1.3 1.3 3.2 1.3Zm0-4.5Z"/></svg>
|
After Width: | Height: | Size: 704 B |
1
resources/icons/share_location.svg
Normal file
1
resources/icons/share_location.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M26.1 43.9v-3q2.25-.3 4.375-1.175T34.45 37.4l2.05 2.2q-2.3 1.9-4.9 2.95-2.6 1.05-5.5 1.35Zm13.45-7.4-2.15-2.05q1.35-1.8 2.25-3.875.9-2.075 1.25-4.475h3.05q-.4 3-1.525 5.625T39.55 36.5Zm1.35-14.6q-.35-2.4-1.25-4.45-.9-2.05-2.25-3.9l2.15-2.05q1.9 2.5 2.9 4.95t1.5 5.45Zm-19.05 22q-7.6-.85-12.7-6.525Q4.05 31.7 4.05 24t5.1-13.375q5.1-5.675 12.7-6.525v3q-6.35.85-10.575 5.675T7.05 24q0 6.4 4.225 11.225Q15.5 40.05 21.85 40.9ZM34.5 10.6q-1.95-1.35-4.075-2.225T26.2 7.1v-3q2.7.4 5.35 1.45Q34.2 6.6 36.5 8.4ZM24 34.55q-4.25-3.6-6.3-6.675-2.05-3.075-2.05-5.675 0-3.95 2.525-6.275T24 13.6q3.3 0 5.825 2.325Q32.35 18.25 32.35 22.2q0 2.6-2.05 5.675-2.05 3.075-6.3 6.675Zm0-10.45q.95 0 1.6-.65.65-.65.65-1.6 0-.85-.65-1.55-.65-.7-1.6-.7-.95 0-1.6.7-.65.7-.65 1.55 0 .95.65 1.6.65.65 1.6.65Z"/></svg>
|
After Width: | Height: | Size: 859 B |
|
@ -48,6 +48,7 @@ AbstractButton {
|
|||
property var preferredWidth
|
||||
property real textLeftPadding
|
||||
property real textRightPadding
|
||||
property real fontSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
|
||||
Binding on width {
|
||||
when: root.preferredWidth !== undefined ||
|
||||
|
@ -183,7 +184,7 @@ AbstractButton {
|
|||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: contentColorProvider
|
||||
font.pixelSize: JamiTheme.wizardViewDescriptionFontPixelSize
|
||||
font.pixelSize: fontSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -299,6 +299,16 @@ Item {
|
|||
property string raiseHand: qsTr("Raise hand")
|
||||
property string layoutSettings: qsTr("Layout settings")
|
||||
|
||||
// Share location
|
||||
property string shareLocation: qsTr("Share location")
|
||||
property string stopSharingLocation: qsTr("Stop sharing location")
|
||||
property string shortSharing: qsTr("10 minutes")
|
||||
property string longSharing: qsTr("One hour")
|
||||
property string minutesLeft: qsTr("%1 minutes left")
|
||||
property string minuteLeft: qsTr("%1 minute left")
|
||||
property string locationServicesError: qsTr("Jami needs to access to your location.\nIn Device Settings, please turn on Location Services.\nOther participants' location can still be received.")
|
||||
property string locationServicesClosedError: qsTr("Please check your Internet connection.")
|
||||
|
||||
// Chatview header
|
||||
property string hideChat: qsTr("Hide chat")
|
||||
property string placeAudioCall: qsTr("Place audio call")
|
||||
|
|
|
@ -210,6 +210,10 @@ Item {
|
|||
property color messageWebViewFooterButtonImageColor: darkTheme ? "#838383" : "#656565"
|
||||
property color chatviewUsernameColor : "#A7A7A7"
|
||||
|
||||
//mapPosition
|
||||
property color mapButtonsOverlayColor: darkTheme ? "#000000" : "#f0f0f0"
|
||||
property color mapButtonColor: darkTheme ? "#f0f0f0" : "#000000"
|
||||
|
||||
// Files To Send Container
|
||||
property color removeFileButtonColor: Qt.rgba(96, 95, 97, 0.5)
|
||||
|
||||
|
@ -314,6 +318,7 @@ Item {
|
|||
property real preferredDialogWidth: 400
|
||||
property real preferredDialogHeight: 300
|
||||
property real minimumPreviewWidth: 120
|
||||
property real minimumMapWidth: 230
|
||||
property real pluginHandlersPopupViewHeight: 200
|
||||
property real pluginHandlersPopupViewDelegateHeight: 50
|
||||
property real secondaryDialogDimension: 500
|
||||
|
@ -463,6 +468,9 @@ Item {
|
|||
property real tipBoxTitleFontSize: calcSize(13)
|
||||
property real tipBoxContentFontSize: calcSize(12)
|
||||
|
||||
//sharePosition
|
||||
property real timerButtonsFontSize: calcSize(11)
|
||||
|
||||
//Popups
|
||||
property real popuptextSize: calcSize(15)
|
||||
property real popupButtonsMargin: 20
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
class ConversationListModel final : public ConversationListModelBase
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_PROPERTY(MapStringString, position)
|
||||
|
||||
public:
|
||||
explicit ConversationListModel(LRCInstance* instance, QObject* parent = nullptr);
|
||||
|
@ -49,7 +50,8 @@ public:
|
|||
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
|
||||
|
||||
Q_INVOKABLE void setFilterRequests(bool filterRequests);
|
||||
Q_INVOKABLE void ignoreFiltering(const QStringList& highlighted) {
|
||||
Q_INVOKABLE void ignoreFiltering(const QStringList& highlighted)
|
||||
{
|
||||
ignored_ = highlighted;
|
||||
}
|
||||
|
||||
|
|
|
@ -410,7 +410,10 @@ Rectangle {
|
|||
|
||||
objectName: "chatView"
|
||||
visible: false
|
||||
Component.onCompleted: MessagesAdapter.setQmlObject(this)
|
||||
Component.onCompleted: {
|
||||
MessagesAdapter.setQmlObject(this)
|
||||
PositionManager.setQmlObject(this)
|
||||
}
|
||||
}
|
||||
|
||||
NewSwarmPage {
|
||||
|
|
|
@ -47,10 +47,19 @@ Rectangle {
|
|||
|
||||
color: JamiTheme.chatviewBgColor
|
||||
|
||||
HostPopup {
|
||||
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
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: root
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
@ -185,6 +186,11 @@ Rectangle {
|
|||
|
||||
emojiPickerLoader.openEmojiPicker()
|
||||
}
|
||||
|
||||
onShowMapClicked: {
|
||||
PositionManager.setMapActive(true);
|
||||
}
|
||||
|
||||
onSendFileButtonClicked: jamiFileDialog.open()
|
||||
onSendMessageButtonClicked: {
|
||||
// Send text message
|
||||
|
|
|
@ -38,6 +38,7 @@ ColumnLayout {
|
|||
signal sendFileButtonClicked
|
||||
signal audioRecordMessageButtonClicked
|
||||
signal videoRecordMessageButtonClicked
|
||||
signal showMapClicked
|
||||
signal emojiButtonClicked
|
||||
|
||||
implicitHeight: messageBarRowLayout.height
|
||||
|
@ -129,6 +130,27 @@ ColumnLayout {
|
|||
Component.onCompleted: JamiQmlUtils.videoRecordMessageButtonObj = videoRecordMessageButton
|
||||
}
|
||||
|
||||
PushButton {
|
||||
id: showMapButton
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: JamiTheme.chatViewFooterButtonSize
|
||||
Layout.preferredHeight: JamiTheme.chatViewFooterButtonSize
|
||||
visible: WITH_WEBENGINE
|
||||
|
||||
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
|
||||
|
||||
|
|
131
src/app/positioning.cpp
Normal file
131
src/app/positioning.cpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "positioning.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
|
||||
Positioning::Positioning(QString uri, QObject* parent)
|
||||
: QObject(parent)
|
||||
, uri_(uri)
|
||||
{
|
||||
source_ = QGeoPositionInfoSource::createDefaultSource(this);
|
||||
QTimer* timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &Positioning::requestPosition);
|
||||
timer->start(2000);
|
||||
connect(source_, &QGeoPositionInfoSource::errorOccurred, this, &Positioning::slotError);
|
||||
connect(source_, &QGeoPositionInfoSource::positionUpdated, this, &Positioning::positionUpdated);
|
||||
// if location services are activated, positioning will be activated automatically
|
||||
connect(source_,
|
||||
&QGeoPositionInfoSource::supportedPositioningMethodsChanged,
|
||||
this,
|
||||
&Positioning::locationServicesActivated);
|
||||
}
|
||||
|
||||
Positioning::~Positioning()
|
||||
{
|
||||
sendStopSharingMsg();
|
||||
}
|
||||
|
||||
void
|
||||
Positioning::start()
|
||||
{
|
||||
if (source_ && !isPositioning) {
|
||||
source_->startUpdates();
|
||||
isPositioning = true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Positioning::stop()
|
||||
{
|
||||
if (source_ && isPositioning)
|
||||
source_->stopUpdates();
|
||||
isPositioning = false;
|
||||
}
|
||||
|
||||
void
|
||||
Positioning::sendStopSharingMsg()
|
||||
{
|
||||
QJsonObject jsonObj;
|
||||
jsonObj.insert("type", QJsonValue("Stop"));
|
||||
QJsonDocument doc(jsonObj);
|
||||
QString strJson(doc.toJson(QJsonDocument::Compact));
|
||||
Q_EMIT newPosition(uri_, strJson, -1, "");
|
||||
}
|
||||
|
||||
QString
|
||||
Positioning::convertToJson(const QGeoPositionInfo& info)
|
||||
{
|
||||
QJsonObject jsonObj;
|
||||
jsonObj.insert("type", QJsonValue("Position"));
|
||||
jsonObj.insert("lat", QJsonValue(info.coordinate().latitude()));
|
||||
jsonObj.insert("long", QJsonValue(info.coordinate().longitude()));
|
||||
jsonObj.insert("time", QJsonValue(info.timestamp().toMSecsSinceEpoch()));
|
||||
|
||||
QJsonDocument doc(jsonObj);
|
||||
QString strJson(doc.toJson(QJsonDocument::Compact));
|
||||
|
||||
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, "");
|
||||
}
|
||||
|
||||
void
|
||||
Positioning::requestPosition()
|
||||
{
|
||||
if (source_)
|
||||
source_->requestUpdate();
|
||||
}
|
||||
|
||||
void
|
||||
Positioning::locationServicesActivated()
|
||||
{
|
||||
Q_EMIT positioningError("");
|
||||
start();
|
||||
}
|
||||
|
||||
static QString
|
||||
errorToString(QGeoPositionInfoSource::Error error)
|
||||
{
|
||||
if (error == 0) {
|
||||
return QObject::tr("locationServicesError");
|
||||
}
|
||||
if (error == 1) {
|
||||
return QObject::tr("locationServicesClosedError");
|
||||
}
|
||||
return QObject::tr("locationServicesUnknownError");
|
||||
}
|
||||
|
||||
void
|
||||
Positioning::slotError(QGeoPositionInfoSource::Error error)
|
||||
{
|
||||
Q_EMIT positioningError(errorToString(error));
|
||||
}
|
73
src/app/positioning.h
Normal file
73
src/app/positioning.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtPositioning/QGeoPositionInfoSource>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
class Positioning : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Positioning(QString uri, QObject* parent = 0);
|
||||
~Positioning();
|
||||
|
||||
/**
|
||||
* start to retreive the current position
|
||||
*/
|
||||
void start();
|
||||
/**
|
||||
* stop to retreive the current position
|
||||
*/
|
||||
void stop();
|
||||
/**
|
||||
* send a stop signal to other peers to tell them
|
||||
* you stoped sharing yout position
|
||||
*/
|
||||
void sendStopSharingMsg();
|
||||
QString convertToJson(const QGeoPositionInfo& info);
|
||||
|
||||
void setUri(QString uri);
|
||||
|
||||
private Q_SLOTS:
|
||||
void slotError(QGeoPositionInfoSource::Error error);
|
||||
void positionUpdated(const QGeoPositionInfo& info);
|
||||
/**
|
||||
* Force to send position at regular intervals
|
||||
*/
|
||||
void requestPosition();
|
||||
/**
|
||||
* Triggered when location services are activated
|
||||
*/
|
||||
void locationServicesActivated();
|
||||
|
||||
Q_SIGNALS:
|
||||
void newPosition(const QString& peerId,
|
||||
const QString& body,
|
||||
const uint64_t& timestamp,
|
||||
const QString& daemonId);
|
||||
void positioningError(const QString error);
|
||||
|
||||
private:
|
||||
QString uri_;
|
||||
QGeoPositionInfoSource* source_ = nullptr;
|
||||
bool isPositioning = false;
|
||||
};
|
252
src/app/positionmanager.cpp
Normal file
252
src/app/positionmanager.cpp
Normal file
|
@ -0,0 +1,252 @@
|
|||
#include "positionmanager.h"
|
||||
|
||||
#include "qtutils.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBuffer>
|
||||
#include <QList>
|
||||
#include <QTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QImageReader>
|
||||
|
||||
PositionManager::PositionManager(LRCInstance* instance, QObject* parent)
|
||||
: QmlAdapterBase(instance, parent)
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::safeInit()
|
||||
{
|
||||
connect(lrcInstance_, &LRCInstance::currentAccountIdChanged, [this]() {
|
||||
connectConversationModel();
|
||||
localPositioning_->setUri(lrcInstance_->getCurrentAccountInfo().profileInfo.uri);
|
||||
});
|
||||
localPositioning_.reset(new Positioning(lrcInstance_->getCurrentAccountInfo().profileInfo.uri));
|
||||
connectConversationModel();
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::connectConversationModel()
|
||||
{
|
||||
auto currentConversationModel = lrcInstance_->getCurrentConversationModel();
|
||||
|
||||
QObject::connect(currentConversationModel,
|
||||
&ConversationModel::newPosition,
|
||||
this,
|
||||
&PositionManager::onPositionReceived,
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::startPositioning()
|
||||
{
|
||||
sharingUris_.clear();
|
||||
|
||||
localPositioning_->start();
|
||||
connect(localPositioning_.get(),
|
||||
&Positioning::newPosition,
|
||||
this,
|
||||
&PositionManager::onPositionReceived,
|
||||
Qt::UniqueConnection);
|
||||
connect(localPositioning_.get(),
|
||||
&Positioning::positioningError,
|
||||
this,
|
||||
&PositionManager::onPositionErrorReceived,
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::stopPositioning()
|
||||
{
|
||||
localPositioning_->stop();
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::onOwnPositionReceived(const QString& peerId, const QString& body)
|
||||
{
|
||||
try {
|
||||
Q_FOREACH (const auto& id, positionShareConvIds_) {
|
||||
const auto& convInfo = lrcInstance_->getConversationFromConvUid(id);
|
||||
Q_FOREACH (const QString& uri, convInfo.participantsUris()) {
|
||||
if (peerId != uri) {
|
||||
lrcInstance_->getCurrentAccountInfo()
|
||||
.contactModel->sendDhtMessage(uri, body, APPLICATION_GEO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
qDebug() << Q_FUNC_INFO << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::sharePosition(int maximumTime)
|
||||
{
|
||||
connect(localPositioning_.get(),
|
||||
&Positioning::newPosition,
|
||||
this,
|
||||
&PositionManager::onOwnPositionReceived,
|
||||
Qt::UniqueConnection);
|
||||
|
||||
try {
|
||||
startPositionTimers(maximumTime);
|
||||
const auto convUid = lrcInstance_->get_selectedConvUid();
|
||||
positionShareConvIds_.append(convUid);
|
||||
Q_EMIT positionShareConvIdsChanged();
|
||||
} catch (...) {
|
||||
qDebug() << "Exception during sharePosition:";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::stopSharingPosition()
|
||||
{
|
||||
localPositioning_->sendStopSharingMsg();
|
||||
stopPositionTimers();
|
||||
set_positionShareConvIds({});
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::setMapActive(bool state)
|
||||
{
|
||||
set_isMapActive(state);
|
||||
Q_EMIT isMapActiveChanged();
|
||||
}
|
||||
|
||||
QString
|
||||
PositionManager::getAvatar(const QString& uri)
|
||||
{
|
||||
QString avatarBase64;
|
||||
QByteArray ba;
|
||||
QBuffer bu(&ba);
|
||||
|
||||
auto& accInfo = lrcInstance_->getCurrentAccountInfo();
|
||||
auto currentAccountUri = accInfo.profileInfo.uri;
|
||||
if (currentAccountUri == uri) {
|
||||
// use accountPhoto
|
||||
Utils::accountPhoto(lrcInstance_, accInfo.id).save(&bu, "PNG");
|
||||
} else {
|
||||
// use contactPhoto
|
||||
Utils::contactPhoto(lrcInstance_, uri).save(&bu, "PNG");
|
||||
}
|
||||
return ba.toBase64();
|
||||
}
|
||||
|
||||
QVariantMap
|
||||
PositionManager::parseJsonPosition(const QString& body, const QString& peerId)
|
||||
{
|
||||
QJsonDocument temp = QJsonDocument::fromJson(body.toUtf8());
|
||||
QJsonObject jsonObject = temp.object();
|
||||
QVariantMap pos;
|
||||
|
||||
for (auto i = jsonObject.begin(); i != jsonObject.end(); i++) {
|
||||
if (i.key() == "long")
|
||||
pos["long"] = i.value().toVariant();
|
||||
if (i.key() == "lat")
|
||||
pos["lat"] = i.value().toVariant();
|
||||
if (i.key() == "type")
|
||||
pos["type"] = i.value().toVariant();
|
||||
if (i.key() == "time")
|
||||
pos["time"] = i.value().toVariant();
|
||||
|
||||
pos["author"] = peerId;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::startPositionTimers(int timeSharing)
|
||||
{
|
||||
set_timeSharingRemaining(timeSharing);
|
||||
timerTimeLeftSharing_->start(1000);
|
||||
timerStopSharing_->start(timeSharing);
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::stopPositionTimers()
|
||||
{
|
||||
set_timeSharingRemaining(0);
|
||||
timerTimeLeftSharing_->stop();
|
||||
timerStopSharing_->stop();
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::onPositionErrorReceived(const QString error)
|
||||
{
|
||||
Q_EMIT positioningError(error);
|
||||
}
|
||||
|
||||
void
|
||||
PositionManager::onPositionReceived(const QString& peerId,
|
||||
const QString& body,
|
||||
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();
|
||||
bool isPeerIdInConv = (std::find(convParticipants.begin(), convParticipants.end(), peerId)
|
||||
!= convParticipants.end());
|
||||
if (!isPeerIdInConv)
|
||||
return;
|
||||
|
||||
QVariantMap newPosition = parseJsonPosition(body, peerId);
|
||||
auto getShareInfo = [&](bool update) -> QVariantMap {
|
||||
QVariantMap shareInfo;
|
||||
shareInfo["author"] = peerId;
|
||||
if (!update) {
|
||||
shareInfo["avatar"] = getAvatar(peerId);
|
||||
}
|
||||
shareInfo["long"] = newPosition["long"];
|
||||
shareInfo["lat"] = newPosition["lat"];
|
||||
return shareInfo;
|
||||
};
|
||||
auto endSharing = newPosition["type"] == "Stop";
|
||||
|
||||
if (!endSharing) {
|
||||
// open map on position reception
|
||||
if (!isMapActive_ && mapAutoOpening_
|
||||
&& peerId != lrcInstance_->getCurrentAccountInfo().profileInfo.uri) {
|
||||
set_isMapActive(true);
|
||||
}
|
||||
}
|
||||
auto iter = std::find(sharingUris_.begin(), sharingUris_.end(), peerId);
|
||||
if (iter == sharingUris_.end()) {
|
||||
// New share
|
||||
if (!endSharing) {
|
||||
sharingUris_.insert(peerId);
|
||||
Q_EMIT positionShareAdded(getShareInfo(false));
|
||||
}
|
||||
|
||||
} else {
|
||||
// Update/remove existing
|
||||
if (endSharing) {
|
||||
// Remove (avoid self)
|
||||
if (peerId != lrcInstance_->getCurrentAccountInfo().profileInfo.uri) {
|
||||
sharingUris_.remove(peerId);
|
||||
Q_EMIT positionShareRemoved(peerId);
|
||||
// close the map if you're not sharing and the only remaining position is yours
|
||||
if (!positionShareConvIds_.length() && sharingUris_.size() == 1
|
||||
&& sharingUris_.contains(
|
||||
lrcInstance_->getCurrentAccountInfo().profileInfo.uri)) {
|
||||
set_isMapActive(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Update
|
||||
Q_EMIT positionShareUpdated(getShareInfo(true));
|
||||
}
|
||||
}
|
||||
}
|
74
src/app/positionmanager.h
Normal file
74
src/app/positionmanager.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lrcinstance.h"
|
||||
#include "qmladapterbase.h"
|
||||
#include "positioning.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class PositionManager : public QmlAdapterBase
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_RO_PROPERTY(bool, isMapActive)
|
||||
QML_RO_PROPERTY(int, timeSharingRemaining)
|
||||
QML_PROPERTY(QList<QString>, positionShareConvIds)
|
||||
QML_PROPERTY(bool, mapAutoOpening)
|
||||
public:
|
||||
explicit PositionManager(LRCInstance* instance, QObject* parent = nullptr);
|
||||
~PositionManager() = default;
|
||||
|
||||
Q_SIGNALS:
|
||||
void positioningError(const QString error);
|
||||
void positionShareAdded(const QVariantMap& shareInfo);
|
||||
void positionShareUpdated(const QVariantMap& posInfo);
|
||||
void positionShareRemoved(const QString& uri);
|
||||
|
||||
protected:
|
||||
void safeInit() override;
|
||||
|
||||
QString getAvatar(const QString& peerId);
|
||||
QVariantMap parseJsonPosition(const QString& body, const QString& peerId);
|
||||
void positionWatchDog();
|
||||
void startPositionTimers(int timeSharing);
|
||||
void stopPositionTimers();
|
||||
|
||||
Q_INVOKABLE void connectConversationModel();
|
||||
Q_INVOKABLE void setMapActive(bool state);
|
||||
Q_INVOKABLE void sharePosition(int maximumTime);
|
||||
Q_INVOKABLE void startPositioning();
|
||||
Q_INVOKABLE void stopPositioning();
|
||||
Q_INVOKABLE void stopSharingPosition();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onPositionErrorReceived(const QString error);
|
||||
void onPositionReceived(const QString& peerId,
|
||||
const QString& body,
|
||||
const uint64_t& timestamp,
|
||||
const QString& daemonId);
|
||||
void onOwnPositionReceived(const QString& peerId, const QString& body);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Positioning> localPositioning_;
|
||||
QTimer* timerTimeLeftSharing_ = nullptr;
|
||||
QTimer* timerStopSharing_ = nullptr;
|
||||
QSet<QString> sharingUris_;
|
||||
};
|
|
@ -24,6 +24,7 @@
|
|||
#include "contactadapter.h"
|
||||
#include "pluginadapter.h"
|
||||
#include "messagesadapter.h"
|
||||
#include "positionmanager.h"
|
||||
#include "tipsmodel.h"
|
||||
#include "previewengine.h"
|
||||
#include "utilsadapter.h"
|
||||
|
@ -110,6 +111,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(lrcInstance, parent);
|
||||
auto conversationsAdapter = new ConversationsAdapter(systemTray, lrcInstance, parent);
|
||||
auto avAdapter = new AvAdapter(lrcInstance, parent);
|
||||
auto contactAdapter = new ContactAdapter(lrcInstance, parent);
|
||||
|
@ -126,6 +128,7 @@ registerTypes(QQmlEngine* engine,
|
|||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, callAdapter, "CallAdapter");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, tipsModel, "TipsModel");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, messagesAdapter, "MessagesAdapter");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, positionManager, "PositionManager");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, conversationsAdapter, "ConversationsAdapter");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, avAdapter, "AvAdapter");
|
||||
QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, contactAdapter, "ContactAdapter");
|
||||
|
|
|
@ -817,6 +817,9 @@ QByteArray
|
|||
Utils::QByteArrayFromFile(const QString& filename)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.exists()) {
|
||||
qDebug() << "QByteArrayFromFile: file does not exist" << filename;
|
||||
}
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
return file.readAll();
|
||||
} else {
|
||||
|
|
351
src/app/webengine/map/MapPosition.qml
Normal file
351
src/app/webengine/map/MapPosition.qml
Normal file
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* 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.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtWebEngine
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
Rectangle {
|
||||
id: mapPopup
|
||||
|
||||
x: xPos
|
||||
y: yPos
|
||||
width: isFullScreen ? root.width : windowSize
|
||||
height: isMinimised
|
||||
? buttonOverlay.height + buttonsChoseSharing.height + 30
|
||||
: isFullScreen ? root.height - yPos : windowSize
|
||||
|
||||
property bool isFullScreen: false
|
||||
property bool isMinimised: false
|
||||
property real windowSize: windowPreferedSize > JamiTheme.minimumMapWidth
|
||||
? windowPreferedSize
|
||||
: JamiTheme.minimumMapWidth
|
||||
property real windowPreferedSize: root.width > root.height
|
||||
? root.height / 3
|
||||
: root.width / 3
|
||||
property real xPos: 0
|
||||
property real yPos: JamiTheme.chatViewHeaderPreferredHeight
|
||||
|
||||
WebEngineView {
|
||||
id: webView
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
property string mapHtml: ":/webengine/map/map.html"
|
||||
property string olCss: ":/webengine/map/ol.css"
|
||||
property string mapJs: "../../webengine/map/map.js"
|
||||
property string olJs: "../../webengine/map/ol.js"
|
||||
property bool isLoaded: false
|
||||
property var positionList: PositionManager.positionList;
|
||||
property var avatarPositionList: PositionManager.avatarPositionList;
|
||||
property bool isSharing: (PositionManager.positionShareConvIds.length !== 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.onDestruction: {
|
||||
PositionManager.stopSharingPosition();
|
||||
PositionManager.stopPositioning();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadHtml(UtilsAdapter.qStringFromFile(mapHtml), mapHtml)
|
||||
loadScripts()
|
||||
}
|
||||
|
||||
onLoadingChanged: function (loadingInfo) {
|
||||
if (loadingInfo.status === WebEngineView.LoadSucceededStatus) {
|
||||
runJavaScript(UtilsAdapter.getStyleSheet("olcss",UtilsAdapter.qStringFromFile(olCss)))
|
||||
webView.isLoaded = true
|
||||
runJavaScript("setMapView([" + 0 + ","+ 0 + "], " + 1 + " );" );
|
||||
PositionManager.startPositioning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: buttonsChoseSharing
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
id: sharePositionButton
|
||||
|
||||
preferredWidth: text.contentWidth
|
||||
textLeftPadding: JamiTheme.buttontextPadding
|
||||
textRightPadding: JamiTheme.buttontextPadding
|
||||
primary: true
|
||||
text: webView.isSharing ? JamiStrings.stopSharingLocation : JamiStrings.shareLocation
|
||||
color: isError
|
||||
? JamiTheme.buttonTintedGreyInactive
|
||||
: webView.isSharing ? JamiTheme.buttonTintedRed : JamiTheme.buttonTintedBlue
|
||||
hoveredColor: isError
|
||||
? JamiTheme.buttonTintedGreyInactive
|
||||
: webView.isSharing ? JamiTheme.buttonTintedRedHovered : JamiTheme.buttonTintedBlueHovered
|
||||
pressedColor: isError
|
||||
? JamiTheme.buttonTintedGreyInactive
|
||||
: webView.isSharing ? JamiTheme.buttonTintedRedPressed: 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 (webView.isSharing) {
|
||||
PositionManager.stopSharingPosition();
|
||||
} else {
|
||||
if (buttonsChoseSharing.shortSharing)
|
||||
PositionManager.sharePosition(10 * 60 * 1000);
|
||||
else
|
||||
PositionManager.sharePosition(60 * 60 * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
imageColor: JamiTheme.mapButtonColor
|
||||
normalColor: JamiTheme.transparentColor
|
||||
source: JamiResources.share_location_svg
|
||||
onClicked: {
|
||||
webView.runJavaScript("zoomTolayersExtent()" );
|
||||
}
|
||||
}
|
||||
|
||||
PushButton {
|
||||
id: btnMove
|
||||
|
||||
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: btnminimise
|
||||
|
||||
imageColor: JamiTheme.mapButtonColor
|
||||
normalColor: JamiTheme.transparentColor
|
||||
source: isMinimised
|
||||
? JamiResources.close_fullscreen_24dp_svg
|
||||
: JamiResources.minimize_svg
|
||||
onClicked: {
|
||||
isMinimised = !isMinimised
|
||||
isFullScreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
PushButton {
|
||||
id: btnmaximise
|
||||
|
||||
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
|
||||
|
||||
imageColor: JamiTheme.mapButtonColor
|
||||
normalColor: JamiTheme.transparentColor
|
||||
source: JamiResources.round_close_24dp_svg
|
||||
|
||||
onClicked: {
|
||||
PositionManager.setMapActive(false);
|
||||
PositionManager.mapAutoOpening = false;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
src/app/webengine/map/map.css
Normal file
30
src/app/webengine/map/map.css
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#map {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
37
src/app/webengine/map/map.html
Normal file
37
src/app/webengine/map/map.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
<!--
|
||||
Copyright (C) 2022 Savoir-faire Linux Inc.
|
||||
|
||||
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/>.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%
|
||||
}
|
||||
#map {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
</body>
|
||||
</html>
|
134
src/app/webengine/map/map.js
Normal file
134
src/app/webengine/map/map.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
const {Map,View} = ol
|
||||
const TileLayer = ol.layer.Tile
|
||||
const ImageLayer = ol.layer.Image
|
||||
const {OSM,ImageStatic} = ol.source
|
||||
|
||||
var basemap = new TileLayer({ source: new OSM() })
|
||||
basemap.layer_type = "map"
|
||||
|
||||
var dict = []
|
||||
|
||||
const map = new Map({
|
||||
target: 'map',
|
||||
layers: [basemap],
|
||||
view: new View({
|
||||
center: ol.proj.fromLonLat([2.1734, 41.3851]),
|
||||
zoom: 2
|
||||
})
|
||||
})
|
||||
|
||||
function setMapView(coordos, zoom) {
|
||||
map.getView().setCenter(ol.proj.fromLonLat(coordos))
|
||||
map.getView().setZoom(zoom)
|
||||
}
|
||||
|
||||
function dynamicZoom(longMin, latMin, longMax, latMax) {
|
||||
var coordMin = ol.proj.fromLonLat([longMin,latMin])
|
||||
var coordMax = ol.proj.fromLonLat([longMax,latMax])
|
||||
var extent = [coordMin[0],coordMin[1],coordMax[0],coordMax[1]]
|
||||
map.getView().fit(extent, {size: map.getSize(), maxZoom: 16, duration:500,
|
||||
padding: [80 ,80 ,80 ,80]})
|
||||
}
|
||||
|
||||
var extent = [0,0,50,50]
|
||||
var projection = new ol.proj.Projection({
|
||||
code: 'local_image',
|
||||
units: 'pixels',
|
||||
extent: extent
|
||||
})
|
||||
|
||||
var proj = new ol.proj.Projection({
|
||||
code: 'static-image',
|
||||
units: 'pixels',
|
||||
extent: extent
|
||||
})
|
||||
|
||||
function setSource (coordos, authorI,imageI) {
|
||||
var coord = ol.proj.fromLonLat(coordos)
|
||||
var pointFeature = new ol.Feature({
|
||||
geometry: new ol.geom.Point(coord),
|
||||
weight: 20
|
||||
})
|
||||
|
||||
var preStyle = new ol.style.Icon({
|
||||
src: "data:image/png;base64," + imageI})
|
||||
|
||||
//resize the image to 40 px
|
||||
var image = preStyle.getImage()
|
||||
if (!image.width) {
|
||||
image.addEventListener('load', function () {
|
||||
preStyle.setScale([40 / image.width, 40 / image.height])
|
||||
})
|
||||
} else {
|
||||
preStyle.setScale([40 / image.width, 40 / image.height])
|
||||
}
|
||||
|
||||
var iconStyle = new ol.style.Style({
|
||||
image: preStyle
|
||||
})
|
||||
|
||||
pointFeature.setStyle(iconStyle)
|
||||
var vectorSource = new ol.source.Vector({
|
||||
features: [pointFeature],
|
||||
})
|
||||
|
||||
return vectorSource
|
||||
}
|
||||
|
||||
function newPosition (coordos, authorI, image) {
|
||||
vectorSource = setSource(coordos, authorI, image)
|
||||
var iconLayer = new ol.layer.Vector({source: vectorSource})
|
||||
iconLayer.layer_type = authorI
|
||||
map.addLayer(iconLayer)
|
||||
}
|
||||
|
||||
function updatePosition (coordos, authorI) {
|
||||
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) {
|
||||
layerArray[i].getSource().getFeatures()[0].getGeometry().setCoordinates(coord)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function zoomTolayersExtent() {
|
||||
var ext = ol.extent.createEmpty();
|
||||
var layerArray = map.getLayers().getArray();
|
||||
for (var i = 0; i < layerArray.length; i++ ){
|
||||
if(layerArray[i].layer_type !== "map") {
|
||||
ext = ol.extent.extend(ext, layerArray[i].getSource().getExtent());
|
||||
}
|
||||
}
|
||||
map.getView().fit(ext, {size: map.getSize(), maxZoom: 16, duration:500,
|
||||
padding: [80 ,80 ,80 ,80]})
|
||||
}
|
||||
|
||||
function removePosition (authorI) {
|
||||
var layerArray = map.getLayers().getArray();
|
||||
for (var i = 0; i < layerArray.length; i++ ){
|
||||
if(layerArray[i].layer_type === authorI) {
|
||||
map.removeLayer(layerArray[i])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
371
src/app/webengine/map/ol.css
Normal file
371
src/app/webengine/map/ol.css
Normal file
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* BSD 2-Clause License
|
||||
*
|
||||
* Copyright 2005-present, OpenLayers Contributors All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
:root,
|
||||
:host {
|
||||
--ol-background-color: white;
|
||||
--ol-accent-background-color: #F5F5F5;
|
||||
--ol-subtle-background-color: rgba(128, 128, 128, 0.25);
|
||||
--ol-partial-background-color: rgba(255, 255, 255, 0.75);
|
||||
--ol-foreground-color: #333333;
|
||||
--ol-subtle-foreground-color: #666666;
|
||||
--ol-brand-color: #00AAFF;
|
||||
}
|
||||
|
||||
.ol-box {
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
border: 1.5px solid var(--ol-background-color);
|
||||
background-color: var(--ol-partial-background-color);
|
||||
}
|
||||
|
||||
.ol-mouse-position {
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ol-scale-line {
|
||||
background: var(--ol-partial-background-color);
|
||||
border-radius: 4px;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ol-scale-line-inner {
|
||||
border: 1px solid var(--ol-subtle-foreground-color);
|
||||
border-top: none;
|
||||
color: var(--ol-foreground-color);
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
margin: 1px;
|
||||
will-change: contents, width;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.ol-scale-bar {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.ol-scale-bar-inner {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ol-scale-step-marker {
|
||||
width: 1px;
|
||||
height: 15px;
|
||||
background-color: var(--ol-foreground-color);
|
||||
float: right;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.ol-scale-step-text {
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
font-size: 10px;
|
||||
z-index: 11;
|
||||
color: var(--ol-foreground-color);
|
||||
text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);
|
||||
}
|
||||
|
||||
.ol-scale-text {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
bottom: 25px;
|
||||
color: var(--ol-foreground-color);
|
||||
text-shadow: -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color);
|
||||
}
|
||||
|
||||
.ol-scale-singlebar {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
z-index: 9;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--ol-foreground-color);
|
||||
}
|
||||
|
||||
.ol-scale-singlebar-even {
|
||||
background-color: var(--ol-subtle-foreground-color);
|
||||
}
|
||||
|
||||
.ol-scale-singlebar-odd {
|
||||
background-color: var(--ol-background-color);
|
||||
}
|
||||
|
||||
.ol-unsupported {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ol-viewport,
|
||||
.ol-unselectable {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.ol-viewport canvas {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
.ol-selectable {
|
||||
-webkit-touch-callout: default;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.ol-grabbing {
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.ol-grab {
|
||||
cursor: move;
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.ol-control {
|
||||
position: absolute;
|
||||
background-color: var(--ol-subtle-background-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ol-zoom {
|
||||
top: .5em;
|
||||
left: .5em;
|
||||
}
|
||||
|
||||
.ol-rotate {
|
||||
top: .5em;
|
||||
right: .5em;
|
||||
transition: opacity .25s linear, visibility 0s linear;
|
||||
}
|
||||
|
||||
.ol-rotate.ol-hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity .25s linear, visibility 0s linear .25s;
|
||||
}
|
||||
|
||||
.ol-zoom-extent {
|
||||
top: 4.643em;
|
||||
left: .5em;
|
||||
}
|
||||
|
||||
.ol-full-screen {
|
||||
right: .5em;
|
||||
top: .5em;
|
||||
}
|
||||
|
||||
.ol-control button {
|
||||
display: block;
|
||||
margin: 1px;
|
||||
padding: 0;
|
||||
color: var(--ol-subtle-foreground-color);
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
text-align: center;
|
||||
height: 1.375em;
|
||||
width: 1.375em;
|
||||
line-height: .4em;
|
||||
background-color: var(--ol-background-color);
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.ol-control button::-moz-focus-inner {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ol-zoom-extent button {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.ol-compass {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.ol-touch .ol-control button {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.ol-touch .ol-zoom-extent {
|
||||
top: 5.5em;
|
||||
}
|
||||
|
||||
.ol-control button:hover,
|
||||
.ol-control button:focus {
|
||||
text-decoration: none;
|
||||
outline: 1px solid var(--ol-subtle-foreground-color);
|
||||
color: var(--ol-foreground-color);
|
||||
}
|
||||
|
||||
.ol-zoom .ol-zoom-in {
|
||||
border-radius: 2px 2px 0 0;
|
||||
}
|
||||
|
||||
.ol-zoom .ol-zoom-out {
|
||||
border-radius: 0 0 2px 2px;
|
||||
}
|
||||
|
||||
.ol-attribution {
|
||||
text-align: right;
|
||||
bottom: .5em;
|
||||
right: .5em;
|
||||
max-width: calc(100% - 1.3em);
|
||||
display: flex;
|
||||
flex-flow: row-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ol-attribution a {
|
||||
color: var(--ol-subtle-foreground-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ol-attribution ul {
|
||||
margin: 0;
|
||||
padding: 1px .5em;
|
||||
color: var(--ol-foreground-color);
|
||||
text-shadow: 0 0 2px var(--ol-background-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ol-attribution li {
|
||||
display: inline;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.ol-attribution li:not(:last-child):after {
|
||||
content: " ";
|
||||
}
|
||||
|
||||
.ol-attribution img {
|
||||
max-height: 2em;
|
||||
max-width: inherit;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ol-attribution button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ol-attribution.ol-collapsed ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ol-attribution:not(.ol-collapsed) {
|
||||
background: var(--ol-partial-background-color);
|
||||
}
|
||||
|
||||
.ol-attribution.ol-uncollapsible {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: 4px 0 0;
|
||||
}
|
||||
|
||||
.ol-attribution.ol-uncollapsible img {
|
||||
margin-top: -.2em;
|
||||
max-height: 1.6em;
|
||||
}
|
||||
|
||||
.ol-attribution.ol-uncollapsible button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ol-zoomslider {
|
||||
top: 4.5em;
|
||||
left: .5em;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.ol-zoomslider button {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.ol-touch .ol-zoomslider {
|
||||
top: 5.5em;
|
||||
}
|
||||
|
||||
.ol-overviewmap {
|
||||
left: 0.5em;
|
||||
bottom: 0.5em;
|
||||
}
|
||||
|
||||
.ol-overviewmap.ol-uncollapsible {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-radius: 0 4px 0 0;
|
||||
}
|
||||
|
||||
.ol-overviewmap .ol-overviewmap-map,
|
||||
.ol-overviewmap button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ol-overviewmap .ol-overviewmap-map {
|
||||
border: 1px solid var(--ol-subtle-foreground-color);
|
||||
height: 150px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.ol-overviewmap:not(.ol-collapsed) button {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ol-overviewmap.ol-collapsed .ol-overviewmap-map,
|
||||
.ol-overviewmap.ol-uncollapsible button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ol-overviewmap:not(.ol-collapsed) {
|
||||
background: var(--ol-subtle-background-color);
|
||||
}
|
||||
|
||||
.ol-overviewmap-box {
|
||||
border: 1.5px dotted var(--ol-subtle-foreground-color);
|
||||
}
|
||||
|
||||
.ol-overviewmap .ol-overviewmap-box:hover {
|
||||
cursor: move;
|
||||
}
|
30
src/app/webengine/map/ol.js
Normal file
30
src/app/webengine/map/ol.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -185,6 +185,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
|||
# Here we let find_package(<PackageName>...) try to find Qt 6.
|
||||
# If it is found, find_package will succeed, and the CMake variable
|
||||
# QT_VERSION_MAJOR will be set to 6.
|
||||
|
||||
if(QT6_VER AND QT6_PATH)
|
||||
find_package(QT NAMES Qt6 REQUIRED
|
||||
PATHS ${QT6_PATH} NO_DEFAULT_PATH)
|
||||
|
|
|
@ -108,7 +108,9 @@ public:
|
|||
* @param body
|
||||
* @return id from daemon
|
||||
*/
|
||||
uint64_t sendDhtMessage(const QString& uri, const QString& body) const;
|
||||
uint64_t sendDhtMessage(const QString& uri,
|
||||
const QString& body,
|
||||
const QString& mimeType = {}) const;
|
||||
/**
|
||||
* Get best id for contact
|
||||
* @param contactUri
|
||||
|
|
|
@ -439,6 +439,15 @@ public:
|
|||
member::Role memberRole(const QString& conversationId, const QString& memberUri) const;
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
/**
|
||||
* Emitted when a conversation receives a new position
|
||||
*/
|
||||
void newPosition(const QString& peerId,
|
||||
const QString& body,
|
||||
const uint64_t& timestamp,
|
||||
const QString& daemonId) const;
|
||||
|
||||
/**
|
||||
* Emitted when a conversation receives a new interaction
|
||||
* @param uid of conversation
|
||||
|
|
|
@ -65,7 +65,7 @@ to_type(const QString& type)
|
|||
{
|
||||
if (type == "INITIAL" || type == "initial")
|
||||
return interaction::Type::INITIAL;
|
||||
else if (type == "TEXT" || type == "text/plain")
|
||||
else if (type == "TEXT" || type == TEXT_PLAIN)
|
||||
return interaction::Type::TEXT;
|
||||
else if (type == "CALL" || type == "application/call-history+json")
|
||||
return interaction::Type::CALL;
|
||||
|
|
|
@ -569,8 +569,7 @@ CallbacksHandler::slotIncomingMessage(const QString& accountId,
|
|||
match.captured(2).toInt(),
|
||||
match.captured(3).toInt(),
|
||||
e.second);
|
||||
} else if (e.first.contains(
|
||||
"text/plain")) { // we consider it as an usual message interaction
|
||||
} else if (e.first.contains(TEXT_PLAIN)) { // we consider it as an usual message interaction
|
||||
Q_EMIT incomingCallMessage(accountId, callId, from2, e.second);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1279,7 +1279,7 @@ void
|
|||
CallModel::sendSipMessage(const QString& callId, const QString& body) const
|
||||
{
|
||||
MapStringString payloads;
|
||||
payloads["text/plain"] = body;
|
||||
payloads[TEXT_PLAIN] = body;
|
||||
|
||||
CallManager::instance().sendTextMessage(owner.id, callId, payloads, true /* not used */);
|
||||
}
|
||||
|
|
|
@ -517,11 +517,16 @@ ContactModelPimpl::searchSipContact(const URI& query)
|
|||
}
|
||||
|
||||
uint64_t
|
||||
ContactModel::sendDhtMessage(const QString& contactUri, const QString& body) const
|
||||
ContactModel::sendDhtMessage(const QString& contactUri,
|
||||
const QString& body,
|
||||
const QString& mimeType) const
|
||||
{
|
||||
// Send interaction
|
||||
QMap<QString, QString> payloads;
|
||||
payloads["text/plain"] = body;
|
||||
if (mimeType.isEmpty())
|
||||
payloads[TEXT_PLAIN] = body;
|
||||
else
|
||||
payloads[mimeType] = body;
|
||||
auto msgId = ConfigurationManager::instance().sendTextMessage(QString(owner.id),
|
||||
QString(contactUri),
|
||||
payloads);
|
||||
|
|
|
@ -2213,7 +2213,7 @@ ConversationModelPimpl::initConversations()
|
|||
timestamp = static_cast<uint64_t>(message.received);
|
||||
} catch (...) {
|
||||
}
|
||||
addIncomingMessage(message.from, message.payloads["text/plain"], timestamp);
|
||||
addIncomingMessage(message.from, message.payloads[TEXT_PLAIN], timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3563,8 +3563,12 @@ ConversationModelPimpl::slotNewAccountMessage(const QString& accountId,
|
|||
return;
|
||||
|
||||
for (const auto& payload : payloads.keys()) {
|
||||
if (payload.contains("text/plain")) {
|
||||
if (payload.contains(TEXT_PLAIN)) {
|
||||
addIncomingMessage(peerId, payloads.value(payload), 0, msgId);
|
||||
} else if (payload.contains(APPLICATION_GEO)) {
|
||||
Q_EMIT linked.newPosition(peerId, payloads.value(payload), 0, msgId);
|
||||
} else {
|
||||
qWarning() << payload;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
// Typedefs (required to avoid '<' and '>' in the DBus XML)
|
||||
typedef QMap<QString, QString> MapStringString;
|
||||
typedef QMap<QString, int> MapStringInt;
|
||||
typedef QMap<QString, double> MapStringDouble;
|
||||
typedef QVector<int> VectorInt;
|
||||
typedef QVector<uint> VectorUInt;
|
||||
typedef QVector<qulonglong> VectorULongLong;
|
||||
|
@ -42,6 +43,8 @@ typedef QVector<QByteArray> VectorVectorByte;
|
|||
typedef uint64_t DataTransferId;
|
||||
|
||||
constexpr static const char* TRUE_STR = "true";
|
||||
constexpr static const char* TEXT_PLAIN = "text/plain";
|
||||
constexpr static const char* APPLICATION_GEO = "application/geo";
|
||||
constexpr static const char* FALSE_STR = "false";
|
||||
|
||||
// Adapted from libring libjami::DataTransferInfo
|
||||
|
|
Loading…
Add table
Reference in a new issue