WelcomeScreen.qml 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. import QtQuick 2.12
  2. import QtQuick.Controls 2.12
  3. import QtQuick.Layouts 1.12
  4. import QtQuick.Particles 2.0
  5. import Qt.labs.settings 1.0
  6. import org.qfield 1.0
  7. import Theme 1.0
  8. Page {
  9. id: welcomeScreen
  10. property bool firstShown: false
  11. property alias model: table.model
  12. signal showOpenProjectDialog
  13. signal showQFieldCloudScreen
  14. Settings {
  15. id: registry
  16. category: 'QField'
  17. property string baseMapProject: ''
  18. }
  19. Rectangle {
  20. id: welcomeBackground
  21. anchors.fill: parent
  22. gradient: Gradient {
  23. GradientStop {
  24. position: 0.0
  25. color: "#ffdedede"
  26. }
  27. GradientStop {
  28. position: 0.33
  29. color: "#00dedede"
  30. }
  31. }
  32. }
  33. ScrollView {
  34. padding: 0
  35. ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
  36. ScrollBar.vertical.policy: ScrollBar.AsNeeded
  37. contentItem: welcomeGrid
  38. contentWidth: welcomeGrid.width
  39. contentHeight: welcomeGrid.height
  40. anchors.fill: parent
  41. clip: true
  42. GridLayout {
  43. id: welcomeGrid
  44. columns: 1
  45. rowSpacing: 4
  46. width: mainWindow.width
  47. ImageDial{
  48. id: imageDialLogo
  49. value: 1
  50. Layout.margins: 6
  51. Layout.topMargin: 14
  52. Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
  53. Layout.preferredWidth: Math.min( 138, mainWindow.height / 4 )
  54. Layout.preferredHeight: Math.min( 138, mainWindow.height / 4 )
  55. source: "qrc:/images/qfield_logo.svg"
  56. rotationOffset: 220
  57. }
  58. SwipeView {
  59. id: feedbackView
  60. visible: false
  61. Layout.margins: 6
  62. Layout.topMargin: 10
  63. Layout.bottomMargin: 10
  64. Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
  65. Layout.preferredWidth: Math.min( 410, mainWindow.width - 30 )
  66. Layout.preferredHeight: Math.max(ohno.childrenRect.height, intro.childrenRect.height, ohyeah.childrenRect.height)
  67. clip: true
  68. Behavior on Layout.preferredHeight {
  69. NumberAnimation { duration: 100; easing.type: Easing.InQuad; }
  70. }
  71. interactive: false
  72. currentIndex: 1
  73. Item {
  74. id: ohno
  75. Rectangle {
  76. anchors.fill: parent
  77. gradient: Gradient {
  78. GradientStop {
  79. position: 0.0
  80. color: "#4480cc28"
  81. }
  82. GradientStop {
  83. position: 0.88
  84. color: "#0580cc28"
  85. }
  86. }
  87. radius: 6
  88. }
  89. ColumnLayout {
  90. spacing: 0
  91. anchors.centerIn: parent
  92. Text {
  93. Layout.margins: 6
  94. Layout.maximumWidth: feedbackView.width - 12
  95. text: qsTr("We're sorry to hear that. Click on the button below to comment or seek support.")
  96. font: Theme.defaultFont
  97. horizontalAlignment: Text.AlignHCenter
  98. wrapMode: Text.WordWrap
  99. }
  100. RowLayout {
  101. spacing: 6
  102. Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
  103. Layout.bottomMargin: 10
  104. QfButton {
  105. leftPadding: 20
  106. rightPadding: 20
  107. text: qsTr("Reach out")
  108. icon.source: Theme.getThemeIcon( 'ic_create_white_24dp' )
  109. onClicked: {
  110. Qt.openUrlExternally("https://www.qfield.org/")
  111. feedbackView.Layout.preferredHeight = 0
  112. }
  113. }
  114. }
  115. }
  116. }
  117. Item {
  118. id: intro
  119. Rectangle {
  120. anchors.fill: parent
  121. gradient: Gradient {
  122. GradientStop {
  123. position: 0.0
  124. color: "#4480cc28"
  125. }
  126. GradientStop {
  127. position: 0.88
  128. color: "#0580cc28"
  129. }
  130. }
  131. radius: 6
  132. }
  133. ColumnLayout {
  134. spacing: 0
  135. anchors.centerIn: parent
  136. Text {
  137. Layout.margins: 6
  138. Layout.maximumWidth: feedbackView.width - 12
  139. text: qsTr("Hey there, how do you like your experience with QField so far?")
  140. font: Theme.defaultFont
  141. horizontalAlignment: Text.AlignHCenter
  142. wrapMode: Text.WordWrap
  143. }
  144. RowLayout {
  145. spacing: 6
  146. Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
  147. Layout.bottomMargin: 10
  148. QfToolButton {
  149. iconSource: Theme.getThemeVectorIcon('ic_dissatisfied_white_24dp')
  150. bgcolor: Theme.mainColor
  151. round: true
  152. onClicked: {
  153. feedbackView.currentIndex = 0
  154. }
  155. }
  156. QfToolButton {
  157. iconSource: Theme.getThemeVectorIcon('ic_satisfied_white_24dp')
  158. bgcolor: Theme.mainColor
  159. round: true
  160. onClicked: {
  161. feedbackView.currentIndex = 2
  162. }
  163. }
  164. }
  165. }
  166. }
  167. Item {
  168. id: ohyeah
  169. Rectangle {
  170. anchors.fill: parent
  171. gradient: Gradient {
  172. GradientStop {
  173. position: 0.0
  174. color: "#4480cc28"
  175. }
  176. GradientStop {
  177. position: 0.88
  178. color: "#0580cc28"
  179. }
  180. }
  181. radius: 6
  182. }
  183. ColumnLayout {
  184. spacing: 0
  185. anchors.centerIn: parent
  186. Text {
  187. Layout.margins: 6
  188. Layout.maximumWidth: feedbackView.width - 12
  189. text: qsTr("That's great! We'd love for you to click on the button below and leave a comment on the store.")
  190. font: Theme.defaultFont
  191. horizontalAlignment: Text.AlignHCenter
  192. wrapMode: Text.WordWrap
  193. }
  194. RowLayout {
  195. spacing: 6
  196. Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
  197. Layout.margins: 6
  198. Layout.bottomMargin: 10
  199. QfButton {
  200. leftPadding: 20
  201. rightPadding: 20
  202. text: qsTr("Rate us")
  203. icon.source: Theme.getThemeVectorIcon( 'ic_star_white_24dp' )
  204. onClicked: {
  205. Qt.openUrlExternally("market://details?id=ch.opengis.qfield")
  206. feedbackView.Layout.preferredHeight = 0
  207. }
  208. }
  209. }
  210. }
  211. }
  212. }
  213. Text {
  214. id: welcomeText
  215. visible: !feedbackView.visible
  216. Layout.leftMargin: 6
  217. Layout.rightMargin: 6
  218. Layout.topMargin: 2
  219. Layout.bottomMargin: 2
  220. Layout.fillWidth: true
  221. Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
  222. text: ""
  223. font: Theme.defaultFont
  224. horizontalAlignment: Text.AlignHCenter
  225. wrapMode: Text.WordWrap
  226. }
  227. Rectangle {
  228. Layout.leftMargin: 6
  229. Layout.rightMargin: 6
  230. Layout.topMargin: 2
  231. Layout.bottomMargin: 2
  232. Layout.fillWidth: true
  233. Layout.maximumWidth: 410
  234. Layout.minimumHeight: welcomeActions.height
  235. Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
  236. color: "transparent"
  237. ColumnLayout {
  238. id: welcomeActions
  239. width: parent.width
  240. spacing: 12
  241. QfButton {
  242. id: localProjectButton
  243. Layout.fillWidth: true
  244. text: qsTr( "Open local file" )
  245. onClicked: {
  246. showOpenProjectDialog()
  247. }
  248. }
  249. QfButton {
  250. id: cloudProjectButton
  251. Layout.fillWidth: true
  252. text: qsTr( "QField Cloud projects" )
  253. onClicked: {
  254. showQFieldCloudScreen()
  255. }
  256. }
  257. Text {
  258. id: recentText
  259. text: qsTr( "Recent Projects" )
  260. font.pointSize: Theme.tipFont.pointSize
  261. font.bold: true
  262. horizontalAlignment: Text.AlignHCenter
  263. wrapMode: Text.WordWrap
  264. Layout.fillWidth: true
  265. }
  266. Rectangle {
  267. Layout.fillWidth: true
  268. height: table.height
  269. color: "transparent"
  270. border.color: "transparent"
  271. border.width: 1
  272. ListView {
  273. id: table
  274. ScrollBar.vertical: ScrollBar {}
  275. flickableDirection: Flickable.VerticalFlick
  276. boundsBehavior: Flickable.StopAtBounds
  277. clip: true
  278. width: parent.width
  279. height: childrenRect.height
  280. delegate: Rectangle {
  281. id: rectangle
  282. objectName: "loadProjectItem_1" // todo, suffix with e.g. ProjectTitle
  283. property string path: ProjectPath
  284. property string title: ProjectTitle
  285. property var type: ProjectType
  286. width: parent ? parent.width : undefined
  287. height: line.height
  288. color: "transparent"
  289. Row {
  290. id: line
  291. Layout.fillWidth: true
  292. leftPadding: 6
  293. rightPadding: 10
  294. topPadding: 9
  295. bottomPadding: 3
  296. spacing: 0
  297. Image {
  298. id: type
  299. anchors.verticalCenter: inner.verticalCenter
  300. source: switch(ProjectType) {
  301. case 0: return Theme.getThemeIcon('ic_map_green_48dp'); // local project
  302. case 1: return Theme.getThemeIcon('ic_cloud_project_48dp'); // cloud project
  303. case 2: return Theme.getThemeIcon('ic_file_green_48dp'); // local dataset
  304. default: return '';
  305. }
  306. sourceSize.width: 80
  307. sourceSize.height: 80
  308. width: 40
  309. height: 40
  310. }
  311. ColumnLayout {
  312. id: inner
  313. width: rectangle.width - type.width - 10
  314. Text {
  315. id: projectTitle
  316. topPadding: 5
  317. leftPadding: 3
  318. text: ProjectTitle
  319. font.pointSize: Theme.tipFont.pointSize
  320. font.underline: true
  321. color: Theme.mainColor
  322. wrapMode: Text.WordWrap
  323. Layout.fillWidth: true
  324. }
  325. Text {
  326. id: projectNote
  327. leftPadding: 3
  328. text: {
  329. var notes = [];
  330. if ( index == 0 ) {
  331. var firstRun = settings && !settings.value( "/QField/FirstRunFlag", false )
  332. if (!firstRun && firstShown === false) notes.push( qsTr( "Last session" ) );
  333. }
  334. if ( ProjectPath === registry.baseMapProject ) {
  335. notes.push( qsTr( "Base map project" ) );
  336. }
  337. if ( notes.length > 0 ) {
  338. return notes.join( '; ' );
  339. } else {
  340. return "";
  341. }
  342. }
  343. visible: text != ""
  344. font.pointSize: Theme.tipFont.pointSize - 2
  345. font.italic: true
  346. wrapMode: Text.WordWrap
  347. Layout.fillWidth: true
  348. }
  349. }
  350. }
  351. }
  352. MouseArea {
  353. property Item pressedItem
  354. anchors.fill: parent
  355. onClicked: {
  356. var item = table.itemAt(mouse.x, mouse.y)
  357. if (item) {
  358. if ( item.type == 1 && cloudConnection.hasToken && cloudConnection.status !== QFieldCloudConnection.LoggedIn ) {
  359. cloudConnection.login()
  360. }
  361. iface.loadFile(item.path,item.title)
  362. }
  363. }
  364. onPressed: {
  365. var item = table.itemAt(mouse.x, mouse.y)
  366. if (item) {
  367. pressedItem = item.children[0].children[1].children[0];
  368. pressedItem.color = "#5a8725"
  369. }
  370. }
  371. onCanceled: {
  372. if (pressedItem) {
  373. pressedItem.color = Theme.mainColor
  374. pressedItem = null
  375. }
  376. }
  377. onReleased: {
  378. if (pressedItem) {
  379. pressedItem.color = Theme.mainColor
  380. pressedItem = null
  381. }
  382. }
  383. onPressAndHold: {
  384. var item = table.itemAt(mouse.x, mouse.y)
  385. if (item) {
  386. recentProjectActions.recentProjectPath = item.path;
  387. recentProjectActions.recentProjectType = item.type;
  388. recentProjectActions.popup(mouse.x, mouse.y)
  389. }
  390. }
  391. }
  392. Menu {
  393. id: recentProjectActions
  394. property string recentProjectPath: ''
  395. property int recentProjectType: 0
  396. title: 'Recent Project Actions'
  397. width: {
  398. var result = 0;
  399. var padding = 0;
  400. for (var i = 0; i < count; ++i) {
  401. var item = itemAt(i);
  402. result = Math.max(item.contentItem.implicitWidth, result);
  403. padding = Math.max(item.padding, padding);
  404. }
  405. return Math.min( result + padding * 2,mainWindow.width - 20);
  406. }
  407. MenuItem {
  408. id: baseMapProject
  409. visible: recentProjectActions.recentProjectType != 2;
  410. font: Theme.defaultFont
  411. width: parent.width
  412. height: visible ? 48: 0
  413. checkable: true
  414. checked: recentProjectActions.recentProjectPath === registry.baseMapProject
  415. text: qsTr( "Base Map Project" )
  416. onTriggered: {
  417. registry.baseMapProject = recentProjectActions.recentProjectPath === registry.baseMapProject ? '' : recentProjectActions.recentProjectPath;
  418. }
  419. }
  420. MenuSeparator {
  421. visible: baseMapProject.visible
  422. width: parent.width
  423. height: visible ? undefined : 0
  424. }
  425. MenuItem {
  426. id: removeProject
  427. font: Theme.defaultFont
  428. width: parent.width
  429. height: visible ? 48: 0
  430. leftPadding: 10
  431. text: qsTr( "Remove from Recent Projects" )
  432. onTriggered: {
  433. iface.removeRecentProject( recentProjectActions.recentProjectPath );
  434. model.reloadModel();
  435. }
  436. }
  437. }
  438. }
  439. }
  440. }
  441. }
  442. }
  443. }
  444. QfToolButton {
  445. id: currentProjectButton
  446. visible: false
  447. anchors {
  448. top: parent.top
  449. left: parent.left
  450. }
  451. iconSource: Theme.getThemeIcon( 'ic_chevron_left_black_24dp' )
  452. bgcolor: "transparent"
  453. onClicked: {
  454. welcomeScreen.visible = false;
  455. welcomeScreen.focus = false;
  456. }
  457. }
  458. // Sparkles & unicorns
  459. Rectangle {
  460. anchors.fill: parent
  461. color: "#00000000"
  462. visible: imageDialLogo.value < 0.1
  463. MouseArea {
  464. id: mouseArea
  465. anchors.fill: parent
  466. acceptedButtons: Qt.LeftButton | Qt.RightButton
  467. hoverEnabled: true
  468. propagateComposedEvents: true
  469. onReleased: mouse.accepted = false
  470. onDoubleClicked: mouse.accepted = false
  471. onPressAndHold: mouse.accepted = false
  472. onClicked: {
  473. burstSomeSparkles(mouse.x, mouse.y)
  474. mouse.accepted = false
  475. }
  476. onPressed: {
  477. burstSomeSparkles(mouse.x, mouse.y)
  478. mouse.accepted = false
  479. }
  480. onPositionChanged: {
  481. burstSomeSparkles(mouse.x, mouse.y)
  482. mouse.accepted = false
  483. }
  484. }
  485. ParticleSystem {
  486. id: particles
  487. running: imageDialLogo.value < 0.1
  488. }
  489. ParticleSystem {
  490. id: unicorns
  491. running: imageDialLogo.value < 0.1
  492. }
  493. ImageParticle {
  494. anchors.fill: parent
  495. system: particles
  496. source: "qrc:///particleresources/star.png"
  497. sizeTable: "qrc:///images/sparkleSize.png"
  498. alpha: 1
  499. colorVariation: 0.3
  500. }
  501. ImageParticle {
  502. anchors.fill: parent
  503. system: unicorns
  504. source: "qrc:///images/icons/unicorn.png"
  505. alpha: 1
  506. redVariation: 0
  507. blueVariation: 0
  508. greenVariation: 0
  509. rotation: 0
  510. rotationVariation: 360
  511. }
  512. Emitter {
  513. id: emitterParticles
  514. x: -100
  515. y: -100
  516. system: particles
  517. emitRate: 60
  518. lifeSpan: 700
  519. size: 50
  520. sizeVariation: 10
  521. maximumEmitted: 100
  522. velocity: AngleDirection {
  523. angle: 0
  524. angleVariation: 360
  525. magnitude: 100
  526. magnitudeVariation: 50
  527. }
  528. }
  529. Emitter {
  530. id: emitterUnicorns
  531. x: -100
  532. y: -100
  533. system: unicorns
  534. emitRate: 20
  535. lifeSpan: 900
  536. size: 70
  537. sizeVariation: 10
  538. maximumEmitted: 100
  539. velocity: AngleDirection {
  540. angle: 90
  541. angleVariation: 20
  542. magnitude: 200
  543. magnitudeVariation: 50
  544. }
  545. }
  546. }
  547. function burstSomeSparkles(x, y) {
  548. emitterParticles.burst(50, x, y)
  549. emitterUnicorns.burst(1, x, y)
  550. }
  551. function adjustWelcomeScreen() {
  552. if (visible) {
  553. if (firstShown) {
  554. welcomeText.text = " ";
  555. currentProjectButton.visible = true
  556. } else {
  557. var firstRun = !settings.value( "/QField/FirstRunFlag", false )
  558. if ( firstRun ) {
  559. welcomeText.text = qsTr( "Welcome to QField. First time using this application? Try out a few demos listed in the recent projects below." )
  560. } else {
  561. welcomeText.text = qsTr( "Welcome back to QField." )
  562. }
  563. currentProjectButton.visible = false
  564. }
  565. }
  566. }
  567. Component.onCompleted: {
  568. adjustWelcomeScreen()
  569. var runCount = settings.value("/QField/RunCount",0) * 1
  570. var feedbackFormShown = settings.value("/QField/FeedbackFormShown",false)
  571. if (!feedbackFormShown) {
  572. var now = new Date()
  573. var dt = settings.value("/QField/FirstRunDate", "")
  574. if (dt != "") {
  575. dt = new Date(dt)
  576. var daysToPrompt = 30;
  577. var runsToPrompt = 5;
  578. if (runCount >= runsToPrompt && (now - dt) >= (daysToPrompt * 24 * 60 * 60 * 1000)) {
  579. feedbackView.visible = true
  580. settings.setValue("/QField/FeedbackFormShown",true)
  581. }
  582. } else {
  583. settings.setValue("/QField/FirstRunDate", now.toISOString())
  584. }
  585. }
  586. settings.setValue("/QField/RunCount",runCount + 1)
  587. }
  588. onVisibleChanged: {
  589. adjustWelcomeScreen()
  590. if (!visible) {
  591. feedbackView.visible = false
  592. firstShown = true
  593. }
  594. }
  595. }