Skip to content

Commit 8741c82

Browse files
committed
Merge branch 'master' into dev/2025.7.0
2 parents 00b087b + 40a01fb commit 8741c82

46 files changed

Lines changed: 1853 additions & 821 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Click below to download the app on your mobile device:
3232
<p>
3333
<a href='https://play.google.com/store/apps/details?id=uk.co.lutraconsulting&ah=GSqwibzO2n63iMlCjHmMuBk89t4&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://raw.githubusercontent.com/MerginMaps/.github/main/images/google-play-store.png' height="57" /></a>
3434
<a href='https://apps.apple.com/us/app/input/id1478603559?ls=1'><img alt='Download it from TestFlight' src='https://raw.githubusercontent.com/MerginMaps/.github/main/images/app-store.png' width="170" /></a>
35-
<a href='https://github.com/MerginMaps/mobile/releases/latest'><img alt='Available on Windows' src='https://raw.githubusercontent.com/MerginMaps/.github/main/images/app_download_windows.png' height="57" /></a>
35+
<a href='https://github.com/MerginMaps/mobile/releases/tag/2025.3.0'><img alt='Available on Windows' src='https://raw.githubusercontent.com/MerginMaps/.github/main/images/app_download_windows.png' height="57" /></a>
3636
</p>
3737

3838
**Beta Release**

app/activeproject.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#endif
2727

2828
const QString ActiveProject::LOADING_FLAG_FILE_PATH = QString( "%1/.input_loading_project" ).arg( QStandardPaths::standardLocations( QStandardPaths::TempLocation ).first() );
29+
const int ActiveProject::LOADING_FLAG_FILE_EXPIRATION_MS = 5000;
2930

3031
ActiveProject::ActiveProject( AppSettings &appSettings
3132
, ActiveLayer &activeLayer
@@ -197,7 +198,13 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force )
197198

198199
bool foundErrorsInLoadedProject = validateProject();
199200

