From cc694226b1618e27bf76162df0357ec743268cb9 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 11 Jun 2026 00:20:57 +0200 Subject: [PATCH 1/6] Forbid duplicate network provider --- app/position/providers/positionprovidersmodel.cpp | 8 ++++++++ app/position/providers/positionprovidersmodel.h | 1 + app/qml/gps/MMNetworkProviderDrawer.qml | 9 +++++---- app/qml/gps/MMPositionProviderPage.qml | 7 ++++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/position/providers/positionprovidersmodel.cpp b/app/position/providers/positionprovidersmodel.cpp index 5c7365e7a..4e7f81993 100644 --- a/app/position/providers/positionprovidersmodel.cpp +++ b/app/position/providers/positionprovidersmodel.cpp @@ -164,6 +164,14 @@ void PositionProvidersModel::addProvider( const QString &name, const QString &pr } } +bool PositionProvidersModel::providerExists( const QString &providerId ) +{ + return std::any_of( mProviders.begin(), mProviders.end(), [providerId]( const PositionProvider & provider ) + { + return provider.providerId == providerId; + } ); +} + AppSettings *PositionProvidersModel::appSettings() const { return mAppSettings; diff --git a/app/position/providers/positionprovidersmodel.h b/app/position/providers/positionprovidersmodel.h index 0f86831fc..866e1fddb 100644 --- a/app/position/providers/positionprovidersmodel.h +++ b/app/position/providers/positionprovidersmodel.h @@ -69,6 +69,7 @@ class PositionProvidersModel : public QAbstractListModel Q_INVOKABLE void removeProvider( const QString &providerId ); Q_INVOKABLE void addProvider( const QString &providerName, const QString &providerId, const QString &providerType ); + Q_INVOKABLE bool providerExists( const QString &providerId ); AppSettings *appSettings() const; void setAppSettings( AppSettings * ); diff --git a/app/qml/gps/MMNetworkProviderDrawer.qml b/app/qml/gps/MMNetworkProviderDrawer.qml index 670e4e811..55a448a9f 100644 --- a/app/qml/gps/MMNetworkProviderDrawer.qml +++ b/app/qml/gps/MMNetworkProviderDrawer.qml @@ -110,14 +110,15 @@ MMComponents.MMDrawer { const deviceAddress = ip + ":" + port root.confirmed( aliasInput.text.trim(), deviceAddress ) - root.close() - ipAddressInput.textField.clear() - portInput.textField.clear() - aliasInput.textField.clear() } } } + function showDuplicateProviderError() { + ipAddressInput.errorMsg = qsTr( "Network position provider with this IP address & port already exists" ) + portInput.errorMsg = qsTr( "Network position provider with this IP address & port already exists" ) + } + onClosed: { ipAddressInput.textField.clear() portInput.textField.clear() diff --git a/app/qml/gps/MMPositionProviderPage.qml b/app/qml/gps/MMPositionProviderPage.qml index 4c9c59b38..39657d28a 100644 --- a/app/qml/gps/MMPositionProviderPage.qml +++ b/app/qml/gps/MMPositionProviderPage.qml @@ -154,7 +154,12 @@ MMComponents.MMPage { id: networkProviderDrawer onConfirmed: function( alias, deviceAddress ) { - root.activateProvider( "external_ip", deviceAddress, alias ) + if ( providersModel.providerExists( deviceAddress ) ) { + showDuplicateProviderError() + } else { + close() + root.activateProvider( "external_ip", deviceAddress, alias ) + } } } From ec4cfdfba69e4b102c5e1c6a44bd7177d75d2597 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 11 Jun 2026 00:36:18 +0200 Subject: [PATCH 2/6] Fix default naming for position providers --- app/qml/gps/MMPositionProviderPage.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/qml/gps/MMPositionProviderPage.qml b/app/qml/gps/MMPositionProviderPage.qml index 39657d28a..ecaab89ce 100644 --- a/app/qml/gps/MMPositionProviderPage.qml +++ b/app/qml/gps/MMPositionProviderPage.qml @@ -69,6 +69,7 @@ MMComponents.MMPage { if ( listdelegate.providerName ) return listdelegate.providerName return qsTr( "Unknown device" ) } + secondaryText: { if ( listdelegate.isActive ) { if ( listdelegate.providerType === "external_ip" ) @@ -273,8 +274,8 @@ MMComponents.MMPage { return // do not construct the same provider again } - providersModel.addProvider( name, id, type ) PositionKit.positionProvider = PositionKit.constructProvider( type, id, name ) + providersModel.addProvider( PositionKit.positionProvider.name(), id, type ) if ( type === "external_bt" ) { connectingDialogLoader.open( "bluetooth" ) From 4882d393e81f6916974550578d24af8f2897440a Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 11 Jun 2026 09:41:52 +0200 Subject: [PATCH 3/6] Fix connecting status drawer visuals --- app/qml/gps/MMExternalProviderConnectionDrawer.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/qml/gps/MMExternalProviderConnectionDrawer.qml b/app/qml/gps/MMExternalProviderConnectionDrawer.qml index e4db97a9b..bdcdb3d6f 100644 --- a/app/qml/gps/MMExternalProviderConnectionDrawer.qml +++ b/app/qml/gps/MMExternalProviderConnectionDrawer.qml @@ -67,7 +67,7 @@ MMComponents.MMDrawer { : "" ) message.description: root.providerType === "bluetooth" ? qsTr( "We were not able to connect to the specified device. Please make sure your device is powered on and can be connected to." ) - : qsTr( "We were not able to connect to the specified IP address. Please try again later." ) + : qsTr( "We were not able to connect to the specified IP address." ) message.linkText: qsTr( "Learn more" ) } }, @@ -75,10 +75,10 @@ MMComponents.MMDrawer { name: "waitingToReconnect" when: root.positionProvider && root.positionProvider.state === PositionProvider.WaitingToReconnect PropertyChanges { - message.image: root.providerType === "bluetooth" ? __style.externalBluetoothGreenImage : __style.externalNetworkGreenImage + message.image: __style.externalGpsRedImage message.title: root.providerType === "bluetooth" ? qsTr( "We were not able to connect to the specified device. Please make sure your device is powered on and can be connected to." ) - : qsTr( "We were not able to connect to the specified IP address. Please try again later." ) + : qsTr( "We were not able to connect to the specified IP address." ) message.description: root.positionProvider.stateMessage + "

" + qsTr( "You can close this message, we will try to repeatedly connect to your device." ) message.linkText: qsTr( "Learn more" ) } From 110377fb681d5bba2af37e76bc8a7cf028646c8b Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 11 Jun 2026 10:18:57 +0200 Subject: [PATCH 4/6] Fix sections in position providers list --- .../providers/positionprovidersmodel.cpp | 16 +++++++++++++--- app/position/providers/positionprovidersmodel.h | 3 ++- app/qml/gps/MMPositionProviderPage.qml | 4 ++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/position/providers/positionprovidersmodel.cpp b/app/position/providers/positionprovidersmodel.cpp index 4e7f81993..32345d658 100644 --- a/app/position/providers/positionprovidersmodel.cpp +++ b/app/position/providers/positionprovidersmodel.cpp @@ -19,7 +19,7 @@ PositionProvidersModel::PositionProvidersModel( QObject *parent ) : QAbstractLis { if ( !InputUtils::isMobilePlatform() ) { - const PositionProvider simulated( "Simulated provider", "Simulated position around point", "internal", "simulated" ); + const PositionProvider simulated( tr( "Simulated provider" ), tr( "Simulated position around point" ), QStringLiteral( "internal" ), QStringLiteral( "simulated" ) ); mProviders.push_front( simulated ); } @@ -30,8 +30,8 @@ PositionProvidersModel::PositionProvidersModel( QObject *parent ) : QAbstractLis PositionProvider internal; internal.name = tr( "Internal" ); internal.description = tr( "GPS receiver of this device" ); - internal.providerType = "internal"; - internal.providerId = "devicegps"; + internal.providerType = QStringLiteral( "internal" ); + internal.providerId = QStringLiteral( "devicegps" ); mProviders.push_front( internal ); @@ -68,6 +68,7 @@ QHash PositionProvidersModel::roleNames() const roles.insert( DataRoles::ProviderName, QByteArray( "providerName" ) ); roles.insert( DataRoles::ProviderDescription, QByteArray( "providerDescription" ) ); roles.insert( DataRoles::ProviderType, QByteArray( "providerType" ) ); + roles.insert( DataRoles::ProviderGroup, QByteArray( "providerGroup" ) ); roles.insert( DataRoles::ProviderId, QByteArray( "providerId" ) ); return roles; } @@ -103,6 +104,15 @@ QVariant PositionProvidersModel::data( const QModelIndex &index, const int role case DataRoles::ProviderType: return provider.providerType; + case DataRoles::ProviderGroup: + { + if ( provider.providerType == QStringLiteral( "internal" ) ) + { + return QStringLiteral( "internal" ); + } + return QStringLiteral( "external" ); + } + default: return {}; } diff --git a/app/position/providers/positionprovidersmodel.h b/app/position/providers/positionprovidersmodel.h index 866e1fddb..8b26b8e6c 100644 --- a/app/position/providers/positionprovidersmodel.h +++ b/app/position/providers/positionprovidersmodel.h @@ -58,7 +58,8 @@ class PositionProvidersModel : public QAbstractListModel ProviderName = Qt::UserRole + 1, // name of bluetooth device or custom name for network device ProviderDescription, // device address (IP/BT) + device type ProviderId, // device address (IP/BT) - ProviderType // external_ip (connected) / external_bt (connected) / internal (device) / simulated (device) + ProviderType, // external_ip (connected) / external_bt (connected) / internal (device) / simulated (device) + ProviderGroup // internal / external }; Q_ENUM( DataRoles ) diff --git a/app/qml/gps/MMPositionProviderPage.qml b/app/qml/gps/MMPositionProviderPage.qml index ecaab89ce..34ceb63ff 100644 --- a/app/qml/gps/MMPositionProviderPage.qml +++ b/app/qml/gps/MMPositionProviderPage.qml @@ -100,12 +100,12 @@ MMComponents.MMPage { } section { - property: "providerType" + property: "providerGroup" delegate: MMComponents.MMText { required property string section width: ListView.view.width - text: section === "internal" ? qsTr( "Internal receivers" ) : qsTr( "External receivers" ) + text: qsTr( "%1 receivers" ).arg( section === "internal" ? qsTr( "Internal" ) : qsTr( "External" ) ) font: __style.p6 color: __style.nightColor From 2584ba5d4e71b3434183ce3c08e327bcc12a1cc0 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 11 Jun 2026 10:31:37 +0200 Subject: [PATCH 5/6] Use silence timer for UDP connection in network provider --- app/position/providers/networkpositionprovider.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/position/providers/networkpositionprovider.cpp b/app/position/providers/networkpositionprovider.cpp index 5f59a1def..1cc9bc16c 100644 --- a/app/position/providers/networkpositionprovider.cpp +++ b/app/position/providers/networkpositionprovider.cpp @@ -126,14 +126,16 @@ void NetworkPositionProvider::positionUpdateReceived() { mUdpSocket->connectToHost( peerAddress.toString(), peerPort ); } + + // restart UDP silence timer + mUdpReconnectTimer.start(); return; } - // stop the UDP silence timer, we just received data - // kills the timer when the app was minimized, and we were able to reconnect in the meantime + // restart the UDP silence timer, we just received data if ( socket->socketType() == QAbstractSocket::UdpSocket ) { - mUdpReconnectTimer.stop(); + mUdpReconnectTimer.start(); } const QByteArray rawNmeaData = socket->readAll(); From e71176f981b3a049230c292ee3fdda5bb9d6e5a9 Mon Sep 17 00:00:00 2001 From: Matej Bagar Date: Thu, 11 Jun 2026 10:44:28 +0200 Subject: [PATCH 6/6] Slightly refactor code --- app/position/providers/networkpositionprovider.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/position/providers/networkpositionprovider.cpp b/app/position/providers/networkpositionprovider.cpp index 1cc9bc16c..8f16307c4 100644 --- a/app/position/providers/networkpositionprovider.cpp +++ b/app/position/providers/networkpositionprovider.cpp @@ -51,7 +51,7 @@ NetworkPositionProvider::NetworkPositionProvider( const QString &addr, const QSt void NetworkPositionProvider::startUpdates() { - // TODO: QHostAddress doesn't support hostname lookup (QHostInfo does) + // NOTE: QHostAddress doesn't support hostname lookup (QHostInfo does) mTcpSocket->connectToHost( mTargetAddress, mTargetPort ); mUdpSocket->bind( QHostAddress::LocalHost, mTargetPort ); mUdpReconnectTimer.start( ReconnectDelay::ExtraLongDelay ); @@ -172,14 +172,9 @@ void NetworkPositionProvider::socketStateChanged( const QAbstractSocket::SocketS else if ( state == QAbstractSocket::UnconnectedState ) { const bool isUdpSocketListening = mUdpSocket->state() == QAbstractSocket::ConnectedState || mUdpSocket->state() == QAbstractSocket::BoundState || mUdpReconnectTimer.isActive(); - if ( socket->socketType() == QAbstractSocket::TcpSocket && !isUdpSocketListening && QApplication::applicationState() == Qt::ApplicationActive ) - { - setState( tr( "No connection" ), State::NoConnection ); - startReconnectTimer(); - // let's also invalidate current position since we no longer have connection - emit positionChanged( GeoPosition() ); - } - else if ( socket->socketType() == QAbstractSocket::UdpSocket && QApplication::applicationState() == Qt::ApplicationActive ) + const bool isTcpSocketAndUdpNotListening = socket->socketType() == QAbstractSocket::TcpSocket && !isUdpSocketListening && QApplication::applicationState() == Qt::ApplicationActive; + const bool isUdpSocket = socket->socketType() == QAbstractSocket::UdpSocket && QApplication::applicationState() == Qt::ApplicationActive; + if ( isTcpSocketAndUdpNotListening || isUdpSocket ) { setState( tr( "No connection" ), State::NoConnection ); startReconnectTimer();