diff --git a/Feature Layer (Shapefile)/MyApp.qml b/Feature Layer (Shapefile)/MyApp.qml new file mode 100644 index 0000000..c604a85 --- /dev/null +++ b/Feature Layer (Shapefile)/MyApp.qml @@ -0,0 +1,108 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 +import Esri.ArcGISRuntime 100.2 + +import "controls" as Controls + +App { + id: app + width: 414 + height: 736 + function units(value) { + return AppFramework.displayScaleFactor * value + } + property real scaleFactor: AppFramework.displayScaleFactor + property int baseFontSize : app.info.propertyValue("baseFontSize", 15 * scaleFactor) + (isSmallScreen ? 0 : 3) + property bool isSmallScreen: (width || height) < units(400) + + property string dataPath: AppFramework.userHomeFolder.filePath("ArcGIS/AppStudio/Data/Shapefile") + + property FileFolder sourceFolder: app.folder.folder("data/Shapefile") + property FileFolder destFolder: AppFramework.userHomeFolder.folder("ArcGIS/AppStudio/Data/Shapefile") + + function copyLocalData() { + if(!destFolder.exists) + AppFramework.userHomeFolder.makePath(dataPath); + var filesList = sourceFolder.fileNames() + for(var i =0; i < sourceFolder.fileNames().length; i++) + { + if(!destFolder.fileExists(filesList[i])) + sourceFolder.copyFile(filesList[i], dataPath + "/" + filesList[i]); + } + } + + Page{ + anchors.fill: parent + header: ToolBar{ + id:header + width: parent.width + height: 50 * scaleFactor + Material.background: "#8f499c" + Controls.HeaderBar{} + } + + // sample starts here ------------------------------------------------------------------ + contentItem: Rectangle{ + anchors.top:header.bottom + + MapView { + id: mapView + anchors.fill: parent + + Map { + id: map + BasemapStreetsVector {} + + FeatureLayer { + + ShapefileFeatureTable { + id:shpTable + + path: AppFramework.userHomeFolder.fileUrl(dataPath + "/Public_Art.shp") + } + + onLoadStatusChanged: { + if (loadStatus !== Enums.LoadStatusLoaded) + return; + + mapView.setViewpointCenterAndScale(fullExtent.center, 80000); + } + } + } + } + } + } + + // sample ends here ------------------------------------------------------------------------ + Controls.DescriptionPage{ + id:descPage + visible: false + } + + Component.onCompleted: { + copyLocalData() + } +} + diff --git a/Feature Layer (Shapefile)/MyApp.qmlproject b/Feature Layer (Shapefile)/MyApp.qmlproject new file mode 100644 index 0000000..b8ff5f5 --- /dev/null +++ b/Feature Layer (Shapefile)/MyApp.qmlproject @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ + +import QmlProject 1.1 + +Project { + mainFile: "MyApp.qml" + + QmlFiles { + directory: "." + recursive: true + } + + JavaScriptFiles { + directory: "." + recursive: true + } + + ImageFiles { + directory: "." + recursive: true + } + + Files { + directory: "." + recursive: true + filter: "*.json;*.html;*.txt" + } + + importPaths: [ + ] +} + diff --git a/Feature Layer (Shapefile)/README.md b/Feature Layer (Shapefile)/README.md new file mode 100644 index 0000000..3ab1354 --- /dev/null +++ b/Feature Layer (Shapefile)/README.md @@ -0,0 +1,43 @@ +## Feature Layer (Shapefile) + +This sample demonstrates how to open a shapefile stored on the device and display it as a feature layer with default symbology. The shapefile will be downloaded from an ArcGIS Online portal automatically. + +A ShapefileFeatureTable is created using the path to a shapefile (.shp) on the file system. The ShapefileFeatureTable is then used to create a FeatureLayer. The FeatureLayer is added to the map as an operational layer, using default symbology and rendering. + +[Resource Level](https://geonet.esri.com/groups/appstudio/blog/2016/12/06/how-to-describe-our-resources-in-terms-of-difficulty-complexity-and-time-to-digest): 🍌 + + +## Instructions to run this sample in AppStudio Desktop + +1. Download the `.zip` file +2. Unzip and copy this folder into AppStudio Apps folder (Windows: `C:\Users\\ArcGIS\AppStudio\Apps` Mac or linux: `Home\ArcGIS\AppStudio\Apps`) +3. The new app will now appear in the AppStudio Desktop. Run the application or open it in the bundled Qt-Creator IDE to look at the code and modify. + +## Issues + +Find a bug or want to request a new feature? Please let us know by submitting an issue. + +## Contributing + +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). + +## Licensing +Copyright 2017 Esri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +A copy of the license is available in the repository's [license.txt](license.txt) file. + + +[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) +[](Esri Language: Qt QML JavaScript) diff --git a/Feature Layer (Shapefile)/appicon.png b/Feature Layer (Shapefile)/appicon.png new file mode 100644 index 0000000..39504b9 Binary files /dev/null and b/Feature Layer (Shapefile)/appicon.png differ diff --git a/Feature Layer (Shapefile)/appinfo.json b/Feature Layer (Shapefile)/appinfo.json new file mode 100644 index 0000000..7a79dbc --- /dev/null +++ b/Feature Layer (Shapefile)/appinfo.json @@ -0,0 +1,89 @@ +{ + "arcgisRuntime": 1002, + "capabilities": { + "audio": false, + "backgroundLocation": false, + "biometricAuthentication": false, + "bluetooth": false, + "camera": false, + "fileSharing": false, + "highAccuracyLocation": false, + "ios": { + "externalAccessoryProtocolStrings": [ + ] + }, + "localnotification": false, + "location": true, + "microphone": false, + "network": true, + "storage": false, + "vibration": false + }, + "deployment": { + "android": { + "packageName": "" + }, + "arcgisRuntimeExtensionsLicense": "", + "arcgisRuntimeLicense": "", + "clientId": "", + "ios": { + "bundleId": "", + "codeSignIdentity": "" + }, + "macos": { + "bundleId": "", + "codeSignIdentity": "" + }, + "publisherName": "", + "uwp": { + "packageName": "", + "productID": "" + }, + "winphone": { + "packageDisplayName": "" + } + }, + "devicesTypes": [ + "desktop", + "tablet", + "phone" + ], + "display": { + "desktop": { + "minimumHeight": 0, + "minimumWidth": 0, + "windowMode": "default" + }, + "phone": { + "landscape": true, + "portrait": true, + "showStatusBar": true + }, + "tablet": { + "landscape": true, + "portrait": true, + "showStatusBar": true + } + }, + "environment": { + }, + "launchUrlSchemes": [ + ], + "mainFile": "MyApp.qml", + "multipleInstances": true, + "projectFile": "MyApp.qmlproject", + "properties": { + }, + "resources": { + "appIcon": "default-app.png", + "launchImageBackground": "launchimage-background.png", + "launchImageBackgroundColor": "#ffffff", + "launchImageOverlay": "launchimage-overlay.png" + }, + "type": "app", + "urlScheme": "", + "version": { + "major": 1, + "micro": 1 + } +} diff --git a/Feature Layer (Shapefile)/assets/clear.png b/Feature Layer (Shapefile)/assets/clear.png new file mode 100644 index 0000000..6b717e0 Binary files /dev/null and b/Feature Layer (Shapefile)/assets/clear.png differ diff --git a/Feature Layer (Shapefile)/assets/info.png b/Feature Layer (Shapefile)/assets/info.png new file mode 100644 index 0000000..c41a5fc Binary files /dev/null and b/Feature Layer (Shapefile)/assets/info.png differ diff --git a/Feature Layer (Shapefile)/controls/DescriptionPage.qml b/Feature Layer (Shapefile)/controls/DescriptionPage.qml new file mode 100644 index 0000000..0572ab6 --- /dev/null +++ b/Feature Layer (Shapefile)/controls/DescriptionPage.qml @@ -0,0 +1,93 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + id: descPage + width: parent.width + height: parent.height + + Rectangle{ + anchors.fill:parent + + ColumnLayout{ + anchors.fill:parent + spacing: 0 + clip:true + + Rectangle{ + id:descPageheader + color:"#8f499c" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 50 * scaleFactor + + ImageButton { + source: "../assets/clear.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + right: parent.right + rightMargin: 10 * scaleFactor + verticalCenter: parent.verticalCenter + } + onClicked: { + descPage.visible = 0 + } + } + + Text { + id: aboutApp + text:qsTr("About") + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + anchors.centerIn: parent + maximumLineCount: 2 + elide: Text.ElideRight + } + } + + Rectangle{ + color:"black" + Layout.fillWidth: true + Layout.fillHeight: true + + Flickable { + anchors.fill:parent + contentHeight: descText.height + clip:true + + Text{ + id: descText + y: 30 * scaleFactor + text:app.info.description + anchors.horizontalCenterOffset: 0 + color:"white" + width: 0.85 * parent.width + horizontalAlignment: Text.AlignLeft + linkColor: "#e5e6e7" + wrapMode: Text.WordWrap + elide: Text.ElideRight + anchors.horizontalCenter: parent.horizontalCenter + font { + pixelSize: app.baseFontSize + } + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + } + } +} + + + + + diff --git a/Feature Layer (Shapefile)/controls/HeaderBar.qml b/Feature Layer (Shapefile)/controls/HeaderBar.qml new file mode 100644 index 0000000..2ddd736 --- /dev/null +++ b/Feature Layer (Shapefile)/controls/HeaderBar.qml @@ -0,0 +1,59 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +RowLayout{ + anchors.fill: parent + spacing:0 + clip:true + + Rectangle{ + Layout.preferredWidth: 50*scaleFactor + } + + Text { + text:app.info.title + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + maximumLineCount:2 + wrapMode: Text.Wrap + elide: Text.ElideRight + anchors{ + verticalCenter: parent.verticalCenter + horizontalCenter:parent.horizontalCenter + } + } + + Rectangle{ + id:infoImageRect + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 50*scaleFactor + + ImageButton { + id:infoImage + source: "../assets/info.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + centerIn: parent + } + onClicked: { + descPage.visible = 1 + } + } + } +} + + + + + diff --git a/Feature Layer (Shapefile)/data/Shapefile/Public_Art.cpg b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.cpg new file mode 100644 index 0000000..3ad133c --- /dev/null +++ b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.cpg @@ -0,0 +1 @@ +UTF-8 \ No newline at end of file diff --git a/Feature Layer (Shapefile)/data/Shapefile/Public_Art.dbf b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.dbf new file mode 100644 index 0000000..2d58e40 Binary files /dev/null and b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.dbf differ diff --git a/Feature Layer (Shapefile)/data/Shapefile/Public_Art.prj b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.prj new file mode 100644 index 0000000..5685517 --- /dev/null +++ b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.prj @@ -0,0 +1 @@ +PROJCS["NAD_1983_StatePlane_Colorado_Central_FIPS_0502_Feet",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Lambert_Conformal_Conic"],PARAMETER["False_Easting",3000000.000316083],PARAMETER["False_Northing",999999.999996],PARAMETER["Central_Meridian",-105.5],PARAMETER["Standard_Parallel_1",38.45],PARAMETER["Standard_Parallel_2",39.75],PARAMETER["Latitude_Of_Origin",37.83333333333334],UNIT["Foot_US",0.3048006096012192]] \ No newline at end of file diff --git a/Feature Layer (Shapefile)/data/Shapefile/Public_Art.sbn b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.sbn new file mode 100644 index 0000000..ad7a51f Binary files /dev/null and b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.sbn differ diff --git a/Feature Layer (Shapefile)/data/Shapefile/Public_Art.sbx b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.sbx new file mode 100644 index 0000000..8d8794f Binary files /dev/null and b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.sbx differ diff --git a/Feature Layer (Shapefile)/data/Shapefile/Public_Art.shp b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.shp new file mode 100644 index 0000000..c5f342d Binary files /dev/null and b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.shp differ diff --git a/Feature Layer (Shapefile)/data/Shapefile/Public_Art.shp.xml b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.shp.xml new file mode 100644 index 0000000..f5d2586 --- /dev/null +++ b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.shp.xml @@ -0,0 +1,42 @@ + +20171009095830001.0ISO 19139 Metadata Implementation Specification GML3.2FALSEPublic_Art3175378.6012503231926.9635001640731.1067191703110.44946910.000file://\\TTILTONWIN\C$\Data\aurora\Public_Art.shpLocal Area Network002ProjectedGCS_North_American_1983Linear Unit: Foot_US (0.304801)NAD_1983_StatePlane_Colorado_Central_FIPS_0502_Feet<ProjectedCoordinateSystem xsi:type='typens:ProjectedCoordinateSystem' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xs='http://www.w3.org/2001/XMLSchema' xmlns:typens='http://www.esri.com/schemas/ArcGIS/2.0.0'><WKT>PROJCS[&quot;NAD_1983_StatePlane_Colorado_Central_FIPS_0502_Feet&quot;,GEOGCS[&quot;GCS_North_American_1983&quot;,DATUM[&quot;D_North_American_1983&quot;,SPHEROID[&quot;GRS_1980&quot;,6378137.0,298.257222101]],PRIMEM[&quot;Greenwich&quot;,0.0],UNIT[&quot;Degree&quot;,0.0174532925199433]],PROJECTION[&quot;Lambert_Conformal_Conic&quot;],PARAMETER[&quot;False_Easting&quot;,3000000.000316083],PARAMETER[&quot;False_Northing&quot;,999999.999996],PARAMETER[&quot;Central_Meridian&quot;,-105.5],PARAMETER[&quot;Standard_Parallel_1&quot;,38.45],PARAMETER[&quot;Standard_Parallel_2&quot;,39.75],PARAMETER[&quot;Latitude_Of_Origin&quot;,37.83333333333334],UNIT[&quot;Foot_US&quot;,0.3048006096012192],AUTHORITY[&quot;EPSG&quot;,2232]]</WKT><XOrigin>-118767900</XOrigin><YOrigin>-94525500</YOrigin><XYScale>36985113.707064793</XYScale><ZOrigin>-100000</ZOrigin><ZScale>10000</ZScale><MOrigin>-100000</MOrigin><MScale>10000</MScale><XYTolerance>0.0032808333333333331</XYTolerance><ZTolerance>0.001</ZTolerance><MTolerance>0.001</MTolerance><HighPrecision>true</HighPrecision><WKID>102654</WKID><LatestWKID>2232</LatestWKID></ProjectedCoordinateSystem>201710101707560020171010170756001500000005000ISO19139/9j/4AAQSkZJRgABAQEAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a +HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy +MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCACFAMgDASIA +AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA +AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3 +ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm +p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA +AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx +BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK +U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3 +uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iii +gAooooAKKKKACql3frZ3NnC1vcym6lMQaGIusfyltzkfdXjGfUii2mvZL68juLNIbaNlFvMJtxmB +XLErj5cHjqc1boAKKjlnhgMYlljjMj7Iw7Ab2wTgepwDx7VJQBUkv1j1WDTzb3LNNE8omWImJdpU +bWfoGO7gd8GrdVL8agyQf2c9sreehm+0KxBiz84XBHzY6Z4q3QAUUUUAFFFFABRRRQAUUUUAFFFF +ABRRRQAUUUUAFFFFABRRRQAVy3j3xDrPhvQFutC0SfVr6SZY1ijiaQID1LBfmxjgY7kZrqaKAILG +5a8sLe5e3lt2mjWQwzDDxkjO1h2I6Gie9tbWW3iuLmGKS4fy4UdwpkbBOFB6nAJ49Knqlfw2khjm +ls47u6tQ09vHtUyBgCMpu6E5xnI69aAJriytbxoGubaKYwSCWIyIG8txkBlz0PJ5qeoLKeS6sbe4 +ltpLaSWNXaCQgtGSMlTgkZHTip6ACiqmpXrafYtcpZ3N4VZV8m2UM5ywGQCQMDOTz0Bq3QAVRu9a +0uwmeK81G0t5ERZHSWZVKqzbVYgnoW4z61erM1fR11OICOSO3nLxF5jbpKXjSQPsO4Hg8/QnIoA0 +6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKrHTrM6mupG1iN8sJgFxtG8Rkhiu +fTIzim6jaTXtssUF9PZuJUkMkIUkhWBK/MCMEDB+tW6ACiqmo6dDqdssE7zqiypKDDM0ZyjBhypB +xkciszUPD13f+GL7SP7ev457lnK3y7VliBfcFXaBwB8vrigDeoqlpGnvpWkWtjJe3N88EYQ3N0+6 +WT3Y9zV2gAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKo6le3Vn9kF +rp0t6ZrhIpPLdVEKHO6Rtx5AA6DknFAF6iiigCOczLbytbojzBCY0dtqs2OATg4Ge+DTLJ7l7G3e +9ijiumjUzRxvuVXxyAcDIz3xU9FABRRVS0hvYrm8e6vEnhklDW8aw7DCm0DaTk7uQTnjrQBboooo +AKKKqWEd/Gk41C4gmYzu0RhiKbYiflU5JywHU8Z9KALdFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF +FFFAFS7jv3ubNrS4gihSUm5WSIsZE2nAUgjad2Dnnp+duiigAooooAqW13NPfXkEljPBHbsojncr +snBUElcHPB4OQPardRzyNDbyypE8zohZYkI3OQOgyQMnpyQKZZTyXVjb3EttJbSSxq7QSEFoyRkq +cEjI6cUAT0UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBUtJ +r2W5vEurNIIY5QtvIs28zJtB3EYG3kkY56VJaXtrfxNLZ3MNxGrtGXicMAynDDI7ggg1PUcMENuh +SCKOJCzOVRQoLE5J47kkk+5oAJ54baIyzyxxRggF5GCgEnA5PqSB+NSVBeWVrqFsbe8toriBiCY5 +kDqSDkcHjggGp6ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKK +KKACiiigAooooAKKKKACiimq6MzKrKWU4YA8jvzQA6iiigAooooAKKKKACiiigAooooAKKKKACii +igAooooAKKKKACiiigAooqjZW+oQ31/Jd36XFtLIrWsKwBDAoUAqWz82Tk5Pr+QBeooqpp+pWeq2 +7XFjcLPEsrwsy54dGKsPwINAFuq1vp1na3l1dwWsUdzdlWuJVUBpSo2ruPfA4qzRQAUUUUAFFFFA +BRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABQAB0GKKKACiiigAooooAKKKKACi +iigD/9k=externalcec84d6d2231fc0070b73ed4adffaa18EsriEnvironmental Systems Research Institute, Inc. (Esri)380 New York StRedlandsCa92373info@esri.comUS909-793-2853909-793-5953Hours: 8:00 a.m.-5:30 p.m. Pacific time, Monday-FridayIn the United States- +Please direct all inquiries regarding software/data pricing and consulting services to your local Esri Regional Office. For support, you may contact Technical Support by telephone (voice) between 6:00 a.m. and 6:00 p.m. Pacific time, Monday through Friday, by dialing 909-793-3774; facsimile (fax) available at 909-792-0960; electronic mail (e-mail) support@esri.com; or visit http://support.esri.com; Esri holidays excluded. + +Outside the United States- +Please direct all inquiries regarding software/data pricing, sales, support, and consulting services to your local Esri International Distributor. This information can be found at http://www.esri.com/about-esri/locations.html.TrueEsriTo define the publicly available art in the city of Aurorapoints file showing the location of publicly available art in auroracity of auroraauroraCOUS United statespublicartThe City of Aurora, Colorado, makes no warranties or guarantees express or implied, as to the completeness, accuracy, or correctness of this data, nor shall the City of Aurora, Colorado incur any liability from any incorrect, incomplete, or misleading information contained therein. The City of Aurora, Colorado makes no warranties, either express or implied, of the value, design, condition, title, merchantability, or fitness for a particular purpose. The City of Aurora, Colorado shall not be liable for any direct, indirect, incidental, consequential, punitive, or special damages, whether foreseeable or unforeseeable, arising out of the authorized or unauthorized use of this data or the inability to use this data or out of any breach of warranty whatsoever. This data is not a legal representation of the City of Aurora and is to be used as an aid in graphic representation only.Microsoft Windows 8 Version 6.2 (Build 9200) ; Esri ArcGIS 12.0.1.8933Public_Art1-104.877688-104.67503139.76219139.589711The City of Aurora, Colorado, makes no warranties or guarantees express or implied, as to the completeness, accuracy, or correctness of this data, nor shall the City of Aurora, Colorado incur any liability from any incorrect, incomplete, or misleading information contained therein. The City of Aurora, Colorado makes no warranties, either express or implied, of the value, design, condition, title, merchantability, or fitness for a particular purpose. The City of Aurora, Colorado shall not be liable for any direct, indirect, incidental, consequential, punitive, or special damages, whether foreseeable or unforeseeable, arising out of the authorized or unauthorized use of this data or the inability to use this data or out of any breach of warranty whatsoever. This data is not a legal representation of the City of Aurora and is to be used as an aid in graphic representation only.Public_ArtFeature Class0FIDFIDOID400Internal feature number.EsriSequential unique whole numbers that are automatically generated.ShapeShapeGeometry000Feature geometry.EsriCoordinates defining the features.SITE_NAMESITE_NAMEString10000ARTWORK_TIARTWORK_TIString10000ARTISTARTISTString5000ADDRESSADDRESSString10000CITY_STATECITY_STATEString5000ZIPZIPDouble1900DESCRIPTIODESCRIPTIOString25400CONTACTCONTACTString5000PHONEPHONEString2000TYPETYPEString1000POINT_XPOINT_XDouble1900POINT_YPOINT_YDouble1900created_uscreated_usString25400created_dacreated_daDate800last_editelast_editeString25400last_edi_1last_edi_1Date800Shapefile0.000datasetEPSG5.3(9.0.0)0SimpleFALSE0FALSEFALSE20171010 diff --git a/Feature Layer (Shapefile)/data/Shapefile/Public_Art.shx b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.shx new file mode 100644 index 0000000..18554d0 Binary files /dev/null and b/Feature Layer (Shapefile)/data/Shapefile/Public_Art.shx differ diff --git a/Feature Layer (Shapefile)/default-app.png b/Feature Layer (Shapefile)/default-app.png new file mode 100644 index 0000000..8fa9021 Binary files /dev/null and b/Feature Layer (Shapefile)/default-app.png differ diff --git a/Feature Layer (Shapefile)/image b/Feature Layer (Shapefile)/image new file mode 100644 index 0000000..e69de29 diff --git a/Feature Layer (Shapefile)/iteminfo.json b/Feature Layer (Shapefile)/iteminfo.json new file mode 100644 index 0000000..dece00e --- /dev/null +++ b/Feature Layer (Shapefile)/iteminfo.json @@ -0,0 +1,66 @@ +{ + "access": "public", + "accessInformation": null, + "appCategories": [ + ], + "avgRating": 0, + "banner": null, + "categories": [ + ], + "commentsEnabled": true, + "created": 1524100356000, + "culture": "en-au", + "description": "\n

A ShapefileFeatureTable is created using the path to a shapefile (.shp) on the file system. The ShapefileFeatureTable is then used to create a FeatureLayer. The FeatureLayer is added to the map as an operational layer, using default symbology and rendering.

\n

Resource Level:🍌

", + "documentation": null, + "extent": [ + ], + "groupDesignations": null, + "guid": null, + "id": "454f7d35235546b996d7e18a0acca6dc", + "industries": [ + ], + "itemControl": "admin", + "languages": [ + ], + "largeThumbnail": null, + "licenseInfo": null, + "listed": false, + "modified": 1524100358000, + "name": "454f7d35235546b996d7e18a0acca6dc.zip", + "numComments": 1, + "numRatings": 0, + "numViews": 3, + "orgId": "2U3NfasNQ9o9LkLt", + "owner": "appstudio_samples", + "ownerFolder": "f4a20283d02c41a1bba3642a7d6b42b5", + "properties": null, + "protected": false, + "proxyFilter": null, + "scoreCompleteness": 80, + "screenshots": [ + ], + "size": 53999, + "snippet": "This sample demonstrates how to open a shapefile stored on the device and display it as a feature layer with default symbology. The shapefile will be downloaded from an ArcGIS Online portal automatically.", + "spatialReference": null, + "tags": [ + "AppStudio", + "Quartz", + "Runtime", + "Sample", + "features", + "layers" + ], + "thumbnail": "thumbnail/thumbnail.png", + "title": "Feature Layer (Shapefile)", + "type": "Native Application", + "typeKeywords": [ + "API_QML", + "App", + "Application", + "AppStudio", + "Configuration", + "Native", + "qml" + ], + "url": null +} diff --git a/Feature Layer (Shapefile)/qtquickcontrols2.conf b/Feature Layer (Shapefile)/qtquickcontrols2.conf new file mode 100644 index 0000000..1ddd1cd --- /dev/null +++ b/Feature Layer (Shapefile)/qtquickcontrols2.conf @@ -0,0 +1,10 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Material + +[Universal] +Theme=Light +;Accent=Steel diff --git a/Feature Layer (Shapefile)/thumbnail.png b/Feature Layer (Shapefile)/thumbnail.png new file mode 100644 index 0000000..7bac0eb Binary files /dev/null and b/Feature Layer (Shapefile)/thumbnail.png differ diff --git a/Font Awesome Icons/HeaderBar.qml b/Font Awesome Icons/HeaderBar.qml index d70dd69..3b20286 100644 --- a/Font Awesome Icons/HeaderBar.qml +++ b/Font Awesome Icons/HeaderBar.qml @@ -4,7 +4,6 @@ import QtQuick.Layouts 1.1 import ArcGIS.AppFramework 1.0 import ArcGIS.AppFramework.Controls 1.0 -import Esri.ArcGISRuntime 100.0 //Add a rectangle item as header Rectangle{ diff --git a/Font Awesome Icons/README.md b/Font Awesome Icons/README.md index 077abff..231311b 100644 --- a/Font Awesome Icons/README.md +++ b/Font Awesome Icons/README.md @@ -5,7 +5,7 @@ This sample shows you how to incorporate font based icons into your AppStudio fo ![](https://github.com/Esri/arcgis-appstudio-samples/raw/master/images/font-awesome-sample.JPG) -blog url +[Read Geonet blog for more info.](https://geonet.esri.com/groups/appstudio/blog/2017/04/17/using-font-awesome-with-appstudio-for-arcgis-apps) [Resource Level](https://geonet.esri.com/groups/appstudio/blog/2016/12/06/how-to-describe-our-resources-in-terms-of-difficulty-complexity-and-time-to-digest): 🍌 @@ -39,8 +39,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -A copy of the license is available in the repository's [license.txt](license.txt) file. - - -[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) -[](Esri Language: Qt QML JavaScript) +A copy of the license is available in the repository's [license.txt](license.txt) file. \ No newline at end of file diff --git a/GNSS Info/GNSSInfo.qml b/GNSS Info/GNSSInfo.qml new file mode 100644 index 0000000..ddc7fec --- /dev/null +++ b/GNSS Info/GNSSInfo.qml @@ -0,0 +1,329 @@ +/* Copyright 2018 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQml 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Devices 1.0 +import ArcGIS.AppFramework.Speech 1.0 + +import "controls" as Controls +import "views" + +App { + id: app + + width: 400 * scaleFactor + height: 750 * scaleFactor + + property alias positionSource: sources.positionSource + property alias satelliteInfoSource: sources.satelliteInfoSource + property alias nmeaSource: sources.nmeaSource + property alias tcpSocket: sources.tcpSocket + property alias discoveryAgent: sources.discoveryAgent + + property Device currentDevice: sources.currentDevice + property bool isConnecting: sources.isConnecting + property bool isConnected: sources.isConnected + + property real scaleFactor: AppFramework.displayScaleFactor + property int baseFontSize: 14 * scaleFactor + + property color primaryColor: "#8f499c" + property color darkPrimaryColor: "#662472" + property color backgroundColor: "#EEEEEE" + property color navBarColor: "#FFFFFF" + property color greyTextColor: "#555555" + + readonly property string disconnectedText: qsTr("Device disconnected") + readonly property string connectedText: qsTr("Device connected") + + signal showLocationPage() + + //-------------------------------------------------------------------------- + + onIsConnectedChanged: { + if (isConnected) { + textToSpeech.say(connectedText); + showLocationPage(); + } else { + textToSpeech.say(disconnectedText); + } + } + + //-------------------------------------------------------------------------- + + onShowLocationPage: { + locationPage.clear(); + skyplotPage.clear(); + debugPage.clear(); + + if (footer.currentIndex === 0) { + footer.currentIndex = 1; + } + } + + //-------------------------------------------------------------------------- + + Page { + anchors.fill: parent + + header: ToolBar { + id: header + + width: parent.width + height: 50 * scaleFactor + Material.background: primaryColor + + Controls.HeaderBar { + headerText: app.info.title + } + } + + //-------------------------------------------------------------------------- + + contentItem: Rectangle { + anchors.top: header.bottom + color: backgroundColor + + DevicePage { + id: devicePage + + anchors.fill: parent + visible: footer.currentIndex === 0 + + discoveryAgent: app.discoveryAgent + currentDevice: app.currentDevice + isConnecting: app.isConnecting + isConnected: app.isConnected + } + + LocationPage { + id: locationPage + + anchors.fill: parent + visible: footer.currentIndex === 1 + + positionSource: app.positionSource + isConnected: app.isConnected + } + + SkyPlotPage { + id: skyplotPage + + anchors.fill: parent + visible: footer.currentIndex === 2 + + positionSource: app.positionSource + satelliteInfoSource: app.satelliteInfoSource + } + + QualityPage { + id: qualityPage + + anchors.fill: parent + visible: footer.currentIndex === 3 + + positionSource: app.positionSource + } + + DebugPage { + id: debugPage + + anchors.fill: parent + visible: footer.currentIndex === 4 + + nmeaSource: app.nmeaSource + } + } + + Rectangle { + id: lineAboveFooter + + anchors.bottom: parent.bottom + width: parent.width + height: 1.3 * scaleFactor + color: "lightgrey" + } + + footer: TabBar { + id: footer + + width: parent.width + height: 52 * scaleFactor + Material.accent: "#00000000" + + currentIndex: 0 + + background: Rectangle { + anchors.fill: parent + color: navBarColor + } + + Controls.CustomizedTabButton { + id: deviceIcon + + height: footer.height + imageSource: "assets/device.png" + imageColor: checked ? primaryColor : "grey" + imageText: qsTr("Device") + } + + Controls.CustomizedTabButton { + id: locationIcon + + height: footer.height + imageSource: "assets/location.png" + imageColor: checked ? primaryColor : "grey" + imageText: qsTr("Location") + } + + Controls.CustomizedTabButton { + id: skyplotIcon + + height: footer.height + imageSource: "assets/skyplot.png" + imageColor: checked ? primaryColor : "grey" + imageText: qsTr("SkyPlot") + } + + Controls.CustomizedTabButton { + id: qualityIcon + + height: footer.height + imageSource: "assets/quality.png" + imageColor: checked ? primaryColor : "grey" + imageText: qsTr("Quality") + } + + Controls.CustomizedTabButton { + id: debugIcon + + height: footer.height + imageSource: "assets/debug.png" + imageColor: checked ? primaryColor : "grey" + imageText: qsTr("Log") + } + } + } + + //-------------------------------------------------------------------------- + + DescriptionPage { + id: descPage + + visible: false + } + + //-------------------------------------------------------------------------- + + PositioningSources { + id: sources + } + + //-------------------------------------------------------------------------- + + Connections { + target: tcpSocket + + onErrorChanged: { + console.log("Connection error:", tcpSocket.error, tcpSocket.errorString) + + errorDialog.text = tcpSocket.errorString; + errorDialog.open(); + } + } + + // ------------------------------------------------------------------------- + + Connections { + target: currentDevice + + onErrorChanged: { + if (currentDevice) { + console.log("Connection error:", currentDevice.error) + + errorDialog.text = currentDevice.error; + errorDialog.open(); + } + } + } + + // ------------------------------------------------------------------------- + + Dialog { + id: errorDialog + + property alias text: label.text + + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + modal: true + + standardButtons: Dialog.Ok + title: qsTr("Unable to connect"); + text: "" + + Label { + id: label + + Layout.fillWidth: true + font.pixelSize: baseFontSize + Material.accent: primaryColor + } + } + + //-------------------------------------------------------------------------- + + TextToSpeech { + id: textToSpeech + } + + //-------------------------------------------------------------------------- + + function convertValueToLengthString(value) { + switch (Qt.locale().measurementSystem) { + case Locale.MetricSystem: + return qsTr("%1 m").arg(round(value, 3)); + case Locale.ImperialUKSystem: + case Locale.ImperialUSSystem: + case Locale.ImperialSystem: + return qsTr("%1 ft").arg(round(value / 0.3048, 3)); + } + } + + function convertValueToSpeedString(value) { + switch (Qt.locale().measurementSystem) { + case Locale.MetricSystem: + return qsTr("%1 km/h").arg(round(value * 3.6, 2)); + case Locale.ImperialUKSystem: + case Locale.ImperialUSSystem: + case Locale.ImperialSystem: + return qsTr("%1 mph").arg(round(value * 2.23694, 2)); + } + } + + function round(value, numberOfDigits) { + var pow = Math.pow(10, numberOfDigits); + return (Math.round(value * pow) / pow).toFixed(numberOfDigits); + } + + //-------------------------------------------------------------------------- +} diff --git a/GNSS Info/GNSSInfo.qmlproject b/GNSS Info/GNSSInfo.qmlproject new file mode 100644 index 0000000..3ed254b --- /dev/null +++ b/GNSS Info/GNSSInfo.qmlproject @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ + +import QmlProject 1.1 + +Project { + mainFile: "GNSSInfo.qml" + + QmlFiles { + directory: "." + recursive: true + } + + JavaScriptFiles { + directory: "." + recursive: true + } + + ImageFiles { + directory: "." + recursive: true + } + + Files { + directory: "." + recursive: true + filter: "*.json;*.html;*.txt;*.md;*.psd;*.ai" + } + + importPaths: [ + ] +} + diff --git a/GNSS Info/LICENSE b/GNSS Info/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/GNSS Info/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/GNSS Info/PositioningSources.qml b/GNSS Info/PositioningSources.qml new file mode 100644 index 0000000..cf2adcc --- /dev/null +++ b/GNSS Info/PositioningSources.qml @@ -0,0 +1,225 @@ +/* Copyright 2018 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 + +import ArcGIS.AppFramework.Positioning 1.0 +import ArcGIS.AppFramework.Devices 1.0 +import ArcGIS.AppFramework.Networking 1.0 + +Item { + readonly property var eConnectionType: { + "undefined": 0, + "internal": 1, + "external": 2, + "network": 3 + } + + property alias positionSource: positionSource + property alias satelliteInfoSource: satelliteInfoSource + property alias nmeaSource: nmeaSource + property alias tcpSocket: tcpSocket + property alias discoveryAgent: discoveryAgent + + property Device currentDevice + property bool isConnecting + property bool isConnected + property var connectionType: eConnectionType.internal + + signal networkHostSelected(string hostname, int port) + signal deviceSelected(Device device) + signal disconnect() + + //-------------------------------------------------------------------------- + + Component.onDestruction: { + discoveryAgent.stop(); + disconnect(); + } + + //-------------------------------------------------------------------------- + + PositionSource { + id: positionSource + + active: true + nmeaSource: nmeaSource + } + + //-------------------------------------------------------------------------- + + SatelliteInfoSource { + id: satelliteInfoSource + + active: true + nmeaSource: nmeaSource + } + + //-------------------------------------------------------------------------- + + NmeaSource { + id: nmeaSource + + onSourceChanged: positionSource.update() + + onReceivedNmeaData: { + if (!isConnected && nmeaSource.receivedSentence.trim() > "") { + isConnected = true; + isConnecting = false; + } + } + } + + //-------------------------------------------------------------------------- + + TcpSocket { + id: tcpSocket + + onErrorChanged: disconnect() + } + + // ------------------------------------------------------------------------- + + Connections { + target: currentDevice + + onConnectedChanged: { + // cleanup in case the connection to the device is lost + if (currentDevice && !currentDevice.connected) { + disconnect(); + } + } + + onErrorChanged: { + if (currentDevice) { + currentDevice = null; + disconnect(); + } + } + } + + //-------------------------------------------------------------------------- + + DeviceDiscoveryAgent { + id: discoveryAgent + + property bool detectBluetooth: true + property bool detectSerialPort: false + + deviceFilter: function(device) { + var types = []; + + if (detectBluetooth) { + types.push(Device.DeviceTypeBluetooth); + } + + if (detectSerialPort) { + types.push(Device.DeviceTypeSerialPort); + } + + for (var i in types) { + if (device.deviceType === types[i]) { + return true; + } + } + + return false; + } + + onDeviceDiscovered: { + console.log("Device discovered: ", device.name); + } + + onRunningChanged: { + console.log("DeviceDiscoveryAgent running", running) + } + + onDiscoverDevicesCompleted: { + console.log("Device discovery completed"); + stop(); + } + } + + //-------------------------------------------------------------------------- + + onNetworkHostSelected: { + console.log("Connecting to remote host:", hostname, "port:", port); + + disconnect(); + + isConnected = false; + isConnecting = true; + + nmeaSource.source = tcpSocket; + connectionType = eConnectionType.network; + tcpSocket.connectToHost(hostname, port); + } + + //-------------------------------------------------------------------------- + + onDeviceSelected: { + console.log("Connecting to device:", device.name, "address:", device.address, "type:", device.deviceType, device); + + disconnect(); + + isConnected = false; + isConnecting = true; + + currentDevice = device; + nmeaSource.source = currentDevice; + connectionType = eConnectionType.external; + currentDevice.connected = true; + } + + //-------------------------------------------------------------------------- + + onDisconnect: { + if (tcpSocket.valid && tcpSocket.state === AbstractSocket.StateConnected) { + tcpSocket.disconnectFromHost(); + } + + if (currentDevice && currentDevice.connected) { + currentDevice.connected = false; + currentDevice = null; + } + + isConnected = false; + isConnecting = false; + + nmeaSource.source = null; + connectionType = eConnectionType.undefined; + } + + //-------------------------------------------------------------------------- + + onIsConnectedChanged: { + if (isConnected) { + if (connectionType === eConnectionType.external) { + console.log("Connected to device:", currentDevice.name, "address:", currentDevice.address); + } else { + console.log("Connected to remote host:", tcpSocket.remoteName, "port:", tcpSocket.remotePort) + } + } else { + if (connectionType === eConnectionType.external) { + console.log("Disconnecting device:", currentDevice.name, "address", currentDevice.address); + } else { + console.log("Disconnecting from remote host:", tcpSocket.remoteName, "port:", tcpSocket.remotePort); + } + } + } + + //-------------------------------------------------------------------------- +} diff --git a/GNSS Info/README.md b/GNSS Info/README.md new file mode 100644 index 0000000..51c2c4f --- /dev/null +++ b/GNSS Info/README.md @@ -0,0 +1,43 @@ +## GNSS Info + +This sample demonstrates how to connect to a GNSS receiver and display position metadata. + +Choose a connected GNSS receiver and see the returned position displayed on a map and its properties as a list of parameters. A read-only tracklog (for the current session) is also displayed on the map. For troubleshooting, the raw sentences coming from the receiver can also be displayed. + +[Resource Level](https://geonet.esri.com/groups/appstudio/blog/2016/12/06/how-to-describe-our-resources-in-terms-of-difficulty-complexity-and-time-to-digest): 🍌🍌 + + +## Instructions to run this sample in AppStudio Desktop + +1. Download the `.zip` file +2. Unzip and copy this folder into AppStudio Apps folder (Windows: `C:\Users\\ArcGIS\AppStudio\Apps` Mac or linux: `Home\ArcGIS\AppStudio\Apps`) +3. The new app will now appear in the AppStudio Desktop. Run the application or open it in the bundled Qt-Creator IDE to look at the code and modify. + +## Issues + +Find a bug or want to request a new feature? Please let us know by submitting an issue. + +## Contributing + +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). + +## Licensing +Copyright 2017 Esri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +A copy of the license is available in the repository's [license.txt](license.txt) file. + + +[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) +[](Esri Language: Qt QML JavaScript) diff --git a/GNSS Info/appicon.png b/GNSS Info/appicon.png new file mode 100644 index 0000000..3b3c1b1 Binary files /dev/null and b/GNSS Info/appicon.png differ diff --git a/GNSS Info/appinfo.json b/GNSS Info/appinfo.json new file mode 100644 index 0000000..6f6c09b --- /dev/null +++ b/GNSS Info/appinfo.json @@ -0,0 +1,93 @@ +{ + "arcgisRuntime": 0, + "capabilities": { + "audio": false, + "backgroundLocation": true, + "biometricAuthentication": false, + "bluetooth": true, + "camera": false, + "fileSharing": false, + "highAccuracyLocation": true, + "ios": { + "backgroundExternalAccessory": true, + "externalAccessoryProtocolStrings": [ + "com.bad-elf.gps", + "com.eos-gnss.positioningsource" + ] + }, + "localnotification": false, + "location": true, + "microphone": false, + "network": true, + "storage": true, + "vibration": false + }, + "deployment": { + "android": { + "packageName": "" + }, + "arcgisRuntimeExtensionsLicense": "", + "arcgisRuntimeLicense": "", + "clientId": "", + "ios": { + "bundleId": "", + "codeSignIdentity": "" + }, + "macos": { + "bundleId": "", + "codeSignIdentity": "" + }, + "publisherName": "", + "uwp": { + "packageName": "", + "productID": "" + }, + "winphone": { + "packageDisplayName": "" + } + }, + "devicesTypes": [ + "desktop", + "tablet", + "phone" + ], + "display": { + "desktop": { + "minimumHeight": 0, + "minimumWidth": 0, + "windowMode": "default" + }, + "enableHighDpi": true, + "phone": { + "landscape": false, + "portrait": true, + "showStatusBar": true + }, + "tablet": { + "landscape": false, + "portrait": true, + "showStatusBar": true + } + }, + "environment": { + }, + "launchUrlSchemes": [ + ], + "mainFile": "GNSSInfo.qml", + "multipleInstances": false, + "projectFile": "GNSSInfo.qmlproject", + "properties": { + }, + "resources": { + "appIcon": "appicon.png", + "launchImageBackground": "launchimage-background.png", + "launchImageBackgroundColor": "#ffffff", + "launchImageOverlay": "launchimage-overlay.png" + }, + "type": "app", + "urlScheme": "", + "version": { + "major": 1, + "micro": 3 + } +} diff --git a/GNSS Info/assets/bluetooth.png b/GNSS Info/assets/bluetooth.png new file mode 100644 index 0000000..d674866 Binary files /dev/null and b/GNSS Info/assets/bluetooth.png differ diff --git a/GNSS Info/assets/clear.png b/GNSS Info/assets/clear.png new file mode 100644 index 0000000..6b717e0 Binary files /dev/null and b/GNSS Info/assets/clear.png differ diff --git a/GNSS Info/assets/compass.png b/GNSS Info/assets/compass.png new file mode 100644 index 0000000..9f83414 Binary files /dev/null and b/GNSS Info/assets/compass.png differ diff --git a/GNSS Info/assets/debug.png b/GNSS Info/assets/debug.png new file mode 100644 index 0000000..e0b5b19 Binary files /dev/null and b/GNSS Info/assets/debug.png differ diff --git a/GNSS Info/assets/device.png b/GNSS Info/assets/device.png new file mode 100644 index 0000000..a1c8f0f Binary files /dev/null and b/GNSS Info/assets/device.png differ diff --git a/GNSS Info/assets/deviceType-0.png b/GNSS Info/assets/deviceType-0.png new file mode 100644 index 0000000..d674866 Binary files /dev/null and b/GNSS Info/assets/deviceType-0.png differ diff --git a/GNSS Info/assets/deviceType-1.png b/GNSS Info/assets/deviceType-1.png new file mode 100644 index 0000000..ce5c358 Binary files /dev/null and b/GNSS Info/assets/deviceType-1.png differ diff --git a/GNSS Info/assets/home.png b/GNSS Info/assets/home.png new file mode 100644 index 0000000..d12cf14 Binary files /dev/null and b/GNSS Info/assets/home.png differ diff --git a/GNSS Info/assets/info.png b/GNSS Info/assets/info.png new file mode 100644 index 0000000..c41a5fc Binary files /dev/null and b/GNSS Info/assets/info.png differ diff --git a/GNSS Info/assets/location.png b/GNSS Info/assets/location.png new file mode 100644 index 0000000..6b2213a Binary files /dev/null and b/GNSS Info/assets/location.png differ diff --git a/GNSS Info/assets/map_cursor.png b/GNSS Info/assets/map_cursor.png new file mode 100644 index 0000000..69d5f8e Binary files /dev/null and b/GNSS Info/assets/map_cursor.png differ diff --git a/GNSS Info/assets/map_pin.png b/GNSS Info/assets/map_pin.png new file mode 100644 index 0000000..b8b016c Binary files /dev/null and b/GNSS Info/assets/map_pin.png differ diff --git a/GNSS Info/assets/pause.png b/GNSS Info/assets/pause.png new file mode 100644 index 0000000..660ac65 Binary files /dev/null and b/GNSS Info/assets/pause.png differ diff --git a/GNSS Info/assets/play.png b/GNSS Info/assets/play.png new file mode 100644 index 0000000..be5c062 Binary files /dev/null and b/GNSS Info/assets/play.png differ diff --git a/GNSS Info/assets/quality.png b/GNSS Info/assets/quality.png new file mode 100644 index 0000000..8c8f00e Binary files /dev/null and b/GNSS Info/assets/quality.png differ diff --git a/GNSS Info/assets/right.png b/GNSS Info/assets/right.png new file mode 100644 index 0000000..5ada41b Binary files /dev/null and b/GNSS Info/assets/right.png differ diff --git a/GNSS Info/assets/serial.png b/GNSS Info/assets/serial.png new file mode 100644 index 0000000..ce5c358 Binary files /dev/null and b/GNSS Info/assets/serial.png differ diff --git a/GNSS Info/assets/skyplot.png b/GNSS Info/assets/skyplot.png new file mode 100644 index 0000000..3e7da76 Binary files /dev/null and b/GNSS Info/assets/skyplot.png differ diff --git a/GNSS Info/controls/CustomizedDelegate.qml b/GNSS Info/controls/CustomizedDelegate.qml new file mode 100644 index 0000000..2244224 --- /dev/null +++ b/GNSS Info/controls/CustomizedDelegate.qml @@ -0,0 +1,54 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.3 + +Item { + width: parent.width + height: 35 * scaleFactor + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Rectangle { + Layout.fillWidth: true + anchors.top: parent.top + height: 1 * scaleFactor + color: "lightgrey" + } + + Row { + anchors.fill: parent + anchors.topMargin: 1 * scaleFactor + Layout.fillWidth: true + + Rectangle { + width: parent.width * 0.5 + height: parent.height + color: "white" + + Text { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + text: label + font.pixelSize: baseFontSize * 0.9 + color: "grey" + } + } + + Rectangle { + width: parent.width * 0.5 + height: parent.height + color: "white" + + Text { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + + text: qualityPage[attr] || qsTr("No Data") + font.pixelSize: baseFontSize * 0.9 + } + } + } + } +} diff --git a/GNSS Info/controls/CustomizedTabButton.qml b/GNSS Info/controls/CustomizedTabButton.qml new file mode 100644 index 0000000..1e3c292 --- /dev/null +++ b/GNSS Info/controls/CustomizedTabButton.qml @@ -0,0 +1,69 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 + +TabButton { + property alias imageSource: tabButtonImage.source + property alias imageColor: overLayColor.color + property alias imageText: iconText.text + + anchors.verticalCenter: parent.verticalCenter + + indicator: Image { + source: "" + } + + contentItem: Item { + id: tab + + ColumnLayout { + width: parent.width + height: 43 * scaleFactor + anchors.centerIn: parent + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 24 * scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + + Image { + id: tabButtonImage + + anchors.fill: parent + + fillMode: Image.PreserveAspectFit + mipmap: true + smooth: true + } + + ColorOverlay { + id: overLayColor + + anchors.fill: tabButtonImage + source: tabButtonImage + } + } + + Item { + id: spacer + + Layout.preferredHeight: 5 * scaleFactor + } + + Label { + id: iconText + + Layout.preferredHeight: 12 * scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: spacer.bottom + + font.pixelSize: 10.5 * scaleFactor + horizontalAlignment: Label.AlignHCenter + verticalAlignment: Label.AlignVCenter + color: imageColor + } + } + } +} diff --git a/GNSS Info/controls/DescriptionPage.qml b/GNSS Info/controls/DescriptionPage.qml new file mode 100644 index 0000000..a5211ec --- /dev/null +++ b/GNSS Info/controls/DescriptionPage.qml @@ -0,0 +1,115 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + id: descPage + + width: parent.width + height: parent.height + + ColumnLayout { + anchors.fill: parent + spacing: 0 + clip: true + + Rectangle { + id: descPageheader + + Layout.fillWidth: true + Layout.preferredHeight: 50 * scaleFactor + color: primaryColor + + Text { + id: aboutApp + + anchors.centerIn: parent + + text: qsTr("About") + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + wrapMode: Text.WordWrap + elide: Text.ElideRight + color: "white" + } + + ImageButton { + source: "../assets/clear.png" + + height: 30 * scaleFactor + width: 30 * scaleFactor + anchors.right: parent.right + anchors.rightMargin: 10 * scaleFactor + anchors.verticalCenter: parent.verticalCenter + + checkedColor: "transparent" + pressedColor: "transparent" + hoverColor: "transparent" + glowColor: "transparent" + + onClicked: descPage.visible = false + } + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + + color: "black" + + Flickable { + anchors.fill: parent + contentHeight: descText.height + clip: true + + Text { + id: descText + + y: 30 * scaleFactor + width: 0.85 * parent.width + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: 0 + + text: app.info.description + font.pixelSize: app.baseFontSize + horizontalAlignment: Text.AlignLeft + wrapMode: Text.WordWrap + elide: Text.ElideRight + linkColor: "#e5e6e7" + color: "white" + + onLinkActivated: Qt.openUrlExternally(link) + } + + Row { + anchors.top: descText.bottom + anchors.topMargin: 20 * scaleFactor + anchors.left: descText.left + + Text { + id: appVersion + + text: qsTr("App Version: ") + font.pixelSize: app.baseFontSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + color: "white" + } + + Text { + id: appVersionNumber + + text: app.info.version + font.pixelSize: app.baseFontSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + color: "white" + } + } + } + } + } +} diff --git a/GNSS Info/controls/HeaderBar.qml b/GNSS Info/controls/HeaderBar.qml new file mode 100644 index 0000000..50ccdd0 --- /dev/null +++ b/GNSS Info/controls/HeaderBar.qml @@ -0,0 +1,48 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.1 + +RowLayout { + property alias headerText: headerText.text + + anchors.fill: parent + spacing: 0 + clip: true + + Text { + id: headerText + + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + maximumLineCount: 2 + wrapMode: Text.Wrap + elide: Text.ElideRight + color: "white" + } + + Rectangle { + id: infoImageRect + + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 50 * scaleFactor + + Image { + id: infoImage + + height: 30 * scaleFactor + width: 30 * scaleFactor + anchors.centerIn: parent + + source: "../assets/info.png" + smooth: true + mipmap: true + + MouseArea { + anchors.fill: parent + onClicked: descPage.visible = 1 + } + } + } +} diff --git a/GNSS Info/controls/LocationRow.qml b/GNSS Info/controls/LocationRow.qml new file mode 100644 index 0000000..d8741ac --- /dev/null +++ b/GNSS Info/controls/LocationRow.qml @@ -0,0 +1,47 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.3 + +Item { + property alias nameText: nameText.text + property alias valueText: valueText.text + + height: metrics.height + Layout.fillWidth: true + + RowLayout { + anchors.fill: parent + + Text { + id: nameText + + anchors.left: parent.left + anchors.leftMargin: 5 * scaleFactor + anchors.verticalCenter: parent.verticalCenter + + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pixelSize: baseFontSize + font.bold: true + color: "white" + } + + Text { + id: valueText + + property bool valid: true + + anchors.left: nameText.right + anchors.verticalCenter: parent.verticalCenter + + font.pixelSize: baseFontSize + font.bold: true + font.italic: !valid + color: valid ? "white" : "darkred" + } + } + + TextMetrics { + id: metrics + font: nameText.font + text: " " + } +} diff --git a/GNSS Info/controls/PositionAccuracyIndicator.qml b/GNSS Info/controls/PositionAccuracyIndicator.qml new file mode 100644 index 0000000..52d05c7 --- /dev/null +++ b/GNSS Info/controls/PositionAccuracyIndicator.qml @@ -0,0 +1,50 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 +import QtLocation 5.9 + +MapCircle { + property PositionIndicator positionIndicator + + readonly property var position: positionIndicator.positionSource.position + readonly property bool active: visible && position && position.latitudeValid && position.longitudeValid && position.horizontalAccuracyValid + + //-------------------------------------------------------------------------- + + visible: positionIndicator.visible && positionIndicator.horizontalAccuracy > 0 + + center: positionIndicator.center + radius: positionIndicator.horizontalAccuracy + + color: "transparent" + border { + color: "#00b2ff" + width: 3 * scaleFactor + } + + //-------------------------------------------------------------------------- + + ScaleAnimator on scale { + running: active + loops: Animation.Infinite + from: 0.0 + to: 1.1 + duration: 2000 + } + + //-------------------------------------------------------------------------- +} diff --git a/GNSS Info/controls/PositionIndicator.qml b/GNSS Info/controls/PositionIndicator.qml new file mode 100644 index 0000000..224bab6 --- /dev/null +++ b/GNSS Info/controls/PositionIndicator.qml @@ -0,0 +1,58 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 +import QtLocation 5.9 + +import ArcGIS.AppFramework.Positioning 1.0 + +MapCircle { + id: positionIndicator + + property PositionSource positionSource + property real horizontalAccuracy + + //-------------------------------------------------------------------------- + + visible: positionSource.active + opacity: 0.5 + + radius: horizontalAccuracy + + color: horizontalAccuracy > 0 ? "#00b2ff" : "#ff0000" + border { + color: "#ffffff" + width: 1 + } + + //-------------------------------------------------------------------------- + + Connections { + target: positionSource + + onPositionChanged: { + positionIndicator.center = positionSource.position.coordinate; + + if (positionSource.position.horizontalAccuracyValid) { + positionIndicator.horizontalAccuracy = positionSource.position.horizontalAccuracy; + } else { + positionIndicator.horizontalAccuracy = -1; + } + } + } + + //-------------------------------------------------------------------------- +} diff --git a/GNSS Info/controls/PositionMarker.qml b/GNSS Info/controls/PositionMarker.qml new file mode 100644 index 0000000..0043d67 --- /dev/null +++ b/GNSS Info/controls/PositionMarker.qml @@ -0,0 +1,45 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 +import QtLocation 5.9 + +MapQuickItem { + id: positionMarker + + property PositionIndicator positionIndicator + + //-------------------------------------------------------------------------- + + visible: positionIndicator.visible + + anchorPoint.x: image.width/2 + anchorPoint.y: image.height + + coordinate: positionIndicator.center + + //-------------------------------------------------------------------------- + + sourceItem: Image { + id: image + + source: "../assets/map_pin.png" + width: 32 * scaleFactor + fillMode: Image.PreserveAspectFit + } + + //-------------------------------------------------------------------------- +} diff --git a/GNSS Info/iteminfo.json b/GNSS Info/iteminfo.json new file mode 100644 index 0000000..d1a388e --- /dev/null +++ b/GNSS Info/iteminfo.json @@ -0,0 +1,63 @@ +{ + "access": "public", + "accessInformation": null, + "appCategories": [ + ], + "avgRating": 0, + "banner": null, + "categories": [ + ], + "commentsEnabled": true, + "created": 1520220466000, + "culture": "en-au", + "description": "\n

Choose a connected GNSS receiver and see the returned position displayed on a map and its properties as a list of parameters. A read-only tracklog (for the current session) is also displayed on the map. For troubleshooting, the raw sentences coming from the receiver can also be displayed.

\n


\n

Resource Level:🍌🍌

", + "documentation": null, + "extent": [ + ], + "groupDesignations": null, + "guid": null, + "id": "6630ef5649854c389a2a8921c444789b", + "industries": [ + ], + "itemControl": "admin", + "languages": [ + ], + "largeThumbnail": null, + "licenseInfo": null, + "listed": false, + "modified": 1523947438000, + "name": "6630ef5649854c389a2a8921c444789b.zip", + "numComments": 3, + "numRatings": 0, + "numViews": 33, + "orgId": "2U3NfasNQ9o9LkLt", + "owner": "appstudio_samples", + "ownerFolder": null, + "properties": null, + "protected": false, + "proxyFilter": null, + "scoreCompleteness": 81, + "screenshots": [ + ], + "size": 81241, + "snippet": "This sample demonstrates how to connect to a GNSS receiver and display position metadata.", + "spatialReference": null, + "tags": [ + "AppStudio", + "Sample", + "Plugins" + ], + "thumbnail": "thumbnail/thumbnail.png", + "title": "GNSS Info", + "type": "Native Application", + "typeKeywords": [ + "API_QML", + "App", + "Application", + "AppStudio", + "Configuration", + "Native", + "qml" + ], + "url": null +} diff --git a/GNSS Info/qtquickcontrols2.conf b/GNSS Info/qtquickcontrols2.conf new file mode 100644 index 0000000..1ddd1cd --- /dev/null +++ b/GNSS Info/qtquickcontrols2.conf @@ -0,0 +1,10 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Material + +[Universal] +Theme=Light +;Accent=Steel diff --git a/GNSS Info/thumbnail.png b/GNSS Info/thumbnail.png new file mode 100644 index 0000000..a690d63 Binary files /dev/null and b/GNSS Info/thumbnail.png differ diff --git a/GNSS Info/views/DebugPage.qml b/GNSS Info/views/DebugPage.qml new file mode 100644 index 0000000..4583aef --- /dev/null +++ b/GNSS Info/views/DebugPage.qml @@ -0,0 +1,170 @@ +/* Copyright 2018 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import QtGraphicalEffects 1.0 +import QtQuick.Controls.Material 2.1 + +import ArcGIS.AppFramework.Devices 1.0 + +Rectangle { + id: debugRec + + property NmeaSource nmeaSource + + property bool isPaused + + signal clear(); + + color: "black" + + //-------------------------------------------------------------------------- + + onClear: dataModel.clear() + + //-------------------------------------------------------------------------- + + Connections { + target: nmeaSource + + onReceivedNmeaData: { + if (!isPaused) { + dataModel.append({ + dataText: nmeaSource.receivedSentence.trim(), + isValid: true + }); + } + + if (dataModel.count > 100) { + dataModel.remove(0); + } + } + } + + //-------------------------------------------------------------------------- + + ColumnLayout { + anchors.fill: parent + anchors.margins: 5 * scaleFactor + + ListView { + id: listView + + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 3 * scaleFactor + clip: true + + model: dataModel + delegate: dataDelegate + } + } + + //-------------------------------------------------------------------------- + + ListModel { + id: dataModel + + onCountChanged: { + if (count > 0) { + listView.positionViewAtEnd(); + } + } + } + + //-------------------------------------------------------------------------- + + Component { + id: dataDelegate + + Text { + width: ListView.view.width + + text: dataText + color: isValid ? "#00ff00" : "#ff0000" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + + Rectangle { + anchors { + left: parent.left + right: parent.right + top: parent.bottom + } + + height: 1 + color: "#80808080" + } + } + } + + //-------------------------------------------------------------------------- + + RoundButton { + width: 56 * scaleFactor + height: this.width + + anchors.bottom: parent.bottom + anchors.bottomMargin: 80 * scaleFactor + anchors.right: parent.right + anchors.rightMargin: 15 * scaleFactor + + Material.elevation: 6 + Material.background: primaryColor + contentItem: Image { + id:pauseImage + + source: isPaused ? "../assets/play.png" : "../assets/pause.png" + anchors.centerIn: parent + mipmap: true + } + + onClicked: isPaused = isPaused ? false : true; + } + + //-------------------------------------------------------------------------- + + RoundButton { + width: 56 * scaleFactor + height: this.width + + anchors.bottom: parent.bottom + anchors.bottomMargin: 15 * scaleFactor + anchors.right: parent.right + anchors.rightMargin: 15 * scaleFactor + + Material.elevation: 6 + Material.background: primaryColor + contentItem: Image { + id:clearImage + + source: "../assets/clear.png" + anchors.centerIn: parent + mipmap: true + } + + ColorOverlay { + anchors.fill: clearImage + source: clearImage + color: "white" + } + + onClicked: clear(); + } + + //-------------------------------------------------------------------------- +} diff --git a/GNSS Info/views/DescriptionPage.qml b/GNSS Info/views/DescriptionPage.qml new file mode 100644 index 0000000..b7a1475 --- /dev/null +++ b/GNSS Info/views/DescriptionPage.qml @@ -0,0 +1,142 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + id: descPage + + width: parent.width + height: parent.height + + ColumnLayout { + anchors.fill: parent + spacing: 0 + clip: true + + Rectangle { + id: descPageheader + + Layout.fillWidth: true + Layout.preferredHeight: 50 * scaleFactor + color: primaryColor + + Text { + id: aboutApp + + anchors.centerIn: parent + + text: qsTr("About") + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + wrapMode: Text.WordWrap + elide: Text.ElideRight + color: "white" + } + + ImageButton { + source: "../assets/clear.png" + + height: 30 * scaleFactor + width: 30 * scaleFactor + anchors.right: parent.right + anchors.rightMargin: 10 * scaleFactor + anchors.verticalCenter: parent.verticalCenter + + checkedColor: "transparent" + pressedColor: "transparent" + hoverColor: "transparent" + glowColor: "transparent" + + onClicked: descPage.visible = false + } + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + + color: "black" + + Flickable { + anchors.fill: parent + contentHeight: descText.height + clip: true + + Text { + id: descText + + y: 30 * scaleFactor + width: 0.85 * parent.width + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: 0 + + text: app.info.description + font.pixelSize: app.baseFontSize + horizontalAlignment: Text.AlignLeft + wrapMode: Text.WordWrap + elide: Text.ElideRight + linkColor: "#e5e6e7" + color: "white" + + onLinkActivated: Qt.openUrlExternally(link) + } + + RowLayout { + id: appRow + + anchors.top: descText.bottom + anchors.topMargin: 20 * scaleFactor + anchors.left: descText.left + + Text { + id: appVersion + + text: qsTr("App Version: ") + font.pixelSize: app.baseFontSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + color: "white" + } + + Text { + id: appVersionNumber + + text: app.info.version + font.pixelSize: app.baseFontSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + color: "white" + } + } + + RowLayout { + anchors.top: appRow.bottom + anchors.topMargin: 10 * scaleFactor + anchors.left: appRow.left + + Text { + id: frameworkVersion + + text: qsTr("AppFramework Version: ") + font.pixelSize: app.baseFontSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + color: "white" + } + + Text { + id: frameworkVersionNumber + + text: AppFramework.version + font.pixelSize: app.baseFontSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + color: "white" + } + } + } + } + } +} diff --git a/GNSS Info/views/DevicePage.qml b/GNSS Info/views/DevicePage.qml new file mode 100644 index 0000000..539c3f1 --- /dev/null +++ b/GNSS Info/views/DevicePage.qml @@ -0,0 +1,507 @@ +/* Copyright 2018 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.1 +import QtGraphicalEffects 1.0 +import QtQuick.Controls.Material 2.1 + +import ArcGIS.AppFramework.Devices 1.0 + +Item { + id: devicePage + + property DeviceDiscoveryAgent discoveryAgent + property Device currentDevice + property bool isConnecting + property bool isConnected + + property string hostname: hostnameTF.text + property string port: portTF.text + + property bool showDevices: true + property bool bluetoothOnly: Qt.platform.os === "ios" || Qt.platform.os === "android" + + signal networkHostSelected(string hostname, int port) + signal deviceSelected(Device device) + signal disconnect() + + //-------------------------------------------------------------------------- + + onNetworkHostSelected: { + app.settings.setValue("hostname", hostname); + app.settings.setValue("port", port); + + sources.networkHostSelected(hostname, port); + } + + //-------------------------------------------------------------------------- + + onDeviceSelected: { + app.settings.setValue("device", device.name); + + sources.deviceSelected(device); + } + + //-------------------------------------------------------------------------- + + onDisconnect: { + sources.disconnect(); + } + + //-------------------------------------------------------------------------- + + ButtonGroup { + id: buttonGroup + + buttons: [tcpRadioButton, deviceRadioButton] + } + + //-------------------------------------------------------------------------- + + ColumnLayout { + anchors.fill: parent + Layout.fillHeight: true + Layout.fillWidth: true + spacing: 0 + + Label { + Layout.fillWidth: true + + text: qsTr("DISCOVERY SETTINGS") + font.pixelSize: baseFontSize + topPadding: 30 * scaleFactor + bottomPadding: 8 * scaleFactor + leftPadding: 12 * scaleFactor + color: "grey" + } + + Rectangle { + Layout.fillWidth: true + height: 1 * scaleFactor + color: "lightgrey" + } + + //-------------------------------------------------------------------------- + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 200 * scaleFactor + color: navBarColor + + GridLayout { + columns: 4 + rows: 5 + rowSpacing: 0 + + anchors.fill: parent + anchors.leftMargin: 12 * scaleFactor + anchors.rightMargin: 12 * scaleFactor + Material.accent: primaryColor + + //-------------------------------------------------------------------------- + + RadioButton { + id: tcpRadioButton + + Layout.row: 0 + Layout.column: 0 + Layout.columnSpan: 4 + Layout.fillWidth: true + + text: "TCP/UDP Connection" + font.pixelSize: baseFontSize + Material.accent: primaryColor + + checked: false + } + + //-------------------------------------------------------------------------- + + Label { + enabled: !showDevices + visible: !showDevices + + Layout.row: 1 + Layout.column: 0 + + text: "Hostname" + font.pixelSize: baseFontSize + Material.accent: primaryColor + } + + TextField { + id: hostnameTF + + enabled: !showDevices + visible: !showDevices + + Layout.row: 1 + Layout.column: 1 + Layout.columnSpan: 2 + Layout.fillWidth: true + + text: app.settings.value("hostname", ""); + placeholderText: "Hostname" + font.pixelSize: baseFontSize + Material.accent: primaryColor + } + + //-------------------------------------------------------------------------- + + Label { + enabled: !showDevices + visible: !showDevices + + Layout.row: 2 + Layout.column: 0 + + text: "Port" + font.pixelSize: baseFontSize + Material.accent: primaryColor + } + + TextField { + id: portTF + + enabled: !showDevices + visible: !showDevices + + Layout.row: 2 + Layout.column: 1 + Layout.columnSpan: 2 + Layout.fillWidth: true + + text: app.settings.value("port", "").toString(); + placeholderText: "Port" + font.pixelSize: baseFontSize + Material.accent: primaryColor + } + + Button { + id: connectBtn + + enabled: !showDevices && hostname && port + visible: !showDevices + + Layout.row: 2 + Layout.column: 3 + Layout.alignment: Qt.AlignHCenter + + text: qsTr("Connect") + font.pixelSize: baseFontSize + Material.accent: primaryColor + + onClicked: networkHostSelected(hostname, port) + } + + //-------------------------------------------------------------------------- + + RadioButton { + id: deviceRadioButton + + Layout.row: 3 + Layout.column: 0 + Layout.columnSpan: 4 + Layout.fillWidth: true + + text: "External device" + font.pixelSize: baseFontSize + Material.accent: primaryColor + + checked: true + + onCheckedChanged: { + disconnect(); + showDevices = checked + discoverySwitch.checked = false; + } + } + + //-------------------------------------------------------------------------- + + Switch { + id: discoverySwitch + + enabled: showDevices && (bluetoothCheckBox.checked || usbCheckBox.checked) + visible: showDevices + + Layout.row: 4 + Layout.column: 0 + Layout.columnSpan: 2 + Layout.fillWidth: true + + text: "Discovery %1".arg(checked ? "on" : "off") + font.pixelSize: baseFontSize + Material.accent: primaryColor + + Component.onCompleted: { + checked = true; + } + + onCheckedChanged: { + if (checked) { + disconnect(); + discoveryAgent.start(); + } else { + discoveryAgent.stop(); + } + } + + Connections { + target: discoveryAgent + + onRunningChanged: discoverySwitch.checked = discoveryAgent.running + } + } + + CheckBox { + id: bluetoothCheckBox + + enabled: showDevices && !discoverySwitch.checked + visible: showDevices && !bluetoothOnly + + Layout.row: 4 + Layout.column: 2 + + text: "Bluetooth" + font.pixelSize: baseFontSize + Material.accent: primaryColor + + checked: true + + onCheckedChanged: discoveryAgent.detectBluetooth = checked + } + + CheckBox { + id: usbCheckBox + + enabled: showDevices && !discoverySwitch.checked + visible: showDevices && !bluetoothOnly + + Layout.row: 4 + Layout.column: 3 + + text: "USB/COM" + font.pixelSize: baseFontSize + Material.accent: primaryColor + + checked: false + + onCheckedChanged: discoveryAgent.detectSerialPort = checked + } + } + } + + //-------------------------------------------------------------------------- + + Rectangle { + Layout.fillWidth: true + height: 1 * scaleFactor + color: "lightgrey" + } + + Item { + height: 20 * scaleFactor + } + + //-------------------------------------------------------------------------- + + RowLayout { + id: deviceRowLayout + + Label { + Layout.fillWidth: true + + text: qsTr("SELECT A DEVICE") + font.pixelSize: baseFontSize + leftPadding: 12 * scaleFactor + color: "grey" + } + + Item { + height: 25 * scaleFactor + width: height + + BusyIndicator { + anchors.fill: parent + + running: discoveryAgent.running + Material.accent: "#8f499c" + } + } + } + + Item { + height: 5 * scaleFactor + } + + Rectangle { + Layout.fillWidth: true + height: 1 * scaleFactor + color: "lightgrey" + } + + ListView { + id: deviceListView + + enabled: showDevices + visible: showDevices + + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 0 + clip: true + + model: discoveryAgent.devices + delegate: deviceDelegate + } + + Item { + visible: !deviceListView.visible + + Layout.fillWidth: true + Layout.fillHeight: true + } + } + + //-------------------------------------------------------------------------- + + Component { + id: deviceDelegate + + Rectangle { + width: ListView.view.width + height: deviceLayout.height + + color: navBarColor + opacity: parent.enabled ? 1.0 : 0.7 + + ColumnLayout { + id: deviceLayout + + width: parent.width + spacing: 20 * scaleFactor + + RowLayout { + Layout.fillWidth: true + anchors.verticalCenter: parent.verticalCenter + + Item { + width: 12 * scaleFactor + } + + Image { + id: deviceImage + + width: 25 * scaleFactor + height: width + Layout.preferredWidth: 25 * scaleFactor + Layout.preferredHeight: Layout.preferredWidth + + source:"../assets/deviceType-%1.png".arg(deviceType) + fillMode: Image.PreserveAspectFit + } + + ColorOverlay { + anchors.fill: deviceImage + source: deviceImage + color: currentDevice && (currentDevice.name === name) && (isConnecting || isConnected) ? app.primaryColor : "black" + } + + Item { + width: 2 * scaleFactor + } + + Text { + Layout.fillWidth: true + + text: currentDevice && (currentDevice.name === name) ? isConnecting ? name + qsTr(" (Connecting...)") : isConnected ? name + qsTr(" (Connected)") : name : name + font.pixelSize: baseFontSize * 0.9 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + color: currentDevice && (currentDevice.name === name) && (isConnecting || isConnected) ? app.primaryColor : "black" + } + + Image { + id:rightImage + + anchors.right: parent.right + width: 25 * scaleFactor + height: width + Layout.preferredWidth: 25 * scaleFactor + Layout.preferredHeight: Layout.preferredWidth + + source:"../assets/right.png" + fillMode: Image.PreserveAspectFit + } + + ColorOverlay { + anchors.fill: rightImage + source: rightImage + color: currentDevice && (currentDevice.name === name) && (isConnecting || isConnected) ? app.primaryColor : "black" + } + } + + Rectangle { + Layout.fillWidth: true + height: 1 * scaleFactor + color: "lightgrey" + } + } + + MouseArea { + anchors.fill: parent + + onClicked: { + if (!isConnecting && !isConnected || currentDevice && currentDevice.name !== name) { + deviceListView.currentIndex = index; + deviceSelected(discoveryAgent.devices.get(index)); + } else { + app.settings.remove("device"); + + deviceListView.currentIndex = -1; + disconnect(); + } + } + + Component.onCompleted: { + var stored = app.settings.value("device", ""); + + if (showDevices && !isConnecting && !isConnected && stored > "" && stored === name) { + deviceListView.currentIndex = index; + deviceSelected(discoveryAgent.devices.get(index)); + } + } + } + } + } + + //-------------------------------------------------------------------------- + + BusyIndicator { + running: isConnecting + visible: running + + height: 48 * scaleFactor + width: height + anchors.centerIn: parent + + Material.accent:"#8f499c" + } + + // ------------------------------------------------------------------------- +} diff --git a/GNSS Info/views/LocationPage.qml b/GNSS Info/views/LocationPage.qml new file mode 100644 index 0000000..17a2620 --- /dev/null +++ b/GNSS Info/views/LocationPage.qml @@ -0,0 +1,428 @@ +/* Copyright 2018 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtLocation 5.9 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework.Positioning 1.0 +import ArcGIS.AppFramework.Sql 1.0 + +import "../controls" as Controls + +Item { + id: locationPage + + property PositionSource positionSource + property Position position: positionSource.position + property bool isConnected + + property var coordinate: position.coordinate + property var coordinateInfo: Coordinate.convert(coordinate, "dd" , { precision: 8 } ).dd + + property var latitude: coordinate.isValid && coordinateInfo && coordinateInfo.latitudeText ? coordinateInfo.latitudeText : qsTr("No Data") + property var longitude: coordinate.isValid && coordinateInfo && coordinateInfo.longitudeText ? coordinateInfo.longitudeText : qsTr("No Data") + property var gridReference: coordinate.isValid && coordinateInfo && coordinateInfo.text ? coordinateInfo.text : qsTr("No Data") + property var altitude: position.altitudeValid ? app.convertValueToLengthString(coordinate.altitude) : qsTr("No Data") + property var geoidSeparation: position.geoidSeparationValid ? app.convertValueToLengthString(position.geoidSeparation) : qsTr("No Data") + property var speed: position.speedValid ? app.convertValueToSpeedString(position.speed) : qsTr("No Data") + property string course: position.directionValid ? qsTr("%1°").arg(Math.round(position.direction)) : qsTr("No Data") + property string timestamp: coordinate.isValid ? Qt.formatDate(position.timestamp) + " " + Qt.formatTime(position.timestamp, Qt.DefaultLocaleLongDate) : qsTr("No Data") + + property real locationAge + + property string clearBtnText: qsTr("Graphics Cleared") + + //-------------------------------------------------------------------------- + + onCoordinateChanged: { + if (coordinate.isValid) { + map.center = coordinate; + } + } + + //-------------------------------------------------------------------------- + + Map { + id: map + + anchors.fill: parent + + plugin: Plugin { + preferred: ["AppStudio", "ArcGIS", "esri"] + } + + activeMapType: supportedMapTypes[0] + zoomLevel: 19 + center { + latitude: 34.056249 + longitude: -117.195664 + } + + onCopyrightLinkActivated: { + Qt.openUrlExternally(link); + } + + MapItemView { + model: trackPointsModel + + delegate: trackPointItem + } + + Controls.PositionIndicator { + id: positionIndicator + + visible: isConnected + positionSource: locationPage.positionSource + + z: 10001 + } + + Controls.PositionAccuracyIndicator { + positionIndicator: positionIndicator + + z: 10002 + } + + Controls.PositionMarker { + positionIndicator: positionIndicator + + z: 10003 + } + + Column { + id: mapControls + spacing: 5 + + anchors { + right: parent.right + rightMargin: 16 * scaleFactor + bottom: map.bottom + bottomMargin: 16 * scaleFactor + } + + RoundButton { + width: 50 * scaleFactor + height: this.width + + Material.elevation: 6 + Material.background:"white" + + contentItem: Image{ + id:clearImage + + source: "../assets/clear.png" + anchors.centerIn: parent + mipmap: true + } + + ColorOverlay{ + anchors.fill: clearImage + source: clearImage + color: "#4c4c4c" + } + + onClicked: { + if (clearToastMessage.visible === false) { + clearToastMessage.setVisible(); + clear(); + } + } + } + } + } + + //-------------------------------------------------------------------------- + + BusyIndicator { + running: !coordinate.isValid === true + visible: running + + height: 48 * scaleFactor + width: height + anchors.centerIn: parent + + Material.accent:"#8f499c" + } + + //-------------------------------------------------------------------------- + + Rectangle { + id: infoBackground + + width: parent.width + height: infoColumn.height + anchors.top: parent.top + + color: primaryColor + opacity: 0.8 + + ColumnLayout { + id: infoColumn + + Layout.fillHeight: true + + Item { + height: 2 * scaleFactor + } + + Controls.LocationRow { + nameText: "Grid Reference: " + valueText: gridReference + visible: coordBox.currentText === "MGRS" + } + + Controls.LocationRow { + // intentionally blank + visible: coordBox.currentText === "MGRS" + } + + Controls.LocationRow { + nameText: "Latitude: " + valueText: latitude + visible: coordBox.currentText !== "MGRS" + } + + Controls.LocationRow { + nameText: "Longitude: " + valueText: longitude + visible: coordBox.currentText !== "MGRS" + } + + Controls.LocationRow { + nameText: "Altitude: " + valueText: altitude + } + + Controls.LocationRow { + nameText: "Geoid Separation: " + valueText: geoidSeparation + } + + Controls.LocationRow { + nameText: "Timestamp: " + valueText: timestamp + } + + Controls.LocationRow { + nameText: "Speed Over Ground: " + valueText: speed + } + + Controls.LocationRow { + nameText: "True Course Over Ground: " + valueText: course + } + + Item { + height: 2 * scaleFactor + } + } + + ComboBox { + id: coordBox + anchors.top: parent.top + anchors.topMargin: 3 * scaleFactor + anchors.right: parent.right + anchors.rightMargin: 5 * scaleFactor + + font.pixelSize: baseFontSize * 0.9 + background.implicitWidth: 90 * scaleFactor + background.implicitHeight: 32 * scaleFactor + + delegate: ItemDelegate { + width: coordBox.width + contentItem: Text { + text: modelData + font: coordBox.font + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + highlighted: coordBox.highlightedIndex === index + } + + model: ["DD", "DDM", "DMS", "MGRS"] + + currentIndex: 2 + + onCurrentIndexChanged: { + if (coordinate.isValid) { + setCoordinateInfo(); + } + } + + function setCoordinateInfo() { + switch(model[currentIndex]) { + case "DD": + coordinateInfo = Coordinate.convert(coordinate, "dd", { precision: 8 }).dd; + break; + case "DDM": + coordinateInfo = Coordinate.convert(coordinate, "ddm", { precision: 6 }).ddm; + break; + case "DMS": + coordinateInfo = Coordinate.convert(coordinate, "dms", { precision: 4 }).dms; + break; + case "MGRS": + // "precision" here is in metres, whereas it's the number of digits in the above + coordinateInfo = Coordinate.convert(coordinate, "mgrs", { precision: 1, spaces: true }).mgrs; + break; + } + } + } + } + + Connections { + target: positionSource + + onPositionChanged: { + if (position.coordinate.isValid) { + coordBox.setCoordinateInfo(); + } + } + } + + //-------------------------------------------------------------------------- + + Text { + id: warning + + visible: locationAge > 30 || !isConnected + + anchors { + top: infoBackground.bottom + horizontalCenter: parent.horizontalCenter + } + + text: !isConnected ? qsTr("Device disconnected") : qsTr("%1s since last location received").arg(Math.round(locationAge)) + color: app.primaryColor + font.bold: true + font.pointSize: baseFontSize + } + + //-------------------------------------------------------------------------- + + Rectangle { + id: clearToastMessage + + signal setVisible() + + onSetVisible: { + opacity = 0.8; + opacityAnimator.running = true; + } + + visible: opacity !== 0 + + height: 40 * scaleFactor + width: clearBtnLabel.width + 50 * scaleFactor + + anchors.bottom: parent.bottom + anchors.bottomMargin: 15* scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + radius: 20 * scaleFactor + color: app.primaryColor + opacity: 0 + + Label { + id: clearBtnLabel + anchors.centerIn: parent + font.bold: true + font.pixelSize: baseFontSize * 1.1 + color: "white" + text: clearBtnText + } + + OpacityAnimator { + id: opacityAnimator + + target: clearToastMessage + duration: 5000 + easing.type: Easing.InCubic + from: clearToastMessage.opacity + to: 0 + } + } + + //-------------------------------------------------------------------------- + + ListModel { + id: trackPointsModel + } + + //-------------------------------------------------------------------------- + + Component { + id: trackPointItem + + MapCircle { + visible: positionIndicator.visible + + center { + latitude: trackLatitude + longitude: trackLongitude + } + + radius: horizontalAccuracy + color: '#40ff0000' + border { + color: "#40ffffff" + width: 1 + } + } + + } + + //-------------------------------------------------------------------------- + + Timer { + running: isConnected && coordinate.isValid + interval: 10000 + repeat: true + + onTriggered: { + addTrackPoint(position.coordinate, position.horizontalAccuracyValid ? position.horizontalAccuracy : 10); + locationAge = coordinate.isValid ? (((new Date().getTime()) - position.timestamp.getTime()))/1000 : 0 + } + } + + //-------------------------------------------------------------------------- + + function addTrackPoint(coordinate, horizontalAccuracy) { + var trackPoint = { + trackLatitude: coordinate.latitude, + trackLongitude: coordinate.longitude, + horizontalAccuracy: horizontalAccuracy + }; + + console.log("trackPoint:", JSON.stringify(trackPoint, 2, undefined)); + + trackPointsModel.append(trackPoint); + } + + //-------------------------------------------------------------------------- + + function clear() { + trackPointsModel.clear(); + locationAge = 0; + } + + //-------------------------------------------------------------------------- +} + + diff --git a/GNSS Info/views/OldLocationPage.txt b/GNSS Info/views/OldLocationPage.txt new file mode 100644 index 0000000..592876e --- /dev/null +++ b/GNSS Info/views/OldLocationPage.txt @@ -0,0 +1,328 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import Esri.ArcGISRuntime 100.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Devices 1.0 +import ArcGIS.AppFramework.Positioning 1.0 +import ArcGIS.AppFramework.Sql 1.0 + +import "../controls" as Controls + +Item { + id: locationPage + + property var coordinate: position.coordinate + property var coordinateInfo: Coordinate.convert(position.coordinate, "dd" , { precision: 8 } ) + property var latitude: coordinate.isValid ? coordinateInfo.dd.latitudeText : null + property var longitude: coordinate.isValid ? coordinateInfo.dd.longitudeText : null + property var altitude: position.altitudeValid ? qsTr("%1 meters").arg(coordinate.altitude) : null + property var geoidsSeparation: position.geoidSeparationValid ? qsTr("%1 meters").arg(position.geoidSeparation) : null + property date timestamp: position.timestamp + property var locationAge + property var speed: position.speedValid ? qsTr("%1 mps").arg(position.speed.toFixed(2)): null + property string course: position.directionValid ? qsTr("%1 degrees").arg(position.direction) : null + + property alias pointGraphicsOverlay: _pointGraphicsOverlay + + property string clearBtnText: qsTr("Graphics Cleared") + + property string dataPath: AppFramework.userHomeFolder.filePath("ArcGIS/AppStudio/Data") + property string inputdata: "WorldMap_WM.mmpk" + property string outputdata: dataPath + "/" + inputdata + + MapView { + id: mapView + anchors.fill: parent + + property real initialMapRotation: 0 + + rotationByPinchingEnabled: true + zoomByPinchingEnabled: true + wrapAroundMode: Enums.WrapAroundModeEnabledWhenSupported + + Rectangle { + id: infoBackground + width: parent.width + height: parent.height * 0.28 + anchors.top: parent.top + opacity: 0.8 + + ListView { + id: list + anchors.fill: parent + clip: true + model: statusModel + interactive: false + + delegate: Rectangle { + width: ListView.view.width + height: infoBackground.height / 7 + color: lightPrimaryColor + + Text { + id: nameText + text: name + anchors.left: parent.left + anchors.leftMargin: 5 * scaleFactor + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: baseFontSize + font.bold: true + color: "white" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + + Text { + property bool valid: true + + anchors.left: nameText.right + text: locationPage[attr] || qsTr("No Data") + font.pixelSize: baseFontSize + anchors.verticalCenter: parent.verticalCenter + font.bold: true + font.italic: !valid + color: valid ? "white" : "darkred" + } + } + } + } + + Map { + id: onlineMap + BasemapTopographic{} + + onLoadStatusChanged: { + if (loadStatus === Enums. LoadStatusLoaded) { + + if (!mapView.locationDisplay.started) { + mapView.locationDisplay.start() + mapView.locationDisplay.autoPanMode = Enums.LocationDisplayAutoPanModeRecenter + timer.start() + } else { + mapView.locationDisplay.stop() + timer.stop() + } + } + } + } + + GraphicsOverlay { + id: _pointGraphicsOverlay + } + + Text { + id: warning + anchors { + top: infoBackground.bottom + horizontalCenter: parent.horizontalCenter + } + color: app.primaryColor + font.bold: true + font.pointSize: baseFontSize + visible: locationAge > 30 + text: Math.round(locationAge) + "s since last location received" + } + + Column { + id: mapControls + spacing: 5 + + anchors{ + right: parent.right + rightMargin: 16 * scaleFactor + verticalCenter: mapView.verticalCenter + } + + RoundButton { + width: 50 * scaleFactor + height: this.width + Material.elevation: 6 + Material.background:"white" + opacity: mapView.mapRotation ? 1 : 0 + rotation: mapView.mapRotation + contentItem: Image{ + id:compassImage + source: "../assets/compass.png" + anchors.centerIn: parent + mipmap: true + } + + onClicked: { + mapView.setViewpointRotation(mapView.initialMapRotation) + } + } + + RoundButton { + width: 50 * scaleFactor + height: this.width + Material.elevation: 6 + Material.background:"white" + contentItem: Image{ + id:locationImage + source: "../assets/location.png" + anchors.centerIn: parent + mipmap: true + } + ColorOverlay{ + id: colorOverlay + anchors.fill: locationImage + source: locationImage + color: "#4c4c4c" + } + + onClicked: { + mapView.locationDisplay.autoPanMode = Enums.LocationDisplayAutoPanModeRecenter + colorOverlay.color = "steelblue" + } + } + + RoundButton { + width: 50 * scaleFactor + height: this.width + Material.elevation: 6 + Material.background:"white" + contentItem: Image{ + id:clearImage + source: "../assets/clear.png" + anchors.centerIn: parent + mipmap: true + } + ColorOverlay{ + anchors.fill: clearImage + source: clearImage + color: "#4c4c4c" + } + onClicked: { + if (clearToastMessage.visible === false){ + clearToastMessage.visible = true + _pointGraphicsOverlay.graphics.clear() + } + } + } + } + + locationDisplay { + positionSource: positionSource + } + + onMousePressed: { + colorOverlay.color = "#4c4c4c" + } + + Rectangle { + id: clearToastMessage + anchors. bottom: parent.bottom + anchors.bottomMargin: 15* scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + height: 40 * scaleFactor + width: clearBtnLabel.width + 50 * scaleFactor + radius: 20 * scaleFactor + color: app.primaryColor + opacity: 0.8 + visible: false + + Label { + id: clearBtnLabel + anchors.centerIn: parent + font.bold: true + font.pixelSize: baseFontSize * 1.1 + color: "white" + text: clearBtnText + } + } + + Timer { + interval: 5000 // triggers every 5000 ms + onTriggered: { + if (clearToastMessage.visible === true){ + clearToastMessage.visible = false + } + } + running: true + repeat: true + } + + BusyIndicator { + anchors.centerIn: parent + height: 48 * scaleFactor + width: height + running: true + Material.accent:"#8f499c" + visible: !coordinate.isValid === true + } + } + + property var wgs84: SpatialReference.createWgs84() + + function addTrackPoint(y, x) { + console.log("Create Symbol"); + var symbol = ArcGISRuntimeEnvironment.createObject("SimpleMarkerSymbol", { + style: Enums.SimpleMarkerSymbolStyleCircle, + size: 10, + color: "red" + } ); + var point = ArcGISRuntimeEnvironment.createObject("Point", { x: x , y: y, spatialReference: wgs84} ); + var projpoint = GeometryEngine.project(point,mapView.spatialReference); + var graphic = ArcGISRuntimeEnvironment.createObject("Graphic", { geometry: projpoint, symbol: symbol } ); + + //console.log(JSON.stringify(positionSource.position.coordinate, 2, undefined)); + _pointGraphicsOverlay.graphics.append(graphic); + } + + Timer { + id: timer + running: false + repeat: true + interval: 10000 + + onTriggered: { + addTrackPoint(position.coordinate.latitude, position.coordinate.longitude) + locationAge = coordinate.isValid ? (((new Date().getTime()) - position.timestamp.getTime()))/1000 : null // This will be in seconds + } + } + + ListModel{ + id: statusModel + + ListElement { + name: qsTr("Latitude: ") + attr: "latitude" + } + + ListElement { + name: qsTr("Longitude: ") + attr: "longitude" + } + + ListElement { + name: qsTr("Altitude: ") + attr: "altitude" + } + + + ListElement { + name: qsTr("Geoid Separation: ") + attr: "geoidsSeparation" + } + + ListElement { + name: "Timestamp: "; + attr: "timestamp" + } + + ListElement { + name: qsTr("Speed Over Ground: ") + attr: "speed" + } + + ListElement { + name: qsTr("True Course Over Ground: ") + attr: "course" + } + } +} + + diff --git a/GNSS Info/views/QualityPage.qml b/GNSS Info/views/QualityPage.qml new file mode 100644 index 0000000..98c2a09 --- /dev/null +++ b/GNSS Info/views/QualityPage.qml @@ -0,0 +1,388 @@ +/* Copyright 2018 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 + +import ArcGIS.AppFramework.Positioning 1.0 + +import "../controls" as Controls + +Page { + id: qualityPage + + property PositionSource positionSource + property Position position: positionSource.position + + property var fixType: position.fixType + property var gpsMode: gpsModeText(fixType) + property var accuracyType: position.accuracyType + property var accuracyMode: accuracyText(accuracyType) + + property var hdop: position.hdopValid ? position.hdop : null + property var vdop: position.vdopValid ? position.vdop : null + property var pdop: position.pdopValid ? position.pdop : null + + property var hpe: position.horizontalAccuracyValid ? app.convertValueToLengthString(position.horizontalAccuracy) : null + property var vpe: position.verticalAccuracyValid ? app.convertValueToLengthString(position.verticalAccuracy) : null + property var epe: position.positionAccuracyValid ? app.convertValueToLengthString(position.positionAccuracy) : null + + property var laterr: position.latitudeErrorValid ? app.convertValueToLengthString(position.latitudeError) : null + property var lonerr: position.longitudeErrorValid ? app.convertValueToLengthString(position.longitudeError) : null + property var alterr: position.altitudeErrorValid ? app.convertValueToLengthString(position.altitudeError) : null + + property var diffAge: position.differentialAgeValid ? qsTr("%1 s").arg(Math.round(position.differentialAge)) : null + + property var satInUse: position.satellitesInUseValid ? position.satellitesInUse : null + property var satVisible: position.satellitesVisibleValid ? position.satellitesVisible : null + + //-------------------------------------------------------------------------- + + Flickable { + anchors.fill: parent + contentWidth: parent.width + contentHeight: qualityColumn.height + clip: true + + ColumnLayout { + id: qualityColumn + Layout.fillHeight: true + Layout.fillWidth: true + spacing: 0 + + Column { + topPadding: 20 * scaleFactor + spacing: 0 + + Label { + id: accuracyLabel + + text: qsTr("GPS ACCURACY") + font.pixelSize: baseFontSize * 0.95 + leftPadding: 12 * scaleFactor + bottomPadding: 8 * scaleFactor + color: "grey" + } + } + + //-------------------------------------------------------------------------- + + ListView { + id: gpsModeListView + + Layout.preferredWidth: app.width + Layout.preferredHeight: 70 * scaleFactor + spacing: 0 + clip: true + + model: gpsModeListModel + delegate: Controls.CustomizedDelegate {} + interactive: false + } + + ListModel { + id: gpsModeListModel + + ListElement { + label: qsTr("GPS mode: ") + attr : "gpsMode" + } + + ListElement { + label: qsTr("Differential age: ") + attr : "diffAge" + } + } + + Rectangle { + Layout.preferredWidth: app.width + Layout.preferredHeight: 1 * scaleFactor + color: "lightgrey" + } + + Item { + height: 20 * scaleFactor + } + + //-------------------------------------------------------------------------- + + ListView { + id: positionAccuracyListView + + Layout.preferredWidth: app.width + Layout.preferredHeight: 140 * scaleFactor + spacing: 0 + clip: true + + model: positionAccuracyModel + delegate: Controls.CustomizedDelegate {} + interactive: false + } + + ListModel { + id: positionAccuracyModel + + ListElement { + label: qsTr("Accuracy mode: ") + attr : "accuracyMode" + } + + ListElement { + label: qsTr("Horizontal accuracy: ") + attr : "hpe" + } + + ListElement { + label: qsTr("Vertical accuracy: ") + attr : "vpe" + } + + ListElement { + label: qsTr("Position accuracy: ") + attr : "epe" + } + } + + Rectangle { + Layout.preferredWidth: app.width + Layout.preferredHeight: 1 * scaleFactor + color: "lightgrey" + } + + Item { + Layout.preferredHeight: 20 * scaleFactor + } + + //-------------------------------------------------------------------------- + + ListView { + id: positionErrorListView + + Layout.preferredWidth: app.width + Layout.preferredHeight: 105 * scaleFactor + spacing: 0 + clip: true + + model: positionErrorModel + delegate: Controls.CustomizedDelegate {} + interactive: false + } + + ListModel { + id: positionErrorModel + + ListElement { + label: qsTr("Latitude error: ") + attr : "laterr" + } + + ListElement { + label: qsTr("Longitude error: ") + attr : "lonerr" + } + + ListElement { + label: qsTr("Altitude error: ") + attr : "alterr" + } + } + + Rectangle { + Layout.preferredWidth: app.width + Layout.preferredHeight: 1 * scaleFactor + color: "lightgrey" + } + + Item { + Layout.preferredHeight: 20 * scaleFactor + } + + //-------------------------------------------------------------------------- + + ListView { + id: dopListView + + Layout.preferredWidth: app.width + Layout.preferredHeight: 105 * scaleFactor + spacing: 0 + clip: true + + model: dopModel + delegate: Controls.CustomizedDelegate {} + interactive: false + } + + ListModel { + id: dopModel + + ListElement { + label: qsTr("HDOP: ") + attr : "hdop" + } + + ListElement { + label: qsTr("VDOP: ") + attr : "vdop" + } + + ListElement { + label: qsTr("PDOP: ") + attr : "pdop" + } + } + + Rectangle { + Layout.preferredWidth: app.width + Layout.preferredHeight: 1 * scaleFactor + color: "lightgrey" + } + + Item { + Layout.preferredHeight: 20 * scaleFactor + } + + //-------------------------------------------------------------------------- + + Column { + topPadding: 0 * scaleFactor + + Label { + id: satLabel + + text: qsTr("SATELLITE INFORMATION") + font.pixelSize: baseFontSize * 0.95 + leftPadding: 12 * scaleFactor + bottomPadding: 8 * scaleFactor + color: "grey" + } + } + + //-------------------------------------------------------------------------- + + ListView { + id: sateListView + + Layout.preferredWidth: app.width + Layout.preferredHeight: 70 * scaleFactor + spacing: 0 + clip: true + + model: sateListModel + delegate: Controls.CustomizedDelegate {} + interactive: false + } + + ListModel { + id: sateListModel + + ListElement { + label: qsTr("Satellites in view: ") + attr : "satVisible" + } + + ListElement { + label: qsTr("Satellites in use: ") + attr : "satInUse" + } + } + + Rectangle { + Layout.preferredWidth: app.width + Layout.preferredHeight: 1 * scaleFactor + color: "lightgrey" + } + } + } + + //-------------------------------------------------------------------------- + + function gpsModeText (fixType) { + var result = "" ; + + switch (fixType) { + case Position.NoFix: + result = qsTr("No Fix"); + break; + + case Position.GPS: + result = qsTr("GPS"); + break; + + case Position.DifferentialGPS: + result = qsTr("Differential GPS"); + break; + + case Position.PrecisePositioningService: + result = qsTr("Precise Positioning Service"); + break; + + case Position.RTKFixed: + result = qsTr("RKT Fixed"); + break; + + case Position.RTKFloat: + result = qsTr("RKT Float"); + break; + + case Position.Estimated: + result = qsTr("Estimated"); + break; + + case Position.Manual: + result = qsTr("Manual"); + break; + + case Position.Simulator: + result = qsTr("Simulator"); + break; + + case Position.Sbas: + result = qsTr("Sbas"); + break; + } + + return result; + } + + //-------------------------------------------------------------------------- + + function accuracyText (accuracyType) { + var result = "" ; + + switch (accuracyType) { + case Position.RMS: + result = qsTr("Error RMS"); + break; + + case Position.DOP: + result = qsTr("DOP Based"); + break; + + default: + result = qsTr("Unknown"); + break; + } + + return result; + } + + //-------------------------------------------------------------------------- +} + + + + diff --git a/GNSS Info/views/SkyPlotPage.qml b/GNSS Info/views/SkyPlotPage.qml new file mode 100644 index 0000000..8c32938 --- /dev/null +++ b/GNSS Info/views/SkyPlotPage.qml @@ -0,0 +1,332 @@ +/* Copyright 2018 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 +import QtCharts 2.2 + +import ArcGIS.AppFramework.Positioning 1.0 + +Page { + id: skyPlotPage + + property PositionSource positionSource + property Position position: positionSource.position + property SatelliteInfoSource satelliteInfoSource + + property int numNotInUse + property int numInUse + + property bool doSteregraphicProjection: true + + signal clear(); + + //-------------------------------------------------------------------------- + + onClear: { + notInUseSeries.clear(); + inUseSeries.clear(); + + numNotInUse = 0; + numInUse = 0; + } + + //-------------------------------------------------------------------------- + + PolarChartView { + id: chartView + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: app.height * 0.6 + + title: qsTr("In View: %1 / In Use: %2").arg(position.satellitesVisible).arg(position.satellitesInUse) + + legend.visible: true + legend.font.pointSize: 11 + antialiasing: true + + CategoryAxis { + id: angularAxis + min: 0 + max: 360 + labelsPosition: CategoryAxis.AxisLabelsPositionOnValue + + CategoryRange { + label: "N" + endValue: 0 + } + CategoryRange { + label: "NE" + endValue: 45 + } + CategoryRange { + label: "E" + endValue: 90 + } + CategoryRange { + label: "SE" + endValue: 135 + } + CategoryRange { + label: "S" + endValue: 180 + } + CategoryRange { + label: "SW" + endValue: 225 + } + CategoryRange { + label: "W" + endValue: 270 + } + CategoryRange { + label: "NW" + endValue: 315 + } + } + + CategoryAxis { + id: radialAxis + min: 0 + max: 90 + labelsPosition: CategoryAxis.AxisLabelsPositionOnValue + + CategoryRange { + label: "90" + endValue: project(90, doSteregraphicProjection) + } + CategoryRange { + label: "60" + endValue: project(60, doSteregraphicProjection) + } + CategoryRange { + label: "30" + endValue: project(30, doSteregraphicProjection) + } + CategoryRange { + label: "0" + endValue: project(0, doSteregraphicProjection) + } + } + + ScatterSeries { + id: notInUseSeries + + name: qsTr("Not Used: %1").arg(numNotInUse) + axisAngular: angularAxis + axisRadial: radialAxis + markerSize: 10 + color: "grey" + borderColor: "dimgrey" + markerShape: ScatterSeries.MarkerShapeRectangle + } + + ScatterSeries { + id: inUseSeries + + name: qsTr("In Use: %1").arg(numInUse) + axisAngular: angularAxis + axisRadial: radialAxis + markerSize: 10 + color: primaryColor + borderColor: darkPrimaryColor + markerShape: ScatterSeries.MarkerShapeRectangle + } + } + + //-------------------------------------------------------------------------- + + Connections { + target: satelliteInfoSource + + onSatellitesInViewChanged : { + clear(); + + for (var i = 0; i < satelliteInfoSource.satellitesInView.count; i++) { + var info = satelliteInfoSource.satellitesInView.get(i); + + if (info.satelliteIdentifier > -1) { + if (!info.isInUse) { + notInUseSeries.append(info.azimuth, project(info.elevation, doSteregraphicProjection)); + numNotInUse++; + } else { + inUseSeries.append(info.azimuth, project(info.elevation, doSteregraphicProjection)); + numInUse++; + } + } + } + } + } + + Rectangle { + id: chart + + anchors.top: chartView.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: rect.myMargin + border.width: 3 + radius: 10 + border.color: "black" + + Item { + id: rect + + anchors.fill: parent + anchors.margins: myMargin + property int myMargin: 5 + + Row { + id: view + + property int rows: satelliteInfoSource.satellitesInView.count + property int singleWidth: ((rect.width - scale.width) / rows) - rect.myMargin + spacing: rect.myMargin + + Rectangle { + id: scale + width: strengthLabel.width+10 + height: rect.height + color: "#32cd32" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: lawngreenRect.top + font.pointSize: 11 + text: "50" + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + font.pointSize: 11 + text: "100" + } + + Rectangle { + id: redRect + width: parent.width + color: "red" + height: parent.height*10/100 + anchors.bottom: parent.bottom + Text { + id: strengthLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "00" + } + } + Rectangle { + id: orangeRect + height: parent.height*10/100 + anchors.bottom: redRect.top + width: parent.width + color: "#ffa500" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "10" + } + } + Rectangle { + id: goldRect + height: parent.height*10/100 + anchors.bottom: orangeRect.top + width: parent.width + color: "#ffd700" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "20" + } + } + Rectangle { + id: yellowRect + height: parent.height*10/100 + anchors.bottom: goldRect.top + width: parent.width + color: "yellow" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "30" + } + } + Rectangle { + id: lawngreenRect + height: parent.height*10/100 + anchors.bottom: yellowRect.top + width: parent.width + color: "#7cFc00" + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + font.pointSize: 11 + text: "40" + } + } + } + + Repeater { + id: repeater + + model: satelliteInfoSource.satellitesInView + delegate: Rectangle { + height: rect.height + width: view.singleWidth + + Rectangle { + id: bar + + anchors.bottom: parent.bottom + width: parent.width + height: parent.height*signalStrength/100 < parent.height ? parent.height*signalStrength/100 : parent.height + color: isInUse ? primaryColor : "darkgrey" + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: bar.top + text: satelliteIdentifier + Layout.alignment: horizontalAlignment + horizontalAlignment: Text.AlignHCenter + font.pointSize: 11 + } + } + } + } + } + } + + //-------------------------------------------------------------------------- + + // stereographic projection with origin at the nadir + function project(elevation, doProjection) { + return (doProjection ? 90 * Math.tan((90-elevation)/2 * Math.PI/180) : 90-elevation); + } + + //-------------------------------------------------------------------------- +} + + + + diff --git a/Listed Related Features/MyApp.qml b/Listed Related Features/MyApp.qml new file mode 100644 index 0000000..883508a --- /dev/null +++ b/Listed Related Features/MyApp.qml @@ -0,0 +1,264 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.2 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 +import Esri.ArcGISRuntime 100.2 + +import "controls" as Controls + +App { + id: app + width: 414 + height: 736 + function units(value) { + return AppFramework.displayScaleFactor * value + } + property real scaleFactor: AppFramework.displayScaleFactor + property int baseFontSize : app.info.propertyValue("baseFontSize", 15 * scaleFactor) + (isSmallScreen ? 0 : 3) + property bool isSmallScreen: (width || height) < units(400) + property FeatureLayer alaskaNationalParks: null + + Page{ + anchors.fill: parent + header: ToolBar{ + id:header + width: parent.width + height: 50 * scaleFactor + Material.background: "#8f499c" + Controls.HeaderBar{} + } + + // sample starts here ------------------------------------------------------------------ + contentItem: Rectangle { + anchors.top:header.bottom + + MapView { + id: mapView + anchors.fill: parent + + // bind the insets to the attribute view so the attribution text shows when the view expands + viewInsets.bottom: attributeView.height / scaleFactor + + Map { + id: map + initUrl: "https://arcgis.com/home/item.html?id=dcc7466a91294c0ab8f7a094430ab437" + + onLoadStatusChanged: { + if (loadStatus !== Enums.LoadStatusLoaded) + return; + + // get the Alaska National Parks feature layer + map.operationalLayers.forEach(function(fl) { + if (fl.name.indexOf("- Alaska National Parks") !== -1) { + alaskaNationalParks = fl; + alaskaNationalParks.selectionColor = "yellow"; + alaskaNationalParks.selectionWidth = 5; + } + }); + } + } + + onMouseClicked: { + // hide the attribute view + attributeView.height = 0; + + // clear the list model + relatedFeaturesModel.clear(); + + // create objects required to do a selection with a query + var clickPoint = mouse.mapPoint; + var mapTolerance = 10 * mapView.unitsPerDIP; + var envelope = ArcGISRuntimeEnvironment.createObject("Envelope", { + xMin: clickPoint.x - mapTolerance, + yMin: clickPoint.y - mapTolerance, + xMax: clickPoint.x + mapTolerance, + yMax: clickPoint.y + mapTolerance, + spatialReference: map.spatialReference + }); + var queryParams = ArcGISRuntimeEnvironment.createObject("QueryParameters"); + queryParams.geometry = envelope; + queryParams.spatialRelationship = Enums.SpatialRelationshipIntersects; + + // clear any selections + alaskaNationalParks.clearSelection(); + + // select features + alaskaNationalParks.selectFeaturesWithQuery(queryParams, Enums.SelectionModeNew); + } + } + + Connections { + target: alaskaNationalParks + + onSelectFeaturesStatusChanged: { + if (alaskaNationalParks.selectFeaturesStatus === Enums.TaskStatusErrored) { + var errorString = "Error: %1".arg(alaskaNationalParks.error.message); + msgDialog.text = errorString; + msgDialog.open(); + console.log(errorString); + } else if (alaskaNationalParks.selectFeaturesStatus === Enums.TaskStatusCompleted) { + var featureQueryResult = alaskaNationalParks.selectFeaturesResult; + + // iterate over features returned + while (featureQueryResult.iterator.hasNext) { + var arcGISFeature = featureQueryResult.iterator.next(); + var selectedTable = arcGISFeature.featureTable; + + // connect signal + selectedTable.queryRelatedFeaturesStatusChanged.connect(function() { + if (selectedTable.queryRelatedFeaturesStatus !== Enums.TaskStatusCompleted) + return; + + var relatedFeatureQueryResultList = selectedTable.queryRelatedFeaturesResults; + + // iterate over returned RelatedFeatureQueryResults + for (var i = 0; i < relatedFeatureQueryResultList.length; i++) { + + // iterate over Features returned + var iter = relatedFeatureQueryResultList[i].iterator; + while (iter.hasNext) { + var feat = iter.next(); + var displayFieldName = feat.featureTable.layerInfo.displayFieldName; + var serviceLayerName = feat.featureTable.layerInfo.serviceLayerName; + var displayFieldValue = feat.attributes.attributeValue(displayFieldName); + + // add the related feature info to a list model + var listElement = { + "displayFieldName" : displayFieldName, + "displayFieldValue" : displayFieldValue, + "serviceLayerName" : serviceLayerName + }; + relatedFeaturesModel.append(listElement); + } + } + + // show the attribute view + attributeView.height = 200 * scaleFactor + }); + + // zoom to the feature + mapView.setViewpointGeometryAndPadding(arcGISFeature.geometry, 100) + + // query related features + selectedTable.queryRelatedFeatures(arcGISFeature); + } + } + } + } + + Rectangle { + id: attributeView + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 0 + + // Animate the expand and collapse of the legend + Behavior on height { + SpringAnimation { + spring: 3 + damping: 0.4 + } + } + + ListView { + anchors { + fill: parent + margins: 5 * scaleFactor + } + + clip: true + model: relatedFeaturesModel + spacing: 5 * scaleFactor + + // Create delegate to display the attributes + delegate: Rectangle { + width: app.width + height: 15 * scaleFactor + color: "transparent" + + Label { + anchors.horizontalCenter: parent.horizontalCenter + anchors.margins: 10 * scaleFactor + text: displayFieldValue + font.pixelSize: 12 * scaleFactor + } + } + + // Create a section to separate features by table + section { + property: "serviceLayerName" + criteria: ViewSection.FullString + labelPositioning: ViewSection.CurrentLabelAtStart | ViewSection.InlineLabels + delegate: Rectangle { + width: app.width + height: 20 * scaleFactor + color: "#8f499c" + + Label { + anchors.horizontalCenter: parent.horizontalCenter + text: section + font { + bold: true + pixelSize: 13 * scaleFactor + } + elide: Text.ElideRight + clip: true + color: "white" + } + } + } + } + } + + ListModel { + id: relatedFeaturesModel + } + + MessageDialog { + id: msgDialog + } + + //Busy Indicator + BusyIndicator { + id:mapDrawingWindow + anchors.centerIn: parent + height: 48 * scaleFactor + width: height + running: true + Material.accent:"#8f499c" + visible: (mapView.drawStatus === Enums.DrawStatusInProgress) + } + } + } + + // sample ends here ------------------------------------------------------------------------ + Controls.DescriptionPage{ + id:descPage + visible: false + } +} + diff --git a/Listed Related Features/MyApp.qmlproject b/Listed Related Features/MyApp.qmlproject new file mode 100644 index 0000000..b8ff5f5 --- /dev/null +++ b/Listed Related Features/MyApp.qmlproject @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ + +import QmlProject 1.1 + +Project { + mainFile: "MyApp.qml" + + QmlFiles { + directory: "." + recursive: true + } + + JavaScriptFiles { + directory: "." + recursive: true + } + + ImageFiles { + directory: "." + recursive: true + } + + Files { + directory: "." + recursive: true + filter: "*.json;*.html;*.txt" + } + + importPaths: [ + ] +} + diff --git a/Listed Related Features/README.md b/Listed Related Features/README.md new file mode 100644 index 0000000..906dba3 --- /dev/null +++ b/Listed Related Features/README.md @@ -0,0 +1,43 @@ +## Listed Related Features + +This sample demonstrates how to query a layer for features that are in a related table + +A Map is constructed by passing a URL to a web map stored in ArcGIS Online. A connection is made to the mouseClicked signal of the MapView, and when this signal emits, selectFeatures is executed on the FeatureLayer. Once this completes, a FeatureQueryResult is returned, giving access to the FeatureIterator. This FeatureIterator contains all of the Feature objects selected with the mouse click. These Feature objects are iterated over, and for each object, the queryRelatedFeatures method is called. Once this completes, the returned RelatedFeatureQueryResult gives access to all of the related features, and the UI is populated with this information. + +[Resource Level](https://geonet.esri.com/groups/appstudio/blog/2016/12/06/how-to-describe-our-resources-in-terms-of-difficulty-complexity-and-time-to-digest): 🍌 + + +## Instructions to run this sample in AppStudio Desktop + +1. Download the `.zip` file +2. Unzip and copy this folder into AppStudio Apps folder (Windows: `C:\Users\\ArcGIS\AppStudio\Apps` Mac or linux: `Home\ArcGIS\AppStudio\Apps`) +3. The new app will now appear in the AppStudio Desktop. Run the application or open it in the bundled Qt-Creator IDE to look at the code and modify. + +## Issues + +Find a bug or want to request a new feature? Please let us know by submitting an issue. + +## Contributing + +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). + +## Licensing +Copyright 2017 Esri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +A copy of the license is available in the repository's [license.txt](license.txt) file. + + +[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) +[](Esri Language: Qt QML JavaScript) diff --git a/Listed Related Features/appicon.png b/Listed Related Features/appicon.png new file mode 100644 index 0000000..39504b9 Binary files /dev/null and b/Listed Related Features/appicon.png differ diff --git a/Listed Related Features/appinfo.json b/Listed Related Features/appinfo.json new file mode 100644 index 0000000..7a79dbc --- /dev/null +++ b/Listed Related Features/appinfo.json @@ -0,0 +1,89 @@ +{ + "arcgisRuntime": 1002, + "capabilities": { + "audio": false, + "backgroundLocation": false, + "biometricAuthentication": false, + "bluetooth": false, + "camera": false, + "fileSharing": false, + "highAccuracyLocation": false, + "ios": { + "externalAccessoryProtocolStrings": [ + ] + }, + "localnotification": false, + "location": true, + "microphone": false, + "network": true, + "storage": false, + "vibration": false + }, + "deployment": { + "android": { + "packageName": "" + }, + "arcgisRuntimeExtensionsLicense": "", + "arcgisRuntimeLicense": "", + "clientId": "", + "ios": { + "bundleId": "", + "codeSignIdentity": "" + }, + "macos": { + "bundleId": "", + "codeSignIdentity": "" + }, + "publisherName": "", + "uwp": { + "packageName": "", + "productID": "" + }, + "winphone": { + "packageDisplayName": "" + } + }, + "devicesTypes": [ + "desktop", + "tablet", + "phone" + ], + "display": { + "desktop": { + "minimumHeight": 0, + "minimumWidth": 0, + "windowMode": "default" + }, + "phone": { + "landscape": true, + "portrait": true, + "showStatusBar": true + }, + "tablet": { + "landscape": true, + "portrait": true, + "showStatusBar": true + } + }, + "environment": { + }, + "launchUrlSchemes": [ + ], + "mainFile": "MyApp.qml", + "multipleInstances": true, + "projectFile": "MyApp.qmlproject", + "properties": { + }, + "resources": { + "appIcon": "default-app.png", + "launchImageBackground": "launchimage-background.png", + "launchImageBackgroundColor": "#ffffff", + "launchImageOverlay": "launchimage-overlay.png" + }, + "type": "app", + "urlScheme": "", + "version": { + "major": 1, + "micro": 1 + } +} diff --git a/Listed Related Features/assets/clear.png b/Listed Related Features/assets/clear.png new file mode 100644 index 0000000..6b717e0 Binary files /dev/null and b/Listed Related Features/assets/clear.png differ diff --git a/Listed Related Features/assets/info.png b/Listed Related Features/assets/info.png new file mode 100644 index 0000000..c41a5fc Binary files /dev/null and b/Listed Related Features/assets/info.png differ diff --git a/Listed Related Features/controls/DescriptionPage.qml b/Listed Related Features/controls/DescriptionPage.qml new file mode 100644 index 0000000..0572ab6 --- /dev/null +++ b/Listed Related Features/controls/DescriptionPage.qml @@ -0,0 +1,93 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + id: descPage + width: parent.width + height: parent.height + + Rectangle{ + anchors.fill:parent + + ColumnLayout{ + anchors.fill:parent + spacing: 0 + clip:true + + Rectangle{ + id:descPageheader + color:"#8f499c" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 50 * scaleFactor + + ImageButton { + source: "../assets/clear.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + right: parent.right + rightMargin: 10 * scaleFactor + verticalCenter: parent.verticalCenter + } + onClicked: { + descPage.visible = 0 + } + } + + Text { + id: aboutApp + text:qsTr("About") + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + anchors.centerIn: parent + maximumLineCount: 2 + elide: Text.ElideRight + } + } + + Rectangle{ + color:"black" + Layout.fillWidth: true + Layout.fillHeight: true + + Flickable { + anchors.fill:parent + contentHeight: descText.height + clip:true + + Text{ + id: descText + y: 30 * scaleFactor + text:app.info.description + anchors.horizontalCenterOffset: 0 + color:"white" + width: 0.85 * parent.width + horizontalAlignment: Text.AlignLeft + linkColor: "#e5e6e7" + wrapMode: Text.WordWrap + elide: Text.ElideRight + anchors.horizontalCenter: parent.horizontalCenter + font { + pixelSize: app.baseFontSize + } + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + } + } +} + + + + + diff --git a/Listed Related Features/controls/HeaderBar.qml b/Listed Related Features/controls/HeaderBar.qml new file mode 100644 index 0000000..2ddd736 --- /dev/null +++ b/Listed Related Features/controls/HeaderBar.qml @@ -0,0 +1,59 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +RowLayout{ + anchors.fill: parent + spacing:0 + clip:true + + Rectangle{ + Layout.preferredWidth: 50*scaleFactor + } + + Text { + text:app.info.title + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + maximumLineCount:2 + wrapMode: Text.Wrap + elide: Text.ElideRight + anchors{ + verticalCenter: parent.verticalCenter + horizontalCenter:parent.horizontalCenter + } + } + + Rectangle{ + id:infoImageRect + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 50*scaleFactor + + ImageButton { + id:infoImage + source: "../assets/info.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + centerIn: parent + } + onClicked: { + descPage.visible = 1 + } + } + } +} + + + + + diff --git a/Listed Related Features/default-app.png b/Listed Related Features/default-app.png new file mode 100644 index 0000000..8fa9021 Binary files /dev/null and b/Listed Related Features/default-app.png differ diff --git a/Listed Related Features/image b/Listed Related Features/image new file mode 100644 index 0000000..e69de29 diff --git a/Listed Related Features/iteminfo.json b/Listed Related Features/iteminfo.json new file mode 100644 index 0000000..4d9cdd2 --- /dev/null +++ b/Listed Related Features/iteminfo.json @@ -0,0 +1,64 @@ +{ + "access": "private", + "accessInformation": null, + "appCategories": [ + ], + "avgRating": 0, + "banner": null, + "categories": [ + ], + "commentsEnabled": true, + "created": 1523903245000, + "culture": "en-au", + "description": "\n

AΒ MapΒ is constructed by passing a URL to a web map stored in ArcGIS Online. A connection is made to theΒ mouseClickedΒ signal of theΒ MapView, and when this signal emits,Β selectFeaturesΒ is executed on theΒ FeatureLayer. Once this completes, aΒ FeatureQueryResultΒ is returned, giving access to theΒ FeatureIterator. ThisΒ FeatureIteratorΒ contains all of theΒ FeatureΒ objects selected with the mouse click. TheseΒ FeatureΒ objects are iterated over, and for each object, theΒ queryRelatedFeaturesΒ method is called. Once this completes, the returnedΒ RelatedFeatureQueryResultΒ gives access to all of the related features, and the UI is populated with this information.

\n

Resource Level:🍌

", + "documentation": null, + "extent": [ + ], + "groupDesignations": null, + "guid": null, + "id": "c6ac08a4f6e54eb3910fcf03516dedc6", + "industries": [ + ], + "itemControl": "admin", + "languages": [ + ], + "largeThumbnail": null, + "licenseInfo": null, + "listed": false, + "modified": 1523903247000, + "name": "c6ac08a4f6e54eb3910fcf03516dedc6.zip", + "numComments": 1, + "numRatings": 0, + "numViews": 0, + "owner": "appstudio_samples", + "ownerFolder": null, + "properties": null, + "protected": false, + "proxyFilter": null, + "scoreCompleteness": 83, + "screenshots": [ + ], + "size": 37689, + "snippet": "This sample demonstrates how to query a layer for features that are in a related table ", + "spatialReference": null, + "tags": [ + "AppStudio", + "Quartz", + "Runtime", + "Sample", + "Layer" + ], + "thumbnail": "thumbnail/thumbnail.png", + "title": "Listed Related Features", + "type": "Native Application", + "typeKeywords": [ + "API_QML", + "App", + "Application", + "AppStudio", + "Configuration", + "Native", + "qml" + ], + "url": null +} diff --git a/Listed Related Features/qtquickcontrols2.conf b/Listed Related Features/qtquickcontrols2.conf new file mode 100644 index 0000000..1ddd1cd --- /dev/null +++ b/Listed Related Features/qtquickcontrols2.conf @@ -0,0 +1,10 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Material + +[Universal] +Theme=Light +;Accent=Steel diff --git a/Listed Related Features/thumbnail.png b/Listed Related Features/thumbnail.png new file mode 100644 index 0000000..7bac0eb Binary files /dev/null and b/Listed Related Features/thumbnail.png differ diff --git a/OAuth+BiometricAuthentication/MyApp.qml b/OAuth+BiometricAuthentication/MyApp.qml new file mode 100644 index 0000000..c7037e8 --- /dev/null +++ b/OAuth+BiometricAuthentication/MyApp.qml @@ -0,0 +1,168 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.2 + + +import Esri.ArcGISRuntime 100.2 +import Esri.ArcGISRuntime.Toolkit.Dialogs 100.2 +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import ArcGIS.AppFramework.SecureStorage 1.0 +import ArcGIS.AppFramework.Authentication 1.0 + +import "./controls" as Controls +import "./views" as Views + +App { + id: app + width: 400 * scaleFactor + height: 640 * scaleFactor + + property real scaleFactor: AppFramework.displayScaleFactor + property int baseFontSize : app.info.propertyValue("baseFontSize", 15 * scaleFactor) + (isSmallScreen ? 0 : 3) + property bool isSmallScreen: (width || height) < units(400) + + property color primaryColor: "#7461A8" + property alias stackView: stackView + + property var rtCreate: ArcGISRuntimeEnvironment.createObject + property Portal portal + property Portal securityPortal + + property bool isIPhoneX: false + property bool isAutoSignIn: true + property bool isBioAuth: false + property bool canUseBioAuth: BiometricAuthenticator.supported && BiometricAuthenticator.activated + readonly property string enable_auto_sign_in_toast:qsTr("Auto sign in enabled!") + readonly property string disable_auto_sign_in_toast: qsTr("Auto sign in disabled!") + readonly property string enable_touchID_toast: qsTr("Touch ID enabled!") + readonly property string disable_touchID_toast: qsTr("Touch ID disabled!") + readonly property string enable_fingerprint_toast: qsTr("Fingerprint authentication enabled!") + readonly property string disable_fingerprint_toast: qsTr("Fingerprint authentication disabled!") + readonly property string enable_faceid_toast: qsTr("Face ID enabled!") + readonly property string disable_faceid_toast: qsTr("Face ID disabled!") + readonly property string app_description: qsTr("This sample app demonstrates how to use ArcGIS online OAuth authentication in conjunction with biometric authentication. It also shows you how token is stored in system's secure storage using Secure Storage plug-in.") + + StackView { + id:stackView + anchors.fill: parent + initialItem: signInPage + } + + Component { + id: signInPage + + Views.SignIn { + onNext: { + stackView.push(profilePage) + } + } + } + + Component { + id: profilePage + + Views.Profile { + + onBack: { + stackView.pop(); + } + } + } + + Controls.SecureStorageHelper { + id: secureStorage + } + + Connections { + target: securityPortal + + onLoadStatusChanged: { + if (securityPortal.loadStatus === Enums.LoadStatusLoaded) { + secureStorage.setContent("oAuthRefreshToken", securityPortal.credential.oAuthRefreshToken ); + secureStorage.setContent("tokenServiceUrl", securityPortal.credential.tokenServiceUrl); + } + } + } + + function loadPortal() { + var oauthInfo = rtCreate("OAuthClientInfo", {oAuthMode: Enums.OAuthModeUser, clientId: "AJhw6AaHp2y0Bmx0"}) + var credential = rtCreate("Credential", { + oAuthClientInfo: oauthInfo, + oAuthRefreshToken: secureStorage.getContent("oAuthRefreshToken"), + tokenServiceUrl:"http://www.arcgis.com/sharing/rest/oauth2/token" + }); + app.securityPortal = rtCreate("Portal", { url: "http://arcgis.com", credential: credential}); + if (securityPortal.loadStatus === 1) { + securityPortal.retryLoad() + + } + securityPortal.load(); + console.log(secureStorage.getContent("tokenServiceUrl")) + } + + function appInitialization() { + if (Qt.platform.os === "ios" && AppFramework.systemInformation.hasOwnProperty("unixMachine")) { + if (AppFramework.systemInformation.unixMachine === "iPhone10,3" || AppFramework.systemInformation.unixMachine === "iPhone10,6") { + isIPhoneX = true; + } + } + + isAutoSignIn = app.settings.value("appAutoSignIn",true); + isBioAuth = app.settings.value("appBioAuth"); + + if (isAutoSignIn) { + if (isBioAuth && canUseBioAuth ) { + BiometricAuthenticator.message = qsTr("authenticate to proceed") + BiometricAuthenticator.authenticate() + } else { + loadPortal() + stackView.push(profilePage) + } + } + } + + Connections { + target: BiometricAuthenticator + onAccepted: { + loadPortal() + stackView.push(profilePage) + } + } + + Component.onCompleted: { + appInitialization() + } + + Controls.DescriptionPage{ + id:descPage + visible: false + } +} + + + + + + + diff --git a/OAuth+BiometricAuthentication/MyApp.qmlproject b/OAuth+BiometricAuthentication/MyApp.qmlproject new file mode 100644 index 0000000..754767c --- /dev/null +++ b/OAuth+BiometricAuthentication/MyApp.qmlproject @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ + +import QmlProject 1.1 + +Project { + mainFile: "MyApp.qml" + + QmlFiles { + directory: "." + recursive: true + } + + JavaScriptFiles { + directory: "." + recursive: true + } + + ImageFiles { + directory: "." + recursive: true + } + + Files { + directory: "." + recursive: true + filter: "*.json;*.html;*.txt;*.conf" + } + + importPaths: [ + ] +} + diff --git a/OAuth+BiometricAuthentication/README.md b/OAuth+BiometricAuthentication/README.md new file mode 100644 index 0000000..e54e440 --- /dev/null +++ b/OAuth+BiometricAuthentication/README.md @@ -0,0 +1,46 @@ +## OAuth + Biometric Authentication + +This sample demonstrates how to use ArcGIS online OAuth authentication in conjuction with biometric authentication + +It also shows you how token is stored in system's secure storage using Secure Storage plug-in. + +If you wish to use the code from this sample app, please update client ID with your own at MyApp.qml file, line 109. + + +[Resource Level](https://geonet.esri.com/groups/appstudio/blog/2016/12/06/how-to-describe-our-resources-in-terms-of-difficulty-complexity-and-time-to-digest): 🍌🍌 + + +## Instructions to run this sample in AppStudio Desktop + +1. Download the `.zip` file +2. Unzip and copy this folder into AppStudio Apps folder (Windows: `C:\Users\\ArcGIS\AppStudio\Apps` Mac or linux: `Home\ArcGIS\AppStudio\Apps`) +3. The new app will now appear in the AppStudio Desktop. Run the application or open it in the bundled Qt-Creator IDE to look at the code and modify. + +## Issues + +Find a bug or want to request a new feature? Please let us know by submitting an issue. + +## Contributing + +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). + +## Licensing +Copyright 2017 Esri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +A copy of the license is available in the repository's [license.txt](license.txt) file. + + +[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) +[](Esri Language: Qt QML JavaScript) diff --git a/OAuth+BiometricAuthentication/appicon.png b/OAuth+BiometricAuthentication/appicon.png new file mode 100644 index 0000000..39504b9 Binary files /dev/null and b/OAuth+BiometricAuthentication/appicon.png differ diff --git a/OAuth+BiometricAuthentication/appinfo.json b/OAuth+BiometricAuthentication/appinfo.json new file mode 100644 index 0000000..eb45699 --- /dev/null +++ b/OAuth+BiometricAuthentication/appinfo.json @@ -0,0 +1,90 @@ +{ + "arcgisRuntime": 1002, + "capabilities": { + "audio": false, + "backgroundLocation": false, + "biometricAuthentication": false, + "bluetooth": false, + "camera": false, + "fileSharing": false, + "highAccuracyLocation": false, + "ios": { + "externalAccessoryProtocolStrings": [ + ] + }, + "localnotification": false, + "location": true, + "microphone": false, + "network": true, + "storage": false, + "vibration": false + }, + "deployment": { + "android": { + "packageName": "" + }, + "arcgisRuntimeExtensionsLicense": "", + "arcgisRuntimeLicense": "", + "clientId": "", + "ios": { + "bundleId": "", + "codeSignIdentity": "" + }, + "macos": { + "bundleId": "", + "codeSignIdentity": "" + }, + "publisherName": "", + "uwp": { + "packageName": "", + "productID": "" + }, + "winphone": { + "packageDisplayName": "" + } + }, + "devicesTypes": [ + "desktop", + "tablet", + "phone" + ], + "display": { + "desktop": { + "minimumHeight": 0, + "minimumWidth": 0, + "windowMode": "default" + }, + "enableHighDpi": true, + "phone": { + "landscape": true, + "portrait": true, + "showStatusBar": true + }, + "tablet": { + "landscape": true, + "portrait": true, + "showStatusBar": true + } + }, + "environment": { + }, + "launchUrlSchemes": [ + ], + "mainFile": "MyApp.qml", + "multipleInstances": true, + "projectFile": "MyApp.qmlproject", + "properties": { + }, + "resources": { + "appIcon": "appicon.png", + "launchImageBackground": "launchimage-background.png", + "launchImageBackgroundColor": "#ffffff", + "launchImageOverlay": "launchimage-overlay.png" + }, + "type": "app", + "urlScheme": "", + "version": { + "major": 1, + "micro": 2 + } +} diff --git a/OAuth+BiometricAuthentication/controls/CustomDialog.qml b/OAuth+BiometricAuthentication/controls/CustomDialog.qml new file mode 100644 index 0000000..d0770b3 --- /dev/null +++ b/OAuth+BiometricAuthentication/controls/CustomDialog.qml @@ -0,0 +1,55 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + +import ArcGIS.AppFramework 1.0 + +Dialog { + id: root + + property Item content + + property bool flickable: true + property real pageHeaderHeight: units(56) + property real defaultMargin: units(24) + + x: 0.5 * (parent.width - width) + y: 0.5 * (parent.height - height - pageHeaderHeight) + + topPadding: 0 + bottomPadding: 0 + topMargin: 0 + bottomMargin: 0 + closePolicy: Popup.NoAutoClose + width: Math.min(parent.width - 2 * defaultMargin, units(400)) + height: Math.min(parent.width - 2 * defaultMargin, units(400)) + + contentItem: ColumnLayout { + antialiasing: true + spacing: 0 + + Flickable { + interactive: flickable + Layout.preferredHeight: parent.height + Layout.fillWidth: true + clip: true + contentHeight: flickableContent.height + ColumnLayout { + id: flickableContent + + spacing: 0 + width: parent.width + children: [content] + } + } + } + + Component.onCompleted: { + content.Layout.preferredWidth = Qt.binding(function () { + return flickableContent.width + }) + } + function units (num) { + return num ? num * AppFramework.displayScaleFactor : num + } +} diff --git a/OAuth+BiometricAuthentication/controls/DescriptionPage.qml b/OAuth+BiometricAuthentication/controls/DescriptionPage.qml new file mode 100644 index 0000000..4783557 --- /dev/null +++ b/OAuth+BiometricAuthentication/controls/DescriptionPage.qml @@ -0,0 +1,105 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 + + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Page { + id: descPage + width: parent.width + height: parent.height + anchors.fill:parent + + header: ToolBar { + height: 52 * scaleFactor + width: parent.width + Material.elevation: 6 + Material.background: primaryColor + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item{ + Layout.preferredWidth: 0.2 * app.scaleFactor + Layout.fillHeight: true + } + + ToolButton { + Layout.preferredHeight: 42 * scaleFactor + Layout.preferredWidth: 42 * scaleFactor + + indicator: Image { + id: image + anchors.fill: parent + anchors.centerIn: parent + source: "../image/left.png" + fillMode: Image.PreserveAspectFit + mipmap: true + + } + + onClicked: { + descPage.visible = 0 + } + } + + Text { + id: aboutApp + text:qsTr("About") + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + anchors.centerIn: parent + maximumLineCount: 2 + elide: Text.ElideRight + } + } + } + + ColumnLayout{ + anchors.fill:parent + spacing: 0 + clip:true + + Rectangle{ + color:"white" + Layout.fillWidth: true + Layout.fillHeight: true + + Flickable { + anchors.fill:parent + contentHeight: descText.height + clip:true + + Text{ + id: descText + text: app.info.description + y: 30 * scaleFactor + textFormat: Text.StyledText + anchors.horizontalCenterOffset: 0 + color:"black" + width: 0.85 * parent.width + horizontalAlignment: Text.AlignLeft + linkColor: "#e5e6e7" + wrapMode: Text.WordWrap + elide: Text.ElideRight + anchors.horizontalCenter: parent.horizontalCenter + font { + pixelSize: app.baseFontSize + } + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + } +} + + + + + + diff --git a/OAuth+BiometricAuthentication/controls/SecureStorageHelper.qml b/OAuth+BiometricAuthentication/controls/SecureStorageHelper.qml new file mode 100644 index 0000000..8c51bbb --- /dev/null +++ b/OAuth+BiometricAuthentication/controls/SecureStorageHelper.qml @@ -0,0 +1,116 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.7 + +import ArcGIS.AppFramework.SecureStorage 1.0 +import ArcGIS.AppFramework 1.0 + +Item { + id: root + + property bool isRunningInPlayer: true + + Component.onCompleted: { + if (parent) { + if (AppFramework.typeOf(parent, true) === "AppLoader") { + var portal = parent.portal + if (portal) { + //console.log("#### AppStudio Player username: " + portal.username) + isRunningInPlayer = true + } + } else { + //console.log("#### Different Player") + isRunningInPlayer = true + } + } else { + isRunningInPlayer = false + } + } + + function hashKey (originalKey) { + var id = (app.info.itemInfo.id || "") + isRunningInPlayer ? "" : "1" + return Qt.md5(originalKey + id) + } + + function setContent (originalKey, value, storageAPI) { + if (!storageAPI) storageAPI = SecureStorage + clearContent(originalKey, storageAPI) + + var key = hashKey(originalKey) + + var maxChars = 214 + var lookupKeys = "" + var quotient = Math.floor(value.length/maxChars) + var remainder = value.length % maxChars + + for (var i=0; i < quotient; i++) { + var lookupKey = "%1%2".arg(key).arg(i) + var lookupValue = value.slice(i*maxChars,(i+1)*maxChars) + + lookupKeys = lookupKeys.length === 0 ? lookupKey : "%1,%2".arg(lookupKeys).arg(lookupKey) + storageAPI.setValue(lookupKey, lookupValue) + } + + if (remainder) { + lookupKey = "%1%2".arg(key).arg(i+1) + lookupValue = value.slice((i)*maxChars) + + lookupKeys = lookupKeys.length === 0 ? lookupKey : "%1,%2".arg(lookupKeys).arg(lookupKey) + storageAPI.setValue(lookupKey, lookupValue) + } + + app.settings.setValue(key, lookupKeys) + } + + function getContent (originalKey, storageAPI) { + if (!storageAPI) storageAPI = SecureStorage + var key = hashKey(originalKey) + + var lookupKeysString = app.settings.value(key) + + if (!lookupKeysString) return "" + + var lookupKeys = lookupKeysString.split(",") + var value = "" + + for (var i=0; iThis sample app demonstrates how to use ArcGIS online OAuth authentication in conjunction with biometric authentication.

\n


\n

It also shows you how token is stored in system's secure storage using Secure Storage plug-in.

\n


\n

If you wish to use the code from this sample app, please update client ID with your own at MyApp.qml file, line 109.

\n


\n


\n


", + "documentation": null, + "extent": [ + ], + "groupDesignations": null, + "guid": null, + "id": "d78a21295f3a42228c56efa29cfde738", + "industries": [ + ], + "itemControl": "admin", + "languages": [ + ], + "largeThumbnail": null, + "licenseInfo": null, + "listed": false, + "modified": 1525286277000, + "name": "d78a21295f3a42228c56efa29cfde738.zip", + "numComments": 2, + "numRatings": 0, + "numViews": 1, + "orgId": "2U3NfasNQ9o9LkLt", + "owner": "appstudio_samples", + "ownerFolder": "f4a20283d02c41a1bba3642a7d6b42b5", + "properties": null, + "protected": false, + "proxyFilter": null, + "scoreCompleteness": 83, + "screenshots": [ + ], + "size": 42975, + "snippet": "This sample demonstrates how to use ArcGIS online OAuth authentication in conjuction with biometric authentication", + "spatialReference": null, + "tags": [ + "AppStudio", + "Sample", + "Plugins" + ], + "thumbnail": "thumbnail/thumbnail.png", + "title": "OAuth + Biometric Authentication", + "type": "Native Application", + "typeKeywords": [ + "API_QML", + "App", + "Application", + "AppStudio", + "Configuration", + "Native", + "qml" + ], + "url": null +} diff --git a/OAuth+BiometricAuthentication/qtquickcontrols2.conf b/OAuth+BiometricAuthentication/qtquickcontrols2.conf new file mode 100644 index 0000000..1ddd1cd --- /dev/null +++ b/OAuth+BiometricAuthentication/qtquickcontrols2.conf @@ -0,0 +1,10 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Material + +[Universal] +Theme=Light +;Accent=Steel diff --git a/OAuth+BiometricAuthentication/thumbnail.png b/OAuth+BiometricAuthentication/thumbnail.png new file mode 100644 index 0000000..ec95292 Binary files /dev/null and b/OAuth+BiometricAuthentication/thumbnail.png differ diff --git a/OAuth+BiometricAuthentication/views/Profile.qml b/OAuth+BiometricAuthentication/views/Profile.qml new file mode 100644 index 0000000..44acb22 --- /dev/null +++ b/OAuth+BiometricAuthentication/views/Profile.qml @@ -0,0 +1,289 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.WebView 1.0 +import ArcGIS.AppFramework.Controls 1.0 +import ArcGIS.AppFramework.Authentication 1.0 + +import Esri.ArcGISRuntime 100.2 +import Esri.ArcGISRuntime.Toolkit.Dialogs 100.2 + + +import QtGraphicalEffects 1.0 + +import "../controls" as Controls + +Page { + id: profilePage + + property var user: securityPortal.portalUser + property Portal myportal: portal + property LocaleInfo localeInfo: AppFramework.localeInfo(Qt.locale().uiLanguages[0]) + + signal back(); + signal next(); + + header: ToolBar { + height: 52 * scaleFactor + width: parent.width + Material.elevation: 4 + Material.background: primaryColor + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item{ + Layout.preferredWidth: 0.2 * app.scaleFactor + Layout.fillHeight: true + } + + ToolButton { + Layout.preferredHeight: 42 * scaleFactor + Layout.preferredWidth: 42 * scaleFactor + + indicator: Image { + id: image + anchors.fill: parent + anchors.centerIn: parent + source: "../image/left.png" + fillMode: Image.PreserveAspectFit + mipmap: true + + } + + onClicked: { + back(); + authenticationView.authChallenge.cancel(); + securityPortal.cancelLoad(); + } + } + } + } + + ColumnLayout { + id: userDetailsColumn + width: parent.width + visible: securityPortal.loadStatus === Enums.LoadStatusLoaded + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + Label { + id: profileLabel + Layout.fillWidth: true + Layout.preferredHeight: 40 * scaleFactor + Layout.topMargin: 8 * scaleFactor + background: Rectangle { + anchors.fill: parent + color:"#00000000" + } + text: qsTr("Profile") + horizontalAlignment: Label.AlignLeft + verticalAlignment: Label.AlignVCenter + leftPadding: 16 * scaleFactor + rightPadding: 16 * scaleFactor + clip: true + elide: Text.ElideRight + color:"black" + font.bold: true + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 56 * scaleFactor + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.preferredWidth: 10 * scaleFactor + Layout.fillHeight: true + } + + Item { + Layout.preferredHeight: 40 * scaleFactor + Layout.preferredWidth: 40 * scaleFactor + + Image { + id: profileImage + smooth: true + visible: false + sourceSize: Qt.size(parent.width, parent.height) + source: user.thumbnailUrl + } + + Rectangle { + id:mask + height: 40 * scaleFactor + width: 40 * scaleFactor + radius: 5 + } + + OpacityMask { + anchors.fill: profileImage + source: profileImage + maskSource: mask + } + } + + Label { + text: user.fullName + font.pixelSize: 16 + anchors.right: parent.right + anchors.rightMargin: 25 * scaleFactor + color:"#444444" + } + } + } + + Label { + id: settingsLabel + Layout.fillWidth: true + Layout.preferredHeight: 40 * scaleFactor + Layout.topMargin: 8 * scaleFactor + background: Rectangle { + anchors.fill: parent + color:"#00000000" + } + text: qsTr("Settings") + horizontalAlignment: Label.AlignLeft + verticalAlignment: Label.AlignVCenter + leftPadding: 16 * scaleFactor + rightPadding: 16 * scaleFactor + clip: true + elide: Text.ElideRight + color:"black" + font.bold: true + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 56 * scaleFactor + + RowLayout { + anchors.fill: parent + spacing: 0 + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Auto Sign In Using Secure Storage") + font.pixelSize: 16 * scaleFactor + verticalAlignment: Label.AlignVCenter + leftPadding: 16 * scaleFactor + clip: true + elide: Text.ElideRight + color:"#444444" + + } + + Switch { + id: autoSignInSwitch + Material.accent: app.primaryColor + checked: isAutoSignIn + onToggled: { + isAutoSignIn =! isAutoSignIn + app.settings.setValue("appAutoSignIn",app.isAutoSignIn); + if (isAutoSignIn) { + toastMessage.displayToast(enable_auto_sign_in_toast); + } else { + toastMessage.displayToast(disable_auto_sign_in_toast); + } + } + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 56 * scaleFactor + visible: BiometricAuthenticator.supported + + RowLayout { + anchors.fill: parent + spacing: 0 + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + text: app.isIphoneX ? qsTr("Face ID") : (Qt.platform.os === "ios" || Qt.platform.os === "osx" ? qsTr("Touch ID") : qsTr("Fingerprint")) + font.pixelSize: 16 * scaleFactor + verticalAlignment: Label.AlignVCenter + leftPadding: 16 * scaleFactor + clip: true + elide: Text.ElideRight + color:"#444444" + } + + Switch { + id: fingerprintSwitch + Material.accent: app.primaryColor + checked: isBioAuth + enabled: canUseBioAuth && app.isAutoSignIn + + onToggled: { + isBioAuth =!isBioAuth + app.settings.setValue("appBioAuth", isBioAuth) + if (isBioAuth) { + toastMessage.displayToast(isIphoneX ? enable_faceid_toast : ((Qt.platform.os === "ios" || Qt.platform.os === "osx") ? enable_touchID_toast : enable_fingerprint_toast)); + } else { + toastMessage.displayToast(isIphoneX ? disable_faceid_toast : ((Qt.platform.os === "ios" || Qt.platform.os === "osx") ? disable_touchID_toast : disable_fingerprint_toast)); + } + } + } + } + } + + Item { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 40 * scaleFactor + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 56 * scaleFactor + + Label { + id: signOutLabel + anchors.fill: parent + font.pixelSize: 16 * scaleFactor + verticalAlignment: Label.AlignVCenter + horizontalAlignment: Label.AlignHCenter + clip: true + elide: Text.ElideRight + color:"#444444" + text: qsTr("Sign Out") + } + + MouseArea{ + anchors.fill: parent + onClicked: { + back() + securityPortal.destroy(); + AuthenticationManager.credentialCache.removeAllCredentials(); + secureStorage.setContent("oAuthRefreshToken", "") + } + } + } + } + + AuthenticationView { + id: authenticationView + authenticationManager: AuthenticationManager + } + + BusyIndicator { + anchors.centerIn: parent + Material.accent: primaryColor + running: securityPortal.loadStatus === Enums.LoadStatusLoading + } + + Controls.ToastMessage { + id: toastMessage + anchors.horizontalCenter: parent.horizontalCenter + } +} diff --git a/OAuth+BiometricAuthentication/views/SignIn.qml b/OAuth+BiometricAuthentication/views/SignIn.qml new file mode 100644 index 0000000..c272626 --- /dev/null +++ b/OAuth+BiometricAuthentication/views/SignIn.qml @@ -0,0 +1,97 @@ + +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.2 + + +import ArcGIS.AppFramework.SecureStorage 1.0 +import ArcGIS.AppFramework.Authentication 1.0 + +import ArcGIS.AppFramework 1.0 +import Esri.ArcGISRuntime 100.2 + +import "../controls" as Controls + +Rectangle { + + signal next(); + + id: signInPage + color: primaryColor + + Image { + id: infoIcon + source: "../image/info.png" + mipmap: true + width: 36 * scaleFactor + height: width + anchors.left: parent.left + anchors.leftMargin: 10 * scaleFactor + anchors.top: parent.top + anchors.topMargin: 10 * scaleFactor + + MouseArea { + anchors.fill: parent + onClicked: { + descPage.visible = 1 + } + } + } + + ColumnLayout { + anchors.centerIn: parent + width: parent.width * 0.7 + height: parent.height * 0.7 + spacing: 0 + + Image { + id: profileImage + source: "../image/profile.png" + mipmap: true + width: 165 * scaleFactor + height: width + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + BiometricAuthenticator.authenticate() + } + } + } + + Label { + Layout.fillWidth: true + wrapMode: Label.WrapAtWordBoundaryOrAnywhere + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + color: "white" + elide: Label.ElideRight + text:qsTr("Welcome") + font.pixelSize: 17 * scaleFactor + horizontalAlignment: Label.AlignHCenter + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 170 * scaleFactor + } + + Button { + id: signinButton + Layout.preferredWidth: 150 * scaleFactor + Layout.preferredHeight: 50 * scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Sign In") + Material.background: "white" + Material.foreground: primaryColor + + onClicked: { + next(); + loadPortal(); + } + } + } +} + diff --git a/Open Mobile Map (mmpk)/MyApp.qml b/Open Mobile Map (mmpk)/MyApp.qml new file mode 100644 index 0000000..24f4736 --- /dev/null +++ b/Open Mobile Map (mmpk)/MyApp.qml @@ -0,0 +1,96 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls.Material 2.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import "controls" as Controls + +App{ + id: app + width: 414 + height: 736 + function units(value) { + return AppFramework.displayScaleFactor * value + } + property real scaleFactor: AppFramework.displayScaleFactor + property int baseFontSize : app.info.propertyValue("baseFontSize", 15 * scaleFactor) + (isSmallScreen ? 0 : 3) + property bool isSmallScreen: (width || height) < units(400) + + property url qmlfile + property string sampleName + property string descriptionText + + property string dataPath: AppFramework.userHomeFolder.filePath("ArcGIS/AppStudio/Data") + property string inputdata: "Yellowstone.mmpk" + property string outputdata: dataPath + "/" + inputdata + + function copyLocalData(input, output) { + var resourceFolder = AppFramework.fileFolder(app.folder.folder("data").path); + AppFramework.userHomeFolder.makePath(dataPath); + resourceFolder.copyFile(input, output); + return output + } + + Page{ + anchors.fill: parent + header: ToolBar{ + id:header + width: parent.width + height: 50 * scaleFactor + Material.background: "#8f499c" + Controls.HeaderBar{} + } + + // Add a Loader to load different samples. + // The sample Qml files can be found in the Samples folder + contentItem: Rectangle{ + id: loader + anchors.top:header.bottom + Loader{ + height: app.height - header.height + width: app.width + source: qmlfile + } + } + } + + Controls.FloatActionButton{ + id:switchBtn + } + + Controls.PopUpPage{ + id:popUp + visible:false + } + + Controls.DescriptionPage{ + id:descPage + visible: false + } +} + + + + + + diff --git a/Open Mobile Map (mmpk)/MyApp.qmlproject b/Open Mobile Map (mmpk)/MyApp.qmlproject new file mode 100644 index 0000000..b8ff5f5 --- /dev/null +++ b/Open Mobile Map (mmpk)/MyApp.qmlproject @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ + +import QmlProject 1.1 + +Project { + mainFile: "MyApp.qml" + + QmlFiles { + directory: "." + recursive: true + } + + JavaScriptFiles { + directory: "." + recursive: true + } + + ImageFiles { + directory: "." + recursive: true + } + + Files { + directory: "." + recursive: true + filter: "*.json;*.html;*.txt" + } + + importPaths: [ + ] +} + diff --git a/Open Mobile Map (mmpk)/README.md b/Open Mobile Map (mmpk)/README.md new file mode 100644 index 0000000..247f859 --- /dev/null +++ b/Open Mobile Map (mmpk)/README.md @@ -0,0 +1,43 @@ +## Open Mobile Map Package(mmpk) + +This sample demonstrates how to open and display a map from a Mobile Map Package. + +This sample takes a Mobile Map Package that was created in ArcGIS Pro, and displays a Map from within the package in a MapView. This is accomplished by calling load on the MobileMapPackage, and waiting for its load status to be Completed. Once the package is loaded, you can access its maps, and assign one of the maps to be viewed in the MapView. + +[Resource Level](https://geonet.esri.com/groups/appstudio/blog/2016/12/06/how-to-describe-our-resources-in-terms-of-difficulty-complexity-and-time-to-digest): 🍌 + + +## Instructions to run this sample in AppStudio Desktop + +1. Download the `.zip` file +2. Unzip and copy this folder into AppStudio Apps folder (Windows: `C:\Users\\ArcGIS\AppStudio\Apps` Mac or linux: `Home\ArcGIS\AppStudio\Apps`) +3. The new app will now appear in the AppStudio Desktop. Run the application or open it in the bundled Qt-Creator IDE to look at the code and modify. + +## Issues + +Find a bug or want to request a new feature? Please let us know by submitting an issue. + +## Contributing + +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). + +## Licensing +Copyright 2017 Esri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +A copy of the license is available in the repository's [license.txt](license.txt) file. + + +[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) +[](Esri Language: Qt QML JavaScript) diff --git a/Open Mobile Map (mmpk)/appicon.png b/Open Mobile Map (mmpk)/appicon.png new file mode 100644 index 0000000..39504b9 Binary files /dev/null and b/Open Mobile Map (mmpk)/appicon.png differ diff --git a/Open Mobile Map (mmpk)/appinfo.json b/Open Mobile Map (mmpk)/appinfo.json new file mode 100644 index 0000000..4036555 --- /dev/null +++ b/Open Mobile Map (mmpk)/appinfo.json @@ -0,0 +1,89 @@ +{ + "arcgisRuntime": 1002, + "capabilities": { + "audio": false, + "backgroundLocation": false, + "biometricAuthentication": false, + "bluetooth": false, + "camera": false, + "fileSharing": false, + "highAccuracyLocation": false, + "ios": { + "externalAccessoryProtocolStrings": [ + ] + }, + "localnotification": false, + "location": true, + "microphone": false, + "network": true, + "storage": true, + "vibration": false + }, + "deployment": { + "android": { + "packageName": "" + }, + "arcgisRuntimeExtensionsLicense": "", + "arcgisRuntimeLicense": "", + "clientId": "", + "ios": { + "bundleId": "", + "codeSignIdentity": "" + }, + "macos": { + "bundleId": "", + "codeSignIdentity": "" + }, + "publisherName": "", + "uwp": { + "packageName": "", + "productID": "" + }, + "winphone": { + "packageDisplayName": "" + } + }, + "devicesTypes": [ + "desktop", + "tablet", + "phone" + ], + "display": { + "desktop": { + "minimumHeight": 0, + "minimumWidth": 0, + "windowMode": "default" + }, + "phone": { + "landscape": true, + "portrait": true, + "showStatusBar": true + }, + "tablet": { + "landscape": true, + "portrait": true, + "showStatusBar": true + } + }, + "environment": { + }, + "launchUrlSchemes": [ + ], + "mainFile": "MyApp.qml", + "multipleInstances": true, + "projectFile": "MyApp.qmlproject", + "properties": { + }, + "resources": { + "appIcon": "default-app.png", + "launchImageBackground": "launchimage-background.png", + "launchImageBackgroundColor": "#ffffff", + "launchImageOverlay": "launchimage-overlay.png" + }, + "type": "app", + "urlScheme": "", + "version": { + "major": 2, + "micro": 2 + } +} diff --git a/Open Mobile Map (mmpk)/assets/add.png b/Open Mobile Map (mmpk)/assets/add.png new file mode 100644 index 0000000..0819594 Binary files /dev/null and b/Open Mobile Map (mmpk)/assets/add.png differ diff --git a/Open Mobile Map (mmpk)/assets/clear.png b/Open Mobile Map (mmpk)/assets/clear.png new file mode 100644 index 0000000..6b717e0 Binary files /dev/null and b/Open Mobile Map (mmpk)/assets/clear.png differ diff --git a/Open Mobile Map (mmpk)/assets/dropdown_arrow.png b/Open Mobile Map (mmpk)/assets/dropdown_arrow.png new file mode 100644 index 0000000..f640ff5 Binary files /dev/null and b/Open Mobile Map (mmpk)/assets/dropdown_arrow.png differ diff --git a/Open Mobile Map (mmpk)/assets/info.png b/Open Mobile Map (mmpk)/assets/info.png new file mode 100644 index 0000000..c41a5fc Binary files /dev/null and b/Open Mobile Map (mmpk)/assets/info.png differ diff --git a/Open Mobile Map (mmpk)/assets/switcher.png b/Open Mobile Map (mmpk)/assets/switcher.png new file mode 100644 index 0000000..5d32d06 Binary files /dev/null and b/Open Mobile Map (mmpk)/assets/switcher.png differ diff --git a/Open Mobile Map (mmpk)/controls/DescriptionPage.qml b/Open Mobile Map (mmpk)/controls/DescriptionPage.qml new file mode 100644 index 0000000..0572ab6 --- /dev/null +++ b/Open Mobile Map (mmpk)/controls/DescriptionPage.qml @@ -0,0 +1,93 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + id: descPage + width: parent.width + height: parent.height + + Rectangle{ + anchors.fill:parent + + ColumnLayout{ + anchors.fill:parent + spacing: 0 + clip:true + + Rectangle{ + id:descPageheader + color:"#8f499c" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 50 * scaleFactor + + ImageButton { + source: "../assets/clear.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + right: parent.right + rightMargin: 10 * scaleFactor + verticalCenter: parent.verticalCenter + } + onClicked: { + descPage.visible = 0 + } + } + + Text { + id: aboutApp + text:qsTr("About") + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + anchors.centerIn: parent + maximumLineCount: 2 + elide: Text.ElideRight + } + } + + Rectangle{ + color:"black" + Layout.fillWidth: true + Layout.fillHeight: true + + Flickable { + anchors.fill:parent + contentHeight: descText.height + clip:true + + Text{ + id: descText + y: 30 * scaleFactor + text:app.info.description + anchors.horizontalCenterOffset: 0 + color:"white" + width: 0.85 * parent.width + horizontalAlignment: Text.AlignLeft + linkColor: "#e5e6e7" + wrapMode: Text.WordWrap + elide: Text.ElideRight + anchors.horizontalCenter: parent.horizontalCenter + font { + pixelSize: app.baseFontSize + } + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + } + } +} + + + + + diff --git a/Open Mobile Map (mmpk)/controls/FloatActionButton.qml b/Open Mobile Map (mmpk)/controls/FloatActionButton.qml new file mode 100644 index 0000000..83a626b --- /dev/null +++ b/Open Mobile Map (mmpk)/controls/FloatActionButton.qml @@ -0,0 +1,37 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 + +RoundButton{ + id:switchBtn + radius: 30 * scaleFactor + width:60 * scaleFactor + height:width + Material.elevation: 6 + Material.background: "#8f499c" + anchors { + right: parent.right + bottom: parent.bottom + rightMargin: 30 * scaleFactor + bottomMargin: 35 * scaleFactor + } + onClicked:{ + popUp.visible = 1 + } + + Image{ + source: "../assets/switcher.png" + height:24 * scaleFactor + width: 24 * scaleFactor + anchors.centerIn: parent + } +} + + + + + + + + + diff --git a/Open Mobile Map (mmpk)/controls/HeaderBar.qml b/Open Mobile Map (mmpk)/controls/HeaderBar.qml new file mode 100644 index 0000000..2ddd736 --- /dev/null +++ b/Open Mobile Map (mmpk)/controls/HeaderBar.qml @@ -0,0 +1,59 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +RowLayout{ + anchors.fill: parent + spacing:0 + clip:true + + Rectangle{ + Layout.preferredWidth: 50*scaleFactor + } + + Text { + text:app.info.title + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + maximumLineCount:2 + wrapMode: Text.Wrap + elide: Text.ElideRight + anchors{ + verticalCenter: parent.verticalCenter + horizontalCenter:parent.horizontalCenter + } + } + + Rectangle{ + id:infoImageRect + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 50*scaleFactor + + ImageButton { + id:infoImage + source: "../assets/info.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + centerIn: parent + } + onClicked: { + descPage.visible = 1 + } + } + } +} + + + + + diff --git a/Open Mobile Map (mmpk)/controls/PopUpPage.qml b/Open Mobile Map (mmpk)/controls/PopUpPage.qml new file mode 100644 index 0000000..f2e8bbb --- /dev/null +++ b/Open Mobile Map (mmpk)/controls/PopUpPage.qml @@ -0,0 +1,125 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import QtGraphicalEffects 1.0 +import QtQuick.Controls.Material 2.1 + +import ArcGIS.AppFramework 1.0 + +Rectangle { + id:popUp + anchors.fill: parent + color: "#80000000" + + MouseArea { + anchors.fill: parent + onClicked: { + mouse.accepted = false + } + } + + Rectangle { + id:popUpWindow + height: 270 * scaleFactor + width: 280 * scaleFactor + anchors.centerIn: parent + radius: 3 * scaleFactor + Material.background: "#FAFAFA" + Material.elevation:24 + + Text { + id: titleText + text: qsTr("Choose a sample") + font{ + pixelSize:app.baseFontSize + bold:true + } + padding: 24 * scaleFactor + anchors.top:parent.top + anchors.bottom:popUpListView.top + } + + ListView{ + id:popUpListView + anchors.topMargin: 64 * scaleFactor + anchors.fill: parent + model:ListModel { + id:sampleItems + + ListElement { name:"MMPK"; url:"../samples/MMPK.qml";description:"

This is the most basic sample for displaying a map. It can be considered the Hello World map app for the ArcGIS Runtime SDK for Qt. It shows how to create a map view, and add in a map that contains the imagery with labels basemap. By default, this map supports basic zooming and panning operations. \

Resource Level:🍌

" } + + ListElement { name:"MMMPK with tile package"; url:"../samples/MMPKwithRasterTilePackage.qml"; description:"

The load status is obtained from the loadStatus property. The map is considered loaded when any of the following are true: the map has a valid spatial reference, the map has an initial viewpoint, or one of the map's predefined layers has been created. A signal handler is set up on the map to handle the loadStatusChanged signal by updating the text at the bottom of the display with the new load status.

Resource Level:🍌

" } + + } + + onCurrentIndexChanged: { + qmlfile = sampleItems.get(currentIndex).url + sampleName = sampleItems.get(currentIndex).name + descriptionText =sampleItems.get(currentIndex).description + } + delegate: Rectangle{ + width:280 * scaleFactor + height: 40 * scaleFactor + color: index===popUpListView.currentIndex? "#808c499c":"transparent" + + Label{ + anchors.verticalCenter: parent.verticalCenter + padding: 24 * scaleFactor + font { + pixelSize: app.baseFontSize * 0.8 + } + text:name + } + + MouseArea{ + anchors.fill:parent + onClicked: { + popUp.visible = 0 + popUpListView.currentIndex = index + qmlfile = sampleItems.get(index).url + sampleName = sampleItems.get(index).name + descriptionText =sampleItems.get(index).description + } + } + } + + Text{ + id:cancelText + anchors.bottom: parent.bottom + anchors.right:parent.right + anchors.bottomMargin: 13 * scaleFactor + anchors.rightMargin: 16 * scaleFactor + text:qsTr("CANCEL") + color:"#8f499c" + font{ + pixelSize: baseFontSize * 0.9 + bold:true + } + + MouseArea{ + anchors.fill: parent + onClicked :{ + popUp.visible = 0 + } + } + } + } + } + + DropShadow { + id: headerbarShadow + source: popUpWindow + anchors.fill: popUpWindow + width: source.width + height: source.height + cached: true + radius: 8.0 + samples: 17 + color: "#80000000" + smooth: true + } +} + + + + diff --git a/Open Mobile Map (mmpk)/data/PalmSprings.mmpk b/Open Mobile Map (mmpk)/data/PalmSprings.mmpk new file mode 100644 index 0000000..d249a3c Binary files /dev/null and b/Open Mobile Map (mmpk)/data/PalmSprings.mmpk differ diff --git a/Open Mobile Map (mmpk)/data/Yellowstone.mmpk b/Open Mobile Map (mmpk)/data/Yellowstone.mmpk new file mode 100644 index 0000000..1942bc9 Binary files /dev/null and b/Open Mobile Map (mmpk)/data/Yellowstone.mmpk differ diff --git a/Open Mobile Map (mmpk)/default-app.png b/Open Mobile Map (mmpk)/default-app.png new file mode 100644 index 0000000..8fa9021 Binary files /dev/null and b/Open Mobile Map (mmpk)/default-app.png differ diff --git a/Open Mobile Map (mmpk)/image b/Open Mobile Map (mmpk)/image new file mode 100644 index 0000000..e69de29 diff --git a/Open Mobile Map (mmpk)/iteminfo.json b/Open Mobile Map (mmpk)/iteminfo.json new file mode 100644 index 0000000..2cdc4f9 --- /dev/null +++ b/Open Mobile Map (mmpk)/iteminfo.json @@ -0,0 +1,66 @@ +{ + "access": "public", + "accessInformation": null, + "appCategories": [ + ], + "avgRating": 0, + "banner": null, + "categories": [ + ], + "commentsEnabled": true, + "created": 1519240779000, + "culture": "en-au", + "description": "\n

This sample takes a Mobile Map Package that was created in ArcGIS Pro, and displays aΒ MapΒ from within the package in aΒ MapView. This is accomplished by callingΒ loadΒ on theΒ MobileMapPackage, and waiting for its load status to be Completed. Once the package is loaded, you can access its maps, and assign one of the maps to be viewed in theΒ MapView.

\n

Resource Level:🍌

", + "documentation": null, + "extent": [ + ], + "groupDesignations": null, + "guid": null, + "id": "f0e1b363495442f4a63ea9edb6b62827", + "industries": [ + ], + "itemControl": "admin", + "languages": [ + ], + "largeThumbnail": null, + "licenseInfo": null, + "listed": false, + "modified": 1523906730000, + "name": "f0e1b363495442f4a63ea9edb6b62827.zip", + "numComments": 2, + "numRatings": 0, + "numViews": 19, + "orgId": "2U3NfasNQ9o9LkLt", + "owner": "appstudio_samples", + "ownerFolder": "f4a20283d02c41a1bba3642a7d6b42b5", + "properties": null, + "protected": false, + "proxyFilter": null, + "scoreCompleteness": 81, + "screenshots": [ + ], + "size": 28104579, + "snippet": "This sample demonstrates how to open and display a map from a Mobile Map Package that includes a Tile Package. ", + "spatialReference": null, + "tags": [ + "AppStudio", + "Quartz", + "Runtime", + "Sample", + "Offline", + "mmpk" + ], + "thumbnail": "thumbnail/thumbnail.png", + "title": "Open Mobile Map (mmpk)", + "type": "Native Application", + "typeKeywords": [ + "API_QML", + "App", + "Application", + "AppStudio", + "Configuration", + "Native", + "qml" + ], + "url": null +} diff --git a/Open Mobile Map (mmpk)/qtquickcontrols2.conf b/Open Mobile Map (mmpk)/qtquickcontrols2.conf new file mode 100644 index 0000000..1ddd1cd --- /dev/null +++ b/Open Mobile Map (mmpk)/qtquickcontrols2.conf @@ -0,0 +1,10 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Material + +[Universal] +Theme=Light +;Accent=Steel diff --git a/Open Mobile Map (mmpk)/samples/MMPK.qml b/Open Mobile Map (mmpk)/samples/MMPK.qml new file mode 100644 index 0000000..b182d94 --- /dev/null +++ b/Open Mobile Map (mmpk)/samples/MMPK.qml @@ -0,0 +1,61 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 +import Esri.ArcGISRuntime 100.2 + + + +Item { + + property string inputdata: "Yellowstone.mmpk" + property string outputdata: dataPath + "/" + inputdata + + // Create MapView + MapView { + id: mapView + anchors.fill: parent + } + + // Create a Mobile Map Package and set the path + MobileMapPackage { + id: mmpk + path: AppFramework.resolvedPathUrl(copyLocalData(inputdata, outputdata)) + + // load the mobile map package + Component.onCompleted: { + mmpk.load(); + } + + // wait for the mobile map package to load + onLoadStatusChanged: { + if (loadStatus === Enums.LoadStatusLoaded) { + // set the map view's map to the first map in the mobile map package + mapView.map = mmpk.maps[0]; + } + } + } + //! [open mobile map package qml api snippet] +} + + diff --git a/Open Mobile Map (mmpk)/samples/MMPKwithRasterTilePackage.qml b/Open Mobile Map (mmpk)/samples/MMPKwithRasterTilePackage.qml new file mode 100644 index 0000000..37657a0 --- /dev/null +++ b/Open Mobile Map (mmpk)/samples/MMPKwithRasterTilePackage.qml @@ -0,0 +1,61 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 +import Esri.ArcGISRuntime 100.2 + + + +Item { + + property string inputdata: "PalmSprings.mmpk" + property string outputdata: dataPath + "/" + inputdata + + // Create MapView + MapView { + id: mapView + anchors.fill: parent + } + + // Create a Mobile Map Package and set the path + MobileMapPackage { + id: mmpk + path: AppFramework.resolvedPathUrl(copyLocalData(inputdata, outputdata)) + + // load the mobile map package + Component.onCompleted: { + mmpk.load(); + } + + // wait for the mobile map package to load + onLoadStatusChanged: { + if (loadStatus === Enums.LoadStatusLoaded) { + // set the map view's map to the first map in the mobile map package + mapView.map = mmpk.maps[0]; + } + } + } + //! [open mobile map package qml api snippet] +} + + diff --git a/Open Mobile Map (mmpk)/thumbnail.png b/Open Mobile Map (mmpk)/thumbnail.png new file mode 100644 index 0000000..d7a91ea Binary files /dev/null and b/Open Mobile Map (mmpk)/thumbnail.png differ diff --git a/README.md b/README.md index 803656c..5e04642 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,3 @@ See the License for the specific language governing permissions and limitations under the License. A copy of the license is available in the repository's [license.txt](license.txt) file. - - -[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) -[](Esri Language: QML) diff --git a/Replicator/Assets/Colors.qml b/Replicator/Assets/Colors.qml new file mode 100644 index 0000000..ca87e43 --- /dev/null +++ b/Replicator/Assets/Colors.qml @@ -0,0 +1,29 @@ +import QtQuick 2.7 + +Item { + // Theme color + readonly property color primary_color: "#0079c1" + + // White + readonly property color white_100: "#FFFFFFFF" + readonly property color white_70: "#B3FFFFFF" + readonly property color white_54: "#8AFFFFFF" + + // Transparent + readonly property color transparent: "#00000000" + + // Black + readonly property color black_100: "black" + readonly property color black_87: "#DE000000" + readonly property color black_54: "#8A000000" + readonly property color black_38: "#61000000" + + // Background color + readonly property color white_background: "#FFEFEFEF" + + // Customized Colors + readonly property color card_background: "#F8F8F8" + readonly property color card_border: "#D1D1D1" + + readonly property color default_content_color: "#EFEFEF" +} diff --git a/Replicator/Assets/Fonts.qml b/Replicator/Assets/Fonts.qml new file mode 100644 index 0000000..35af8e5 --- /dev/null +++ b/Replicator/Assets/Fonts.qml @@ -0,0 +1,20 @@ +import QtQuick 2.7 + +/* This is used to store all the fonts */ +Item { + // References for all the fonts + property alias fontFamily_Regular: fontFamily_Regular + property alias fontFamily_Medium: fontFamily_Medium + + // Regular + FontLoader { + id: fontFamily_Regular + source: app.folder.fileUrl("Assets/Fonts/AvenirNext-Regular.ttf") + } + + // Medium + FontLoader { + id: fontFamily_Medium + source: app.folder.fileUrl("Assets/Fonts/AvenirNext-Medium.ttf") + } +} diff --git a/Replicator/Assets/Fonts/AvenirNext-Medium.ttf b/Replicator/Assets/Fonts/AvenirNext-Medium.ttf new file mode 100644 index 0000000..3258119 Binary files /dev/null and b/Replicator/Assets/Fonts/AvenirNext-Medium.ttf differ diff --git a/Replicator/Assets/Fonts/AvenirNext-Regular.ttf b/Replicator/Assets/Fonts/AvenirNext-Regular.ttf new file mode 100644 index 0000000..070f34e Binary files /dev/null and b/Replicator/Assets/Fonts/AvenirNext-Regular.ttf differ diff --git a/Replicator/Assets/Fonts/Custom-Roboto-Medium.ttf b/Replicator/Assets/Fonts/Custom-Roboto-Medium.ttf new file mode 100644 index 0000000..8628431 Binary files /dev/null and b/Replicator/Assets/Fonts/Custom-Roboto-Medium.ttf differ diff --git a/Replicator/Assets/Fonts/Roboto-Regular.ttf b/Replicator/Assets/Fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..0e58508 Binary files /dev/null and b/Replicator/Assets/Fonts/Roboto-Regular.ttf differ diff --git a/Replicator/Assets/Sources.qml b/Replicator/Assets/Sources.qml new file mode 100644 index 0000000..632def2 --- /dev/null +++ b/Replicator/Assets/Sources.qml @@ -0,0 +1,18 @@ +import QtQuick 2.7 + +Item { + readonly property url homeImage: "../Images/home.png" + readonly property url arrow_left: "../Images/ic_keyboard_arrow_left_white_24dp.png" + readonly property url arrow_right: "../Images/ic_keyboard_arrow_right_white_24dp.png" + readonly property url radio_checked: "../Images/ic_radio_button_checked_white_24dp.png" + readonly property url radio_unchecked: "../Images/ic_radio_button_unchecked_white_24dp.png" + readonly property url placeholder: "../Images/placeholder.jpg" + readonly property url editImage: "../Images/ic_mode_edit_white_24dp.png" + readonly property url arrow_back: "../Images/ic_arrow_back_white_48dp.png" + readonly property url close: "../Images/ic_close_white_48dp.png" + readonly property url ago_portal: "../Images/ago_portal.png" + readonly property url enterpise_portal: "../Images/portal.png" + readonly property url loading_image: "../Images/loading.gif" + readonly property url success_image: "../Images/success.png" + readonly property url failed_image: "../Images/failed.png" +} diff --git a/Replicator/Assets/Strings.qml b/Replicator/Assets/Strings.qml new file mode 100644 index 0000000..b15643a --- /dev/null +++ b/Replicator/Assets/Strings.qml @@ -0,0 +1,53 @@ +import QtQuick 2.7 + +Item { + readonly property string homepage_welcome: qsTr("Hello, welcome to") + readonly property string homepage_app_description: qsTr("Copy your AppStudio for ArcGIS apps fron one organization to the other in just 3 easy steps!") + readonly property string homepage_get_start: qsTr("GET STARTED") + + readonly property string step1_description: qsTr("Sign in to both organization accounts.") + readonly property string sign_in: qsTr("SIGN IN") + readonly property string source_account: qsTr("Source account") + readonly property string dest_account: qsTr("Destination account") + + readonly property string select_account_type: qsTr("Select the account type:") + readonly property string arcgis_enterprise_url: qsTr("ArcGIS Enterprise URL:") + readonly property string arcgis_online: qsTr("ArcGIS Online") + readonly property string arcgis_enterprise: qsTr("ArcGIS Enterprise") + + readonly property string step2_description: qsTr("Select the app you would like to copy.") + readonly property string step2_showing: qsTr("Showing %L1 of %L2") + readonly property string step2_myapps: qsTr("My apps") + readonly property string step2_allapps: qsTr("All apps") + + readonly property string step3_description: qsTr("Does everything look right?") + readonly property string source_app: qsTr("App") + + readonly property string step4_success: qsTr("Copy completed!") + readonly property string step4_failed: qsTr("Copy failed.") + readonly property string copy_another: qsTr("TRANSFER ANOTHER") + readonly property string try_again: qsTr("TRY AGAIN") + readonly property string done: qsTr("DONE") + readonly property string share_app: qsTr("COPY URL") + + readonly property string step_no: qsTr("Step %1") + readonly property string back: qsTr("BACK") + readonly property string next: qsTr("NEXT") + readonly property string confirm: qsTr("YES, COPY NOW") + + readonly property string just_now: qsTr("Just now") + readonly property string single_minute: qsTr("1 minute ago") + readonly property string multi_minutes: qsTr("%1 minutes ago") + readonly property string single_hour: qsTr("1 hour ago") + readonly property string multi_hours: qsTr("%1 hours ago") + readonly property string single_day: qsTr("1 day ago") + readonly property string multi_days: qsTr("%1 days ago") + readonly property string single_week: qsTr("1 week ago") + readonly property string multi_weeks: qsTr("%1 weeks ago") + + readonly property string action_1: qsTr("Fetching appinfo...") + readonly property string action_2: qsTr("Downloading package...") + readonly property string action_3: qsTr("Downloading thumbnail") + readonly property string action_4: qsTr("Creating new item...") + readonly property string action_5: qsTr("Uploading package...") +} diff --git a/Replicator/Components/NetworkManager.qml b/Replicator/Components/NetworkManager.qml new file mode 100644 index 0000000..51e3b50 --- /dev/null +++ b/Replicator/Components/NetworkManager.qml @@ -0,0 +1,68 @@ +import QtQuick 2.7 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + property string rootUrl: portalA.url + property string token: portalA.token + property string owner: portalA.username + + //=================================================================================== + + function makeNetworkConnection(url, obj, callback, method, params) { + var component = networkRequestComponent; + var networkRequest = component.createObject(parent); + networkRequest.url = url; + networkRequest.callback = callback; + if(method > "") networkRequest.method = method; + networkRequest.params = params; + networkRequest.send(obj); + } + + Component { + id: networkRequestComponent + + NetworkRequest { + property var callback + property var params + + followRedirects: true + ignoreSslErrors: true + responseType: "json" + method: "POST" + + onReadyStateChanged: { + if (readyState == NetworkRequest.DONE){ + if (errorCode === 0) { + callback(response, params); + } else { + console.log("ERROR"+ errorCode + ": Request Failed"); + } + } + } + + onError: { + console.log(errorText + ", " + errorCode); + } + } + } + + //=================================================================================== + + function getApps(start, isMyApps, callback){ + var url = rootUrl + "/sharing/rest/search"; + var query = (isMyApps? "owner:%1 ".arg(owner) : "") +"((type:\"Native Application\" AND NOT type:\"Native Application Installer\") OR (tags:\"app\" AND tags:\"qml\" AND type:\"Code Sample\")) AND (access:shared OR access:private OR access:org OR (orgid:%1 AND access:public))".arg(portalA.user.orgId) + var obj = { + "start": start, + "num": "25", + "sortField": "modified", + "sortOrder": "desc", + "q": query, + "f": "json", + "token": token + } + + makeNetworkConnection(url, obj, callback, "GET"); + } +} diff --git a/Replicator/Components/Portal.qml b/Replicator/Components/Portal.qml new file mode 100644 index 0000000..a941105 --- /dev/null +++ b/Replicator/Components/Portal.qml @@ -0,0 +1,538 @@ +import QtQuick 2.7 + +import ArcGIS.AppFramework 1.0 + +Item { + id: portal + + property alias timer: timer + + property string portalName: "" + property string tag: "" + property string name: "" + readonly property url portalUrl: url // TODO: Legacy code workaround to be removed + property url url: app.appPortalUrl + property url tokenServicesUrl + property url owningSystemUrl: portalUrl + readonly property url restUrl: owningSystemUrl + "/sharing/rest" + property string username + property string password + property string token + property bool ssl + property bool ignoreSslErrors: true + property date expires + readonly property bool signedIn: token > "" && info && user + property int expiryMode: expiryModeRenew + property bool isPortal + property bool supportsOAuth: true + + property Settings settings + readonly property string settingsGroup : tag + + property int expiryModeSignal: 0 + property int expiryModeSignOut: 1 + property int expiryModeSignIn: 2 + property int expiryModeRenew: 3 + property var info: null + property var user: null + property url userThumbnailUrl: user && user.thumbnail>""? restUrl + "/community/users/" + username + "/info/" + user.thumbnail + "?token=" + token : "" + property string userFullName: user && user.fullName > "" ? user.fullName : "" + + signal expired() + signal error(var error) + + property bool isBusy: false + + /*--------------------------------------------------------------------------*/ + + property string clientID: "" + property string refreshToken: "" + property date lastLogin + property date lastRenewed + + readonly property string keyRefreshToken: "/refreshToken" + readonly property string keyDateSaved: "/dateSaved" + readonly property string keyPortalURL: "/portalUrl" + + /*--------------------------------------------------------------------------*/ + + property string userAgent + + Component.onCompleted: { + userAgent = buildUserAgent(app); + readSettings(); + } + + /*--------------------------------------------------------------------------*/ + + function autoSignIn() { + if (!settings) { + return; + } + + if (!supportsOAuth) { + return; + } + + var refreshToken = settings.value(settingsGroup + keyRefreshToken, "") + + var dateSaved = settings.value(settingsGroup + keyDateSaved, "") + + var url = settings.value(settingsGroup + keyPortalURL, "") + + if (url > "") { + portal.url = url; + } + + lastLogin = dateSaved > "" ? new Date(dateSaved) : new Date() + + if (clientID > "" && refreshToken > "") { + getTokenFromRefreshToken(clientID, refreshToken); + } + } + + function writeSignedInState() { + if (!settings) { + return; + } + + settings.setValue(settingsGroup + keyRefreshToken, portal.refreshToken); + settings.setValue(settingsGroup + keyDateSaved, new Date().toString()); + settings.setValue(settingsGroup + keyPortalURL, portal.portalUrl); + } + + function clearSignedInState() { + if (!settings) { + return; + } + + settings.remove(settingsGroup + keyRefreshToken); + settings.remove(settingsGroup + keyDateSaved); + settings.remove(settingsGroup + keyPortalURL); + } + + /*--------------------------------------------------------------------------*/ + + function getTokenFromCode(client_id, redirect_uri, auth_code) { + if (auth_code > "" && client_id > "") { + portal.isBusy = true; + portal.refreshToken = ""; + portal.clientID = client_id; + + var params = {}; + params.grant_type = "authorization_code"; + params.client_id = client_id; + params.code = auth_code; + params.redirect_uri = redirect_uri; + timer.stop(); + + oAuthAccessTokenFromAuthCodeRequest.headers.userAgent = portal.userAgent; + oAuthAccessTokenFromAuthCodeRequest.send(params); + } + } + + function getTokenFromRefreshToken(client_id, refresh_token) { + if (refresh_token > "" && client_id > "") { + portal.isBusy = true; + portal.refreshToken = refresh_token; + portal.clientID = client_id; + + var params = {}; + params.grant_type = "refresh_token"; + params.client_id = client_id; + params.refresh_token = refresh_token; + timer.stop(); + + oAuthAccessTokenFromAuthCodeRequest.headers.userAgent = portal.userAgent; + oAuthAccessTokenFromAuthCodeRequest.send(params); + } + } + + NetworkRequest { + id: oAuthAccessTokenFromAuthCodeRequest + + url: portalUrl + "/sharing/rest/oauth2/token" + responseType: "json" + ignoreSslErrors: portal.ignoreSslErrors + + onReadyStateChanged: { + if (readyState === NetworkRequest.ReadyStateComplete) + { + if(response.refresh_token) { + portal.refreshToken = response.refresh_token; + } + + portal.username = response.username || ""; + portal.token = response.access_token || ""; + + var now = new Date(); + portal.lastRenewed = now; + portal.expires = new Date(now.getTime() + response.expires_in * 1000); + + timer.interval = portal.expires - Date.now() - 5000; + timer.start(); + + portal.isBusy = false; + + selfRequest.sendRequest(); + userRequest.sendRequest(); + } + } + + onErrorTextChanged: { + portal.isBusy = false; + } + } + + + /*--------------------------------------------------------------------------*/ + + function renew() { + if (portal.refreshToken > "" && portal.clientID > "" && portal.supportsOAuth && signedIn) { + getTokenFromRefreshToken(portal.clientID, portal.refreshToken) + } else { + signOut(); + } + } + + + function signIn(user, pass) { + username = user; + password = pass; + + if (tokenServicesUrl > "") { + generateToken.generateToken(username, password); + } else { + infoRequest.headers.userAgent = portal.userAgent; + infoRequest.send(); + } + } + + function signOut() { + token = ""; + user = null; + } + + /*--------------------------------------------------------------------------*/ + + onUrlChanged: { + } + + onPortalUrlChanged: { + tokenServicesUrl = ""; + } + + /*--------------------------------------------------------------------------*/ + + Timer { + id: timer + + onTriggered: { + switch (expiryMode) { + case expiryModeSignIn: + signIn(); + break; + + case expiryModeSignOut: + signOut(); + break; + + case expiryModeRenew: + renew(); + break; + + default: + expired(); + break; + } + } + } + + NetworkRequest { + id: infoRequest + + url: portalUrl + "/sharing/rest/info?f=json" + responseType: "json" + ignoreSslErrors: portal.ignoreSslErrors + + onReadyStateChanged: { + if (readyState === NetworkRequest.ReadyStateComplete) + { + tokenServicesUrl = response.authInfo.tokenServicesUrl; + owningSystemUrl = response.owningSystemUrl; + generateToken.generateToken(username, portal.password); + } + } + + onErrorTextChanged: { + console.log("infoRequest error", errorText); + } + } + + NetworkRequest { + id: generateToken + + url: tokenServicesUrl + method: "POST" + responseType: "json" + uploadPrefix: "" + ignoreSslErrors: portal.ignoreSslErrors + + onReadyStateChanged: { + if (readyState === NetworkRequest.ReadyStateComplete) + { + if (response.error) { + portal.error(response.error); + } else if (response.token) { + token = response.token; + expires = new Date(response.expires); + ssl = response.ssl; + timer.interval = expires - Date.now() - 5000; + timer.start(); + + selfRequest.sendRequest(); + userRequest.sendRequest(); + } + } + } + + onErrorTextChanged: { + portal.error( { message: errorText, details: "" }); + } + + function generateToken(username, password, expiration, referer) { + + if (!expiration) { + expiration = 120; + } + if (!referer) { + referer = portalUrl; + } + var formData = { + "username": username, + "password": password, + "referer": referer, + "expiration": expiration, + "f": "json" + }; + + headers.userAgent = portal.userAgent; + send(formData); + } + } + + NetworkRequest { + id: selfRequest + + url: restUrl + "/portals/self" + method: "POST" + responseType: "json" + ignoreSslErrors: portal.ignoreSslErrors + + onReadyStateChanged: { + if (readyState === NetworkRequest.ReadyStateComplete) + { + portal.info = response; + portalName = response.name; + } + } + + onErrorTextChanged: { + console.log("selfRequest error", errorText); + } + + function sendRequest() { + var formData = { + f: "pjson" + }; + + if (portal.token > "") { + formData.token = portal.token; + } + + headers.userAgent = portal.userAgent; + send(formData); + } + } + + NetworkRequest { + id: userRequest + + url: restUrl + "/community/users/" + username + method: "POST" + responseType: "json" + ignoreSslErrors: portal.ignoreSslErrors + + onReadyStateChanged: { + if (readyState === NetworkRequest.ReadyStateComplete) + { + portal.user = response; + } + } + + onErrorTextChanged: { + console.log("userRequest error", errorText); + } + + function sendRequest() { + var formData = { + f: "pjson" + }; + + if (portal.token > "") { + formData.token = portal.token; + } + + headers.userAgent = portal.userAgent; + send(formData); + } + } + + /*--------------------------------------------------------------------------*/ + + function readSettings() { + if (!settings) { + return false; + } + + url = settings.value(settingsGroup + "/url", url); + name = settings.value(settingsGroup + "/name", "Assistant"); + ignoreSslErrors = settings.boolValue(settingsGroup + "/ignoreSslErrors", false); + isPortal = settings.boolValue(settingsGroup + "/isPortal", false); + supportsOAuth = settings.boolValue(settingsGroup + "/supportsOAuth", true); + readUserSettings(); + + return true; + } + + function writeSettings() { + if (!settings) { + return false; + } + + settings.setValue(settingsGroup + "/url", url); + settings.setValue(settingsGroup + "/name", name); + settings.setValue(settingsGroup + "/ignoreSslErrors", ignoreSslErrors); + settings.setValue(settingsGroup + "/isPortal", isPortal); + settings.setValue(settingsGroup + "/supportsOAuth", supportsOAuth); + + return true; + } + + /*--------------------------------------------------------------------------*/ + + function readUserSettings() { + if (!settings) { + return false; + } + + username = settings.value(settingsGroup + "/username", ""); + + return true; + } + + function writeUserSettings() { + if (!settings) { + return false; + } + + settings.setValue(settingsGroup + "/username", portal.username); + } + + function clearUserSettings() { + if (!settings) { + return false; + } + + settings.remove(settingsGroup + "/username"); + settings.remove(settingsGroup + "/password"); + } + + function rot13(s) { + return s.replace(/[A-Za-z]/g, function (c) { + return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".charAt( + "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm".indexOf(c) + ); + } ); + } + + /*--------------------------------------------------------------------------*/ + + function buildUserAgent(app) { + var userAgent = ""; + + function addProduct(name, version, comments) { + if (!(name > "")) { + return; + } + + if (userAgent > "") { + userAgent += " "; + } + + name = name.replace(/\s/g, ""); + userAgent += name; + + if (version > "") { + userAgent += "/" + version.replace(/\s/g, ""); + } + + if (comments) { + userAgent += " ("; + + for (var i = 2; i < arguments.length; i++) { + var comment = arguments[i]; + + if (!(comment > "")) { + continue; + } + + if (i > 2) { + userAgent += "; " + } + + userAgent += arguments[i]; + } + + userAgent += ")"; + } + + return name; + } + + function addAppInfo(app) { + var deployment = app.info.value("deployment"); + + if (!deployment || typeof deployment !== "object") { + deployment = {}; + } + + var appName = deployment.shortcutName > "" + ? deployment.shortcutName + : app.info.title; + + var udid = app.settings.value("udid", ""); + + if (!(udid > "")) { + udid = AppFramework.createUuidString(2); + app.settings.setValue("udid", udid); + } + + appName = addProduct(appName, app.info.version, Qt.locale().name, AppFramework.currentCpuArchitecture, udid) + + return appName; + } + + if (app) { + addAppInfo(app); + } else { + addProduct(Qt.application.name, Qt.application.version, Qt.locale().name, AppFramework.currentCpuArchitecture, Qt.application.organization); + } + + addProduct(Qt.platform.os, AppFramework.osVersion, AppFramework.osDisplayName); + addProduct("AppFramework", AppFramework.version, "Qt " + AppFramework.qtVersion, AppFramework.buildAbi); + addProduct(AppFramework.kernelType, AppFramework.kernelVersion); + + return userAgent; + } +} diff --git a/Replicator/Components/TransferManager.qml b/Replicator/Components/TransferManager.qml new file mode 100644 index 0000000..8a65f57 --- /dev/null +++ b/Replicator/Components/TransferManager.qml @@ -0,0 +1,237 @@ +import QtQuick 2.7 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + property var sourcePortal + property var destPortal + + property string _sourceRootURL: sourcePortal.url + property string _sourceToken: sourcePortal.token + property string _sourceItemId + + property string _destRootURL: destPortal.url + property string _destToken: destPortal.token + property string _destUsername: destPortal.username + property string _destItemId + + property var _itemInfoTemplate + property var _itemThumbnail + + property var requestProgress + property string _action + + property var errorHandler + + property string loadingStatusString: "" + + /*--------------------------------------------------------------------------*/ + + FileFolder{ + id: fileFolder + + path: "~/ArcGIS/AppTransfer/temp" + + Component.onCompleted: { + makeFolder(); + } + } + + Component { + id: networkRequestComponent + + NetworkRequest { + property var callback + property var params + property string action + + followRedirects: true + ignoreSslErrors: true + responseType: "json" + method: "POST" + + onReadyStateChanged: { + if (readyState == NetworkRequest.DONE){ + if (errorCode === 0) { + callback(response, params); + } else { + console.log("ERROR"+ errorCode + ": Request Failed"); + errorHandler(); + } + } + } + + onProgressChanged: { + requestProgress = (progress * 100).toFixed(0) + "%"; + } + + onError: { + console.log(errorText + ", " + errorCode); + } + } + } + + onRequestProgressChanged: { + console.log(_action, requestProgress); + } + + /*--------------------------------------------------------------------------*/ + + function makeNetworkConnection(action, url, obj, method, responseType, path, callback, params) { + var component = networkRequestComponent; + var networkRequest = component.createObject(parent); + networkRequest.url = url; + networkRequest.callback = callback; + if(path > "") networkRequest.responsePath = path; + networkRequest.responseType = responseType; + networkRequest.params = params; + _action = getActionName(action); + networkRequest.send(obj); + } + + function getActionName(action) { + var actionName = ""; + + switch(action) { + case 1: + actionName = strings.action_1; + break; + case 2: + actionName = strings.action_2; + break; + case 3: + actionName = strings.action_3; + break; + case 4: + actionName = strings.action_4; + break; + case 5: + actionName = strings.action_5; + break; + } + + return actionName; + } + + function getItemInfo(itemId, callback) { + var url = _sourceRootURL + ("/sharing/rest/content/items/%1").arg(itemId); + var obj = { + f: "json", + "token": _sourceToken + } + + makeNetworkConnection(1, url, obj, "GET", "json", "", callback); + } + + function downloadData(itemId, callback) { + if(fileFolder.fileExists(itemId)) fileFolder.removeFile(itemId + ".zip"); + var path = [fileFolder.path, itemId + ".zip"].join("/"); + + var url = _sourceRootURL + ("/sharing/rest/content/items/%1/data").arg(itemId); + var obj = { + "token": _sourceToken + } + + makeNetworkConnection(2, url, obj, "GET", "zip", path, callback); + } + + function downloadThumbnail(itemId, thumbnail, callback) { + if(fileFolder.fileExists(itemId)) fileFolder.removeFile(itemId); + var path = [fileFolder.path, itemId].join("/"); + + var url = _sourceRootURL + ("/sharing/rest/content/items/%1/info/%2").arg(itemId).arg(thumbnail); + var obj = { + "token": _sourceToken + } + + makeNetworkConnection(3, url, obj, "GET", "image", path, callback); + } + + function cleanItemInfo(itemInfo) { + itemInfo.id = undefined; + itemInfo.name = undefined; + itemInfo.owner = undefined; + itemInfo.ownerFolder = undefined; + + console.log(JSON.stringify(itemInfo)); + + return itemInfo; + } + + function prepareItemInfo(itemInfo) { + var keys = Object.keys(itemInfo); + keys.forEach(function(key) { + var value = itemInfo[key]; + + if (Array.isArray(value)) { + itemInfo[key] = value.join(", "); + } + }); + + return itemInfo; + } + + function addItem(itemInfo, callback) { + var url = _destRootURL + "/sharing/rest/content/users/" + _destUsername + "/addItem"; + var obj = prepareItemInfo(cleanItemInfo(itemInfo)); + obj.token = _destToken; + obj.f = "pjson"; + + makeNetworkConnection(4, url, obj, "POST", "json", "", callback); + } + + function updateItemContent(itemId, itemInfo, callback) { + var url = _destRootURL + "/sharing/rest/content/users/" + _destUsername + "/items/%1/update".arg(itemId); + + var filePath = [fileFolder.path, itemId + ".zip"].join("/"); + var thumbnailPath = [fileFolder.path, "thumbnail.png"].join("/"); + + if(fileFolder.fileExists(itemId+".zip")) fileFolder.removeFile(itemId+".zip"); + if(fileFolder.fileExists("thumbnail.png")) fileFolder.removeFile("thumbnail.png"); + + fileFolder.renameFile(_sourceItemId + ".zip", itemId+".zip"); + fileFolder.renameFile(_sourceItemId, "thumbnail.png"); + + + var obj = prepareItemInfo(cleanItemInfo(itemInfo)); + obj.name = itemId + ".zip"; + obj.id = itemId; + obj.token = _destToken; + obj.f = "pjson"; + obj.file = "@"+filePath; + obj.thumbnail = "@"+thumbnailPath; + + makeNetworkConnection(5, url, obj, "POST", "json", "", callback); + } + + function transfer(itemId, callback) { + if(!(itemId > "")) return; + + _sourceItemId = itemId; + + getItemInfo(_sourceItemId, function(sourceItemInfo) { + _itemInfoTemplate = sourceItemInfo; + _itemThumbnail = sourceItemInfo.thumbnail; + + downloadData(_sourceItemId, function() { + downloadThumbnail(_sourceItemId, _itemThumbnail, function(){ + addItem(_itemInfoTemplate, function(response){ + _destItemId = response.id; + updateItemContent(_destItemId, _itemInfoTemplate, function(){ + clearAllTempFiles(); + callback(); + }); + }) + }) + }) + }) + } + + function clearAllTempFiles(){ + if(fileFolder.fileExists(_sourceItemId+".zip")) fileFolder.removeFile(_sourceItemId+".zip"); + if(fileFolder.fileExists(_sourceItemId)) fileFolder.removeFile(_sourceItemId); + if(fileFolder.fileExists(_destItemId+".zip")) fileFolder.removeFile(_destItemId+".zip"); + if(fileFolder.fileExists("thumbnail.png")) fileFolder.removeFile("thumbnail.png"); + } +} diff --git a/Replicator/Images/ago_portal.png b/Replicator/Images/ago_portal.png new file mode 100644 index 0000000..276155c Binary files /dev/null and b/Replicator/Images/ago_portal.png differ diff --git a/Replicator/Images/failed.png b/Replicator/Images/failed.png new file mode 100644 index 0000000..b464c3f Binary files /dev/null and b/Replicator/Images/failed.png differ diff --git a/Replicator/Images/home.jpg b/Replicator/Images/home.jpg new file mode 100644 index 0000000..5453618 Binary files /dev/null and b/Replicator/Images/home.jpg differ diff --git a/Replicator/Images/home.png b/Replicator/Images/home.png new file mode 100644 index 0000000..542c314 Binary files /dev/null and b/Replicator/Images/home.png differ diff --git a/Replicator/Images/ic_arrow_back_white_48dp.png b/Replicator/Images/ic_arrow_back_white_48dp.png new file mode 100644 index 0000000..a70e614 Binary files /dev/null and b/Replicator/Images/ic_arrow_back_white_48dp.png differ diff --git a/Replicator/Images/ic_close_white_48dp.png b/Replicator/Images/ic_close_white_48dp.png new file mode 100644 index 0000000..1ab2312 Binary files /dev/null and b/Replicator/Images/ic_close_white_48dp.png differ diff --git a/Replicator/Images/ic_keyboard_arrow_left_white_24dp.png b/Replicator/Images/ic_keyboard_arrow_left_white_24dp.png new file mode 100644 index 0000000..36cfbc9 Binary files /dev/null and b/Replicator/Images/ic_keyboard_arrow_left_white_24dp.png differ diff --git a/Replicator/Images/ic_keyboard_arrow_right_white_24dp.png b/Replicator/Images/ic_keyboard_arrow_right_white_24dp.png new file mode 100644 index 0000000..13341cb Binary files /dev/null and b/Replicator/Images/ic_keyboard_arrow_right_white_24dp.png differ diff --git a/Replicator/Images/ic_mode_edit_white_24dp.png b/Replicator/Images/ic_mode_edit_white_24dp.png new file mode 100644 index 0000000..d6668a0 Binary files /dev/null and b/Replicator/Images/ic_mode_edit_white_24dp.png differ diff --git a/Replicator/Images/ic_radio_button_checked_white_24dp.png b/Replicator/Images/ic_radio_button_checked_white_24dp.png new file mode 100644 index 0000000..663c475 Binary files /dev/null and b/Replicator/Images/ic_radio_button_checked_white_24dp.png differ diff --git a/Replicator/Images/ic_radio_button_unchecked_white_24dp.png b/Replicator/Images/ic_radio_button_unchecked_white_24dp.png new file mode 100644 index 0000000..e32217d Binary files /dev/null and b/Replicator/Images/ic_radio_button_unchecked_white_24dp.png differ diff --git a/Replicator/Images/loading.gif b/Replicator/Images/loading.gif new file mode 100644 index 0000000..716839d Binary files /dev/null and b/Replicator/Images/loading.gif differ diff --git a/Replicator/Images/placeholder.jpg b/Replicator/Images/placeholder.jpg new file mode 100644 index 0000000..7754090 Binary files /dev/null and b/Replicator/Images/placeholder.jpg differ diff --git a/Replicator/Images/portal.png b/Replicator/Images/portal.png new file mode 100644 index 0000000..9909591 Binary files /dev/null and b/Replicator/Images/portal.png differ diff --git a/Replicator/Images/success.png b/Replicator/Images/success.png new file mode 100644 index 0000000..8873c7a Binary files /dev/null and b/Replicator/Images/success.png differ diff --git a/Replicator/Images/test.jpg b/Replicator/Images/test.jpg new file mode 100644 index 0000000..37cced1 Binary files /dev/null and b/Replicator/Images/test.jpg differ diff --git a/Replicator/MyApp.qml b/Replicator/MyApp.qml new file mode 100644 index 0000000..342c2ea --- /dev/null +++ b/Replicator/MyApp.qml @@ -0,0 +1,294 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +import QtQuick 2.7 +import QtQuick.Controls 2.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import "./Assets" as Assets +import "./Components" +import "./Views" + +App { + id: app + width: 380 + height: 640 + + readonly property real scaleFactor: AppFramework.displayScaleFactor + readonly property int maximumScreenWidth: 568 * scaleFactor + property bool isIphoneX: false + + property bool isUserLoggedInPortalA: false + property bool isUserLoggedInPortalB: false + readonly property string appClientId: app.info.json.deployment.clientId + readonly property string appPortalUrl: "https://www.arcgis.com" + + property var tempStackViewItem + + /*--------------------------------------------------------------------------*/ + + Component.onCompleted: { + init(); + } + + /*--------------------------------------------------------------------------*/ + + function init(){ + // check if iphone x + if (Qt.platform.os === "ios" && AppFramework.systemInformation.hasOwnProperty("unixMachine")) { + if (AppFramework.systemInformation.unixMachine === "iPhone10,3" || AppFramework.systemInformation.unixMachine === "iPhone10,6") { + isIphoneX = true; + } + } + + initPortal("portalA"); + initPortal("portalB"); + } + + function initPortal(portalName) { + var status = app.settings.value(portalName+"/isloggedIn"); + console.log("status", status) + var refreshToken = app.settings.value(portalName+"/refreshToken"); + var _appClientId = "", _appPortalUrl = ""; + + // first time log in + if (status === undefined) { + _appClientId = app.settings.setValue("appClientId", appClientId); + _appPortalUrl = app.settings.setValue(portalName + "/appPortalUrl", appPortalUrl); + } + // not the first time + else { + _appClientId = app.settings.value("appClientId"); + // case sensitive doesn't matter + _appPortalUrl = app.settings.value(portalName+"/appPortalUrl"); + + if (status === true && refreshToken > "") { + if(portalName === "portalA") { + isUserLoggedInPortalA = status; + } else { + isUserLoggedInPortalB = status; + } + } + } + + console.log("portal A auto sign in", isUserLoggedInPortalA) + + // if user has changed either clientId or portal url + if (appClientId !== _appClientId) { + // set to false and clear the state + if(portalName === "portalA") { + isUserLoggedInPortalA = false; + } else { + isUserLoggedInPortalB = false; + } + + app.settings.setValue("appClientId", appClientId); + app.settings.setValue(portalName+"/appPortalUrl", appPortalUrl); + clearSettings(portalName); + } + + console.log("portal A auto sign in", isUserLoggedInPortalA) + + if(AppFramework.network.isOnline){ + if(portalName === "portalA") { + if(isUserLoggedInPortalA) { + portalA.autoSignIn(); + } + } else { + if(isUserLoggedInPortalB) { + portalB.autoSignIn(); + } + } + } + } + + function clearSettings(portalName) { + app.settings.setValue(portalName+"/isloggedIn", false); + + if(portalName === "portalA") { + isUserLoggedInPortalA = false; + portalA.clearSignedInState(); + portalA.signOut(); + } else { + isUserLoggedInPortalB = false; + portalB.clearSignedInState(); + portalB.signOut(); + } + } + + /*--------------------------------------------------------------------------*/ + + Assets.Fonts { + id: fonts + } + + Assets.Colors { + id: colors + } + + Assets.Strings { + id: strings + } + + Assets.Sources { + id: sources + } + + /*--------------------------------------------------------------------------*/ + + // Component for copying an app from portal A to portal B + TransferManager { + id: transferManager + + sourcePortal: portalA + destPortal: portalB + } + + /*--------------------------------------------------------------------------*/ + + NetworkManager { + id: networkManager + } + + /*--------------------------------------------------------------------------*/ + + Portal { + id: portalA + tag: "portalA" + clientID: app.appClientId + settings: app.settings + + onSignedInChanged: { + if (signedIn) { + portalA.writeSignedInState(); + app.settings.setValue("portalA/isloggedIn", true); + if(typeof stackView.currentItem.tag !== "undefined" && stackView.currentItem.tag === "websigninpage") stackView.pop(tempStackViewItem); + } + } + } + + Portal { + id: portalB + tag: "portalB" + clientID: app.appClientId + settings: app.settings + + onSignedInChanged: { + if (signedIn) { + console.log("portalB signed in") + portalB.writeSignedInState(); + app.settings.setValue("portalB/isloggedIn", true); + if(typeof stackView.currentItem.tag !== "undefined" && stackView.currentItem.tag === "websigninpage") stackView.pop(tempStackViewItem); + } + } + } + + /*--------------------------------------------------------------------------*/ + + StackView { + id: stackView + anchors.fill: parent + initialItem: homepage + } + + Component { + id: homepage + HomePage { + onStart: { + stackView.push(portalsChooserPage); + } + } + } + + // Page for choosing portals + Component { + id: portalsChooserPage + + PortalsChooserPage { + onNext: { + stackView.push(contentPage) + } + + onBack: { + stackView.pop(); + } + } + } + + // Page for choosing content from portal A + Component { + id: contentPage + + ContentPage { + onNext: { + stackView.push(confirmPage, {itemDetails: itemDetails}) + } + + onBack: { + stackView.pop(); + } + } + } + + // Page for confirming the copy operation + Component { + id: confirmPage + + ConfirmPage { + onNext: { + stackView.push(resultPage, {itemId: itemDetails.id}); + } + + onBack: { + stackView.pop(); + } + } + } + + Component { + id: portalTypePage + PortalTypePage { + + } + } + + Component { + id: portalURLSettingsPage + PortalURLSettingsPage { + + } + } + + Component { + id: webSignInPage + WebSignInPage { + + } + } + + + // Page for loading + Component { + id: resultPage + ResultPage { + + } + } +} + diff --git a/Replicator/MyApp.qmlproject b/Replicator/MyApp.qmlproject new file mode 100644 index 0000000..754767c --- /dev/null +++ b/Replicator/MyApp.qmlproject @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ + +import QmlProject 1.1 + +Project { + mainFile: "MyApp.qml" + + QmlFiles { + directory: "." + recursive: true + } + + JavaScriptFiles { + directory: "." + recursive: true + } + + ImageFiles { + directory: "." + recursive: true + } + + Files { + directory: "." + recursive: true + filter: "*.json;*.html;*.txt;*.conf" + } + + importPaths: [ + ] +} + diff --git a/Replicator/README.md b/Replicator/README.md new file mode 100644 index 0000000..8fc64cb --- /dev/null +++ b/Replicator/README.md @@ -0,0 +1,42 @@ +## Replicator + +You can copy your AppStudio apps and paste them to your other orgs in 3 easy steps with this sample app. First sign in to both org accounts, then select the app that you want to replicate, and finally click on the Copy button to copy app to another org. + + +[Resource Level](https://geonet.esri.com/groups/appstudio/blog/2016/12/06/how-to-describe-our-resources-in-terms-of-difficulty-complexity-and-time-to-digest): 🍌🍌 + + +## Instructions to run this sample in AppStudio Desktop + +1. Download the `.zip` file +2. Unzip and copy this folder into AppStudio Apps folder (Windows: `C:\Users\\ArcGIS\AppStudio\Apps` Mac or linux: `Home\ArcGIS\AppStudio\Apps`) +3. The new app will now appear in the AppStudio Desktop. Run the application or open it in the bundled Qt-Creator IDE to look at the code and modify. + +## Issues + +Find a bug or want to request a new feature? Please let us know by submitting an issue. + +## Contributing + +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). + +## Licensing +Copyright 2017 Esri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +A copy of the license is available in the repository's [license.txt](license.txt) file. + + +[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) +[](Esri Language: Qt QML JavaScript) diff --git a/Replicator/Views/ConfirmPage.qml b/Replicator/Views/ConfirmPage.qml new file mode 100644 index 0000000..da60300 --- /dev/null +++ b/Replicator/Views/ConfirmPage.qml @@ -0,0 +1,262 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import "../Widgets" + +Page { + id: root + + property var itemDetails + + signal next() + signal back() + signal editContent(); + + Flickable { + anchors.fill: parent + contentWidth: parent.width + contentHeight: bodyColumnLayout.height + + ColumnLayout { + id: bodyColumnLayout + width: Math.min(parent.width - 32 * scaleFactor, maximumScreenWidth) + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 25 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: strings.step_no.arg(3) + font { + family: fonts.fontFamily_Regular.name + pixelSize: 24 * scaleFactor + } + color: colors.primary_color + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 8 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: strings.step3_description + font { + family: fonts.fontFamily_Regular.name + pixelSize: 14 * scaleFactor + } + color: colors.black_54 + wrapMode: Label.Wrap + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 24 * scaleFactor + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 132 * scaleFactor + + color: colors.card_background + border.width: 1 + border.color: colors.card_border + radius: 2 * scaleFactor + clip: true + + ColumnLayout { + width: parent.width - 32 * scaleFactor + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: strings.source_app + font { + family: fonts.fontFamily_Medium.name + pixelSize: 12 * scaleFactor + } + color: colors.black_54 + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 52 * scaleFactor + + RowLayout { + anchors.fill: parent + spacing: 0 + + Image { + Layout.fillHeight: true + Layout.preferredWidth: 78 * scaleFactor + source: itemDetails.thumbnail > "" && status != Image.Error ? itemDetails.thumbnail : sources.placeholder + fillMode: Image.PreserveAspectFit + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 16 * scaleFactor + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + ColumnLayout { + width: parent.width + anchors.verticalCenter: parent.verticalCenter + spacing: 0 + + Label { + Layout.fillWidth: true + text: itemDetails.title + leftPadding: rightPadding + rightPadding: 0 + maximumLineCount: 2 + wrapMode: Label.Wrap + font { + family: fonts.fontFamily_Regular.name + pixelSize: 14 * scaleFactor + } + color: colors.black_87 + clip: true + elide: Label.ElideRight + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 4 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: itemDetails.owner + leftPadding: rightPadding + rightPadding: 0 + font { + family: fonts.fontFamily_Regular.name + pixelSize: 12 * scaleFactor + } + color: colors.black_54 + clip: true + elide: Label.ElideRight + } + + Label { + Layout.fillWidth: true + text: itemDetails.modified + leftPadding: rightPadding + rightPadding: 0 + font { + family: fonts.fontFamily_Regular.name + pixelSize: 12 * scaleFactor + } + color: colors.black_54 + clip: true + elide: Label.ElideRight + } + } + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 0 * scaleFactor + } + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + PortalCard { + id: portalCard_A + + portal: portalA + mode: 1 + editEnabled: false + + Layout.fillWidth: true + Layout.preferredHeight: height + + onOpenSignInPage: { + tempStackViewItem = stackView.currentItem; + stackView.push(portalTypePage, {"mode": 1}, StackView.Immediate); + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + PortalCard { + id: portalCard_B + + portal: portalB + mode: 2 + editEnabled: false + + Layout.fillWidth: true + Layout.preferredHeight: height + + onOpenSignInPage: { + tempStackViewItem = stackView.currentItem; + stackView.push(portalTypePage, {"mode": 2}, StackView.Immediate); + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 24 * scaleFactor + } + } + + } + + footer: NavigatorFooter { + id: navigatorFooter + + isNextEnabled: portalCard_A.isSignedIn && portalCard_B.isSignedIn && itemDetails.id > "" + text2: strings.confirm + icon2.visible: false + + onBack: { + root.back(); + } + + onNext: { + root.next(); + } + } +} diff --git a/Replicator/Views/ContentPage.qml b/Replicator/Views/ContentPage.qml new file mode 100644 index 0000000..0a00666 --- /dev/null +++ b/Replicator/Views/ContentPage.qml @@ -0,0 +1,481 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import "../Widgets" + +Page { + id: root + + property string selectedItemId: "" + + property var itemDetails + + property int nextStart: 1 + property bool isPageLoading: false + property bool isNextPageLoading: false + property int totalResultCount: 0 + property bool isMyApps: true + + signal next() + signal back() + + ColumnLayout { + id: bodyColumnLayout + width: Math.min(parent.width, maximumScreenWidth) + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + Item { + Layout.preferredWidth: parent.width - 32 * scaleFactor + Layout.preferredHeight: 25 * scaleFactor + } + + Label { + Layout.preferredWidth: parent.width - 32 * scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + text: strings.step_no.arg(2) + font { + family: fonts.fontFamily_Regular.name + pixelSize: 24 * scaleFactor + } + color: colors.primary_color + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 8 * scaleFactor + } + + Label { + Layout.preferredWidth: parent.width - 32 * scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + text: strings.step2_description + font { + family: fonts.fontFamily_Regular.name + pixelSize: 14 * scaleFactor + } + color: colors.black_54 + wrapMode: Label.Wrap + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + Rectangle { + id: contentInfo + + Layout.fillWidth: true + Layout.preferredHeight: 48 * scaleFactor + + color: colors.default_content_color + + RowLayout { + width: parent.width - 32 * scaleFactor + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + Label { + text: strings.step2_showing.arg(contentModel.count).arg(totalResultCount) + visible: totalResultCount > 0 && contentModel.count > 0 + font { + family: fonts.fontFamily_Regular.name + pixelSize: 12 * scaleFactor + } + color: colors.black_54 + wrapMode: Label.Wrap + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: container.width + + MouseArea { + anchors.fill: parent + onClicked: { + optionMenu.x = contentInfo.x + contentInfo.width - optionMenu.width - 16 * scaleFactor; + optionMenu.y = contentInfo.y + 12 * scaleFactor; + optionMenu.open(); + } + } + + RowLayout { + id: container + + height: parent.height + + Label { + text: isMyApps ? strings.step2_myapps : strings.step2_allapps + font { + family: fonts.fontFamily_Medium.name + pixelSize: 14 * scaleFactor + } + color: colors.black_87 + wrapMode: Label.Wrap + } + + Item { + Layout.preferredWidth: 4 * scaleFactor + Layout.fillHeight: true + } + + IconImage { + Layout.preferredWidth: 15 * scaleFactor + Layout.preferredHeight: 15 * scaleFactor + source: sources.arrow_left + color: "#007472" + rotation: 270 + } + } + } + } + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 0 + clip: true + + model: contentModel + + BusyIndicator { + visible: running + running: isPageLoading && !isNextPageLoading + anchors.centerIn: parent + Material.accent: colors.primary_color + } + + footer: Item { + width: parent.width + height: 56 * scaleFactor + visible: isNextPageLoading + + BusyIndicator { + anchors.centerIn: parent + running: isNextPageLoading + Material.accent: colors.primary_color + } + } + + onAtYEndChanged: { + if(atYEnd && contentY > 0 && !isNextPageLoading && model.count < totalResultCount) { + isNextPageLoading = true; + getContents(nextStart); + } + } + + delegate: Item { + width: parent.width + height: 84 * scaleFactor + + MouseArea { + anchors.fill: parent + onClicked: { + selectedItemId = itemId; + itemDetails = details; + } + } + + Rectangle { + width: Math.min(parent.width, maximumScreenWidth - 32 * scaleFactor) + height: 1 + color: "#1F000000" + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + } + + RowLayout { + width: parent.width - 32 * scaleFactor + height: 52 * scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + spacing: 0 + + Image { + Layout.fillHeight: true + Layout.preferredWidth: 78 * scaleFactor + source: thumbnail > "" && status != Image.Error ? thumbnail : sources.placeholder + fillMode: Image.PreserveAspectFit + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 16 * scaleFactor + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + ColumnLayout { + width: parent.width + anchors.verticalCenter: parent.verticalCenter + spacing: 0 + + Label { + Layout.fillWidth: true + text: title + leftPadding: rightPadding + rightPadding: 0 + font { + family: fonts.fontFamily_Regular.name + pixelSize: 14 * scaleFactor + } + color: colors.black_87 + clip: true + elide: Label.ElideRight + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 4 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: owner + leftPadding: rightPadding + rightPadding: 0 + font { + family: fonts.fontFamily_Regular.name + pixelSize: 12 * scaleFactor + } + color: colors.black_54 + clip: true + elide: Label.ElideRight + } + + Label { + Layout.fillWidth: true + text: modified + leftPadding: rightPadding + rightPadding: 0 + font { + family: fonts.fontFamily_Regular.name + pixelSize: 12 * scaleFactor + } + color: colors.black_54 + clip: true + elide: Label.ElideRight + } + } + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 16 * scaleFactor + } + + IconImage { + Layout.preferredWidth: 21 * scaleFactor + Layout.preferredHeight: 21 * scaleFactor + anchors.verticalCenter: parent.verticalCenter + color: colors.primary_color + source: itemId === selectedItemId ? sources.radio_checked : sources.radio_unchecked + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 4 * scaleFactor + } + } + } + } + } + + footer: NavigatorFooter { + id: navigatorFooter + + isNextEnabled: selectedItemId > "" + + onBack: { + root.back(); + } + + onNext: { + root.next(); + } + } + + ListModel { + id: contentModel + } + + function refresh(){ + nextStart = 1; + isPageLoading = false; + isNextPageLoading = false; + contentModel.clear(); + + getContents(nextStart); + } + + function getContents(start){ + networkManager.getApps(start, isMyApps, function(response){ + nextStart = response.nextStart; + totalResultCount = response.total; + + if (response.hasOwnProperty("results")) { + var results = response.results; + + if(results.length > 0) { + var result; + + for(var i in results) { + result = results[i]; + + var thumbnail = "", itemId = "", modified = "", owner = "", title = ""; + + if(result.hasOwnProperty("id") && result.id !== null) itemId = result.id; + if(result.hasOwnProperty("modified") && result.modified !== null) { + modified = timeConvert(result.modified); + result.modified = modified; + } + if(result.hasOwnProperty("owner") && result.owner !== null) owner = result.owner; + if(result.hasOwnProperty("title") && result.title !== null) title = result.title; + if(result.hasOwnProperty("thumbnail") && result.thumbnail !== null) { + var thumbnailId = result.thumbnail; + + if(thumbnailId > "") { + thumbnail = networkManager.rootUrl + "/sharing/rest/content/items/" + + itemId + "/info/" + thumbnailId + "?token=" + networkManager.token; + result.thumbnail = thumbnail; + } + } + + contentModel.append({itemId: itemId, modified: modified, owner: owner, title: title, thumbnail: thumbnail, details: result}); + } + + isNextPageLoading = false; + } + } + + isPageLoading = false; + }) + } + + function timeConvert(unixTime) { + var modifiedDate = ""; + + var currentTime = new Date().getTime(); + + if (currentTime >= unixTime) { + var timeDifference = currentTime - unixTime; + var minuteDifference = Math.floor(timeDifference / 60 / 1000); + var hourDifference = Math.floor(timeDifference / 60 / 60 / 1000); + var dayDifference = Math.floor(timeDifference / 60 / 60 / 24 / 1000); + var weekDifference = Math.floor(timeDifference / 60 / 60 / 24 / 7 / 1000); + + // if less than a day + if (dayDifference < 1) { + if (minuteDifference < 1) { + modifiedDate = strings.just_now; + } else if (hourDifference < 1) { + if (minuteDifference < 2) { + modifiedDate = strings.single_minute; + } else { + modifiedDate = strings.multi_minutes.arg(minuteDifference); + } + } else { + if (hourDifference < 2) { + modifiedDate = strings.single_hour; + } else { + modifiedDate = strings.multi_hours.arg(hourDifference); + } + } + } else if (dayDifference < 7) { + if (dayDifference < 2) { + modifiedDate = strings.single_day; + } else { + modifiedDate = strings.multi_days.arg(dayDifference); + } + } else if (weekDifference <= 4) { + if (weekDifference < 2) { + modifiedDate = strings.single_week; + } else { + modifiedDate = strings.multi_weeks.arg(weekDifference); + } + } else { + modifiedDate = formatDate(unixTime); + } + } else { + modifiedDate = formatDate(unixTime); + } + + return modifiedDate; + } + + // Format the date + function formatDate(unixTime) { + var date = new Date(unixTime); + + var modifiedDate = date.toLocaleDateString(Qt.locale(), Locale.ShortFormat); + + return modifiedDate; + } + + Menu { + id: optionMenu + width: 192 * scaleFactor + padding: 0 + + MenuItem { + contentItem: Label { + Layout.fillWidth: true + text: strings.step2_myapps + font { + family: fonts.fontFamily_Regular.name + pixelSize: 16 * scaleFactor + } + color: colors.black_87 + clip: true + elide: Label.ElideRight + } + + onTriggered: { + isMyApps = true; + } + } + + MenuItem { + contentItem: Label { + Layout.fillWidth: true + text: strings.step2_allapps + font { + family: fonts.fontFamily_Regular.name + pixelSize: 16 * scaleFactor + } + color: colors.black_87 + clip: true + elide: Label.ElideRight + } + + onTriggered: { + isMyApps = false; + } + } + } + + onIsMyAppsChanged: { + refresh(); + } + + Component.onCompleted: { + isPageLoading = true; + getContents(nextStart) + } +} diff --git a/Replicator/Views/HomePage.qml b/Replicator/Views/HomePage.qml new file mode 100644 index 0000000..a5d18ff --- /dev/null +++ b/Replicator/Views/HomePage.qml @@ -0,0 +1,106 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + + +Item { + signal start() + + ColumnLayout { + width: parent.width - 72 * scaleFactor + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + clip: true + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 36 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: strings.homepage_welcome + font { + family: fonts.fontFamily_Regular.name + pixelSize: 24 * scaleFactor + } + color: colors.black_87 + } + + Label { + Layout.fillWidth: true + text: app.info.title + font { + family: fonts.fontFamily_Medium.name + pixelSize: 24 * scaleFactor + } + color: colors.primary_color + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 8 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: strings.homepage_app_description + font { + family: fonts.fontFamily_Regular.name + pixelSize: 14 * scaleFactor + } + color: colors.black_54 + wrapMode: Label.Wrap + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 60 * scaleFactor + } + + Image { + Layout.fillWidth: true + Layout.fillHeight: true + source: sources.homeImage + fillMode: Image.PreserveAspectFit + mipmap: true + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 62 * scaleFactor + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + topPadding: 9 * scaleFactor + bottomPadding: topPadding + rightPadding: 24 * scaleFactor + leftPadding: rightPadding + + text: strings.homepage_get_start + Material.foreground: colors.white_100 + Material.background: colors.primary_color + + font { + family: fonts.fontFamily_Medium.name + pixelSize: 14 * scaleFactor + } + + onClicked: { + start(); + } + + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 51 * scaleFactor + } + } +} diff --git a/Replicator/Views/PortalTypePage.qml b/Replicator/Views/PortalTypePage.qml new file mode 100644 index 0000000..3cf223d --- /dev/null +++ b/Replicator/Views/PortalTypePage.qml @@ -0,0 +1,203 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import "../Widgets" + +Page { + property int mode: 1 + property var portal: mode === 1 ? portalA : portalB + + header: ToolBar { + height: 56 * scaleFactor + Material.primary: colors.primary_color + Material.elevation: 4 + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.preferredWidth: 56 * scaleFactor + Layout.fillHeight: true + Layout.alignment: Qt.AlignLeft + + SmartToolButton { + imageSource: sources.close + + onClicked: { + stackView.pop(StackView.Immediate); + } + } + } + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + text: mode === 1 ? strings.source_account : strings.dest_account + + font { + family: fonts.fontFamily_Medium.name + pixelSize: 20 * scaleFactor + } + color: colors.white_100 + + horizontalAlignment: Label.AlignLeft + verticalAlignment: Label.AlignVCenter + + clip: true + elide: Text.ElideRight + } + } + } + + Item { + anchors.fill: parent + + ColumnLayout { + width: Math.min(parent.width - 32 * scaleFactor, maximumScreenWidth) + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 24 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: strings.select_account_type + font { + family: fonts.fontFamily_Medium.name + pixelSize: 14 * scaleFactor + } + color: colors.black_87 + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: parent.width * 0.49 + + RowLayout { + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + Rectangle { + Layout.preferredWidth: parent.height + Layout.fillHeight: true + + color: colors.card_background + border.width: 1 + border.color: colors.card_border + radius: 2 * scaleFactor + clip: true + + ColumnLayout { + width: parent.width + spacing: 0 + anchors.centerIn: parent + + IconImage { + Layout.preferredWidth: parent.width * 0.31 + Layout.preferredHeight: parent.width * 0.31 + source: sources.ago_portal + color: colors.primary_color + anchors.horizontalCenter: parent.horizontalCenter + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: parent.width * 0.08 + } + + Label { + text: strings.arcgis_online + font { + family: fonts.fontFamily_Regular.name + pixelSize: 14 * scaleFactor + } + color: colors.black_87 + anchors.horizontalCenter: parent.horizontalCenter + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + portal.url = "https://www.arcgis.com" + stackView.push(webSignInPage, {"mode": mode}, StackView.Immediate); + } + } + } + + Item { + Layout.preferredWidth: parent.width * 0.02 + Layout.fillHeight: true + } + + Rectangle { + Layout.preferredWidth: parent.height + Layout.fillHeight: true + + color: colors.card_background + border.width: 1 + border.color: colors.card_border + radius: 2 * scaleFactor + clip: true + + ColumnLayout { + width: parent.width + spacing: 0 + anchors.centerIn: parent + + IconImage { + Layout.preferredWidth: parent.width * 0.31 + Layout.preferredHeight: parent.width * 0.31 + source: sources.enterpise_portal + color: colors.primary_color + anchors.horizontalCenter: parent.horizontalCenter + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: parent.width * 0.08 + } + + Label { + text: strings.arcgis_enterprise + font { + family: fonts.fontFamily_Regular.name + pixelSize: 14 * scaleFactor + } + color: colors.black_87 + anchors.horizontalCenter: parent.horizontalCenter + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + stackView.push(portalURLSettingsPage, {"mode": mode}, StackView.Immediate); + } + } + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } +} diff --git a/Replicator/Views/PortalURLSettingsPage.qml b/Replicator/Views/PortalURLSettingsPage.qml new file mode 100644 index 0000000..cdffb8b --- /dev/null +++ b/Replicator/Views/PortalURLSettingsPage.qml @@ -0,0 +1,157 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import "../Widgets" + +Page { + property int mode: 1 + property var portal: mode === 1 ? portalA : portalB + + header: ToolBar { + height: 56 * scaleFactor + Material.primary: colors.primary_color + Material.elevation: 4 + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.preferredWidth: 56 * scaleFactor + Layout.fillHeight: true + Layout.alignment: Qt.AlignLeft + + SmartToolButton { + imageSource: sources.close + + onClicked: { + stackView.pop(StackView.Immediate); + } + } + } + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + text: mode === 1 ? strings.source_account : strings.dest_account + + font { + family: fonts.fontFamily_Medium.name + pixelSize: 20 * scaleFactor + } + color: colors.white_100 + + horizontalAlignment: Label.AlignLeft + verticalAlignment: Label.AlignVCenter + + clip: true + elide: Text.ElideRight + } + } + } + + Item { + anchors.fill: parent + + ColumnLayout { + width: Math.min(parent.width - 32 * scaleFactor, maximumScreenWidth) + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 24 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: strings.arcgis_enterprise_url + font { + family: fonts.fontFamily_Medium.name + pixelSize: 14 * scaleFactor + } + color: colors.black_87 + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 24 * scaleFactor + } + + TextField { + id: urlTextField + + Layout.fillWidth: true + Material.accent: colors.primary_color + inputMethodHints: Qt.ImhUrlCharactersOnly + selectByMouse: true + + font { + family: fonts.fontFamily_Regular.name + pixelSize: 16 * scaleFactor + } + color: colors.black_87 + + onAccepted: { + openWebSignInPage(); + } + + Component.onCompleted: { + this.forceActiveFocus(); + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 32 * scaleFactor + } + + Label { + text: strings.goto_signin + anchors.left: parent.left + font { + family: fonts.fontFamily_Medium.name + pixelSize: 16 * scaleFactor + } + color: colors.primary_color + } + + Button { + anchors.right: parent.right + topPadding: 9 * scaleFactor + bottomPadding: topPadding + rightPadding: 24 * scaleFactor + leftPadding: rightPadding + + text: strings.next + Material.foreground: colors.white_100 + Material.background: colors.primary_color + + font { + family: fonts.fontFamily_Regular.name + pixelSize: 16 * scaleFactor + } + + onClicked: { + openWebSignInPage(); + } + + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + } + } + + function openWebSignInPage() { + portal.url = urlTextField.text; + stackView.push(webSignInPage, {"mode": mode}, StackView.Immediate); + } +} diff --git a/Replicator/Views/PortalsChooserPage.qml b/Replicator/Views/PortalsChooserPage.qml new file mode 100644 index 0000000..706e76e --- /dev/null +++ b/Replicator/Views/PortalsChooserPage.qml @@ -0,0 +1,115 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import "../Widgets" + +Page { + id: root + + signal next() + signal back() + + Flickable { + anchors.fill: parent + contentWidth: parent.width + contentHeight: bodyColumnLayout.height + + ColumnLayout { + id: bodyColumnLayout + width: Math.min(parent.width - 32 * scaleFactor, maximumScreenWidth) + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 25 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: strings.step_no.arg(1) + font { + family: fonts.fontFamily_Regular.name + pixelSize: 24 * scaleFactor + } + color: colors.primary_color + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 8 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: strings.step1_description + font { + family: fonts.fontFamily_Regular.name + pixelSize: 14 * scaleFactor + } + color: colors.black_54 + wrapMode: Label.Wrap + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 24 * scaleFactor + } + + PortalCard { + id: portalCard_A + + portal: portalA + mode: 1 + + Layout.fillWidth: true + Layout.preferredHeight: height + + onOpenSignInPage: { + tempStackViewItem = stackView.currentItem; + stackView.push(portalTypePage, {"mode": 1}, StackView.Immediate); + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + PortalCard { + id: portalCard_B + + portal: portalB + mode: 2 + + Layout.fillWidth: true + Layout.preferredHeight: height + + onOpenSignInPage: { + tempStackViewItem = stackView.currentItem; + stackView.push(portalTypePage, {"mode": 2}, StackView.Immediate); + } + } + } + + } + + footer: NavigatorFooter { + id: navigatorFooter + + isNextEnabled: portalCard_A.isSignedIn && portalCard_B.isSignedIn + + onBack: { + root.back(); + } + + onNext: { + root.next(); + } + } +} diff --git a/Replicator/Views/ResultPage.qml b/Replicator/Views/ResultPage.qml new file mode 100644 index 0000000..77cf0bc --- /dev/null +++ b/Replicator/Views/ResultPage.qml @@ -0,0 +1,204 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + property string itemId: "" + + property int resultState: 1 //0 = default, 1 = loading, 2 = error, 3 = done + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + visible: resultState === 1 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 170 * scaleFactor + } + + Item { + Layout.preferredWidth: 120 * scaleFactor + Layout.preferredHeight: 120 * scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + AnimatedImage { + width: 100 * scaleFactor + height: width + source: sources.loading_image + playing: visible + anchors.centerIn: parent + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 26 * scaleFactor + } + + Label { + Layout.preferredWidth: parent.width - 32 * scaleFactor + text: transferManager._action + leftPadding: rightPadding + rightPadding: 0 + horizontalAlignment: Label.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + font { + family: fonts.fontFamily_Medium.name + pixelSize: 16 * scaleFactor + } + color: colors.primary_color + wrapMode: Label.Wrap + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 8 * scaleFactor + } + + Label { + Layout.preferredWidth: parent.width - 32 * scaleFactor + text: transferManager.requestProgress + leftPadding: rightPadding + rightPadding: 0 + horizontalAlignment: Label.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + font { + family: fonts.fontFamily_Medium.name + pixelSize: 16 * scaleFactor + } + color: colors.primary_color + wrapMode: Label.Wrap + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + visible: resultState === 2 || resultState === 3 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 64 * scaleFactor + } + + Label { + text: resultState === 3 ? strings.step4_success : strings.step4_failed + horizontalAlignment: Label.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + font { + family: fonts.fontFamily_Regular.name + pixelSize: 24 * scaleFactor + } + color: resultState === 3 ? colors.primary_color: Material.Red + wrapMode: Label.Wrap + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + Image { + Layout.preferredWidth: Math.min(parent.width - 104 * scaleFactor, 256 * scaleFactor) + Layout.preferredHeight: Math.min(parent.width - 104 * scaleFactor, 256 * scaleFactor) + source: resultState === 3 ? sources.success_image : sources.failed_image + fillMode: Image.PreserveAspectFit + anchors.horizontalCenter: parent.horizontalCenter + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + Button { + Layout.preferredWidth: 192 * scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + topPadding: 9 * scaleFactor + bottomPadding: topPadding + rightPadding: 24 * scaleFactor + leftPadding: rightPadding + visible: resultState === 3 + + text: strings.share_app + Material.foreground: colors.white_100 + Material.background: colors.primary_color + + font { + family: fonts.fontFamily_Medium.name + pixelSize: 14 * scaleFactor + } + + onClicked: { + var webURL = portalB.url + "/home/item.html?id=" + transferManager._destItemId; + AppFramework.clipboard.share(webURL); + } + + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 8 * scaleFactor + visible: resultState === 3 + } + + Button { + Layout.preferredWidth: 192 * scaleFactor + anchors.horizontalCenter: parent.horizontalCenter + topPadding: 9 * scaleFactor + bottomPadding: topPadding + rightPadding: 24 * scaleFactor + leftPadding: rightPadding + + text: resultState === 3 ? strings.done : strings.try_again + Material.foreground: colors.white_100 + Material.background: colors.primary_color + + font { + family: fonts.fontFamily_Medium.name + pixelSize: 14 * scaleFactor + } + + onClicked: { + if(resultState === 3) { + var item = stackView.get(1); + stackView.pop(item); + } else { + makeCopy(); + } + } + + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 79 * scaleFactor + } + } + + function makeCopy(){ + resultState = 1; + transferManager.errorHandler = function(){ + resultState = 2; + } + + transferManager.transfer(itemId, function(){ + resultState = 3; + }); + } + + Component.onCompleted: { + makeCopy(); + } +} diff --git a/Replicator/Views/WebSignInPage.qml b/Replicator/Views/WebSignInPage.qml new file mode 100644 index 0000000..5e0a9b7 --- /dev/null +++ b/Replicator/Views/WebSignInPage.qml @@ -0,0 +1,111 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 +import ArcGIS.AppFramework.WebView 1.0 + +import "../Widgets" + +Page { + id: webSignInPage + + property int mode: 1 + + property string tag: "websigninpage" + property var portal: mode === 1 ? portalA: portalB + property string portalUrl: portal ? portal.portalUrl : "" + property string clientID: portal.clientID + readonly property string redirect_url: "urn:ietf:wg:oauth:2.0:oob" + property string authorizationCode: "" + readonly property string authorizationEndpoint: portalUrl + "/sharing/rest/oauth2/authorize/" + readonly property string authorizationUrl: authorizationEndpoint + "?hidecancel=true&client_id=" + clientID + "&grant_type=code&response_type=code&expiration=-1&redirect_uri=" + redirect_url + "&locale=" + Qt.locale().uiLanguages[0] + + // Header + header: ToolBar { + height: 56 * scaleFactor + Material.primary: colors.primary_color + Material.elevation: 4 + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.preferredWidth: 56 * scaleFactor + Layout.fillHeight: true + Layout.alignment: Qt.AlignLeft + + SmartToolButton { + imageSource: sources.close + + onClicked: { + stackView.pop(StackView.Immediate); + } + } + } + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + text: mode === 1 ? strings.source_account : strings.dest_account + + font { + family: fonts.fontFamily_Medium.name + pixelSize: 20 * scaleFactor + } + color: colors.white_100 + + horizontalAlignment: Label.AlignLeft + verticalAlignment: Label.AlignVCenter + + clip: true + elide: Text.ElideRight + } + } + + Rectangle { + width: webSignInPageWebView.loadProgress / 100 * parent.width + height: 3 * scaleFactor + anchors.bottom: parent.bottom + color: "#FAD817" + visible: webSignInPageWebView.loadProgress < 100 ? true : false + } + } + + // Web view + WebView { + id: webSignInPageWebView + anchors.fill: parent + url: authorizationUrl + visible: AppFramework.network.isOnline + + onLoadingChanged: { + if (title.indexOf("SUCCESS code=") > -1) { + var authCode = title.replace("SUCCESS code=", ""); + authorizationCode = authCode; + visible = false; + portal.getTokenFromCode(clientID, redirect_url, authorizationCode); + + } else if (title.indexOf("error=public_account_access_denied") > -1) { + console.log("Error: public_account_access_denied") + } + } + } + + // Busy indicator + BusyIndicator { + anchors.centerIn: parent + running: webSignInPageWebView.loading + Material.accent: colors.primary_color + } + + function refresh() { + app.forceActiveFocus(); + webSignInPageWebView.reload(); + } +} diff --git a/Replicator/Widgets/IconImage.qml b/Replicator/Widgets/IconImage.qml new file mode 100644 index 0000000..ada173e --- /dev/null +++ b/Replicator/Widgets/IconImage.qml @@ -0,0 +1,27 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + property alias source: image.source + property alias color: imageColorOverLay.color + + Image { + id: image + anchors.fill: parent + source: sources.profileCameraImageSource + fillMode: Image.PreserveAspectFit + mipmap: true + } + + ColorOverlay { + id: imageColorOverLay + anchors.fill: image + source: image + } +} diff --git a/Replicator/Widgets/NavigatorFooter.qml b/Replicator/Widgets/NavigatorFooter.qml new file mode 100644 index 0000000..7f88725 --- /dev/null +++ b/Replicator/Widgets/NavigatorFooter.qml @@ -0,0 +1,143 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import "../Widgets" + +ToolBar { + id: navigatorFooter + + signal back() + signal next() + + height: 56 * scaleFactor + Material.primary: colors.primary_color + Material.elevation: 4 + + property bool isBackEnabled: true + property bool isNextEnabled: false + + property alias text1: label1.text + property alias text2: label2.text + property alias icon1: icon1 + property alias icon2: icon2 + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.fillHeight: true + Layout.preferredWidth: parent.width*0.4 + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.fillHeight: true + Layout.preferredWidth: icon1.visible ? 16 * scaleFactor : 24 * scaleFactor + } + + IconImage { + id: icon1 + Layout.preferredWidth: 36 * scaleFactor + Layout.preferredHeight: 36 * scaleFactor + source: sources.arrow_left + color: navigatorFooter.isBackEnabled ? colors.white_100 : colors.white_54 + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 8 * scaleFactor + visible: icon1.visible + } + + Label { + id: label1 + text: strings.back + font { + family: fonts.fontFamily_Medium.name + pixelSize: 14 * scaleFactor + } + color: navigatorFooter.isBackEnabled ? colors.white_100 : colors.white_54 + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + } + + MouseArea{ + anchors.fill: parent + onClicked: { + back(); + } + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: parent.width*0.4 + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + Label { + id: label2 + text: strings.next + font { + family: fonts.fontFamily_Medium.name + pixelSize: 14 * scaleFactor + } + color: navigatorFooter.isNextEnabled ? colors.white_100 : colors.white_54 + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 8 * scaleFactor + visible: icon2.visible + } + + IconImage { + id: icon2 + Layout.preferredWidth: 36 * scaleFactor + Layout.preferredHeight: 36 * scaleFactor + source: sources.arrow_right + color: colors.white_100 + opacity: navigatorFooter.isNextEnabled ? 1 : 0.48 + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: icon2.visible ? 16 * scaleFactor : 24 * scaleFactor + } + } + + MouseArea{ + anchors.fill: parent + enabled: isNextEnabled + onClicked: { + next(); + } + } + + } + } +} diff --git a/Replicator/Widgets/PortalCard.qml b/Replicator/Widgets/PortalCard.qml new file mode 100644 index 0000000..dc7b534 --- /dev/null +++ b/Replicator/Widgets/PortalCard.qml @@ -0,0 +1,234 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import "../Widgets" + +Rectangle { + property var portal + property var username: portal.userFullName + property var portal_name: portal.portalName + property var portal_url: portal.portalUrl + property var thumbnail: portal.userThumbnailUrl + property bool isSignedIn: portal.signedIn + property int mode: 1 + property bool editEnabled: true + + signal openSignInPage() + + Layout.fillWidth: true + Layout.preferredHeight: isSignedIn ? 132 * scaleFactor : 112 * scaleFactor + width: parent.width + height: isSignedIn ? 132 * scaleFactor : 112 * scaleFactor + + color: colors.card_background + border.width: 1 + border.color: colors.card_border + radius: 2 * scaleFactor + clip: true + + ColumnLayout { + width: parent.width - 32 * scaleFactor + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + visible: isSignedIn + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: mode == 1 ? strings.source_account : strings.dest_account + font { + family: fonts.fontFamily_Medium.name + pixelSize: 12 * scaleFactor + } + color: colors.black_54 + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 16 * scaleFactor + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + RowLayout { + anchors.fill: parent + spacing: 0 + + RoundedImage { + Layout.preferredHeight: 48 * scaleFactor + Layout.preferredWidth: 48 * scaleFactor + imageSource: thumbnail > "" ? thumbnail : sources.placeholder + fillMode: Image.PreserveAspectCrop + mipmap: true + anchors.top: parent.top + radius: 24 * scaleFactor + clip: true + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 16 * scaleFactor + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Label { + Layout.fillWidth: true + text: username + font { + family: fonts.fontFamily_Regular.name + pixelSize: 14 * scaleFactor + } + color: colors.black_87 + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 4 * scaleFactor + } + + Label { + Layout.fillWidth: true + text: portal_name + font { + family: fonts.fontFamily_Regular.name + pixelSize: 12 * scaleFactor + } + color: colors.black_54 + elide: Label.ElideRight + } + + Label { + Layout.fillWidth: true + text: extractHostname(portal_url+"") + font { + family: fonts.fontFamily_Regular.name + pixelSize: 12 * scaleFactor + } + color: colors.black_54 + elide: Label.ElideRight + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 16 * scaleFactor + } + + IconImage { + Layout.preferredHeight: 22 * scaleFactor + Layout.preferredWidth: 22 * scaleFactor + anchors.top: parent.top + anchors.topMargin: 15 * scaleFactor + source: sources.editImage + color: colors.primary_color + visible: editEnabled + + MouseArea { + anchors.fill: parent + onClicked: { + portal.signOut(); + app.clearSettings(mode === 1 ? "portalA" : "portalB"); + } + } + } + + Item { + Layout.fillHeight: true + Layout.preferredWidth: 0 * scaleFactor + } + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 6 * scaleFactor + } + } + + RowLayout { + width: parent.width - 32 * scaleFactor + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + visible: !isSignedIn + spacing: 0 + + Label { + Layout.fillWidth: true + text: mode == 1 ? strings.source_account : strings.dest_account + font { + family: fonts.fontFamily_Regular.name + pixelSize: 16 * scaleFactor + } + color: colors.black_87 + elide: Label.ElideRight + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + Button { + topPadding: 9 * scaleFactor + bottomPadding: topPadding + rightPadding: 24 * scaleFactor + leftPadding: rightPadding + + text: strings.sign_in + Material.foreground: colors.white_100 + Material.background: colors.primary_color + + font { + family: fonts.fontFamily_Medium.name + pixelSize: 14 * scaleFactor + } + + onClicked: { + openSignInPage() + } + + } + } + + function extractHostname(url) { + var hostname; + + if (url.indexOf("://") > -1) { + hostname = url.split('/')[2]; + } + else { + hostname = url.split('/')[0]; + } + + hostname = hostname.split(':')[0]; + hostname = hostname.split('?')[0]; + + return hostname; + } +} diff --git a/Replicator/Widgets/RoundedImage.qml b/Replicator/Widgets/RoundedImage.qml new file mode 100644 index 0000000..dc5c995 --- /dev/null +++ b/Replicator/Widgets/RoundedImage.qml @@ -0,0 +1,65 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import QtGraphicalEffects 1.0 + +Item { + property real radius + property alias imageSource: roundedButtonImage.source + property alias fillMode: roundedButtonImage.fillMode + property alias mipmap: roundedButtonImage.mipmap + property alias enableBusyIndicator: busyIndicator.visible + property alias status: roundedButtonImage.status + property color backgroundColor: colors.transparent + property bool isShowBusyIndicator: true + property bool isShowBorder: false + property bool isBorderOpacity: false + + Image{ + id: roundedButtonImage + anchors.fill: parent + visible: false + mipmap: true + + Rectangle { + anchors.fill: parent + color: backgroundColor + border.width: isShowBorder ? 1 : 0 + border.color: colors.default_content_color + radius: roundedButtonMask.radius + opacity: isBorderOpacity? 0.6 : 1.0 + smooth: true + } + } + + Rectangle { + id: roundedButtonMask + anchors.centerIn: parent + radius: parent.radius + width: roundedButtonImage.width + height: roundedButtonImage.height + visible: false + } + + OpacityMask { + anchors.fill: roundedButtonImage + source: roundedButtonImage + maskSource: roundedButtonMask + } + + BusyIndicator{ + id: busyIndicator + width: parent.width * 0.8 + height: parent.height * 0.8 + opacity: 0.6 + visible: isShowBusyIndicator + Material.accent: colors.primary_color + anchors.centerIn: parent + running: status === Image.Loading + } +} diff --git a/Replicator/Widgets/SmartToolButton.qml b/Replicator/Widgets/SmartToolButton.qml new file mode 100644 index 0000000..5e591e3 --- /dev/null +++ b/Replicator/Widgets/SmartToolButton.qml @@ -0,0 +1,40 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +import QtGraphicalEffects 1.0 + +ToolButton { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + // Image source + property url imageSource: "" + property bool isMirrored: false + + // Image color + property alias imageColor: tabButtonColorOverlay.color + property alias imageRotation: tabButtonImage.rotation + + indicator: Image { + id: tabButtonImage + width: parent.width / 2 + height: parent.height / 2 + anchors.centerIn: parent + source: imageSource + fillMode: Image.PreserveAspectFit + mirror: isMirrored + mipmap: true + } + + ColorOverlay { + id: tabButtonColorOverlay + anchors.fill: tabButtonImage + source: tabButtonImage + rotation: tabButtonImage.rotation + } +} diff --git a/Replicator/appicon.png b/Replicator/appicon.png new file mode 100644 index 0000000..39504b9 Binary files /dev/null and b/Replicator/appicon.png differ diff --git a/Replicator/appinfo.json b/Replicator/appinfo.json new file mode 100644 index 0000000..3c4b7e0 --- /dev/null +++ b/Replicator/appinfo.json @@ -0,0 +1,90 @@ +{ + "arcgisRuntime": 0, + "capabilities": { + "audio": false, + "backgroundLocation": false, + "biometricAuthentication": false, + "bluetooth": false, + "camera": false, + "fileSharing": false, + "highAccuracyLocation": false, + "ios": { + "externalAccessoryProtocolStrings": [ + ] + }, + "localnotification": false, + "location": true, + "microphone": false, + "network": true, + "storage": true, + "vibration": false + }, + "deployment": { + "android": { + "packageName": "" + }, + "arcgisRuntimeExtensionsLicense": "", + "arcgisRuntimeLicense": "", + "clientId": "appstudioweb", + "ios": { + "bundleId": "", + "codeSignIdentity": "" + }, + "macos": { + "bundleId": "", + "codeSignIdentity": "" + }, + "publisherName": "", + "uwp": { + "packageName": "", + "productID": "" + }, + "winphone": { + "packageDisplayName": "" + } + }, + "devicesTypes": [ + "desktop", + "tablet", + "phone" + ], + "display": { + "desktop": { + "minimumHeight": 0, + "minimumWidth": 0, + "windowMode": "default" + }, + "enableHighDpi": true, + "phone": { + "landscape": true, + "portrait": true, + "showStatusBar": true + }, + "tablet": { + "landscape": true, + "portrait": true, + "showStatusBar": true + } + }, + "environment": { + }, + "launchUrlSchemes": [ + ], + "mainFile": "MyApp.qml", + "multipleInstances": true, + "projectFile": "MyApp.qmlproject", + "properties": { + }, + "resources": { + "appIcon": "appicon.png", + "launchImageBackground": "launchimage-background.png", + "launchImageBackgroundColor": "#ffffff", + "launchImageOverlay": "launchimage-overlay.png" + }, + "type": "app", + "urlScheme": "", + "version": { + "major": 1, + "micro": 1 + } +} diff --git a/Replicator/default-app.png b/Replicator/default-app.png new file mode 100644 index 0000000..8fa9021 Binary files /dev/null and b/Replicator/default-app.png differ diff --git a/Replicator/iteminfo.json b/Replicator/iteminfo.json new file mode 100644 index 0000000..81327ec --- /dev/null +++ b/Replicator/iteminfo.json @@ -0,0 +1,65 @@ +{ + "access": "public", + "accessInformation": null, + "appCategories": [ + ], + "avgRating": 0, + "banner": null, + "categories": [ + ], + "commentsEnabled": true, + "created": 1524014354000, + "culture": "en-au", + "description": "\n

You can copy your AppStudio apps and paste them to your other orgs in 3 easy steps with this sample app. First sign in to both org accounts, then select the app that you want to replicate, and finally click on the Copy button to copy app to another org.

\n


\n

Resource Level:🍌

", + "documentation": null, + "extent": [ + ], + "groupDesignations": null, + "guid": null, + "id": "2de5208781af4eec8c0802bd33b68c8e", + "industries": [ + ], + "itemControl": "admin", + "languages": [ + ], + "largeThumbnail": null, + "licenseInfo": null, + "listed": false, + "modified": 1524014597000, + "name": "2de5208781af4eec8c0802bd33b68c8e.zip", + "numComments": 1, + "numRatings": 0, + "numViews": 4, + "orgId": "2U3NfasNQ9o9LkLt", + "owner": "appstudio_samples", + "ownerFolder": "f4a20283d02c41a1bba3642a7d6b42b5", + "properties": null, + "protected": false, + "proxyFilter": null, + "scoreCompleteness": 65, + "screenshots": [ + ], + "size": 574952, + "snippet": "You can use this sample app to copy your AppStudio apps from one organization and paste them to another ", + "spatialReference": null, + "tags": [ + "AppStudio", + "Quartz", + "Runtime", + "Sample", + "Portal" + ], + "thumbnail": "thumbnail/thumbnail.png", + "title": "Replicator", + "type": "Native Application", + "typeKeywords": [ + "API_QML", + "App", + "Application", + "AppStudio", + "Configuration", + "Native", + "qml" + ], + "url": null +} diff --git a/Replicator/qtquickcontrols2.conf b/Replicator/qtquickcontrols2.conf new file mode 100644 index 0000000..1ddd1cd --- /dev/null +++ b/Replicator/qtquickcontrols2.conf @@ -0,0 +1,10 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Material + +[Universal] +Theme=Light +;Accent=Steel diff --git a/Replicator/thumbnail.png b/Replicator/thumbnail.png new file mode 100644 index 0000000..8b7d1a9 Binary files /dev/null and b/Replicator/thumbnail.png differ diff --git a/SD Card/MyApp.qml b/SD Card/MyApp.qml new file mode 100644 index 0000000..4835b46 --- /dev/null +++ b/SD Card/MyApp.qml @@ -0,0 +1,592 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +import QtQuick 2.7 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 +import ArcGIS.AppFramework.Networking 1.0 + +import "controls" as Controls + +App { + id: app + width: 400 + height: 640 + + Material.accent: Material.Teal + + function units(value) { + return AppFramework.displayScaleFactor * value + } + + property real scaleFactor: AppFramework.displayScaleFactor + property int baseFontSize: app.info.propertyValue("baseFontSize", 15 * scaleFactor) + (isSmallScreen ? 0 : 3) + property bool isSmallScreen: (width || height) < units(400) + property var internalStorage: AppFramework.standardPaths.standardLocations(StandardPaths.AppDataLocation); + property string externalStorage + property string sdCardPath + property var mountedVols: [] + property string androidPackageName: (app.info.value("deployment", {}).android || {}).packageName + property string dataPath: AppFramework.userHomeFolder.filePath("ArcGIS/AppStudio/Data") + property string dataFile: "SanFrancisco.tpk" + // property string inputData : dataPath + "/" + dataFile + property bool isRunningInPlayer: false + property bool isSDCardPresent: false + + property string osName: AppFramework.osName + + property bool readComplete: false + property bool writeComplete: false + property bool copyComplete: false + property bool downloadComplete: false + property bool isOnline: Networking.isOnline + + Page { + anchors.fill: parent + header: ToolBar{ + id:header + width: parent.width + height: 50 * scaleFactor + Material.background: "#8f499c" + Controls.HeaderBar{} + } + + // sample starts here ------------------------------------------------------------------ + contentItem: Rectangle { + anchors.top: header.bottom + + Column { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + spacing: 5 + + Row { + spacing: 10 + + Image { + width: 30 + height: 30 + source: "tick.png" + visible: !busyIndicatorRead.visible + } + + BusyIndicator { + id: busyIndicatorRead + width: 30 + height: width + visible: !readComplete + running: visible + } + + Text { + font.pointSize: 20 + color: busyIndicatorRead.running ? "gray" : "teal" + text: busyIndicatorRead.running ? "Reading..." : "Read complete" + } + } + + Row { + spacing: 10 + + Image { + width: 30 + height: 30 + source: "tick.png" + visible: !busyIndicatorWrite.visible + } + + BusyIndicator { + id: busyIndicatorWrite + width: 30 + height: width + visible: !writeComplete + running: visible + } + + Text { + font.pointSize: 20 + color: busyIndicatorWrite.running ? "gray" : "teal" + text: busyIndicatorWrite.running ? "Writing..." : "Write complete" + } + } + + Row { + spacing: 10 + + Image { + width: 30 + height: 30 + source: "tick.png" + visible: !busyIndicatorCopy.visible + } + + BusyIndicator { + id: busyIndicatorCopy + width: 30 + height: width + visible: !copyComplete + running: visible + } + + Text { + font.pointSize: 20 + color: busyIndicatorCopy.running ? "gray" : "teal" + text: busyIndicatorCopy.running ? "Copying..." : "Copy complete" + } + } + + Row { + spacing: 10 + + Image { + width: 30 + height: 30 + source: "tick.png" + visible: !busyIndicatorDownload.visible + } + + BusyIndicator { + id: busyIndicatorDownload + width: 30 + height: width + visible: !downloadComplete + running: visible + } + + Text { + font.pointSize: 20 + color: isOnline ? busyIndicatorDownload.running ? "gray" : "teal" : "red" + text: isOnline ? busyIndicatorDownload.running ? "Downloading..." : "Download complete" : "Device is offline" + } + } + + Button { + width: 200 + text: qsTr("View Results") + highlighted: true + Material.background: Material.Orange + enabled: !busyIndicatorCopy.running && !busyIndicatorDownload.running && !busyIndicatorRead.running && !busyIndicatorWrite.running + + onClicked: { + console.log("file:///"+AppFramework.resolvedPath(logFile.path)) + Qt.openUrlExternally("file:///"+AppFramework.resolvedPath(logFile.path)) + } + } + + Button { + width: 200 + text: qsTr("Send Diagnostics") + highlighted: true + Material.background: Material.Teal + enabled: !busyIndicatorCopy.running && !busyIndicatorDownload.running && !busyIndicatorRead.running && !busyIndicatorWrite.running + + onClicked: { + var urlInfo = AppFramework.urlInfo("mailto:spillai@esri.com"); + fileData.open(File.OpenModeReadWrite) + + urlInfo.queryParameters = { + "subject": "Storage info for ", + "body": fileData.readAll() + }; + fileData.close() + Qt.openUrlExternally(urlInfo.url); + } + } + } + } + } + + StorageInfo { + id: storageInfo + } + + Component.onCompleted: { + + var fs, dev, path + + logFile.path = internalStorage[internalStorage.length-1] + + if (logFile.fileExists("diagnostics.txt")) { + logFile.removeFile("diagnostics.txt") + logFile.removeFile("InternalMemory.txt") + logFile.removeFile("appstudio.jpg") + logFile.removeFile("SanFrancisco.tpk") + } + + logFile.path = internalStorage[internalStorage.length-1] + "/diagnostics.txt" + + console.log("view diagnostics file at " + logFile.path) + + logFile.createSection("Diagnostics for storage") + mountedVols = storageInfo.mountedVolumes + + logFile.logs(osName + " detected") + logFile.logs("osVersion " + AppFramework.osVersion) + logFile.logs("currentCpuArchitecture:" + AppFramework.currentCpuArchitecture) + + for (var i = 0; i < mountedVols.length; i++) { + fs = mountedVols[i].fileSystemType + + if (fs === "sdcardfs" || fs === "fuse") { + + dev = mountedVols[i].device + path = mountedVols[i].path + + if (fs === "fuse") { + + if (dev === "/dev/fuse") { + + if (path !== "/storage/emulated" && + path !== "/storage/emulated/0" && + path !== "/storage/emulated/0/Android/obb" && + path !== "/storage/emulated/legacy" && + path !== "/storage/emulated/legacy/Android/obb" && + path !== "/storage/sdcard0" && + path !== "/mnt/shell/emulated" && + path !== "/storage/udisk1") { + + isSDCardPresent = true + externalStorage = path; + logFile.logs("SD card detected") + logFile.logs("sd card path " + externalStorage) + logFile.logs("fileSystem : " + fs) + + break; + } + + if (path === "/storage/sdcard1") + { + isSDCardPresent = true + externalStorage = path; + logFile.logs("SD card detected") + logFile.logs("sd card path " + externalStorage) + logFile.logs("fileSystem : " + fs) + + break; + } + } + + } else if (fs === "sdcardfs") { + + if (dev.startsWith("/mnt")) { + + isSDCardPresent = true; + externalStorage = path; + logFile.logs("SD card detected") + logFile.logs("sd card path " + externalStorage) + logFile.logs("fileSystem : " + fs) + + break; + } + + } else { + continue; + } + + } else { + continue; + } + } + + // it is always the last one since the first one points to /data/user/0//files + internalStorage = internalStorage[internalStorage.length-1] + logFile.logs("internal storage path \n" + internalStorage) + + // Detect whether the app is running in player + if (parent) { + if (AppFramework.typeOf(parent, true) === "AppLoader") { + logFile.logs("I appear to be running in the AppStudio player"); + isRunningInPlayer = true; + } else { + isRunningInPlayer = true; + logFile.logs("I'm running in a different player or some sort of loader"); + } + } else { + isRunningInPlayer = false; + logFile.logs("I'm running standalone or in AppRun/qmlscene"); + } + + + if (isSDCardPresent) { + if (isRunningInPlayer) { + var playerID = internalStorage.substring(33,59) + + if (playerID === "com.esri.appstudio.player3") { + console.log("player3") + sdCardPath = externalStorage + "/Android/data/"+"com.esri.appstudio.player3"+"/files" + } else { + console.log("player") + sdCardPath = externalStorage + "/Android/data/"+"com.esri.appstudio.player"+"/files" + } + + logFile.logs("external storage path \n" + sdCardPath) + } else { + sdCardPath = externalStorage + "/Android/data/" + androidPackageName + "/files" + logFile.logs("external storage path \n" + sdCardPath) + } + } else { + logFile.logs("sd card not available") + } + + writeFunctions() + + copyFunctions() + + downloadFunctions() + } + + File { + id: file + } + + FileFolder { + id: fileFolder + } + + function copyLocalData(source, target) { + return fileFolder.copyFile(source, target); + } + + NetworkRequest { + id: downloadNetworkRequest + url: "http://appstudio.arcgis.com/images/index/introview.jpg" + responsePath: pathPrefix +"/appstudio.jpg" + property string pathPrefix + + onReadyStateChanged: { + if ( readyState === NetworkRequest.DONE ) { + logFile.logs("File downloaded at \n" + responsePath) + downloadComplete = true + } + } + onError: { + logFile.log("error while downloading", errorText + ", " + errorCode) + downloadComplete = true + } + } + + Controls.DescriptionPage { + id: descPage + visible: false + } + + function copyFunctions() { + + logFile.createSection("Copy") + + var resourceFolder = AppFramework.fileFolder(app.folder.folder("data").path); + if (!isSDCardPresent) { + if (!fileFolder.fileExists(internalStorage + "/SanFrancisco.tpk")) { + if (resourceFolder.copyFile(dataFile, internalStorage + "/SanFrancisco.tpk")) { + logFile.logs("File copied to \n" + internalStorage + "/SanFrancisco.tpk") + } + } else { + logFile.logs("File already exists \n" + internalStorage + "/SanFrancisco.tpk") + } + } else { + if (!fileFolder.fileExists(sdCardPath + "/SanFrancisco.tpk")) { + if (resourceFolder.copyFile(dataFile, sdCardPath + "/SanFrancisco.tpk")) { + logFile.logs("File copied to \n" + sdCardPath + "/SanFrancisco.tpk") + } + } else { + logFile.logs("File already exists \n" + sdCardPath + "/SanFrancisco.tpk") + } + } + + copyComplete = true + } + + function readFunctions() { + logFile.createSection("Read") + + if (!isSDCardPresent) { + fileFolder.path = internalStorage + if (!fileFolder.fileExists(internalStorage + "/InternalMemory.txt")) { + logFile.logs("File not found at " + internalStorage) + } else { + logFile.logs("Reading contents from " + fileFolder.path+"/InternalMemory.txt") + logFile.logs(fileFolder.readFile("InternalMemory.txt")) + } + } else { + fileFolder.path = sdCardPath + if (!fileFolder.fileExists(sdCardPath + "/ExternalMemory.txt")) { + logFile.logs("File not found at " + sdCardPath) + } else { + logFile.logs("Reading contents from " + fileFolder.path+"/ExternalMemory.txt") + logFile.logs(fileFolder.readFile("ExternalMemory.txt")) + } + } + + readComplete = true + + getStorageInfo() + } + + function writeFunctions() { + + logFile.createSection("Write") + + if (!isSDCardPresent) { + fileFolder.path = internalStorage + fileFolder.writeFile("InternalMemory.txt", "AppStudio is awesome.") + file.path = internalStorage + "/InternalMemory.txt" + + if (file.exists) + { + file.open(File.OpenModeReadWrite) + file.writeLine("AppStudio is awesome") + logFile.logs("Write operation complete " + internalStorage+"/InternalMemory.txt") + file.close() + } + } else { + fileFolder.path = sdCardPath + if (fileFolder.makeFolder()) + { + console.log("success") + } + + if (!fileFolder.writeTextFile("ExternalMemory.txt", "AppStudio is awesome.")) + { + console.log("error") + } + else + { + console.log("success") + } + + file.path = sdCardPath + "/ExternalMemory.txt" + + if (file.exists) + { + file.open(File.OpenModeReadWrite) + file.writeLine("AppStudio is awesome"); + logFile.logs("Write operation complete " + sdCardPath+"/ExternalMemory.txt") + file.close(); + } + } + + writeComplete = true + } + + function downloadFunctions() { + + logFile.createSection("Download") + + if (isOnline) { + if (!isSDCardPresent) { + + logFile.logs("Downloading file in internal storage") + downloadNetworkRequest.pathPrefix = internalStorage + downloadNetworkRequest.send({f:"json"}); + + delay(5000, function() { + readFunctions() + }) + + } else { + logFile.logs("Downloading file in external storage") + downloadNetworkRequest.pathPrefix = sdCardPath + downloadNetworkRequest.send({f:"json"}); + + delay(5000, function() { + readFunctions() + }) + + } + } else { + logFile.logs("Device is offline") + logFile.logs("Download Failed") + downloadComplete = true + + delay(5000, function() { + readFunctions() + }) + + } + } + + function getStorageInfo() { + console.log("inside") + + var basePath = "/storage"; + var paths = []; + + fileFolder.path = basePath; + + var basePathSubFolders = fileFolder.folderNames(); + + for (var basePathSubFolder in basePathSubFolders) { + + var folderName = basePathSubFolders[basePathSubFolder]; + var pathToSearch = basePath + "/" + folderName + "/"; + console.log(pathToSearch) + + } + + } + + FileFolder { + id: logFile + property var logArray : [] + + function logs(input) { + if (input) { + logArray.push(input.toString()); + } + logFile.writeTextFile(logFile.path, logArray.join("\n")) + } + + function createSection(section) { + if (section) { + logArray.push("\n------ " + section.toString() + " -------\n") + } + logFile.writeTextFile(logFile.path, logArray.join("\n")) + } + } + + Timer { + id: timer + + onTriggered: { + console.log("read complete") + } + } + + function delay(delayTime, cb) { + timer.interval = delayTime; + timer.repeat = false; + timer.triggered.connect(cb); + timer.start(); + } + + FileInfo { + id: fileInfo + filePath: logFile.path + } + + File { + id: fileData + path: fileInfo.filePath + } +} + diff --git a/SD Card/MyApp.qmlproject b/SD Card/MyApp.qmlproject new file mode 100644 index 0000000..754767c --- /dev/null +++ b/SD Card/MyApp.qmlproject @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ + +import QmlProject 1.1 + +Project { + mainFile: "MyApp.qml" + + QmlFiles { + directory: "." + recursive: true + } + + JavaScriptFiles { + directory: "." + recursive: true + } + + ImageFiles { + directory: "." + recursive: true + } + + Files { + directory: "." + recursive: true + filter: "*.json;*.html;*.txt;*.conf" + } + + importPaths: [ + ] +} + diff --git a/SD Card/README.md b/SD Card/README.md new file mode 100644 index 0000000..bcfa956 --- /dev/null +++ b/SD Card/README.md @@ -0,0 +1,41 @@ +## SD Card + +This sample app demonstrates how to read, write, copy, move, download, data from an SD card + +[Resource Level](https://geonet.esri.com/groups/appstudio/blog/2016/12/06/how-to-describe-our-resources-in-terms-of-difficulty-complexity-and-time-to-digest): 🍌 + + +## Instructions to run this sample in AppStudio Desktop + +1. Download the `.zip` file +2. Unzip and copy this folder into AppStudio Apps folder (Windows: `C:\Users\\ArcGIS\AppStudio\Apps` Mac or linux: `Home\ArcGIS\AppStudio\Apps`) +3. The new app will now appear in the AppStudio Desktop. Run the application or open it in the bundled Qt-Creator IDE to look at the code and modify. + +## Issues + +Find a bug or want to request a new feature? Please let us know by submitting an issue. + +## Contributing + +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). + +## Licensing +Copyright 2017 Esri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +A copy of the license is available in the repository's [license.txt](license.txt) file. + + +[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) +[](Esri Language: Qt QML JavaScript) diff --git a/SD Card/appicon.png b/SD Card/appicon.png new file mode 100644 index 0000000..39504b9 Binary files /dev/null and b/SD Card/appicon.png differ diff --git a/SD Card/appinfo.json b/SD Card/appinfo.json new file mode 100644 index 0000000..e102e0b --- /dev/null +++ b/SD Card/appinfo.json @@ -0,0 +1,90 @@ +{ + "arcgisRuntime": 0, + "capabilities": { + "audio": false, + "backgroundLocation": false, + "biometricAuthentication": false, + "bluetooth": false, + "camera": false, + "fileSharing": false, + "highAccuracyLocation": false, + "ios": { + "externalAccessoryProtocolStrings": [ + ] + }, + "localnotification": false, + "location": true, + "microphone": false, + "network": true, + "storage": true, + "vibration": false + }, + "deployment": { + "android": { + "packageName": "" + }, + "arcgisRuntimeExtensionsLicense": "", + "arcgisRuntimeLicense": "", + "clientId": "", + "ios": { + "bundleId": "", + "codeSignIdentity": "" + }, + "macos": { + "bundleId": "", + "codeSignIdentity": "" + }, + "publisherName": "", + "uwp": { + "packageName": "", + "productID": "" + }, + "winphone": { + "packageDisplayName": "" + } + }, + "devicesTypes": [ + "desktop", + "tablet", + "phone" + ], + "display": { + "desktop": { + "minimumHeight": 0, + "minimumWidth": 0, + "windowMode": "default" + }, + "enableHighDpi": true, + "phone": { + "landscape": true, + "portrait": true, + "showStatusBar": true + }, + "tablet": { + "landscape": true, + "portrait": true, + "showStatusBar": true + } + }, + "environment": { + }, + "launchUrlSchemes": [ + ], + "mainFile": "MyApp.qml", + "multipleInstances": true, + "projectFile": "MyApp.qmlproject", + "properties": { + }, + "resources": { + "appIcon": "sd-card.png", + "launchImageBackground": "launchimage-background.png", + "launchImageBackgroundColor": "#ffffff", + "launchImageOverlay": "launchimage-overlay.png" + }, + "type": "app", + "urlScheme": "", + "version": { + "major": 1, + "micro": 3 + } +} diff --git a/SD Card/assets/clear.png b/SD Card/assets/clear.png new file mode 100644 index 0000000..6b717e0 Binary files /dev/null and b/SD Card/assets/clear.png differ diff --git a/SD Card/assets/info.png b/SD Card/assets/info.png new file mode 100644 index 0000000..c41a5fc Binary files /dev/null and b/SD Card/assets/info.png differ diff --git a/SD Card/controls/DescriptionPage.qml b/SD Card/controls/DescriptionPage.qml new file mode 100644 index 0000000..0572ab6 --- /dev/null +++ b/SD Card/controls/DescriptionPage.qml @@ -0,0 +1,93 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + id: descPage + width: parent.width + height: parent.height + + Rectangle{ + anchors.fill:parent + + ColumnLayout{ + anchors.fill:parent + spacing: 0 + clip:true + + Rectangle{ + id:descPageheader + color:"#8f499c" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 50 * scaleFactor + + ImageButton { + source: "../assets/clear.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + right: parent.right + rightMargin: 10 * scaleFactor + verticalCenter: parent.verticalCenter + } + onClicked: { + descPage.visible = 0 + } + } + + Text { + id: aboutApp + text:qsTr("About") + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + anchors.centerIn: parent + maximumLineCount: 2 + elide: Text.ElideRight + } + } + + Rectangle{ + color:"black" + Layout.fillWidth: true + Layout.fillHeight: true + + Flickable { + anchors.fill:parent + contentHeight: descText.height + clip:true + + Text{ + id: descText + y: 30 * scaleFactor + text:app.info.description + anchors.horizontalCenterOffset: 0 + color:"white" + width: 0.85 * parent.width + horizontalAlignment: Text.AlignLeft + linkColor: "#e5e6e7" + wrapMode: Text.WordWrap + elide: Text.ElideRight + anchors.horizontalCenter: parent.horizontalCenter + font { + pixelSize: app.baseFontSize + } + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + } + } +} + + + + + diff --git a/SD Card/controls/HeaderBar.qml b/SD Card/controls/HeaderBar.qml new file mode 100644 index 0000000..2ddd736 --- /dev/null +++ b/SD Card/controls/HeaderBar.qml @@ -0,0 +1,59 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +RowLayout{ + anchors.fill: parent + spacing:0 + clip:true + + Rectangle{ + Layout.preferredWidth: 50*scaleFactor + } + + Text { + text:app.info.title + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + maximumLineCount:2 + wrapMode: Text.Wrap + elide: Text.ElideRight + anchors{ + verticalCenter: parent.verticalCenter + horizontalCenter:parent.horizontalCenter + } + } + + Rectangle{ + id:infoImageRect + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 50*scaleFactor + + ImageButton { + id:infoImage + source: "../assets/info.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + centerIn: parent + } + onClicked: { + descPage.visible = 1 + } + } + } +} + + + + + diff --git a/SD Card/data/SanFrancisco.tpk b/SD Card/data/SanFrancisco.tpk new file mode 100644 index 0000000..a8b44eb Binary files /dev/null and b/SD Card/data/SanFrancisco.tpk differ diff --git a/SD Card/data/download.png b/SD Card/data/download.png new file mode 100644 index 0000000..bc2febc Binary files /dev/null and b/SD Card/data/download.png differ diff --git a/SD Card/default-app.png b/SD Card/default-app.png new file mode 100644 index 0000000..8fa9021 Binary files /dev/null and b/SD Card/default-app.png differ diff --git a/SD Card/iteminfo.json b/SD Card/iteminfo.json new file mode 100644 index 0000000..10f3c0c --- /dev/null +++ b/SD Card/iteminfo.json @@ -0,0 +1,63 @@ +{ + "access": "public", + "accessInformation": null, + "appCategories": [ + ], + "avgRating": 0, + "banner": null, + "categories": [ + ], + "commentsEnabled": true, + "created": 1525130115000, + "culture": "en-au", + "description": "\n

This sample app demonstrates how to read, write, copy, move, download, data from an SD cardΒ Β 

\n


\n

Resource Level:🍌

", + "documentation": null, + "extent": [ + ], + "groupDesignations": null, + "guid": null, + "id": "81f3237de7d5471e8ff33f5bf30d1328", + "industries": [ + ], + "itemControl": "admin", + "languages": [ + ], + "largeThumbnail": null, + "licenseInfo": null, + "listed": false, + "modified": 1525286136000, + "name": "81f3237de7d5471e8ff33f5bf30d1328.zip", + "numComments": 3, + "numRatings": 0, + "numViews": 3, + "orgId": "2U3NfasNQ9o9LkLt", + "owner": "appstudio_samples", + "ownerFolder": "f4a20283d02c41a1bba3642a7d6b42b5", + "properties": null, + "protected": false, + "proxyFilter": null, + "scoreCompleteness": 80, + "screenshots": [ + ], + "size": 16565389, + "snippet": "This sample app demonstrates how to read, write, copy, move, download, data from an SD card ", + "spatialReference": null, + "tags": [ + "AppStudio", + "Sample", + "Plugins" + ], + "thumbnail": "thumbnail/thumbnail.png", + "title": "SD Card", + "type": "Native Application", + "typeKeywords": [ + "API_QML", + "App", + "Application", + "AppStudio", + "Configuration", + "Native", + "qml" + ], + "url": null +} diff --git a/SD Card/osx b/SD Card/osx new file mode 100644 index 0000000..e261542 --- /dev/null +++ b/SD Card/osx @@ -0,0 +1 @@ +AppStudio is awesome diff --git a/SD Card/qtquickcontrols2.conf b/SD Card/qtquickcontrols2.conf new file mode 100644 index 0000000..1ddd1cd --- /dev/null +++ b/SD Card/qtquickcontrols2.conf @@ -0,0 +1,10 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Material + +[Universal] +Theme=Light +;Accent=Steel diff --git a/SD Card/sd-card.png b/SD Card/sd-card.png new file mode 100644 index 0000000..ce20a6e Binary files /dev/null and b/SD Card/sd-card.png differ diff --git a/SD Card/thumbnail.png b/SD Card/thumbnail.png new file mode 100644 index 0000000..d15b421 Binary files /dev/null and b/SD Card/thumbnail.png differ diff --git a/SD Card/tick.png b/SD Card/tick.png new file mode 100644 index 0000000..8a38ab0 Binary files /dev/null and b/SD Card/tick.png differ diff --git a/SQL - Spatial Functions/MyApp.qml b/SQL - Spatial Functions/MyApp.qml new file mode 100644 index 0000000..5687bf1 --- /dev/null +++ b/SQL - Spatial Functions/MyApp.qml @@ -0,0 +1,66 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 +import Esri.ArcGISRuntime 100.1 + +import "controls" as Controls +import "SQLSpatialFunction" + +App { + id: app + width: 700 + height: 500 + + Material.accent: "#8f499c" + function units(value) { + return AppFramework.displayScaleFactor * value + } + property real scaleFactor: AppFramework.displayScaleFactor + property int baseFontSize : app.info.propertyValue("baseFontSize", 15 * scaleFactor) + (isSmallScreen ? 0 : 3) + property bool isSmallScreen: (width || height) < units(400) + + + Page{ + anchors.fill: parent + header: ToolBar{ + id:header + width: parent.width + height: 50 * scaleFactor + Material.background: "#8f499c" + Controls.HeaderBar{} + } + // Find SQLSpatialFunction code from the SQLSpatialFunction folder. Click on MyApp folder on top left, click on SQLSpatialFunction folder + SQLSpatialFunction{ + anchors.fill: parent + } + } + + + // sample ends here ------------------------------------------------------------------------ + Controls.DescriptionPage{ + id:descPage + visible: false + } +} + diff --git a/SQL - Spatial Functions/MyApp.qmlproject b/SQL - Spatial Functions/MyApp.qmlproject new file mode 100644 index 0000000..95d25ea --- /dev/null +++ b/SQL - Spatial Functions/MyApp.qmlproject @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ + +import QmlProject 1.1 + +Project { + mainFile: "MyApp.qml" + + QmlFiles { + directory: "." + recursive: true + } + + JavaScriptFiles { + directory: "." + recursive: true + } + + ImageFiles { + directory: "." + recursive: true + } + + Files { + directory: "." + recursive: true + filter: "*.json;*.html;*.txt;*.sql" + } + + importPaths: [ + ] +} + diff --git a/SQL - Spatial Functions/README.md b/SQL - Spatial Functions/README.md new file mode 100644 index 0000000..6fd9637 --- /dev/null +++ b/SQL - Spatial Functions/README.md @@ -0,0 +1,44 @@ +## SQL - Spatial Functions +This app demonstrates SQL Spatial Functions via SELECT statements. It uses SELECT statements with FROM clauses omitted to demonstrate a variety of scalar functions involving point geometry. Then, it proceeds to scalar and aggregate functions. + + +Please check this [blog post](https://geonet.esri.com/groups/appstudio/blog/2017/08/18/sql-spatial-functions-in-appstudio) to learn more about the sample + +Resource Level:🍌🍌 + + + +## Instructions to run this sample in AppStudio Desktop + +1. Download the `.zip` file +2. Unzip and copy this folder into AppStudio Apps folder (Windows: `C:\Users\\ArcGIS\AppStudio\Apps` Mac or linux: `Home\ArcGIS\AppStudio\Apps`) +3. The new app will now appear in the AppStudio Desktop. Run the application or open it in the bundled Qt-Creator IDE to look at the code and modify. + +## Issues + +Find a bug or want to request a new feature? Please let us know by submitting an issue. + +## Contributing + +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). + +## Licensing +Copyright 2017 Esri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +A copy of the license is available in the repository's [license.txt](license.txt) file. + + +[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) +[](Esri Language: Qt QML JavaScript) diff --git a/SQL - Spatial Functions/SQLSpatialFunction/SQLSpatialFunction.qml b/SQL - Spatial Functions/SQLSpatialFunction/SQLSpatialFunction.qml new file mode 100644 index 0000000..fab6f59 --- /dev/null +++ b/SQL - Spatial Functions/SQLSpatialFunction/SQLSpatialFunction.qml @@ -0,0 +1,177 @@ +import QtQuick 2.8 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Sql 1.0 + +Item{ + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + + spacing: 10 + + Label { + text: qsTr("Select SQL"); + } + + ComboBox { + id: statements + readonly property var _model: model + Layout.fillWidth: true + onCurrentTextChanged: queryTextArea.text = currentText + } + + Label { + text: qsTr("SQL") + } + + TextArea { + id: queryTextArea + Layout.fillWidth: true + Layout.fillHeight: true + selectByMouse: true + wrapMode: TextEdit.WrapAnywhere + onTextChanged: run() + + + Rectangle { + anchors.fill: parent + anchors.margins: -2 + color: "transparent" + border.color: "black" + } + } + + Label { + text: qsTr("Results") + } + + TableView { + id: tableView + readonly property var _model: model + Layout.fillWidth: true + Layout.preferredHeight: 100 * AppFramework.displayScaleFactor + } + + } + + FileFolder { + id: scriptsFolder + url: "scripts" + } + + SqlDatabase { + id: db + databaseName: ":memory:" + + SqlScalarFunction { + name: "toDegrees" + method: toDegrees + } + + SqlAggregateFunction { + name: "average" + initialize: average_initialize + iterate: average_iterate + finalize: average_finalize + } + } + + function toDegrees(radians) { + return radians * 180.0 / Math.PI; + } + + function average_initialize() { + var context = { + sum: 0, + count: 0 + } + return context; + } + + function average_iterate(context, value) { + context.sum += value; + context.count++; + } + + function average_finalize(context) { + return context.count ? context.sum / context.count : Number.NaN; + } + + function execute(sql) { + var query = db.query(sql); + if (query.error) { + throw new Error(query.error); + } + + var ok = query.first(); + while (ok) { + console.log(JSON.stringify(query.values)); + ok = query.next(); + } + query.finish(); + } + + function uncomment(sql) { + return sql.replace(/--[^\n]*/g, "") + .replace(/\/\*(.|\s)*?\*\//g, ""); + } + + function getQueries(txt) { + return txt.match(/(--.*|\s*)+[^;]*;/g) + .map(function (sql) { + return sql.replace(/(^\s+|\s+$)/g, ""); + } ); + } + + function tableView_removeAllColumns(tableView) { + while (tableView.columnCount) { + tableView.removeColumn(tableView.columnCount - 1); + } + } + + function tableView_addColumns(tableView, model) { + model.roleNames.forEach(function (role) { + tableView.addColumn(tableViewColumn.createObject(tableView, { title: role, role: role } ) ); + } ); + } + + function run() { + var sql = uncomment(queryTextArea.text); + tableView_removeAllColumns(tableView); + execute(sql); + var queryModel = db.queryModel(sql); + if (!queryModel) { + return; + } + tableView_addColumns(tableView, queryModel); + tableView.model = queryModel; + tableView.resizeColumnsToContents(); + } + + Component { + id: tableViewColumn + + TableViewColumn { + title: role + width: 100 * AppFramework.displayScaleFactor + } + } + + Component.onCompleted: { + db.open(); + + var txt = scriptsFolder.readTextFile("initdb.sql"); + + getQueries(txt).forEach(function (sql) { + console.log("sql: ", sql); + execute(sql); + } ); + + statements.model = getQueries(scriptsFolder.readTextFile("statements.sql")); + } +} + diff --git a/SQL - Spatial Functions/SQLSpatialFunction/scripts/initdb.sql b/SQL - Spatial Functions/SQLSpatialFunction/scripts/initdb.sql new file mode 100644 index 0000000..faa9b3e --- /dev/null +++ b/SQL - Spatial Functions/SQLSpatialFunction/scripts/initdb.sql @@ -0,0 +1,12 @@ +CREATE TABLE Locations +( + ID INTEGER NOT NULL, + Title TEXT NOT NULL, + Address TEXT NOT NULL, + Shape POINT, + CONSTRAINT PK_Locations PRIMARY KEY (ID) +); + +INSERT INTO Locations (Title, Address, Shape) VALUES ('Melbourne Office', '111 Coventry St', ST_Point(144.965819, -37.830658, 4326)); +INSERT INTO Locations (Title, Address, Shape) VALUES ('Markets', 'Cecil St', ST_Point(144.9544383, -37.832262, 4326)); + diff --git a/SQL - Spatial Functions/SQLSpatialFunction/scripts/statements.sql b/SQL - Spatial Functions/SQLSpatialFunction/scripts/statements.sql new file mode 100644 index 0000000..e40a99f --- /dev/null +++ b/SQL - Spatial Functions/SQLSpatialFunction/scripts/statements.sql @@ -0,0 +1,137 @@ +-- Using SQLite to evaluate SQL expressions. +-- Expected output: 6 + +SELECT 1 + 2 + 3 as total; + +-- Retrieve SQLite's version number +-- Expected output: 3.16.2 + +SELECT sqlite_version() as sqliteVersion; + +-- First 3 characters of a GeoPackage Binary is "GP with a NUL character +-- Expected output: "GP" + +SELECT ST_Point(144.965819, -37.830658, 4326) as shape; + +-- Returns the size of a GeoPackage Binary for a 2D point +-- Expected output: 61 (bytes) + +SELECT length(ST_Point(144.965819, -37.830658, 4326)) as dataLength; + +-- Returns a hexadecimal representation of a GeoPackage Binary point +-- Expected output: "47500003E6100000605B3FFDE71E6240605B3FFDE71E62409414580053EA42C09414580053EA42C00101000000605B3FFDE71E62409414580053EA42C0" + +SELECT hex(ST_Point(144.965819, -37.830658, 4326)) as shapeHex; + +-- Converts a GeoPackage Binary into Well Known Text representation for Geometry +-- Expected output: ST_POINT(144.965819 -37.830658) + +SELECT ST_AsText(X'47500003E6100000605B3FFDE71E6240605B3FFDE71E62409414580053EA42C09414580053EA42C00101000000605B3FFDE71E62409414580053EA42C0') as wkt; + +-- Demonstrates ST_IsEmpty spatial function +-- Expected output: 0 + +SELECT ST_IsEmpty(ST_Point(144.965819, -37.830658, 4326)) as isEmpty; + +-- Demonstrates ST_GeometryType spatial function +-- Expected output: "ST_POINT" + +SELECT ST_GeometryType(ST_Point(144.965819, -37.830658, 4326)) as geometryType; + +-- Demonstrates ST_X spatial function +-- Expected output: 144.965819 + +SELECT ST_X(ST_Point(144.965819, -37.830658, 4326)) as x; + +-- Demonstrates ST_Y spatial function +-- Expected output: -37.830658 + +SELECT ST_Y(ST_Point(144.965819, -37.830658, 4326)) as y; + +-- Demonstrates ST_Srid spatial function +-- Expected output: 4326 + +SELECT ST_Srid(ST_Point(144.965819, -37.830658, 4326)) as srid; + +-- Demonstrates ST_MinX spatial function +-- Expected output: 144.965819 + +SELECT ST_MinX(ST_Point(144.965819, -37.830658, 4326)) as minx; + +-- Demonstrates ST_MinY spatial function +-- Expected output: -37.830658 + +SELECT ST_MinY(ST_Point(144.965819, -37.830658, 4326)) as miny; + +-- Demonstrates ST_MaxX spatial function +-- Expected output: 144.965819 + +SELECT ST_MaxX(ST_Point(144.965819, -37.830658, 4326)) as maxx; + +-- Demonstrates ST_MinY spatial function +-- Expected output: -37.830658 + +SELECT ST_MaxY(ST_Point(144.965819, -37.830658, 4326)) as maxy; + +-- Computes the distance between Redlands Office and Melbourne Office +-- Expected output: 12858514.476722037 (i.e. 12858 kms) + +SELECT ST_Distance(ST_Point(144.965819, -37.830658, 4326), + ST_Point(-117.1963487,34.0562292, 4326)) + as distance; + +-- Computes the distance between South Melbourne Markets to Melbourne Office +-- Expected output: 1015.7444936798523 (i.e. 1 km) + +SELECT ST_Distance(ST_Point(144.965819, -37.830658, 4326), + ST_Point(144.954433, -37.832262, 4326)) as distance; + +-- Computes the bearing (in radianis) South Melbourne Markets relative to Melbourne Office +-- Expected output: 4.535820702776582 (radians) + +SELECT ST_Azimuth(ST_Point(144.965819, -37.830658, 4326), + ST_Point(144.954433, -37.832262, 4326)) as radians; + +-- Computes the bearing (in degrees) South Melbourne Markets relative to Melbourne Office +-- Expected output: 259.88338289716114 (degrees) + +SELECT toDegrees(ST_Azimuth(ST_Point(144.965819, -37.830658, 4326), + ST_Point(144.954433, -37.832262, 4326))) as degrees; + +-- Displays a point in Well Known Text format +-- Expected output: "POINT(144.965819 -37.830658)" + +SELECT ST_AsText(ST_Point(144.965819, -37.830658, 4326)) as wkt; + +-- Displays a point in PostGIS's Extended WKT format +-- Expected output: "SRID=4326;POINT(144.965819 -37.830658)" + +SELECT ST_AsEWKT(ST_Point(144.965819, -37.830658, 4326)) as ewkt; + +-- Shows the Locations table containing Melbourne Office and South Melbourne Markets + +SELECT *, + ST_X(Shape) as x, + ST_Y(Shape) as y, + ST_AsText(Shape) as wkt, + ST_AsEWKT(Shape) as ewkt +From Locations; + +-- Perform a near me search against all Locations +-- Expected output: "Melbourne Office" + +SELECT *, + ST_Distance(Shape, ST_Point(144.965819, -37.830658, 4326)) distance +FROM Locations +ORDER BY distance +LIMIT 1; + +-- Determine the midpoint between Melbourne Office and South Melbourne Markets +-- Expected result: POINT(144.960129 -37.831460) + +SELECT average(ST_X(Shape)) as midx, + average(ST_Y(Shape)) as midy, + ST_Point(average(ST_X(Shape)), average(ST_Y(Shape)), 4326) as midpoint, + ST_AsText(ST_Point(average(ST_X(Shape)), average(ST_Y(Shape)), 4326)) as wkt, + ST_AsEWKT(ST_Point(average(ST_X(Shape)), average(ST_Y(Shape)), 4326)) as ewkt +From Locations; diff --git a/SQL - Spatial Functions/appicon.png b/SQL - Spatial Functions/appicon.png new file mode 100644 index 0000000..39504b9 Binary files /dev/null and b/SQL - Spatial Functions/appicon.png differ diff --git a/SQL - Spatial Functions/appinfo.json b/SQL - Spatial Functions/appinfo.json new file mode 100644 index 0000000..911a1f9 --- /dev/null +++ b/SQL - Spatial Functions/appinfo.json @@ -0,0 +1,68 @@ +{ + "arcgisRuntime": 1001, + "capabilities": { + "audio": false, + "backgroundLocation": false, + "bluetooth": false, + "camera": false, + "localnotification": false, + "location": true, + "microphone": false, + "network": true, + "storage": false, + "vibration": false + }, + "deployment": { + "android": { + }, + "arcgisRuntimeExtensionsLicense": "", + "arcgisRuntimeLicense": "", + "ios": { + "codeSignIdentity": "" + }, + "macos": { + "codeSignIdentity": "" + }, + "winphone": { + "packageDisplayName": "" + } + }, + "devicesTypes": [ + "desktop", + "tablet", + "phone" + ], + "display": { + "desktop": { + "minimumHeight": 0, + "minimumWidth": 360, + "windowMode": "default" + }, + "phone": { + "landscape": true, + "portrait": true, + "showStatusBar": true + }, + "tablet": { + "landscape": true, + "portrait": true, + "showStatusBar": true + } + }, + "mainFile": "MyApp.qml", + "projectFile": "MyApp.qmlproject", + "properties": { + }, + "resources": { + "appIcon": "default-app.png", + "launchImageBackground": "launchimage-background.png", + "launchImageBackgroundColor": "#ffffff", + "launchImageOverlay": "launchimage-overlay.png" + }, + "type": "app", + "urlScheme": null, + "version": { + "major": 1, + "micro": 1 + } +} diff --git a/SQL - Spatial Functions/assets/clear.png b/SQL - Spatial Functions/assets/clear.png new file mode 100644 index 0000000..6b717e0 Binary files /dev/null and b/SQL - Spatial Functions/assets/clear.png differ diff --git a/SQL - Spatial Functions/assets/ic_menu_closeclear_light_d.png b/SQL - Spatial Functions/assets/ic_menu_closeclear_light_d.png new file mode 100644 index 0000000..9fa1653 Binary files /dev/null and b/SQL - Spatial Functions/assets/ic_menu_closeclear_light_d.png differ diff --git a/SQL - Spatial Functions/assets/ic_menu_collapsedencircled_light_d.png b/SQL - Spatial Functions/assets/ic_menu_collapsedencircled_light_d.png new file mode 100644 index 0000000..05d31b5 Binary files /dev/null and b/SQL - Spatial Functions/assets/ic_menu_collapsedencircled_light_d.png differ diff --git a/SQL - Spatial Functions/assets/info.png b/SQL - Spatial Functions/assets/info.png new file mode 100644 index 0000000..c41a5fc Binary files /dev/null and b/SQL - Spatial Functions/assets/info.png differ diff --git a/SQL - Spatial Functions/assets/pin_circle_red.png b/SQL - Spatial Functions/assets/pin_circle_red.png new file mode 100644 index 0000000..f21eeb4 Binary files /dev/null and b/SQL - Spatial Functions/assets/pin_circle_red.png differ diff --git a/SQL - Spatial Functions/controls/DescriptionPage.qml b/SQL - Spatial Functions/controls/DescriptionPage.qml new file mode 100644 index 0000000..0572ab6 --- /dev/null +++ b/SQL - Spatial Functions/controls/DescriptionPage.qml @@ -0,0 +1,93 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + id: descPage + width: parent.width + height: parent.height + + Rectangle{ + anchors.fill:parent + + ColumnLayout{ + anchors.fill:parent + spacing: 0 + clip:true + + Rectangle{ + id:descPageheader + color:"#8f499c" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 50 * scaleFactor + + ImageButton { + source: "../assets/clear.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + right: parent.right + rightMargin: 10 * scaleFactor + verticalCenter: parent.verticalCenter + } + onClicked: { + descPage.visible = 0 + } + } + + Text { + id: aboutApp + text:qsTr("About") + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + anchors.centerIn: parent + maximumLineCount: 2 + elide: Text.ElideRight + } + } + + Rectangle{ + color:"black" + Layout.fillWidth: true + Layout.fillHeight: true + + Flickable { + anchors.fill:parent + contentHeight: descText.height + clip:true + + Text{ + id: descText + y: 30 * scaleFactor + text:app.info.description + anchors.horizontalCenterOffset: 0 + color:"white" + width: 0.85 * parent.width + horizontalAlignment: Text.AlignLeft + linkColor: "#e5e6e7" + wrapMode: Text.WordWrap + elide: Text.ElideRight + anchors.horizontalCenter: parent.horizontalCenter + font { + pixelSize: app.baseFontSize + } + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + } + } +} + + + + + diff --git a/SQL - Spatial Functions/controls/HeaderBar.qml b/SQL - Spatial Functions/controls/HeaderBar.qml new file mode 100644 index 0000000..2ddd736 --- /dev/null +++ b/SQL - Spatial Functions/controls/HeaderBar.qml @@ -0,0 +1,59 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +RowLayout{ + anchors.fill: parent + spacing:0 + clip:true + + Rectangle{ + Layout.preferredWidth: 50*scaleFactor + } + + Text { + text:app.info.title + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + maximumLineCount:2 + wrapMode: Text.Wrap + elide: Text.ElideRight + anchors{ + verticalCenter: parent.verticalCenter + horizontalCenter:parent.horizontalCenter + } + } + + Rectangle{ + id:infoImageRect + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 50*scaleFactor + + ImageButton { + id:infoImage + source: "../assets/info.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + centerIn: parent + } + onClicked: { + descPage.visible = 1 + } + } + } +} + + + + + diff --git a/SQL - Spatial Functions/default-app.png b/SQL - Spatial Functions/default-app.png new file mode 100644 index 0000000..8fa9021 Binary files /dev/null and b/SQL - Spatial Functions/default-app.png differ diff --git a/SQL - Spatial Functions/image b/SQL - Spatial Functions/image new file mode 100644 index 0000000..e69de29 diff --git a/SQL - Spatial Functions/iteminfo.json b/SQL - Spatial Functions/iteminfo.json new file mode 100644 index 0000000..901afcb --- /dev/null +++ b/SQL - Spatial Functions/iteminfo.json @@ -0,0 +1,57 @@ +{ + "access": "public", + "accessInformation": null, + "appCategories": [ + ], + "avgRating": 0, + "banner": null, + "categories": [ + ], + "commentsEnabled": true, + "created": 1503008379000, + "culture": "en-au", + "description": "\n

This app demonstrates SQL Spatial Functions via SELECT statements. It uses SELECT statements with FROM clauses omitted to demonstrate a variety of scalar functions involving point geometry. Then, it proceeds to scalar and aggregate functions.

\n


\n

Check thisΒ blog postΒ to learn more about the sample.

\n


\n

Resource Level: 🍌🍌

", + "documentation": null, + "extent": [ + ], + "guid": null, + "id": null, + "industries": [ + ], + "itemControl": "admin", + "languages": [ + ], + "largeThumbnail": null, + "licenseInfo": null, + "listed": false, + "modified": 1503014886000, + "name": null, + "numComments": 5, + "numRatings": 0, + "numViews": 6, + "orgId": "2U3NfasNQ9o9LkLt", + "owner": null, + "ownerFolder": null, + "properties": null, + "protected": false, + "proxyFilter": null, + "screenshots": [ + ], + "size": 41703, + "snippet": "This app demonstrates SQL Spatial Functions ", + "spatialReference": null, + "tags": null, + "thumbnail": "thumbnail/thumbnail.png", + "title": "SQL - Spatial Functions", + "type": "Native Application", + "typeKeywords": [ + "API_QML", + "App", + "Application", + "AppStudio", + "Configuration", + "Native", + "qml" + ], + "url": null +} diff --git a/SQL - Spatial Functions/qtquickcontrols2.conf b/SQL - Spatial Functions/qtquickcontrols2.conf new file mode 100644 index 0000000..1ddd1cd --- /dev/null +++ b/SQL - Spatial Functions/qtquickcontrols2.conf @@ -0,0 +1,10 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Material + +[Universal] +Theme=Light +;Accent=Steel diff --git a/SQL - Spatial Functions/thumbnail.png b/SQL - Spatial Functions/thumbnail.png new file mode 100644 index 0000000..927045f Binary files /dev/null and b/SQL - Spatial Functions/thumbnail.png differ diff --git a/WMS Layer/MyApp.qml b/WMS Layer/MyApp.qml new file mode 100644 index 0000000..62c49e2 --- /dev/null +++ b/WMS Layer/MyApp.qml @@ -0,0 +1,102 @@ +/* Copyright 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtGraphicalEffects 1.0 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 +import Esri.ArcGISRuntime 100.2 + +import "controls" as Controls + +App { + id: app + width: 414 + height: 736 + function units(value) { + return AppFramework.displayScaleFactor * value + } + property real scaleFactor: AppFramework.displayScaleFactor + property int baseFontSize : app.info.propertyValue("baseFontSize", 15 * scaleFactor) + (isSmallScreen ? 0 : 3) + property bool isSmallScreen: (width || height) < units(400) + + Page{ + anchors.fill: parent + header: ToolBar{ + id:header + width: parent.width + height: 50 * scaleFactor + Material.background: "#8f499c" + Controls.HeaderBar{} + } + + // sample starts here ------------------------------------------------------------------ + contentItem: Rectangle { + anchors.top:header.bottom + + MapView { + id: mapView + anchors.fill: parent + + Map { + BasemapImagery {} + initialViewpoint: viewpoint + + // Add a WMS Layer by specifying the URL and layer name + WmsLayer { + url: "https://certmapper.cr.usgs.gov/arcgis/services/geology/africa/MapServer/WMSServer?request=GetCapabilities&service=WMS" + layerNames: ["0"] + } + } + } + + // Create the intial Viewpoint + ViewpointCenter { + id: viewpoint + // Specify the center Point + center: Point { + x: 20.346635 + y: 35.017028 + spatialReference: SpatialReference { wkid: 102100 } + } + // Specify the scale + targetScale: 85000000 + } + + //Busy Indicator + BusyIndicator { + id:mapDrawingWindow + anchors.centerIn: parent + height: 48 * scaleFactor + width: height + running: true + Material.accent:"#8f499c" + visible: (mapView.drawStatus === Enums.DrawStatusInProgress) + } + } + } + + // sample ends here ------------------------------------------------------------------------ + Controls.DescriptionPage{ + id:descPage + visible: false + } +} + diff --git a/WMS Layer/MyApp.qmlproject b/WMS Layer/MyApp.qmlproject new file mode 100644 index 0000000..b8ff5f5 --- /dev/null +++ b/WMS Layer/MyApp.qmlproject @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ + +import QmlProject 1.1 + +Project { + mainFile: "MyApp.qml" + + QmlFiles { + directory: "." + recursive: true + } + + JavaScriptFiles { + directory: "." + recursive: true + } + + ImageFiles { + directory: "." + recursive: true + } + + Files { + directory: "." + recursive: true + filter: "*.json;*.html;*.txt" + } + + importPaths: [ + ] +} + diff --git a/WMS Layer/README.md b/WMS Layer/README.md new file mode 100644 index 0000000..8d0bf5a --- /dev/null +++ b/WMS Layer/README.md @@ -0,0 +1,43 @@ +## WMTS Layer + +This sample demonstrates how to load a Web Map Tile Service (WMTS) and display it as a layer in a Map. + +WMTS is a type of web map service developed by the Open Geospatial Consortium (OGC). To consume in ArcGIS Runtime, first create a WmtsService by passing in the URL to the WMTS service. Load the WmtsService by calling load, and wait for the service to load by connecting to the loadStatusChanged signal. Once the service loads, obtain the WmtsLayerServiceInfo and the list of WmtsLayerInfo from the loaded WmtsService. In this particular sample, the first layer is obtained from the list of WmtsLayerInfo, and that Layer's ID is retrieved. Finally, a WmtsLayer is created by setting the URL and the Layer ID that was obtained in the previous step. A basemap is created from the WmtsLayer, and this Basemap is added to a new Map + +[Resource Level](https://geonet.esri.com/groups/appstudio/blog/2016/12/06/how-to-describe-our-resources-in-terms-of-difficulty-complexity-and-time-to-digest): 🍌🍌 + + +## Instructions to run this sample in AppStudio Desktop + +1. Download the `.zip` file +2. Unzip and copy this folder into AppStudio Apps folder (Windows: `C:\Users\\ArcGIS\AppStudio\Apps` Mac or linux: `Home\ArcGIS\AppStudio\Apps`) +3. The new app will now appear in the AppStudio Desktop. Run the application or open it in the bundled Qt-Creator IDE to look at the code and modify. + +## Issues + +Find a bug or want to request a new feature? Please let us know by submitting an issue. + +## Contributing + +Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). + +## Licensing +Copyright 2017 Esri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +A copy of the license is available in the repository's [license.txt](license.txt) file. + + +[](Esri Tags: ArcGIS Runtime SDK Qt QML JavaScript iOS Android Xamarin Ionic PhoneGap Mac linux Windows Apps samples templates appstudio) +[](Esri Language: Qt QML JavaScript) diff --git a/WMS Layer/appicon.png b/WMS Layer/appicon.png new file mode 100644 index 0000000..39504b9 Binary files /dev/null and b/WMS Layer/appicon.png differ diff --git a/WMS Layer/appinfo.json b/WMS Layer/appinfo.json new file mode 100644 index 0000000..ba2c132 --- /dev/null +++ b/WMS Layer/appinfo.json @@ -0,0 +1,89 @@ +{ + "arcgisRuntime": 1002, + "capabilities": { + "audio": false, + "backgroundLocation": false, + "biometricAuthentication": false, + "bluetooth": false, + "camera": false, + "fileSharing": false, + "highAccuracyLocation": false, + "ios": { + "externalAccessoryProtocolStrings": [ + ] + }, + "localnotification": false, + "location": true, + "microphone": false, + "network": true, + "storage": false, + "vibration": false + }, + "deployment": { + "android": { + "packageName": "" + }, + "arcgisRuntimeExtensionsLicense": "", + "arcgisRuntimeLicense": "", + "clientId": "", + "ios": { + "bundleId": "", + "codeSignIdentity": "" + }, + "macos": { + "bundleId": "", + "codeSignIdentity": "" + }, + "publisherName": "", + "uwp": { + "packageName": "", + "productID": "" + }, + "winphone": { + "packageDisplayName": "" + } + }, + "devicesTypes": [ + "desktop", + "tablet", + "phone" + ], + "display": { + "desktop": { + "minimumHeight": 0, + "minimumWidth": 0, + "windowMode": "default" + }, + "phone": { + "landscape": true, + "portrait": true, + "showStatusBar": true + }, + "tablet": { + "landscape": true, + "portrait": true, + "showStatusBar": true + } + }, + "environment": { + }, + "launchUrlSchemes": [ + ], + "mainFile": "MyApp.qml", + "multipleInstances": true, + "projectFile": "MyApp.qmlproject", + "properties": { + }, + "resources": { + "appIcon": "default-app.png", + "launchImageBackground": "launchimage-background.png", + "launchImageBackgroundColor": "#ffffff", + "launchImageOverlay": "launchimage-overlay.png" + }, + "type": "app", + "urlScheme": "", + "version": { + "major": 1, + "micro": 2 + } +} diff --git a/WMS Layer/assets/clear.png b/WMS Layer/assets/clear.png new file mode 100644 index 0000000..6b717e0 Binary files /dev/null and b/WMS Layer/assets/clear.png differ diff --git a/WMS Layer/assets/info.png b/WMS Layer/assets/info.png new file mode 100644 index 0000000..c41a5fc Binary files /dev/null and b/WMS Layer/assets/info.png differ diff --git a/WMS Layer/controls/DescriptionPage.qml b/WMS Layer/controls/DescriptionPage.qml new file mode 100644 index 0000000..0572ab6 --- /dev/null +++ b/WMS Layer/controls/DescriptionPage.qml @@ -0,0 +1,93 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +Item { + id: descPage + width: parent.width + height: parent.height + + Rectangle{ + anchors.fill:parent + + ColumnLayout{ + anchors.fill:parent + spacing: 0 + clip:true + + Rectangle{ + id:descPageheader + color:"#8f499c" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 50 * scaleFactor + + ImageButton { + source: "../assets/clear.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + right: parent.right + rightMargin: 10 * scaleFactor + verticalCenter: parent.verticalCenter + } + onClicked: { + descPage.visible = 0 + } + } + + Text { + id: aboutApp + text:qsTr("About") + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + anchors.centerIn: parent + maximumLineCount: 2 + elide: Text.ElideRight + } + } + + Rectangle{ + color:"black" + Layout.fillWidth: true + Layout.fillHeight: true + + Flickable { + anchors.fill:parent + contentHeight: descText.height + clip:true + + Text{ + id: descText + y: 30 * scaleFactor + text:app.info.description + anchors.horizontalCenterOffset: 0 + color:"white" + width: 0.85 * parent.width + horizontalAlignment: Text.AlignLeft + linkColor: "#e5e6e7" + wrapMode: Text.WordWrap + elide: Text.ElideRight + anchors.horizontalCenter: parent.horizontalCenter + font { + pixelSize: app.baseFontSize + } + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + } + } +} + + + + + diff --git a/WMS Layer/controls/HeaderBar.qml b/WMS Layer/controls/HeaderBar.qml new file mode 100644 index 0000000..2ddd736 --- /dev/null +++ b/WMS Layer/controls/HeaderBar.qml @@ -0,0 +1,59 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 + + +import ArcGIS.AppFramework 1.0 +import ArcGIS.AppFramework.Controls 1.0 + +RowLayout{ + anchors.fill: parent + spacing:0 + clip:true + + Rectangle{ + Layout.preferredWidth: 50*scaleFactor + } + + Text { + text:app.info.title + color:"white" + font.pixelSize: app.baseFontSize * 1.1 + font.bold: true + maximumLineCount:2 + wrapMode: Text.Wrap + elide: Text.ElideRight + anchors{ + verticalCenter: parent.verticalCenter + horizontalCenter:parent.horizontalCenter + } + } + + Rectangle{ + id:infoImageRect + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 50*scaleFactor + + ImageButton { + id:infoImage + source: "../assets/info.png" + height: 30 * scaleFactor + width: 30 * scaleFactor + checkedColor : "transparent" + pressedColor : "transparent" + hoverColor : "transparent" + glowColor : "transparent" + anchors { + centerIn: parent + } + onClicked: { + descPage.visible = 1 + } + } + } +} + + + + + diff --git a/WMS Layer/default-app.png b/WMS Layer/default-app.png new file mode 100644 index 0000000..8fa9021 Binary files /dev/null and b/WMS Layer/default-app.png differ diff --git a/WMS Layer/image b/WMS Layer/image new file mode 100644 index 0000000..e69de29 diff --git a/WMS Layer/iteminfo.json b/WMS Layer/iteminfo.json new file mode 100644 index 0000000..e2e5e24 --- /dev/null +++ b/WMS Layer/iteminfo.json @@ -0,0 +1,65 @@ +{ + "access": "org", + "accessInformation": null, + "appCategories": [ + ], + "avgRating": 0, + "banner": null, + "categories": [ + ], + "commentsEnabled": true, + "created": 1523903483000, + "culture": "en-au", + "description": "\n

A WmsLayer is declared inside a Map by setting its URL and LayerNames properties. The URL must be the URL to the "GetCapabilities" endpoint of the WMS Service. Alternatively, the layer could be construction by setting the layerInfo property.

\n

Resource Level:🍌

", + "documentation": null, + "extent": [ + ], + "groupDesignations": null, + "guid": null, + "id": "7d7d6522170f478f8a560ef1dc8c80e2", + "industries": [ + ], + "itemControl": "admin", + "languages": [ + ], + "largeThumbnail": null, + "licenseInfo": null, + "listed": false, + "modified": 1523903491000, + "name": "7d7d6522170f478f8a560ef1dc8c80e2.zip", + "numComments": 2, + "numRatings": 0, + "numViews": 0, + "owner": "appstudio_samples", + "ownerFolder": null, + "properties": null, + "protected": false, + "proxyFilter": null, + "scoreCompleteness": 80, + "screenshots": [ + ], + "size": 36187, + "snippet": "This sample demonstrates how to display a WMS Layer from an online URL.", + "spatialReference": null, + "tags": [ + "AppStudio", + "Quartz", + "Runtime", + "Sample", + "Layer", + "WMS" + ], + "thumbnail": "thumbnail/thumbnail.png", + "title": "WMS Layer (URL)", + "type": "Native Application", + "typeKeywords": [ + "API_QML", + "App", + "Application", + "AppStudio", + "Configuration", + "Native", + "qml" + ], + "url": null +} diff --git a/WMS Layer/qtquickcontrols2.conf b/WMS Layer/qtquickcontrols2.conf new file mode 100644 index 0000000..1ddd1cd --- /dev/null +++ b/WMS Layer/qtquickcontrols2.conf @@ -0,0 +1,10 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +Style=Material + +[Universal] +Theme=Light +;Accent=Steel diff --git a/WMS Layer/thumbnail.png b/WMS Layer/thumbnail.png new file mode 100644 index 0000000..7bac0eb Binary files /dev/null and b/WMS Layer/thumbnail.png differ