200-
flagFile.remove();
201+
// Remove the loading flag file after a while, in case the app crashes not during load, but during the first renderings
202+
QTimer::singleShot( LOADING_FLAG_FILE_EXPIRATION_MS, this, []()
203+
{
204+
QFile::remove( ActiveProject::LOADING_FLAG_FILE_PATH );
205+
CoreUtils::log( QStringLiteral( "Project loading" ), QStringLiteral( "Removed project loading flag" ) );
206+
} );
207+
201208
if ( !force )
202209
{
203210
emit loadingFinished();

app/activeproject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class ActiveProject: public QObject
111111

112112
//! A File on this path represents that project is loading and exists only during the process.
113113
static const QString LOADING_FLAG_FILE_PATH;
114+
static const int LOADING_FLAG_FILE_EXPIRATION_MS;
114115

115116
const QString &mapTheme() const;
116117

app/attributes/attributecontroller.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1623,7 +1623,7 @@ void AttributeController::renamePhotos()
16231623
const QFileInfo fi( src );
16241624
newName = QStringLiteral( "%1.%2" ).arg( newName, fi.completeSuffix() );
16251625

1626-
const QString dst = InputUtils::getAbsolutePath( newName, targetDir );
1626+
const QString dst = CoreUtils::findUniquePath( InputUtils::getAbsolutePath( newName, targetDir ) );
16271627
if ( InputUtils::renameFile( src, dst ) )
16281628
{
16291629
const QString newValue = InputUtils::getRelativePath( dst, prefix );

app/attributes/attributepreviewcontroller.cpp

Lines changed: 123 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@
1212
* (at your option) any later version. *
1313
* *
1414
***************************************************************************/
15+
#include <QDate>
16+
#include <QTime>
17+
#include <QDateTime>
18+
#include <QLocale>
1519

1620
#include "attributepreviewcontroller.h"
1721
#include "featurelayerpair.h"
1822
#include "qgsfield.h"
1923
#include "qgsvectorlayer.h"
2024
#include "qgsexpressioncontextutils.h"
25+
#include "qgseditorwidgetsetup.h"
26+
2127

2228
AttributePreviewModel::AttributePreviewModel( const QVector<QPair<QString, QString>> &items )
2329
: QAbstractListModel( nullptr )
@@ -79,10 +85,16 @@ QVector<QPair<QString, QString>> AttributePreviewController::mapTipFields( )
7985
{
8086
if ( featureTitleExpression != field.name() )
8187
{
82-
const QPair<QString, QString> item = qMakePair(
83-
field.displayName(),
84-
mFeatureLayerPair.feature().attribute( field.name() ).toString()
85-
);
88+
const int idx = fields.indexFromName( field.name() );
89+
const QVariant raw = mFeatureLayerPair.feature().attribute( idx );
90+
91+
// Use the editor widget setup to retrieve the same display format the form uses.
92+
// DO NOT use .toString() directly for date-time values- that can show raw UTC/ISO.
93+
// This keeps the preview and the editor in perfect sync for locale and timezone.
94+
const QgsEditorWidgetSetup ew = mFeatureLayerPair.layer()->editorWidgetSetup( idx );
95+
const QString pretty = formatDateForPreview( fields[idx], raw, ew.config() );
96+
97+
const QPair<QString, QString> item = qMakePair( field.displayName(), pretty );
8698

8799
lst.append( item );
88100
}
@@ -100,11 +112,12 @@ QVector<QPair<QString, QString>> AttributePreviewController::mapTipFields( )
100112
const int index = fields.indexFromName( lines[i] );
101113
if ( index >= 0 )
102114
{
103-
const QString val = mFeatureLayerPair.feature().attribute( index ).toString();
104-
const QPair<QString, QString> item = qMakePair(
105-
fields[index].displayName(),
106-
val
107-
);
115+
// Type-aware formatting (dates in local time, honor display_format)
116+
const QVariant raw = mFeatureLayerPair.feature().attribute( index );
117+
const QgsEditorWidgetSetup ew = mFeatureLayerPair.layer()->editorWidgetSetup( index );
118+
const QString pretty = formatDateForPreview( fields[index], raw, ew.config() );
119+
120+
const QPair<QString, QString> item = qMakePair( fields[index].displayName(), pretty );
108121

109122
lst.append( item );
110123
}
@@ -115,6 +128,107 @@ QVector<QPair<QString, QString>> AttributePreviewController::mapTipFields( )
115128
return lst;
116129
}
117130

131+
QString AttributePreviewController::formatDateForPreview( const QgsField &field,
132+
const QVariant &value,
133+
const QVariantMap &fieldCfg ) const
134+
{
135+
const QString displayFmt = fieldCfg.value( QStringLiteral( "display_format" ) ).toString();
136+
137+
//fallback value as raw QString
138+
const QString fallback = value.toString();
139+
140+
//QDate
141+
if ( field.type() == QMetaType::QDate )
142+
{
143+
QDate date;
144+
if ( value.canConvert<QDate>() )
145+
{
146+
date = value.toDate();
147+
}
148+
149+
else if ( value.userType() == QMetaType::QString )
150+
{
151+
date = QDate::fromString( value.toString(), Qt::ISODate );
152+
}
153+
154+
if ( !date.isValid() )
155+
{
156+
return fallback;
157+
}
158+
159+
if ( displayFmt.isEmpty() )
160+
{
161+
return QLocale().toString( date, QLocale::ShortFormat );
162+
}
163+
164+
return date.toString( displayFmt );
165+
}
166+
167+
//QTime
168+
if ( field.type() == QMetaType::QTime )
169+
{
170+
QTime time;
171+
if ( value.canConvert<QTime>() )
172+
{
173+
time = value.toTime();
174+
}
175+
176+
else if ( value.userType() == QMetaType::QString )
177+
{
178+
time = QTime::fromString( value.toString(), Qt::ISODate );
179+
}
180+
181+
if ( !time.isValid() )
182+
{
183+
return fallback;
184+
}
185+
186+
const QString fmt = displayFmt.isEmpty() ? QStringLiteral( "HH:mm:ss" ) : displayFmt;
187+
return time.toString( fmt );
188+
}
189+
190+
//QDateTime
191+
if ( field.type() == QMetaType::QDateTime )
192+
{
193+
QDateTime dateTime;
194+
if ( value.canConvert<QDateTime>() )
195+
{
196+
dateTime = value.toDateTime();
197+
}
198+
else if ( value.userType() == QMetaType::QString )
199+
{
200+
dateTime = QDateTime::fromString( value.toString(), Qt::ISODateWithMs );
201+
202+
if ( !dateTime.isValid() )
203+
{
204+
dateTime = QDateTime::fromString( value.toString(), Qt::ISODate );
205+
}
206+
}
207+
208+
if ( !dateTime.isValid() )
209+
{
210+
return fallback;
211+
}
212+
213+
// IMPORTANT If the source was UTC (ex., "...Z"), convert to local so the preview
214+
if ( dateTime.timeSpec() != Qt::LocalTime )
215+
{
216+
dateTime = dateTime.toLocalTime();
217+
}
218+
219+
//force LocalTime to prevent Qt from re-attaching an offset during format
220+
// on some platforms the spec remains "OffsetFromUTC" or "UTC".
221+
dateTime.setTimeSpec( Qt::LocalTime );
222+
223+
// We use the editor widget's display format so the preview obeys the same way
224+
// formatting rules as the form editor "keeps UX consistent".
225+
const QString fmt = displayFmt.isEmpty() ? QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) : displayFmt;
226+
return dateTime.toString( fmt );
227+
}
228+
229+
return fallback;
230+
}
231+
118232
QString AttributePreviewController::mapTipImage()
119233
{
120234
QgsExpressionContext context( globalProjectLayerScopes( mFeatureLayerPair.layer() ) );

app/attributes/attributepreviewcontroller.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class AttributePreviewController: public QObject
142142
QVector<QPair<QString, QString>> mapTipFields();
143143
QString mapTipHtml();
144144
QString featureTitle();
145+
QString formatDateForPreview( const QgsField &field, const QVariant &value, const QVariantMap &fieldCfg ) const;
145146

146147
QgsProject *mProject = nullptr;
147148
FeatureLayerPair mFeatureLayerPair;

app/i18n/input_ca.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -444,17 +444,17 @@ No s&apos;afegirà al projecte.</translation>
444444
<context>
445445
<name>MMAccountController</name>
446446
<message>
447-
<location filename="../qml/account/MMAccountController.qml" line="91"/>
447+
<location filename="../qml/account/MMAccountController.qml" line="93"/>
448448
<source>Please update the app to use the latest features.</source>
449449
<translation>Si us plau actualitzeu l&apos;aplicació per utilitzar les funcions més noves.</translation>
450450
</message>
451451
<message>
452-
<location filename="../qml/account/MMAccountController.qml" line="95"/>
452+
<location filename="../qml/account/MMAccountController.qml" line="97"/>
453453
<source>Server is currently unavailable, check your connection or try again later.</source>
454454
<translation>El servidor no està disponible actualment, comproveu la vostra connexió o torneu-ho a provar més tard.</translation>
455455
</message>
456456
<message>
457-
<location filename="../qml/account/MMAccountController.qml" line="148"/>
457+
<location filename="../qml/account/MMAccountController.qml" line="150"/>
458458
<source>I accept the %1Terms and Conditions%3 and %2Privacy Policy%3</source>
459459
<translation>Accepto els %1Termes i condicions%3 i la %2Política de privadesa%3</translation>
460460
</message>
@@ -873,31 +873,39 @@ No s&apos;afegirà al projecte.</translation>
873873
<context>
874874
<name>MMFormPhotoEditor</name>
875875
<message>
876-
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="81"/>
876+
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="84"/>
877877
<source>Photo is missing.</source>
878878
<translation>Manca la foto</translation>
879879
</message>
880880
<message>
881-
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="108"/>
881+
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="113"/>
882882
<source>Open Image</source>
883883
<translation>Obrir imatge</translation>
884884
</message>
885885
<message>
886-
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="110"/>
886+
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="115"/>
887887
<source>Image files (*.gif *.png *.jpg)</source>
888888
<translation>Arxius d&apos;imatge (*.gif *.png *.jpg)</translation>
889889
</message>
890890
<message>
891-
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="242"/>
891+
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="281"/>
892892
<source>Could not create directory %1.</source>
893893
<translation>No s&apos;ha pogut crear el directori %1.</translation>
894894
</message>
895895
<message>
896-
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="314"/>
896+
<location filename="../qml/form/editors/MMFormPhotoEditor.qml" line="353"/>
897897
<source>Failed to process the image</source>
898898
<translation>No s&apos;ha pogut processar la imatge</translation>
899899
</message>
900900
</context>
901+
<context>
902+
<name>MMFormPhotoSketchingPageDialog</name>
903+
<message>
904+
<location filename="../qml/form/components/MMFormPhotoSketchingPageDialog.qml" line="60"/>
905+
<source>Undo</source>
906+
<translation>Desfer</translation>
907+
</message>
908+
</context>
901909
<context>
902910
<name>MMFormRelationEditor</name>
903911
<message>
@@ -1780,6 +1788,12 @@ No s&apos;afegirà al projecte.</translation>
17801788
<source>Unsupported server, please contact your server administrator.</source>
17811789
<translation>Servidor no compatible, poseu-vos en contacte amb l&apos;administrador del vostre servidor.</translation>
17821790
</message>
1791+
<message>
1792+
<location filename="../qml/project/MMProjectController.qml" line="468"/>
1793+
<source>Download a project and start collecting.</source>
1794+
<translation>Descarrega un projecte i comença a enregistrar.
1795+
 </translation>
1796+
</message>
17831797
</context>
17841798
<context>
17851799
<name>MMProjectDelegate</name>
@@ -2851,28 +2865,28 @@ només permet fins a %1 projectes baixats.</translation>
28512865
<translation>S&apos;ha creat l&apos;espai de treball</translation>
28522866
</message>
28532867
<message>
2854-
<location filename="../../core/merginapi.cpp" line="1881"/>
2868+
<location filename="../../core/merginapi.cpp" line="1902"/>
28552869
<source>Project detached from the server</source>
28562870
<translation>Projecte desconnectat del servidor</translation>
28572871
</message>
28582872
<message>
2859-
<location filename="../../core/merginapi.cpp" line="4119"/>
2873+
<location filename="../../core/merginapi.cpp" line="4164"/>
28602874
<source>Workspace name contains invalid characters</source>
28612875
<translation>El nom de l&apos;espai de treball conté caràcters no vàlids</translation>
28622876
</message>
28632877
<message>
2864-
<location filename="../../core/merginapi.cpp" line="4186"/>
2878+
<location filename="../../core/merginapi.cpp" line="4231"/>
28652879
<source>Workspace %1 already exists</source>
28662880
<translation>L&apos;espai de treball %1 ja existeix</translation>
28672881
</message>
28682882
<message>
2869-
<location filename="../../core/merginapi.cpp" line="4292"/>
2883+
<location filename="../../core/merginapi.cpp" line="4337"/>
28702884
<source>You can now close this page and return to Mergin Maps</source>
28712885
<translation>Ara podeu tancar aquesta pàgina i tornar a Mergin Maps</translation>
28722886
</message>
28732887
<message>
2874-
<location filename="../../core/merginapi.cpp" line="4318"/>
2875-
<location filename="../../core/merginapi.cpp" line="4345"/>
2888+
<location filename="../../core/merginapi.cpp" line="4363"/>
2889+
<location filename="../../core/merginapi.cpp" line="4390"/>
28762890
<source>SSO authorization failed</source>
28772891
<translation>L&apos;autorització SSO ha fallat</translation>
28782892
</message>
@@ -3097,12 +3111,12 @@ només permet fins a %1 projectes baixats.</translation>
30973111
<translation>Posició desconeguda</translation>
30983112
</message>
30993113
<message>
3100-
<location filename="../main.cpp" line="556"/>
3114+
<location filename="../main.cpp" line="557"/>
31013115
<source>Report submitted. Please contact the support</source>
31023116
<translation>Informe enviat. Poseu-vos en contacte amb el servei d&apos;assistència.</translation>
31033117
</message>
31043118
<message>
3105-
<location filename="../main.cpp" line="561"/>
3119+
<location filename="../main.cpp" line="562"/>
31063120
<source>Failed to submit report. Please check your internet connection.</source>
31073121
<translation>No s&apos;ha pogut enviar l&apos;informe. Comproveu la vostra connexió a Internet.</translation>
31083122
</message>

0 commit comments

Comments
 (0)