add book
This commit is contained in:
@@ -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}
|
||||
)
|
||||
99
Qt6QMLBeginnersCode/14-Networking/2-XmlHttpRequest/Main.qml
Normal file
99
Qt6QMLBeginnersCode/14-Networking/2-XmlHttpRequest/Main.qml
Normal 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")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Qt6QMLBeginnersCode/14-Networking/2-XmlHttpRequest/main.cpp
Normal file
19
Qt6QMLBeginnersCode/14-Networking/2-XmlHttpRequest/main.cpp
Normal 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();
|
||||
}
|
||||
41
Qt6QMLBeginnersCode/14-Networking/3-RestAPI/CMakeLists.txt
Normal file
41
Qt6QMLBeginnersCode/14-Networking/3-RestAPI/CMakeLists.txt
Normal 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}
|
||||
)
|
||||
106
Qt6QMLBeginnersCode/14-Networking/3-RestAPI/Main.qml
Normal file
106
Qt6QMLBeginnersCode/14-Networking/3-RestAPI/Main.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Qt6QMLBeginnersCode/14-Networking/3-RestAPI/main.cpp
Normal file
19
Qt6QMLBeginnersCode/14-Networking/3-RestAPI/main.cpp
Normal 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();
|
||||
}
|
||||
57
Qt6QMLBeginnersCode/14-Networking/4-TodolistNetworking/.gitignore
vendored
Normal file
57
Qt6QMLBeginnersCode/14-Networking/4-TodolistNetworking/.gitignore
vendored
Normal 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
|
||||
@@ -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"
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user