aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenny Lofthus <jenny.lofthus@qt.io>2022-07-26 14:25:47 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-09-12 11:40:02 +0000
commit22849f93d860638a02200e68afb499d539f6017e (patch)
treee96ecef05b43325c14faf6149967c0a09bcd0197
parent4a5d76e0d79848ccaae6e3cd8287ac05a970c15a (diff)
Controls: add todolist example for showcasing the iOS Style
Task-number: QTBUG-80261 Change-Id: Ie6d86b0a49bd0684373816d709c7e010aff7e7a5 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io> (cherry picked from commit 6b5cf5969889a88d5f506692c859d1bd4f59d5dd) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--examples/quickcontrols2/ios/todolist/AppSettings.qml14
-rw-r--r--examples/quickcontrols2/ios/todolist/CMakeLists.txt84
-rw-r--r--examples/quickcontrols2/ios/todolist/Database.qml161
-rw-r--r--examples/quickcontrols2/ios/todolist/FontSizePage.qml44
-rw-r--r--examples/quickcontrols2/ios/todolist/HomePage.qml168
-rw-r--r--examples/quickcontrols2/ios/todolist/MaxTasksPage.qml36
-rw-r--r--examples/quickcontrols2/ios/todolist/NavBar.qml44
-rw-r--r--examples/quickcontrols2/ios/todolist/ProjectPage.qml239
-rw-r--r--examples/quickcontrols2/ios/todolist/SettingsPage.qml68
-rw-r--r--examples/quickcontrols2/ios/todolist/ToggleCompletedTasksPage.qml34
-rw-r--r--examples/quickcontrols2/ios/todolist/doc/images/qtquickcontrols2-todolist.pngbin0 -> 67497 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/doc/src/qtquickcontrols2-todolist.qdoc22
-rw-r--r--examples/quickcontrols2/ios/todolist/images/add-new.pngbin0 -> 312 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/add-new@2x.pngbin0 -> 569 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/add-new@3x.pngbin0 -> 1406 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/back-white.pngbin0 -> 201 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/back-white@2x.pngbin0 -> 290 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/back-white@3x.pngbin0 -> 730 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/back.pngbin0 -> 201 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/back@2x.pngbin0 -> 290 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/back@3x.pngbin0 -> 730 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/close-white.pngbin0 -> 319 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/close-white@2x.pngbin0 -> 459 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/close-white@3x.pngbin0 -> 707 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/close.pngbin0 -> 319 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/close@2x.pngbin0 -> 459 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/close@3x.pngbin0 -> 707 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/plus-math.pngbin0 -> 115 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/plus-math@2x.pngbin0 -> 120 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/plus-math@3x.pngbin0 -> 382 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/settings.pngbin0 -> 1399 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/settings@2x.pngbin0 -> 2878 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/images/settings@3x.pngbin0 -> 5929 bytes
-rw-r--r--examples/quickcontrols2/ios/todolist/main.qml32
-rw-r--r--examples/quickcontrols2/ios/todolist/qmldir2
-rw-r--r--examples/quickcontrols2/ios/todolist/src/main.cpp32
-rw-r--r--examples/quickcontrols2/ios/todolist/todolist.pro42
-rw-r--r--tests/auto/quick/examples/tst_examples.cpp1
38 files changed, 1023 insertions, 0 deletions
diff --git a/examples/quickcontrols2/ios/todolist/AppSettings.qml b/examples/quickcontrols2/ios/todolist/AppSettings.qml
new file mode 100644
index 0000000000..c732b9fbc8
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/AppSettings.qml
@@ -0,0 +1,14 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+pragma Singleton
+
+import Qt.labs.settings
+
+Settings {
+ // The properties here are given default values to account for the first run of the application.
+ // After the application has run once, future values will come from the stored settings.
+ property bool showDoneTasks: true
+ property int maxTasks: 30
+ property int fontSize: 18
+}
diff --git a/examples/quickcontrols2/ios/todolist/CMakeLists.txt b/examples/quickcontrols2/ios/todolist/CMakeLists.txt
new file mode 100644
index 0000000000..413ea6ad97
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/CMakeLists.txt
@@ -0,0 +1,84 @@
+cmake_minimum_required(VERSION 3.18)
+project(todolist LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/todolist")
+
+find_package(Qt6 COMPONENTS Gui Qml Quick QuickControls2)
+
+qt_add_executable(todolist
+ src/main.cpp
+)
+
+set_target_properties(todolist PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(todolist PUBLIC
+ Qt${QT_VERSION_MAJOR}::Core
+ Qt${QT_VERSION_MAJOR}::Gui
+ Qt${QT_VERSION_MAJOR}::Qml
+ Qt${QT_VERSION_MAJOR}::Quick
+ Qt${QT_VERSION_MAJOR}::QuickControls2
+)
+
+set_source_files_properties(AppSettings.qml PROPERTIES
+ QT_QML_SINGLETON_TYPE TRUE
+)
+
+set_source_files_properties(Database.qml PROPERTIES
+ QT_QML_SINGLETON_TYPE TRUE
+)
+
+qt_add_qml_module(todolist
+ URI App
+ VERSION 1.0
+ QML_FILES
+ AppSettings.qml
+ Database.qml
+ FontSizePage.qml
+ HomePage.qml
+ MaxTasksPage.qml
+ NavBar.qml
+ ProjectPage.qml
+ SettingsPage.qml
+ ToggleCompletedTasksPage.qml
+ main.qml
+ RESOURCES
+ images/back.png
+ images/back@2x.png
+ images/back@3x.png
+ images/back-white.png
+ images/back-white@2x.png
+ images/back-white@3x.png
+ images/close.png
+ images/close@2x.png
+ images/close@3x.png
+ images/close-white.png
+ images/close-white@2x.png
+ images/close-white@3x.png
+ images/plus-math.png
+ images/plus-math@2x.png
+ images/plus-math@3x.png
+ images/settings.png
+ images/settings@2x.png
+ images/settings@3x.png
+ images/add-new.png
+ images/add-new@2x.png
+ images/add-new@3x.png
+ NO_RESOURCE_TARGET_PATH
+)
+
+set_property(GLOBAL PROPERTY XCODE_EMIT_EFFECTIVE_PLATFORM_NAME ON)
+
+install(TARGETS todolist
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/quickcontrols2/ios/todolist/Database.qml b/examples/quickcontrols2/ios/todolist/Database.qml
new file mode 100644
index 0000000000..31bbd8c1e7
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/Database.qml
@@ -0,0 +1,161 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+pragma Singleton
+
+import QtQml
+import QtQuick.LocalStorage
+
+QtObject {
+ id: root
+
+ property var _db
+
+ function _database() {
+ if (_db)
+ return _db
+
+ try {
+ let db = LocalStorage.openDatabaseSync("ToDoList", "1.0", "ToDoList application database")
+
+ db.transaction(function (tx) {
+ tx.executeSql(`CREATE TABLE IF NOT EXISTS projects (
+ project_id INTEGER PRIMARY KEY AUTOINCREMENT,
+ project_name TEXT NOT NULL CHECK(project_name != ''),
+ project_note TEXT
+ )`);
+ })
+
+ db.transaction(function (tx) {
+ tx.executeSql(`CREATE TABLE IF NOT EXISTS tasks (
+ task_id INTEGER PRIMARY KEY AUTOINCREMENT,
+ task_name TEXT CHECK(task_name != ''),
+ done INTEGER,
+ project_id,
+ FOREIGN KEY(project_id) REFERENCES projects(project_id)
+ )`);
+ })
+
+ _db = db
+ } catch (error) {
+ console.log("Error opening database: " + error)
+ };
+
+ return _db
+ }
+
+ function getProjects() {
+ let projects = []
+ root._database().transaction(function (tx) {
+ let results = tx.executeSql('SELECT * FROM projects')
+ for (let i = 0; i < results.rows.length; i++) {
+ let projectRow = results.rows.item(i)
+ let projectId = projectRow.project_id
+ let completedTasks = Math.max(countDoneTasksByProject(projectId).rows.length, 0)
+ let totalTasks = Math.max(countTaskByProject(projectId).rows.length, 0)
+ projects.push({
+ "projectName": projectRow.project_name,
+ "projectId": projectId,
+ "projectNote": projectRow.project_note ?? "",
+ "completedTasks": completedTasks,
+ "totalTasks": totalTasks
+ })
+ }
+ })
+ return projects
+ }
+
+ function newProject(projectName) {
+ let results
+ root._database().transaction(function (tx) {
+ results = tx.executeSql("INSERT INTO projects (project_name) VALUES(?)", [projectName])
+ })
+ return results
+ }
+
+ function updateProjectNote(projectId, projectNote) {
+ root._database().transaction(function (tx) {
+ tx.executeSql("UPDATE projects set project_note=? WHERE project_id=?", [projectNote, projectId])
+ })
+ }
+
+ function updateProjectName(projectId, projectName) {
+ root._database().transaction(function (tx) {
+ tx.executeSql("UPDATE projects set project_name=? WHERE project_id=?", [projectName, projectId])
+ })
+ }
+
+ function deleteProject(projectId) {
+ root._database().transaction(function (tx) {
+ deleteAllTasks(projectId)
+ tx.executeSql("DELETE FROM projects WHERE project_id = ?", [projectId])
+ })
+ }
+
+ function getTaskByProject(projectId) {
+ if (!projectId)
+ return
+
+ let tasks = []
+ root._database().transaction(function (tx) {
+ let results = tx.executeSql("SELECT * FROM tasks WHERE project_id = " + [projectId] + " ORDER BY done")
+ for (let i = 0; i < results.rows.length; i++) {
+ let row = results.rows.item(i)
+ tasks.push({
+ "taskId": row.task_id,
+ "taskName": row.task_name,
+ "done": row.done === 1 ? true : false
+ })
+ }
+ })
+ return tasks
+ }
+
+ function countTaskByProject(projectId) {
+ let results
+ root._database().transaction(function (tx) {
+ results = tx.executeSql('SELECT task_id FROM tasks WHERE project_id =' + [projectId])
+ })
+ return results
+ }
+
+ function countDoneTasksByProject(projectId) {
+ let results
+ root._database().transaction(function (tx) {
+ results = tx.executeSql("SELECT task_id FROM tasks WHERE project_id =" + [projectId] + " AND done = 1")
+ })
+ return results
+ }
+
+ function newTask(projectId, taskName) {
+ let results
+ root._database().transaction(function (tx) {
+ results = tx.executeSql("INSERT INTO tasks (task_name, done, project_id) VALUES(?, 0, ?)", [taskName, projectId])
+ })
+ return results
+ }
+
+ function updateTaskName(taskId, taskName) {
+ root._database().transaction(function (tx) {
+ tx.executeSql("UPDATE tasks set task_name=? WHERE task_id=?", [taskName, taskId])
+ })
+ }
+
+ function updateDoneState(taskId, doneState) {
+ root._database().transaction(function (tx) {
+ tx.executeSql("UPDATE tasks set done=? WHERE task_id=?", [doneState, taskId])
+ })
+ }
+
+ function deleteAllTasks(projectId) {
+ root._database().transaction(function (tx) {
+ tx.executeSql("DELETE FROM tasks WHERE project_id =" + projectId)
+ })
+ }
+
+ function deleteTask(taskId) {
+ root._database().transaction(function (tx) {
+ tx.executeSql("DELETE FROM tasks WHERE task_id = ?", [taskId])
+ })
+ }
+}
diff --git a/examples/quickcontrols2/ios/todolist/FontSizePage.qml b/examples/quickcontrols2/ios/todolist/FontSizePage.qml
new file mode 100644
index 0000000000..2b0e5b16db
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/FontSizePage.qml
@@ -0,0 +1,44 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Page {
+ Frame {
+ width: parent.width - 60
+ anchors.centerIn: parent
+ topPadding: 12
+ bottomPadding: 12
+
+ RowLayout {
+ anchors.fill: parent
+ spacing: 12
+
+ Label {
+ text: qsTr("A")
+ font.pointSize: 15
+ font.weight: 400
+ }
+
+ Slider {
+ snapMode: Slider.SnapAlways
+ stepSize: 1
+ from: 15
+ value: AppSettings.fontSize
+ to: 21
+
+ Layout.fillWidth: true
+
+ onMoved: AppSettings.fontSize = value
+ }
+
+ Label {
+ text: qsTr("A")
+ font.pointSize: 21
+ font.weight: 400
+ }
+ }
+ }
+}
diff --git a/examples/quickcontrols2/ios/todolist/HomePage.qml b/examples/quickcontrols2/ios/todolist/HomePage.qml
new file mode 100644
index 0000000000..4df6f90b7a
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/HomePage.qml
@@ -0,0 +1,168 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.LocalStorage
+import QtQuick.Controls.iOS
+
+Page {
+ id: root
+
+ header: Label {
+ text: qsTr("Projects")
+ font.pointSize: AppSettings.fontSize + 20
+ font.styleName: "Semibold"
+ leftPadding: 30
+ topPadding: 20
+ bottomPadding: 20
+ }
+
+ ListView {
+ id: projectListView
+ anchors.fill: parent
+ clip: true
+
+ model: ListModel {
+ id: projectsModel
+
+ Component.onCompleted: {
+ let projects = Database.getProjects()
+ for (let project of projects)
+ append(project)
+ }
+ }
+
+ delegate: SwipeDelegate {
+ id: projectDelegate
+ width: ListView.view.width
+ height: projectContent.implicitHeight
+
+ required property int index
+ required property int projectId
+ required property string projectName
+ required property int completedTasks
+ required property int totalTasks
+
+ swipe.right: Rectangle {
+ width: 50
+ height: parent.height
+ color: "red"
+ anchors.right: parent.right
+
+ SwipeDelegate.onClicked: {
+ Database.deleteProject(projectDelegate.projectId)
+ projectsModel.remove(projectDelegate.index, 1)
+ }
+
+ Image {
+ source: IOS.theme === IOS.Dark ? "images/close-white.png"
+ : "images/close.png"
+ width: 20
+ height: 20
+ anchors.centerIn: parent
+ }
+ }
+
+ Column {
+ id: projectContent
+ topPadding: 8
+ bottomPadding: 10
+ leftPadding: 30
+
+ Label {
+ text: completedTasks + " / " + totalTasks
+ font.pointSize: AppSettings.fontSize
+ }
+
+ Label {
+ id: project
+ text: projectName
+ font.pointSize: AppSettings.fontSize
+ font.styleName: "Semibold"
+ }
+ }
+
+ Connections {
+ target: projectDelegate
+ function onClicked() {
+ let project = projectsModel.get(index)
+ root.StackView.view.push("ProjectPage.qml", {
+ "projectsModel": projectsModel,
+ "projectId": project.projectId,
+ "projectName": project.projectName,
+ "projectIndex": index,
+ "projectNote": project.projectNote,
+ "completedTasks": project.completedTasks,
+ "totalTasks": project.totalTasks
+ })
+ }
+ }
+ }
+ }
+
+ Popup {
+ id: newProjectPopup
+ anchors.centerIn: parent
+ height: 200
+ modal: true
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ Label {
+ text: qsTr("Project Name")
+ font.pointSize: AppSettings.fontSize + 2.0
+ font.bold: true
+
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ TextField {
+ id: newProjectTextField
+ placeholderText: qsTr("Enter project name")
+ font.pointSize: AppSettings.fontSize
+
+ Layout.fillWidth: true
+ Layout.leftMargin: 10
+ Layout.rightMargin: 10
+ }
+
+ Button {
+ id: createProjectButton
+ text: qsTr("Create project")
+ flat: true
+ font.pointSize: AppSettings.fontSize
+ enabled: newProjectTextField.length > 0
+
+ Layout.alignment: Qt.AlignHCenter
+
+ onClicked: {
+ let results = Database.newProject(newProjectTextField.text)
+ projectsModel.append({
+ projectId: parseInt(results.insertId),
+ projectName: newProjectTextField.text,
+ totalTasks: 0,
+ completedTasks: 0,
+ projectNote: ""
+ })
+ newProjectTextField.text = ""
+ newProjectPopup.close()
+ }
+ }
+ }
+ }
+
+ footer: ToolBar {
+ ToolButton {
+ anchors.right: parent.right
+ anchors.rightMargin: 5
+ text: qsTr("New project")
+ font.pointSize: AppSettings.fontSize - 2
+ icon.source: "images/add-new.png"
+
+ onClicked: newProjectPopup.open()
+ }
+ }
+}
diff --git a/examples/quickcontrols2/ios/todolist/MaxTasksPage.qml b/examples/quickcontrols2/ios/todolist/MaxTasksPage.qml
new file mode 100644
index 0000000000..509a460357
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/MaxTasksPage.qml
@@ -0,0 +1,36 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Page {
+ ColumnLayout {
+ width: parent.width
+ anchors.verticalCenter: parent.verticalCenter
+
+ Label {
+ id: maxTasksText
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.Wrap
+ text: qsTr("Choose the maximum amount of tasks each project can have.")
+ font.pointSize: AppSettings.fontSize
+
+ Layout.fillWidth: true
+ }
+
+ SpinBox {
+ id: maxTasksSpinbox
+ editable: true
+ from: 5
+ value: AppSettings.maxTasks
+ to: 30
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: 10
+
+ onValueModified: AppSettings.maxTasks = maxTasksSpinbox.value
+ }
+ }
+}
diff --git a/examples/quickcontrols2/ios/todolist/NavBar.qml b/examples/quickcontrols2/ios/todolist/NavBar.qml
new file mode 100644
index 0000000000..4f57353dca
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/NavBar.qml
@@ -0,0 +1,44 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+
+ToolBar {
+ id: root
+ width: parent.width
+
+ required property StackView stackView
+
+ ToolButton {
+ enabled: root.stackView.depth >= 2
+ anchors.left: parent.left
+ anchors.leftMargin: 5
+ visible: root.stackView.depth >= 2
+ anchors.verticalCenter: parent.verticalCenter
+ display: AbstractButton.TextBesideIcon
+ text: root.stackView.depth > 2 ? qsTr("Back") : qsTr("Home")
+ font.pointSize: AppSettings.fontSize
+ icon.source: "images/back.png"
+ icon.height: 20
+ icon.width: 20
+
+ onClicked: root.stackView.pop()
+ }
+
+ ToolButton {
+ anchors.right: parent.right
+ anchors.rightMargin: 5
+ icon.source: "images/settings.png"
+ icon.height: 20
+ icon.width: 20
+ visible: {
+ // Force the binding to re-evaluate so that the title check is run each time the page changes.
+ root.stackView.currentItem
+ !root.stackView.find((item, index) => { return item.title === "settingsPage" })
+ }
+
+ onClicked: root.stackView.push("SettingsPage.qml")
+ }
+}
+
diff --git a/examples/quickcontrols2/ios/todolist/ProjectPage.qml b/examples/quickcontrols2/ios/todolist/ProjectPage.qml
new file mode 100644
index 0000000000..ca86ea521a
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/ProjectPage.qml
@@ -0,0 +1,239 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Controls.iOS
+
+Page {
+ id: root
+
+ required property int projectIndex
+ required property int projectId
+ required property string projectName
+ required property string projectNote
+
+ required property int completedTasks
+ required property int totalTasks
+
+ required property ListModel projectsModel
+
+ header: ColumnLayout {
+ id: titleRow
+ spacing: 4
+
+ TextField {
+ id: projectNameLabel
+ Layout.fillWidth: true
+ text: root.projectName
+ font.pointSize: AppSettings.fontSize + 10
+ font.styleName: "Bold"
+ padding: 0
+ background: null
+ wrapMode: TextField.Wrap
+
+ Layout.topMargin: 10
+ Layout.leftMargin: 20
+ Layout.rightMargin: 20
+
+ onEditingFinished: {
+ Database.updateProjectName(root.projectId, text)
+ projectsModel.setProperty(projectIndex, "projectName", projectNameLabel.text)
+ }
+ }
+
+ Label {
+ text: root.completedTasks + " / " + root.totalTasks
+ Layout.leftMargin: 20
+ }
+
+ ProgressBar {
+ id: progressBar
+ from: 0
+ value: root.completedTasks
+ to: root.totalTasks
+ Layout.leftMargin: 20
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ TextArea {
+ id: textArea
+ leftPadding: 15
+ rightPadding: 15
+ bottomPadding: 10
+ topPadding: 10
+ font.pointSize: AppSettings.fontSize
+ placeholderText: qsTr("Write a note...")
+ text: root.projectNote
+ wrapMode: TextArea.Wrap
+
+ Layout.fillWidth: true
+ Layout.preferredHeight: 80
+ Layout.topMargin: 20
+
+ onEditingFinished: {
+ Database.updateProjectNote(root.projectId, text)
+ projectsModel.setProperty(projectIndex, "projectNote", textArea.text)
+ }
+ }
+
+ ListView {
+ id: taskListView
+ model: taskModel
+ clip: true
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.topMargin: 20
+
+ ListModel {
+ id: taskModel
+
+ Component.onCompleted: {
+ let tasks = Database.getTaskByProject(projectId)
+ for (let task of tasks)
+ append(task)
+ }
+ }
+
+ delegate: CheckDelegate {
+ id: taskList
+ width: taskListView.width
+ height: visible ? 40 : 0
+ checked: done
+ visible: !done || (done && AppSettings.showDoneTasks)
+
+ required property bool done
+ required property int taskId
+ required property string taskName
+ required property int index
+
+ onClicked: {
+ Database.updateDoneState(taskId, checked ? 1 : 0)
+ taskModel.setProperty(index, "done", checked)
+ root.completedTasks = Math.max(Database.countDoneTasksByProject(root.projectId).rows.length, 0)
+ root.totalTasks = Math.max(Database.countTaskByProject(root.projectId).rows.length, 0)
+ root.projectsModel.setProperty(projectIndex, "completedTasks", root.completedTasks)
+ }
+
+ TextField {
+ id: taskNameLabel
+ text: taskList.taskName
+ anchors.left: deleteTaskButton.right
+ anchors.leftMargin: 10
+ padding: 0
+ font.pointSize: AppSettings.fontSize
+ anchors.verticalCenter: parent.verticalCenter
+ background: null
+
+ onEditingFinished: {
+ Database.updateTaskName(taskList.taskId, taskNameLabel.text)
+ taskModel.setProperty(index, "taskName", taskNameLabel.text)
+ }
+ }
+
+ Button {
+ id: deleteTaskButton
+ anchors.left: parent.left
+ width: 15
+ height: 15
+ flat: true
+ topPadding: 0
+ bottomPadding: 0
+ rightPadding: 0
+ leftPadding: 0
+ anchors.leftMargin: 10
+ anchors.verticalCenter: parent.verticalCenter
+ icon.source: "images/close.png"
+ icon.color: IOS.theme === IOS.Dark ? "white" : "black"
+
+ onClicked: {
+ Database.deleteTask(taskList.taskId)
+ taskModel.remove(index, 1)
+ root.totalTasks--
+ if (taskList.done)
+ root.completedTasks--
+ }
+ }
+ }
+ }
+
+ Popup {
+ id: addTaskPopup
+ parent: root
+ anchors.centerIn: parent
+ height: 200
+ modal: true
+ focus: true
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ Label {
+ text: qsTr("Add New Task")
+ font.pointSize: AppSettings.fontSize + 2.0
+ font.bold: true
+
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ TextField {
+ id: newTaskNameTextField
+ placeholderText: qsTr("Enter task name")
+ font.pointSize: AppSettings.fontSize
+
+ Layout.fillWidth: true
+ Layout.leftMargin: 10
+ Layout.rightMargin: 10
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ Button {
+ text: qsTr("Add task")
+ flat: true
+ font.pointSize: AppSettings.fontSize
+ enabled: newTaskNameTextField.length > 0
+
+ Layout.alignment: Qt.AlignHCenter
+
+ onClicked: {
+ const result = Database.newTask(root.projectId, newTaskNameTextField.text)
+ taskModel.append({
+ "taskId": parseInt(result.insertId),
+ "taskName": newTaskNameTextField.text, "done": false
+ })
+ root.projectsModel.setProperty(projectIndex, "totalTasks", taskListView.count)
+ newTaskNameTextField.text = ""
+ root.totalTasks++
+ addTaskPopup.close()
+ }
+ }
+ }
+ }
+
+ Label {
+ text: qsTr("Task limit reached.")
+ Layout.alignment: Qt.AlignHCenter
+ Layout.bottomMargin: 20
+ font.pointSize: AppSettings.fontSize
+ visible: taskListView.count >= AppSettings.maxTasks
+ }
+ }
+
+ footer: ToolBar {
+ ToolButton {
+ anchors.right: parent.right
+ anchors.rightMargin: 5
+ text: qsTr("New task")
+ font.pointSize: AppSettings.fontSize - 2
+ icon.source: "images/add-new.png"
+ enabled: taskListView.count < AppSettings.maxTasks
+
+ onClicked: addTaskPopup.open()
+ }
+ }
+}
diff --git a/examples/quickcontrols2/ios/todolist/SettingsPage.qml b/examples/quickcontrols2/ios/todolist/SettingsPage.qml
new file mode 100644
index 0000000000..85e8ed2125
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/SettingsPage.qml
@@ -0,0 +1,68 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Controls.iOS
+
+Page {
+ title: "settingsPage"
+
+ property bool showDoneTasks: true
+
+ header: Label {
+ text: qsTr("Settings")
+ font.pointSize: AppSettings.fontSize + 20
+ font.styleName: "Semibold"
+ leftPadding: 20
+ topPadding: 20
+ }
+
+ ListView {
+ anchors.fill: parent
+ topMargin: 20
+ model: listModel
+ clip: true
+
+ ListModel {
+ id: listModel
+ ListElement {
+ setting: qsTr("Font size")
+ page: "FontSize"
+ }
+
+ ListElement {
+ setting: qsTr("Maximum number of tasks per project")
+ page: "MaxTasks"
+ }
+
+ ListElement {
+ setting: qsTr("Show completed tasks")
+ page: "ToggleCompletedTasks"
+ }
+ }
+
+ delegate: ItemDelegate {
+ width: parent.width
+ text: setting
+ font.pointSize: AppSettings.fontSize
+
+ onClicked: stackView.push(page + "Page.qml")
+
+ required property string setting
+ required property string page
+
+ Image {
+ source: IOS.theme === IOS.Dark ? "images/back-white.png"
+ : "images/back.png"
+ width: 20
+ height: 20
+ anchors.right: parent.right
+ anchors.rightMargin: 10
+ fillMode: Image.PreserveAspectFit
+ anchors.verticalCenter: parent.verticalCenter
+ mirror: true
+ }
+ }
+ }
+}
diff --git a/examples/quickcontrols2/ios/todolist/ToggleCompletedTasksPage.qml b/examples/quickcontrols2/ios/todolist/ToggleCompletedTasksPage.qml
new file mode 100644
index 0000000000..df2145866a
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/ToggleCompletedTasksPage.qml
@@ -0,0 +1,34 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Page {
+ ColumnLayout {
+ width: parent.width
+ anchors.verticalCenter: parent.verticalCenter
+
+ Label {
+ id: toggleText
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.Wrap
+ text: toggleTasksSwitch.checked ? qsTr("Completed tasks will be shown.")
+ : qsTr("Completed tasks will be hidden.")
+ font.pointSize: AppSettings.fontSize
+
+ Layout.fillWidth: true
+ }
+
+ Switch {
+ id: toggleTasksSwitch
+ checked: AppSettings.showDoneTasks
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: 10
+
+ onClicked: AppSettings.showDoneTasks = checked
+ }
+ }
+}
diff --git a/examples/quickcontrols2/ios/todolist/doc/images/qtquickcontrols2-todolist.png b/examples/quickcontrols2/ios/todolist/doc/images/qtquickcontrols2-todolist.png
new file mode 100644
index 0000000000..5be7190a43
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/doc/images/qtquickcontrols2-todolist.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/doc/src/qtquickcontrols2-todolist.qdoc b/examples/quickcontrols2/ios/todolist/doc/src/qtquickcontrols2-todolist.qdoc
new file mode 100644
index 0000000000..12e2d4fca5
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/doc/src/qtquickcontrols2-todolist.qdoc
@@ -0,0 +1,22 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example ios/todolist
+ \keyword Qt Quick Controls - To Do List
+ \title Qt Quick Controls - To Do List
+ \keyword Qt Quick Controls 2 - To Do List
+ \ingroup qtquickcontrols2-examples
+ \brief To do list application for iOS.
+
+ This example demonstrates how to create a simple to do list application for
+ iOS using the \l {iOS Style}.
+
+ \image qtquickcontrols2-todolist.png
+
+ The example also shows how an in-memory SQL database can be created and
+ used purely through QML, without needing C++, through the use of
+ \l {Qt Quick Local Storage QML Types}{LocalStorage}.
+
+ \include examples-run.qdocinc
+*/
diff --git a/examples/quickcontrols2/ios/todolist/images/add-new.png b/examples/quickcontrols2/ios/todolist/images/add-new.png
new file mode 100644
index 0000000000..2e60e48a2d
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/add-new.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/add-new@2x.png b/examples/quickcontrols2/ios/todolist/images/add-new@2x.png
new file mode 100644
index 0000000000..edb8b62847
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/add-new@2x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/add-new@3x.png b/examples/quickcontrols2/ios/todolist/images/add-new@3x.png
new file mode 100644
index 0000000000..b5b9b6a5d6
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/add-new@3x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/back-white.png b/examples/quickcontrols2/ios/todolist/images/back-white.png
new file mode 100644
index 0000000000..503bdf4d48
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/back-white.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/back-white@2x.png b/examples/quickcontrols2/ios/todolist/images/back-white@2x.png
new file mode 100644
index 0000000000..e201ec6d5b
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/back-white@2x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/back-white@3x.png b/examples/quickcontrols2/ios/todolist/images/back-white@3x.png
new file mode 100644
index 0000000000..eb8b7ffc68
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/back-white@3x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/back.png b/examples/quickcontrols2/ios/todolist/images/back.png
new file mode 100644
index 0000000000..94580efd7f
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/back.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/back@2x.png b/examples/quickcontrols2/ios/todolist/images/back@2x.png
new file mode 100644
index 0000000000..b5cf424e91
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/back@2x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/back@3x.png b/examples/quickcontrols2/ios/todolist/images/back@3x.png
new file mode 100644
index 0000000000..50d5102f4e
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/back@3x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/close-white.png b/examples/quickcontrols2/ios/todolist/images/close-white.png
new file mode 100644
index 0000000000..72163067a6
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/close-white.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/close-white@2x.png b/examples/quickcontrols2/ios/todolist/images/close-white@2x.png
new file mode 100644
index 0000000000..54828e84d1
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/close-white@2x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/close-white@3x.png b/examples/quickcontrols2/ios/todolist/images/close-white@3x.png
new file mode 100644
index 0000000000..dd67cf1b69
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/close-white@3x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/close.png b/examples/quickcontrols2/ios/todolist/images/close.png
new file mode 100644
index 0000000000..eba32b637d
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/close.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/close@2x.png b/examples/quickcontrols2/ios/todolist/images/close@2x.png
new file mode 100644
index 0000000000..72cf76317a
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/close@2x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/close@3x.png b/examples/quickcontrols2/ios/todolist/images/close@3x.png
new file mode 100644
index 0000000000..6cb2ea58d9
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/close@3x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/plus-math.png b/examples/quickcontrols2/ios/todolist/images/plus-math.png
new file mode 100644
index 0000000000..b0d182d23e
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/plus-math.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/plus-math@2x.png b/examples/quickcontrols2/ios/todolist/images/plus-math@2x.png
new file mode 100644
index 0000000000..112a132084
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/plus-math@2x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/plus-math@3x.png b/examples/quickcontrols2/ios/todolist/images/plus-math@3x.png
new file mode 100644
index 0000000000..ca513d200b
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/plus-math@3x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/settings.png b/examples/quickcontrols2/ios/todolist/images/settings.png
new file mode 100644
index 0000000000..600981fb35
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/settings.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/settings@2x.png b/examples/quickcontrols2/ios/todolist/images/settings@2x.png
new file mode 100644
index 0000000000..80541aa028
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/settings@2x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/images/settings@3x.png b/examples/quickcontrols2/ios/todolist/images/settings@3x.png
new file mode 100644
index 0000000000..48c2b2a8c0
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/images/settings@3x.png
Binary files differ
diff --git a/examples/quickcontrols2/ios/todolist/main.qml b/examples/quickcontrols2/ios/todolist/main.qml
new file mode 100644
index 0000000000..a0067643f1
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/main.qml
@@ -0,0 +1,32 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Window
+import QtQuick.Controls
+
+ApplicationWindow {
+ width: 390
+ height: 844
+ visible: true
+ title: "To Do List"
+
+ header: NavBar {
+ stackView: stackView
+ }
+
+ Flickable {
+ width: parent.width
+ height: parent.height
+ flickableDirection: Flickable.VerticalFlick
+ boundsBehavior: Flickable.StopAtBounds
+
+ ScrollIndicator.vertical: ScrollIndicator {}
+
+ StackView {
+ id: stackView
+ anchors.fill: parent
+ initialItem: HomePage {}
+ }
+ }
+}
diff --git a/examples/quickcontrols2/ios/todolist/qmldir b/examples/quickcontrols2/ios/todolist/qmldir
new file mode 100644
index 0000000000..e0c443110e
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/qmldir
@@ -0,0 +1,2 @@
+singleton Database 1.0 Database.qml
+singleton AppSettings 1.0 AppSettings.qml
diff --git a/examples/quickcontrols2/ios/todolist/src/main.cpp b/examples/quickcontrols2/ios/todolist/src/main.cpp
new file mode 100644
index 0000000000..1db4f7ae37
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/src/main.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QtQml/qqmlextensionplugin.h>
+#include <QtQuickControls2/qquickstyle.h>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickStyle::setStyle("iOS");
+
+ QQmlApplicationEngine engine;
+ const QUrl url(u"qrc:/main.qml"_qs);
+ QObject::connect(
+ &engine, &QQmlApplicationEngine::objectCreated, &app,
+ [url](QObject *obj, const QUrl &objUrl) {
+ if (!obj && url == objUrl)
+ QCoreApplication::exit(-1);
+ },
+ Qt::QueuedConnection);
+
+ engine.addImportPath(":/");
+
+ engine.load(url);
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ return app.exec();
+}
diff --git a/examples/quickcontrols2/ios/todolist/todolist.pro b/examples/quickcontrols2/ios/todolist/todolist.pro
new file mode 100644
index 0000000000..d77d718a62
--- /dev/null
+++ b/examples/quickcontrols2/ios/todolist/todolist.pro
@@ -0,0 +1,42 @@
+TEMPLATE = app
+TARGET = todolist
+QT += quick quickcontrols2
+
+SOURCES += src/main.cpp
+
+RESOURCES += \
+ images/back.png \
+ images/back@2x.png \
+ images/back@3x.png \
+ images/back-white.png \
+ images/back-white@2x.png \
+ images/back-white@3x.png \
+ images/close.png \
+ images/close@2x.png \
+ images/close@3x.png \
+ images/close-white.png \
+ images/close-white@2x.png \
+ images/close-white@3x.png \
+ images/plus-math.png \
+ images/plus-math@2x.png \
+ images/plus-math@3x.png \
+ images/settings.png \
+ images/settings@2x.png \
+ images/settings@3x.png \
+ images/add-new.png \
+ images/add-new@2x.png \
+ images/add-new@3x.png \
+ main.qml \
+ AppSettings.qml \
+ Database.qml \
+ FontSizePage.qml \
+ HomePage.qml \
+ MaxTasksPage.qml \
+ NavBar.qml \
+ ProjectPage.qml \
+ SettingsPage.qml \
+ ToggleCompletedTasksPage.qml \
+ qmldir
+
+target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols2/ios/todolist
+INSTALLS += target
diff --git a/tests/auto/quick/examples/tst_examples.cpp b/tests/auto/quick/examples/tst_examples.cpp
index c56cad7d90..fd58dab328 100644
--- a/tests/auto/quick/examples/tst_examples.cpp
+++ b/tests/auto/quick/examples/tst_examples.cpp
@@ -62,6 +62,7 @@ tst_examples::tst_examples()
excludedDirs << "snippets/qml/imports";
excludedDirs << "examples/quickcontrols2/imagine";
excludedDirs << "examples/quickcontrols2/texteditor";
+ excludedDirs << "examples/quickcontrols2/ios/todolist"; // Must be run via executable.
excludedFiles << "snippets/qml/image-ext.qml";
excludedFiles << "examples/quick/shapes/content/main.qml"; // relies on resources
excludedFiles << "examples/quick/shapes/content/interactive.qml"; // relies on resources