This commit is contained in:
2025-09-28 17:05:51 +08:00
parent 8eb80ab66d
commit d97fa3e0fe
398 changed files with 18737 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.16)
project(2-XmlHttpRequest VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 6.4 REQUIRED COMPONENTS Quick)
qt_standard_project_setup()
qt_add_executable(app2-XmlHttpRequest
main.cpp
)
qt_add_qml_module(app2-XmlHttpRequest
URI 2-XmlHttpRequest
VERSION 1.0
QML_FILES Main.qml
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(app2-XmlHttpRequest PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.app2-XmlHttpRequest
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(app2-XmlHttpRequest
PRIVATE Qt6::Quick
)
include(GNUInstallDirs)
install(TARGETS app2-XmlHttpRequest
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@@ -0,0 +1,99 @@
import QtQuick
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("XmlHttpRequest")
//Process the data right away
/*
function downloadData(url){
//Create the object
var xhr = new XMLHttpRequest()
//Apply initial settings to the object
xhr.onreadystatechange = function(){
//HEADERS_RECEIVED
//DONE
if(xhr.readyState === XMLHttpRequest.HEADERS_RECEVED){
console.log("Headers received")
}else if(xhr.readyState === XMLHttpRequest.DONE){
if(xhr.status == 200){
//console.log("Got the data from the server it is: " + xhr.responseText.toString())
textAreaId.text = xhr.responseText.toString()
}else{
console.log("Something went wrong")
}
}
}
xhr.open("GET",url)
xhr.send()
}
*/
//Go through a callback
function downloadData(url,callback){
//Create the object
var xhr = new XMLHttpRequest()
//Apply initial settings to the object
xhr.onreadystatechange = function(){
//HEADERS_RECEIVED
//DONE
if(xhr.readyState === XMLHttpRequest.HEADERS_RECEVED){
console.log("Headers received")
}else if(xhr.readyState === XMLHttpRequest.DONE){
if(xhr.status == 200){
//console.log("Got the data from the server it is: " + xhr.responseText.toString())
//textAreaId.text = xhr.responseText.toString()
callback(xhr.responseText.toString())
}else{
console.log("Something went wrong")
}
}
}
xhr.open("GET",url)
xhr.send()
}
TextArea{
id: textAreaId
anchors.fill: parent
textFormat: TextArea.RichText
text: "Click to download html data"
}
MouseArea{
anchors.fill: parent
onClicked: function(){
/*
Urls to try :
. https://www.qt.io/
. https://github.com/
. https://jsonplaceholder.typicode.com/
*/
//Download data right away
/*
console.log("Clicked")
downloadData("https://www.qt.io")
*/
//Download the data through a callback
downloadData("https://jsonplaceholder.typicode.com/",function(response){
if(response){
textAreaId.text = response
}else{
console.log("Something went wrong")
}
})
}
}
}

View File

@@ -0,0 +1,19 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/2-XmlHttpRequest/Main.qml"_qs);
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.load(url);
return app.exec();
}

View File

@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.16)
project(3-RestAPI VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 6.4 REQUIRED COMPONENTS Quick)
qt_standard_project_setup()
qt_add_executable(app3-RestAPI
main.cpp
)
qt_add_qml_module(app3-RestAPI
URI 3-RestAPI
VERSION 1.0
QML_FILES Main.qml
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(app3-RestAPI PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.app3-RestAPI
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(app3-RestAPI
PRIVATE Qt6::Quick
)
include(GNUInstallDirs)
install(TARGETS app3-RestAPI
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@@ -0,0 +1,106 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Window {
width: 640
height: 480
visible: true
title: qsTr("Rest API")
function fetchData(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
print('HEADERS_RECEIVED');
} else if (xhr.readyState === XMLHttpRequest.DONE) {
print('DONE');
if (xhr.status == 200) {
console.log("resource found" + xhr.responseText.toString());
callback(xhr.responseText.toString());
} else {
callback(null);
}
}
};
xhr.open("GET", url);
xhr.send();
}
ColumnLayout{
anchors.fill: parent
spacing: 0
ListModel{
id: listModelId
}
ListView{
id: listViewId
model: listModelId
delegate: delegateId
Layout.fillWidth: true
Layout.fillHeight: true
}
Button{
id: buttonId
Layout.fillWidth: true
text: "Fetch"
onClicked: function(){
listModelId.clear()
//Call the function to download data
fetchData("https://jsonplaceholder.typicode.com/users",function(response){
if(response){
console.log("Response is good")
//Parse the data
var object = JSON.parse(response)
//Loop over the JSON array
object.forEach(function(userdata){
listModelId.append({
"userdata": userdata.name
})
})
}else{
console.log("Something went wrong")
}
})
}
}
Component {
id: delegateId
Rectangle {
id: rectangleId
width: parent.width
height: textId.implicitHeight + 30
color: "beige"
border.color: "yellowgreen"
radius: 5
Text {
id: textId
width: parent.width
height: parent.height
anchors.centerIn: parent
text: userdata //Or modelData
//text: modelData
font.pointSize: 13
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/3-RestAPI/Main.qml"_qs);
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.load(url);
return app.exec();
}

View File

@@ -0,0 +1,57 @@
# Qt QML Language Server configuration
qmlls.ini
# Build directories
build/
Desktop_Qt_6_9_0_MinGW_64_bit-Debug/
# Qt Creator user settings
CMakeLists.txt.user
# Compiled Object files
*.o
*.obj
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Compiled Static libraries
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Qt-specific
*.qm
*.prl
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
Makefile*
*build-*
# QML cache and compiled files
*.qmlc
*.jsc
# Qt temporary files
*~
# macOS
.DS_Store
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini

View File

@@ -0,0 +1,5 @@
[General]
buildDir="D:/Sandbox/Qt6QMLBeginnersCode/14-Networking/4-TodolistNetworking/build"
no-cmake-calls=false
docDir=C:/Qt/Docs/Qt-6.9.0
importPaths="C:/Qt/6.9.0/mingw_64/qml"

View File

@@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.16)
project(TodoList VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Quick QuickControls2)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(TodoList
src/main.cpp
)
qt_add_qml_module(TodoList
URI TodoList
VERSION 1.0
QML_FILES
# Main View
src/views/MainView.qml
# Components
src/components/AppHeader.qml
src/components/AddTaskBar.qml
src/components/TaskItem.qml
src/components/TaskStats.qml
# Models
src/models/TaskListModel.qml
# Utils
src/utils/TaskStorage.qml
QML_FILES src/utils/RestStorage.qml
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(TodoList PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.app01_todolist_starter
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(TodoList
PRIVATE Qt6::Quick Qt6::QuickControls2
)
include(GNUInstallDirs)
install(TARGETS TodoList
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@@ -0,0 +1,110 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
height: 60
signal taskAdded(string taskText)
property color backgroundColor: "#ffffff"
property color textColor: "#333333"
property color primaryColor: "#007aff"
property bool darkMode: false
Rectangle {
id: background
anchors.fill: parent
color: "transparent"
Rectangle {
anchors.fill: parent
color: root.backgroundColor
radius: 12
border.color: root.darkMode ? "#404040" : "#e0e0e0"
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 12
// Add icon
Rectangle {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
color: root.primaryColor
radius: 18
Text {
anchors.centerIn: parent
text: "+"
color: "#ffffff"
font.pixelSize: 20
font.weight: Font.Bold
}
}
// Text input
TextField {
id: taskInput
Layout.fillWidth: true
Layout.preferredHeight: 36
placeholderText: qsTr("Add a new task...")
placeholderTextColor: root.darkMode ? "#888888" : "#999999"
color: root.textColor
font.pixelSize: 16
selectByMouse: true
background: Rectangle {
color: "transparent"
border.color: "transparent"
}
Keys.onReturnPressed: root.addTask()
Keys.onEnterPressed: root.addTask()
}
// Add button
Button {
id: addButton
Layout.preferredWidth: 80
Layout.preferredHeight: 36
text: qsTr("Add")
enabled: taskInput.text.trim().length > 0
background: Rectangle {
color: addButton.enabled ?
(addButton.pressed ? Qt.darker(root.primaryColor, 1.2) : root.primaryColor) :
(root.darkMode ? "#404040" : "#e0e0e0")
radius: 8
}
contentItem: Text {
text: addButton.text
color: addButton.enabled ? "#ffffff" : (root.darkMode ? "#888888" : "#cccccc")
font.pixelSize: 14
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: root.addTask()
}
}
}
}
function addTask() {
if (taskInput.text.trim().length > 0) {
root.taskAdded(taskInput.text.trim())
taskInput.text = ""
taskInput.focus = false
}
}
}

View File

@@ -0,0 +1,86 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
height: 80
signal toggleDarkMode()
// Properties that will be bound from parent
property color textColor: "#333333"
property color textSecondary: "#999999"
property bool darkMode: false
Rectangle {
anchors.fill: parent
color: "transparent"
RowLayout{
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
spacing: 12
// App icon
Text {
text: "📝"
font.pixelSize: 32
}
// App title and subtitle
Column {
Layout.fillWidth: true
spacing: 2
Text {
text: "My Tasks"
font.pixelSize: 28
font.weight: Font.Bold
color: root.textColor
}
Text {
text: qsTr("Stay organized, stay productive")
font.pixelSize: 12
color: root.textSecondary
opacity: 0.8
}
}
// Dark mode toggle
Button {
id: themeToggle
Layout.preferredWidth: 50
Layout.preferredHeight: 50
background: Rectangle {
color: themeToggle.pressed ?
(root.darkMode ? "#404040" : "#e0e0e0") :
(root.darkMode ? "#2d2d2d" : "#f5f5f5")
radius: 25
border.color: root.darkMode ? "#555555" : "#d0d0d0"
border.width: 1
Behavior on color {
ColorAnimation { duration: 150 }
}
}
contentItem: Text{
text: root.darkMode ? "☀️" : "🌙"
font.pixelSize: 20
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
//Emit the signal when the button is clicked
onClicked: root.toggleDarkMode()
}
}
}
}

View File

@@ -0,0 +1,209 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
height: 56
required property string taskTitle
required property bool taskDone
signal toggleDone()
signal deleteTask()
property color backgroundColor: "#ffffff"
property color textColor: "#333333"
property color completedColor: "#999999"
property color primaryColor: "#007aff"
property bool darkMode: false
// Constants for this component
readonly property int itemHeight: 56
readonly property int margins: 4
readonly property int innerMargins: 12
readonly property int checkboxSize: 24
readonly property int deleteButtonSize: 32
Rectangle {
id: taskItemBackground
anchors.fill: parent
color: "transparent"
Rectangle {
id: itemBackground
anchors.fill: parent
anchors.margins: root.margins
color: root.backgroundColor
radius: 8
border.color: root.taskDone ? "transparent" : (root.darkMode ? "#404040" : "#f0f0f0")
border.width: 1
opacity: root.taskDone ? 0.7 : 1.0
scale: itemBackground.hovered ? 1.02 : 1.0
// Smooth transitions for background properties
Behavior on opacity {
NumberAnimation { duration: 300 }
}
Behavior on border.color {
ColorAnimation { duration: 300 }
}
Behavior on scale {
NumberAnimation { duration: 150; easing.type: Easing.OutQuad }
}
RowLayout {
anchors.fill: parent
anchors.margins: root.innerMargins
spacing: 12
// Checkbox
Rectangle {
id: checkbox
Layout.preferredWidth: root.checkboxSize
Layout.preferredHeight: root.checkboxSize
color: root.taskDone ? root.primaryColor : "transparent"
border.color: root.taskDone ? root.primaryColor : (root.darkMode ? "#666666" : "#cccccc")
border.width: 2
radius: 4
// Smooth transitions for checkbox state changes
Behavior on color {
ColorAnimation { duration: 200 }
}
Behavior on border.color {
ColorAnimation { duration: 200 }
}
Behavior on scale {
NumberAnimation { duration: 100; easing.type: Easing.OutBack }
}
// Checkmark
Text {
anchors.centerIn: parent
text: "✓"
color: "#ffffff"
font.pixelSize: 16
font.weight: Font.Bold
opacity: root.taskDone ? 1.0 : 0.0
scale: root.taskDone ? 1.0 : 0.3
Behavior on opacity {
NumberAnimation { duration: 200 }
}
Behavior on scale {
NumberAnimation { duration: 200; easing.type: Easing.OutBack }
}
}
MouseArea {
anchors.fill: parent
onClicked: {
// Add click animation
checkbox.scale = 0.9
scaleResetTimer.restart()
//Toggle the task
root.toggleDone()
}
cursorShape: Qt.PointingHandCursor
}
// Timer to reset scale after click
Timer {
id: scaleResetTimer
interval: 100
onTriggered: checkbox.scale = 1.0
}
}
// Task text
Text {
id: taskText
Layout.fillWidth: true
text: root.taskTitle
color: root.taskDone ? root.completedColor : root.textColor
font.pixelSize: 16
font.strikeout: root.taskDone
wrapMode: Text.WordWrap
// Smooth color transition when task state changes
Behavior on color {
ColorAnimation { duration: 300 }
}
// Subtle scale animation on completion
scale: root.taskDone ? 0.95 : 1.0
Behavior on scale {
NumberAnimation { duration: 200; easing.type: Easing.OutQuad }
}
MouseArea {
anchors.fill: parent
onClicked: root.toggleDone()
cursorShape: Qt.PointingHandCursor
}
}
// Delete button
Button {
id: deleteButton
Layout.preferredWidth: root.deleteButtonSize
Layout.preferredHeight: root.deleteButtonSize
opacity: itemBackground.hovered ? 1.0 : 0.3
scale: deleteButton.pressed ? 0.9 : 1.0
// Smooth transitions
Behavior on opacity {
NumberAnimation { duration: 200 }
}
Behavior on scale {
NumberAnimation { duration: 100; easing.type: Easing.OutQuad }
}
background: Rectangle {
color: deleteButton.pressed ? "#ff3b30" : "transparent"
radius: 16
Behavior on color {
ColorAnimation { duration: 150 }
}
}
contentItem: Text {
text: "🗑"
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
rotation: deleteButton.pressed ? 15 : 0
Behavior on rotation {
NumberAnimation { duration: 100 }
}
}
onClicked: {
root.deleteTask()
}
}
}
// Hover effect
property bool hovered: false
HoverHandler {
onHoveredChanged: itemBackground.hovered = hovered
}
}
}
}

View File

@@ -0,0 +1,95 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
height: visible ? 60 : 0
visible: totalTasks > 0
property int totalTasks: 0
property int completedTasks: 0
property int remainingTasks: 0
property bool hasCompleted: false
property color backgroundColor: "#ffffff"
property color textColor: "#333333"
property color secondaryTextColor: "#666666"
property color primaryColor: "#007aff"
property color dangerColor: "#ff3b30"
property bool darkMode: false
signal clearCompleted()
Rectangle {
anchors.fill: parent
color: "transparent"
Rectangle {
anchors.fill: parent
color: root.backgroundColor
radius: 12
border.color: root.darkMode ? "#404040" : "#e0e0e0"
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 16
// Statistics text
Column {
Layout.fillWidth: true
spacing: 2
Text {
text: root.totalTasks === 1 ?
qsTr("%1 task").arg(root.totalTasks) :
qsTr("%1 tasks").arg(root.totalTasks)
color: root.textColor
font.pixelSize: 16
font.weight: Font.Medium
}
Text {
text: root.completedTasks > 0 ?
qsTr("%1 completed, %2 remaining").arg(root.completedTasks).arg(root.remainingTasks) :
qsTr("No tasks completed yet")
color: root.secondaryTextColor
font.pixelSize: 14
visible: root.totalTasks > 0
}
}
// Clear completed button
Button {
id: clearButton
Layout.preferredHeight: 36
text: qsTr("Clear Completed")
visible: root.hasCompleted
enabled: root.hasCompleted
background: Rectangle {
color: clearButton.pressed ?
Qt.darker(root.dangerColor, 1.2) :
(clearButton.hovered ? root.dangerColor : "transparent")
border.color: root.dangerColor
border.width: 1
radius: 8
}
contentItem: Text {
text: clearButton.text
color: clearButton.hovered ? "#ffffff" : root.dangerColor
font.pixelSize: 14
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: root.clearCompleted()
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
#include <QSettings>
#include <QDebug>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
// Set application identifiers for Settings
app.setOrganizationName("TodoApp");
app.setOrganizationDomain("todoapp.local");
app.setApplicationName("TodoList");
// Set Qt Quick Controls style to Basic to enable customization
QQuickStyle::setStyle("Basic");
// Print the settings location
QSettings settings;
qDebug() << "Settings location:" << settings.fileName();
QQmlApplicationEngine engine;
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("TodoList", "MainView");
return app.exec();
}

View File

@@ -0,0 +1,198 @@
import QtQuick
Item {
id: root
// Signal emitted when storage is initialized and ready
signal storageInitialized()
// Signal emitted when settings are loaded
signal settingsLoaded(bool darkMode)
// The actual ListModel for tasks
property alias model: taskListModel
property alias count: taskListModel.count
ListModel {
id: taskListModel
}
RestStorage{
id: storage
onTasksLoaded: function(tasks) {
root.loadTasksFromArray(tasks)
}
onTaskCreated: function(task) {
// Add the newly created task to the model - filter out unsupported fields
taskListModel.append({
"id": task.id,
"title": task.title,
"completed": task.completed || false
// Note: We don't include createdAt, updatedAt as they're not needed in the UI
})
}
onTaskUpdated: function(task) {
// Update the task in the model - only update the fields we need
for (let i = 0; i < taskListModel.count; i++) {
if (taskListModel.get(i).id === task.id) {
// Only set the properties that exist in our model
if (task.title !== undefined) {
taskListModel.setProperty(i, "title", task.title)
}
if (task.completed !== undefined) {
taskListModel.setProperty(i, "completed", task.completed)
}
break
}
}
}
onTaskDeleted: function(taskId) {
// Remove the task from the model
for (let i = 0; i < taskListModel.count; i++) {
if (taskListModel.get(i).id === taskId) {
taskListModel.remove(i)
break
}
}
}
onSettingsLoaded: function(settings) {
console.log("Settings loaded:", JSON.stringify(settings))
// Emit signal with dark mode value
root.settingsLoaded(settings.darkMode || false)
}
onSettingsUpdated: function(settings) {
console.log("Settings updated:", JSON.stringify(settings))
// Settings have been successfully updated
}
Component.onCompleted: {
//Emit signal that storage is read
root.storageInitialized()
//Load existing tasks from REST API
storage.loadTasks()
}
}
// Load tasks from an array (used by storage)
function loadTasksFromArray(taskArray) {
taskListModel.clear()
for (let i = 0; i < taskArray.length; i++) {
let task = taskArray[i]
taskListModel.append({
"id": task.id || generateId(),
"title": task.title || "",
"completed": task.completed || false
})
}
}
// Add a new task
function addTask(title) {
if (title && title.trim().length > 0) {
// Use REST API to create task
storage.createTask(title.trim())
// The task will be added to the model via onTaskCreated signal
}
}
// Toggle task completion status
function toggleTask(index) {
if (index >= 0 && index < taskListModel.count) {
let task = taskListModel.get(index)
let newCompleted = !task.completed
// Update via REST API
storage.updateTask(task.id, { completed: newCompleted })
// The model will be updated via onTaskUpdated signal
}
}
// Delete a task
function deleteTask(index) {
if (index >= 0 && index < taskListModel.count) {
let task = taskListModel.get(index)
// Delete via REST API
storage.deleteTask(task.id)
// The task will be removed from the model via onTaskDeleted signal
}
}
// Clear all completed tasks
function clearCompletedTasks() {
// Get all completed task IDs and delete them via REST API
let completedTasks = []
let i
for (i = 0; i < taskListModel.count; i++) {
if (taskListModel.get(i).completed) {
completedTasks.push(taskListModel.get(i).id)
}
}
// Delete each completed task via REST API
for (i = 0; i < completedTasks.length; i++) {
storage.deleteTask(completedTasks[i])
}
// Tasks will be removed from the model via onTaskDeleted signals
}
// Get statistics
function getStats() {
let total = taskListModel.count
let completed = 0
for (let i = 0; i < taskListModel.count; i++) {
if (taskListModel.get(i).completed) {
completed++
}
}
return {
"total": total,
"completed": completed,
"remaining": total - completed
}
}
// Check if there are any completed tasks
function hasCompletedTasks() {
for (let i = 0; i < taskListModel.count; i++) {
if (taskListModel.get(i).completed) {
return true
}
}
return false
}
// Get item at index
function get(index) {
return taskListModel.get(index)
}
// Access to storage for dark mode preference
function saveDarkMode(darkMode) {
storage.updateSettings({ darkMode: darkMode })
}
function loadDarkMode() {
storage.loadSettings()
}
}

View File

@@ -0,0 +1,192 @@
import QtQuick
Item{
id: root
// Base URL for the REST API
readonly property string baseUrl: "http://localhost:3000"
// Signals
signal tasksLoaded(var tasks)
signal taskCreated(var task)
signal taskUpdated(var task)
signal taskDeleted(string taskId)
signal settingsLoaded(var settings)
signal settingsUpdated(var settings)
signal error(string message)
Component.onCompleted: {
// Test connection on startup
checkConnection()
}
// Check if API is available
function checkConnection() {
performRequest("GET", "/health", null, function(response) {
console.log("API connection established")
}, function(error) {
console.error("API connection failed:", error)
root.error("Cannot connect to API server at " + baseUrl)
})
}
// Load all tasks from the API
function loadTasks() {
performRequest("GET", "/api/tasks", null, function(response) {
try {
let data = JSON.parse(response)
let tasks = data.data || []
console.log("Tasks loaded from API:", tasks.length, "tasks")
root.tasksLoaded(tasks)
} catch (e) {
console.error("Error parsing tasks response:", e)
root.error("Failed to parse tasks data")
}
}, function(error) {
console.error("Error loading tasks:", error)
root.error("Failed to load tasks from server")
})
}
// Create a new task
function createTask(title) {
if (!title || title.trim().length === 0) {
root.error("Task title cannot be empty")
return
}
let taskData = {
title: title.trim()
}
performRequest("POST", "/api/tasks", taskData, function(response) {
try {
let data = JSON.parse(response)
let task = data.data
console.log("Task created:", task.title)
root.taskCreated(task)
} catch (e) {
console.error("Error parsing create task response:", e)
root.error("Failed to parse server response")
}
}, function(error) {
console.error("Error creating task:", error)
root.error("Failed to create task")
})
}
// Update an existing task
function updateTask(taskId, updates) {
if (!taskId) {
root.error("Task ID is required for update")
return
}
performRequest("PUT", "/api/tasks/" + taskId, updates, function(response) {
try {
let data = JSON.parse(response)
let task = data.data
console.log("Task updated:", task.id)
root.taskUpdated(task)
} catch (e) {
console.error("Error parsing update task response:", e)
root.error("Failed to parse server response")
}
}, function(error) {
console.error("Error updating task:", error)
root.error("Failed to update task")
})
}
// Delete a task
function deleteTask(taskId) {
if (!taskId) {
root.error("Task ID is required for deletion")
return
}
performRequest("DELETE", "/api/tasks/" + taskId, null, function(response) {
console.log("Task deleted:", taskId)
root.taskDeleted(taskId)
}, function(error) {
console.error("Error deleting task:", error)
root.error("Failed to delete task")
})
}
// Load user settings
function loadSettings() {
performRequest("GET", "/api/settings", null, function(response) {
try {
let data = JSON.parse(response)
let settings = data.data
console.log("Settings loaded from API")
root.settingsLoaded(settings)
} catch (e) {
console.error("Error parsing settings response:", e)
root.error("Failed to parse settings data")
}
}, function(error) {
console.error("Error loading settings:", error)
root.error("Failed to load settings from server")
})
}
// Update user settings
function updateSettings(settings) {
performRequest("PUT", "/api/settings", settings, function(response) {
try {
let data = JSON.parse(response)
let updatedSettings = data.data
console.log("Settings updated")
root.settingsUpdated(updatedSettings)
} catch (e) {
console.error("Error parsing update settings response:", e)
root.error("Failed to parse server response")
}
}, function(error) {
console.error("Error updating settings:", error)
root.error("Failed to update settings")
})
}
// Generic HTTP request function
function performRequest(method, endpoint, data, successCallback, errorCallback) {
// Create XMLHttpRequest object
var xhr = new XMLHttpRequest()
let url = baseUrl + endpoint
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
if (successCallback) {
successCallback(xhr.responseText)
}
} else {
let errorMessage = "HTTP " + xhr.status
try {
let errorData = JSON.parse(xhr.responseText)
errorMessage = errorData.error || errorMessage
} catch (e) {
// Use default error message
}
if (errorCallback) {
errorCallback(errorMessage)
}
}
}
}
xhr.open(method, url, true)
xhr.setRequestHeader("Content-Type", "application/json")
if (data) {
xhr.send(JSON.stringify(data))
} else {
xhr.send()
}
}
}

View File

@@ -0,0 +1,83 @@
import QtQuick
import QtCore
Item {
id: root
// Signal emitted when tasks are loaded from storage
signal tasksLoaded(var tasks)
// Settings component for persistent storage
Settings {
id: settings
category: "Tasks"
// Store tasks as a JSON string
property string tasksData: "[]"
// Store dark mode preference
property bool darkMode: false
}
// Save tasks to storage
function saveTasks(tasks) {
try {
// Convert ListModel data to a proper array
let taskArray = []
for (let i = 0; i < tasks.count; i++) {
let task = tasks.get(i)
taskArray.push({
id: task.id,
title: task.title,
completed: task.completed
})
}
// Save as JSON string
settings.tasksData = JSON.stringify(taskArray)
settings.sync() // Force immediate save
console.log("Tasks saved to storage:", taskArray.length, "tasks")
} catch (error) {
console.error("Error saving tasks:", error)
}
}
// Load tasks from storage
function loadTasks() {
try {
if (settings.tasksData && settings.tasksData !== "[]") {
let taskArray = JSON.parse(settings.tasksData)
console.log("Tasks loaded from storage:", taskArray.length, "tasks")
root.tasksLoaded(taskArray)
return taskArray
} else {
console.log("No saved tasks found, starting with empty list")
root.tasksLoaded([])
return []
}
} catch (error) {
console.error("Error loading tasks:", error)
root.tasksLoaded([])
return []
}
}
// Save dark mode preference
function saveDarkMode(darkMode) {
settings.darkMode = darkMode
settings.sync()
}
// Load dark mode preference
function loadDarkMode() {
return settings.darkMode
}
// Clear all stored data
function clearStorage() {
settings.tasksData = "[]"
settings.darkMode = false
settings.sync()
console.log("Storage cleared")
}
}

View File

@@ -0,0 +1,274 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ApplicationWindow {
id: root
width: 400
height: 700
visible: true
title: qsTr("My Tasks")
property bool isInitializing: true
// Theme properties
property bool darkMode: false
property color backgroundColor: darkMode ? "#1e1e1e" : "#f0f2f5"
property color primaryColor: "#007aff"
property color textColor: darkMode ? "#ffffff" : "#333333"
property color cardColor: darkMode ? "#2d2d2d" : "#ffffff"
property color completedColor: darkMode ? "#666666" : "#999999"
//Change 1
// Task model - Using the dedicated TaskListModel
/*
TaskListModel {
id: taskModel
}
*/
// Task model - Using the dedicated TaskListModel
TaskListModel {
id: taskModel
// Connect to storage ready signal to load dark mode
onStorageInitialized: {
loadDarkMode()
}
// Handle settings loaded from REST API
onSettingsLoaded: function(darkModeValue) {
console.log("Dark mode loaded from API:", darkModeValue)
root.darkMode = darkModeValue
root.isInitializing = false
}
}
// Save dark mode preference when changed
onDarkModeChanged: {
if(!root.isInitializing){
taskModel.saveDarkMode(darkMode)
}
}
// Background
Rectangle {
anchors.fill: parent
color: root.backgroundColor
}
//The components of the ui
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 20
// Header
AppHeader {
id: header
Layout.fillWidth: true
textColor: root.textColor
textSecondary: root.completedColor
darkMode: root.darkMode
onToggleDarkMode: root.darkMode = !root.darkMode
}
// Add task bar
AddTaskBar {
id: addTaskBar
Layout.fillWidth: true
backgroundColor: root.cardColor
textColor: root.textColor
primaryColor: root.primaryColor
darkMode: root.darkMode
onTaskAdded: function(taskText) {
taskModel.addTask(taskText)
}
}
// Tasks list
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: root.cardColor
radius: 12
border.width: 1
border.color: root.darkMode ? "#404040" : "#e0e0e0"
ScrollView {
anchors.fill: parent
anchors.margins: 1
clip: true
ListView {
id: taskListView
//Change 2
//model: taskModel
model: taskModel.model
spacing: 8
anchors.margins: 12
// Add animation transitions
add: Transition {
NumberAnimation {
properties: "x"
from: 100; to: 0
duration: 300
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "opacity"
from: 0; to: 1
duration: 300
}
}
remove: Transition {
NumberAnimation {
properties: "x"
to: 100
duration: 250
easing.type: Easing.InCubic
}
NumberAnimation {
property: "opacity"
to: 0
duration: 250
}
}
displaced: Transition {
NumberAnimation {
properties: "x,y"
duration: 200
easing.type: Easing.OutQuad
}
}
delegate: TaskItem {
required property int index
required property string title
required property bool completed
width: taskListView.width
taskTitle: title
taskDone: completed
backgroundColor: root.cardColor
textColor: root.textColor
completedColor: root.completedColor
primaryColor: root.primaryColor
darkMode: root.darkMode
onToggleDone: {
taskModel.toggleTask(index)
}
onDeleteTask: {
taskModel.deleteTask(index)
}
}
// Empty state
Rectangle {
anchors.centerIn: parent
width: parent.width - 40
height: 120
color: "transparent"
visible: taskListView.count === 0
opacity: taskListView.count === 0 ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation { duration: 400; easing.type: Easing.InOutQuad }
}
Column {
anchors.centerIn: parent
spacing: 16
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "📝"
font.pixelSize: 48
opacity: 0.3
// Subtle floating animation
SequentialAnimation on y {
running: taskListView.count === 0
loops: Animation.Infinite
NumberAnimation { from: 0; to: -5; duration: 1000; easing.type: Easing.InOutSine }
NumberAnimation { from: -5; to: 0; duration: 1000; easing.type: Easing.InOutSine }
}
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("No tasks yet")
color: root.completedColor
font.pixelSize: 18
font.weight: Font.Medium
// Gentle opacity breathing animation
SequentialAnimation on opacity {
running: taskListView.count === 0
loops: Animation.Infinite
NumberAnimation { from: 0.7; to: 1.0; duration: 1500; easing.type: Easing.InOutSine }
NumberAnimation { from: 1.0; to: 0.7; duration: 1500; easing.type: Easing.InOutSine }
}
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Add a task above to get started")
color: root.completedColor
font.pixelSize: 14
horizontalAlignment: Text.AlignHCenter
// Gentle opacity breathing animation with slight delay
SequentialAnimation on opacity {
running: taskListView.count === 0
loops: Animation.Infinite
PauseAnimation { duration: 300 } // Small delay for staggered effect
NumberAnimation { from: 0.6; to: 0.9; duration: 1500; easing.type: Easing.InOutSine }
NumberAnimation { from: 0.9; to: 0.6; duration: 1500; easing.type: Easing.InOutSine }
}
}
}
}
}
}
}
// Task statistics at bottom
TaskStats {
id: taskStats
Layout.fillWidth: true
backgroundColor: root.cardColor
textColor: root.textColor
secondaryTextColor: root.completedColor
primaryColor: root.primaryColor
darkMode: root.darkMode
totalTasks: taskModel.count
completedTasks: taskModel.getStats().completed
remainingTasks: taskModel.getStats().remaining
hasCompleted: taskModel.hasCompletedTasks()
onClearCompleted: {
taskModel.clearCompletedTasks()
}
}
}
}