diff --git a/src/StatusQ/Components/StatusChatList.qml b/src/StatusQ/Components/StatusChatList.qml index 8fe20149..29e28740 100644 --- a/src/StatusQ/Components/StatusChatList.qml +++ b/src/StatusQ/Components/StatusChatList.qml @@ -11,12 +11,14 @@ import StatusQ.Controls 0.1 Column { id: statusChatList - spacing: 4 + spacing: 0 width: 288 + property string uuid: Utils.uuid() + property string categoryId: "" property string selectedChatId: "" - property alias chatListItems: delegateModel + property alias chatListItems: statusChatListItems property bool draggableItems: false property alias statusChatListItems: statusChatListItems @@ -29,7 +31,7 @@ Column { signal chatItemSelected(string id) signal chatItemUnmuted(string id) - signal chatItemReordered(string id, int from, int to) + signal chatItemReordered(string categoryId, string id, int from, int to) onPopupMenuChanged: { if (!!popupMenu) { @@ -37,191 +39,254 @@ Column { } } - DelegateModel { - id: delegateModel + Column { + id: statusChatListItemsWrapper + width: parent.width + spacing: 4 + onImplicitHeightChanged: { + // For some reason, the binding on emptyListDropAreaLoaoder.active alone doesn't work. + // We have to explicitly reassign it here. + emptyListDropAreaLoader.active = implicitHeight == 0 + } - delegate: Item { - id: draggable - width: statusChatListItem.width - height: statusChatListItem.height + Repeater { + id: statusChatListItems + delegate: Item { + id: draggable + width: statusChatListItem.width + height: statusChatListItem.height - property alias chatListItem: statusChatListItem + property alias chatListItem: statusChatListItem - visible: { - if (!!statusChatList.filterFn) { - return statusChatList.filterFn(model, statusChatList.categoryId) + visible: { + if (!!statusChatList.filterFn) { + return statusChatList.filterFn(model, statusChatList.categoryId) + } + return true } - return true - } - MouseArea { - id: dragSensor + MouseArea { + id: dragSensor - anchors.fill: parent - cursorShape: active ? Qt.ClosedHandCursor : Qt.PointingHandCursor - hoverEnabled: true - pressAndHoldInterval: 150 - enabled: statusChatList.draggableItems - - property bool active: false - property real startY: 0 - property real startX: 0 - - drag.target: draggedListItemLoader.item - drag.threshold: 0.1 - drag.filterChildren: true - - onPressed: { - startY = mouseY - startX = mouseX - } - onPressAndHold: active = true - onReleased: { - if (active) { - statusChatList.chatItemReordered(statusChatListItem.chatId, statusChatListItem.originalOrder, statusChatListItem.originalOrder) + anchors.fill: parent + cursorShape: active ? Qt.ClosedHandCursor : Qt.PointingHandCursor + hoverEnabled: true + pressAndHoldInterval: 150 + enabled: statusChatList.draggableItems + + property bool active: false + property real startY: 0 + property real startX: 0 + + drag.target: draggedListItemLoader.item + drag.threshold: 0.1 + drag.filterChildren: true + + onPressed: { + startY = mouseY + startX = mouseX } - active = false - } - onMouseYChanged: { - if ((Math.abs(startY - mouseY) > 1) && pressed) { - active = true + + onPressAndHold: active = true + onReleased: { + if (active && (statusChatListItem.originalOder !== statusChatListItem.newOrder || + statusChatListItem.categoryId !== statusChatListItem.newCategoryId)) { + statusChatList.chatItemReordered( + statusChatListItem.newCategoryId, + statusChatListItem.chatId, + statusChatListItem.originalOrder, + statusChatListItem.newOrder + ) + } + active = false } - } - onMouseXChanged: { - if ((Math.abs(startX - mouseX) > 1) && pressed) { - active = true + onMouseYChanged: { + if ((Math.abs(startY - mouseY) > 1) && pressed) { + active = true + } + } + onMouseXChanged: { + if ((Math.abs(startX - mouseX) > 1) && pressed) { + active = true + } } - } - StatusChatListItem { + StatusChatListItem { - id: statusChatListItem + id: statusChatListItem - property string profileImage: "" + property string chatListId: statusChatList.uuid + property string profileImage: "" + property var newOrder: model.position + property var newCategoryId: model.categoryId - opacity: dragSensor.active ? 0.0 : 1.0 - Component.onCompleted: { - if (typeof statusChatList.profileImageFn === "function") { - profileImage = statusChatList.profileImageFn(model.chatId || model.id) || "" + anchors.centerIn: parent + opacity: dragSensor.active ? 0.3 : 1.0 + Component.onCompleted: { + if (typeof statusChatList.profileImageFn === "function") { + profileImage = statusChatList.profileImageFn(model.chatId || model.id) || "" + } } - } - originalOrder: model.position - chatId: model.chatId || model.id - categoryId: model.categoryId || "" - name: !!statusChatList.chatNameFn ? statusChatList.chatNameFn(model) : model.name - type: model.chatType - muted: !!model.muted - hasUnreadMessages: !!model.hasUnreadMessages || model.unviewedMessagesCount > 0 - hasMention: model.mentionsCount > 0 - badge.value: model.chatType === StatusChatListItem.Type.OneToOneChat ? - model.unviewedMessagesCount || 0 : - model.mentionsCount || 0 - selected: (model.chatId || model.id) === statusChatList.selectedChatId - - icon.color: model.color || "" - image.isIdenticon: !!!profileImage && !!!model.identityImage && !!model.identicon - image.source: profileImage || model.identityImage || model.identicon || "" - - sensor.cursorShape: dragSensor.cursorShape - onClicked: { - if (mouse.button === Qt.RightButton && !!statusChatList.popupMenu) { - statusChatListItem.highlighted = true - - let originalOpenHandler = popupMenuSlot.item.openHandler - let originalCloseHandler = popupMenuSlot.item.closeHandler - - popupMenuSlot.item.openHandler = function () { - if (!!originalOpenHandler) { - originalOpenHandler((model.chatId || model.id)) + originalOrder: model.position + chatId: model.chatId || model.id + categoryId: model.categoryId || "" + /* name: (!!statusChatList.chatNameFn ? statusChatList.chatNameFn(model) : model.name) + " POSITION: " + statusChatListItem.newOrder */ + name: (!!statusChatList.chatNameFn ? statusChatList.chatNameFn(model) : model.name) + type: model.chatType + muted: !!model.muted + hasUnreadMessages: !!model.hasUnreadMessages || model.unviewedMessagesCount > 0 + hasMention: model.mentionsCount > 0 + badge.value: model.chatType === StatusChatListItem.Type.OneToOneChat ? + model.unviewedMessagesCount || 0 : + model.mentionsCount || 0 + selected: (model.chatId || model.id) === statusChatList.selectedChatId + + icon.color: model.color || "" + image.isIdenticon: !!!profileImage && !!!model.identityImage && !!model.identicon + image.source: profileImage || model.identityImage || model.identicon || "" + + sensor.cursorShape: dragSensor.cursorShape + onClicked: { + if (mouse.button === Qt.RightButton && !!statusChatList.popupMenu) { + statusChatListItem.highlighted = true + + let originalOpenHandler = popupMenuSlot.item.openHandler + let originalCloseHandler = popupMenuSlot.item.closeHandler + + popupMenuSlot.item.openHandler = function () { + if (!!originalOpenHandler) { + originalOpenHandler((model.chatId || model.id)) + } } - } - popupMenuSlot.item.closeHandler = function () { - if (statusChatListItem) { - statusChatListItem.highlighted = false - } - if (!!originalCloseHandler) { - originalCloseHandler() + popupMenuSlot.item.closeHandler = function () { + if (statusChatListItem) { + statusChatListItem.highlighted = false + } + if (!!originalCloseHandler) { + originalCloseHandler() + } } + + popupMenuSlot.item.popup(mouse.x + 4, statusChatListItem.y + mouse.y + 6) + popupMenuSlot.item.openHandler = originalOpenHandler + return + } + if (!statusChatListItem.selected) { + statusChatList.chatItemSelected(model.chatId || model.id) } + } + onUnmute: statusChatList.chatItemUnmuted(model.chatId || model.id) - popupMenuSlot.item.popup(mouse.x + 4, statusChatListItem.y + mouse.y + 6) - popupMenuSlot.item.openHandler = originalOpenHandler - return + StatusChatListDropIndicator { + anchors.bottom: parent.bottom + anchors.bottomMargin: -2 + visible: dropArea.containsDrag } - if (!statusChatListItem.selected) { - statusChatList.chatItemSelected(model.chatId || model.id) + } + } + DropArea { + id: dropArea + width: parent.width + height: parent.height + keys: ["chat-item"] + + onEntered: reorderDelay.start() + + Timer { + id: reorderDelay + interval: 100 + repeat: false + onTriggered: { + if (dropArea.containsDrag) { + let newOrder = statusChatListItem.originalOrder + + if (dropArea.drag.source.chatListItem.originalOrder > statusChatListItem.originalOrder) { + newOrder = newOrder + 1 + } + dropArea.drag.source.chatListItem.newOrder = newOrder + dropArea.drag.source.chatListItem.newCategoryId = statusChatListItem.categoryId + } + } + } + } + Loader { + id: draggedListItemLoader + active: dragSensor.active + sourceComponent: StatusChatListItem { + property string chatListId: draggable.chatListItem.chatListId + property var globalPosition: Utils.getAbsolutePosition(draggable) + parent: QC.Overlay.overlay + sensor.cursorShape: dragSensor.cursorShape + Drag.active: dragSensor.active + Drag.hotSpot.x: width / 2 + Drag.hotSpot.y: height / 2 + Drag.keys: ["chat-item"] + Drag.source: draggable + + Component.onCompleted: { + x = globalPosition.x + y = globalPosition.y } + chatId: draggable.chatListItem.chatId + categoryId: draggable.chatListItem.categoryId + name: draggable.chatListItem.name + type: draggable.chatListItem.type + muted: draggable.chatListItem.muted + dragged: true + hasUnreadMessages: draggable.chatListItem.hasUnreadMessages + hasMention: draggable.chatListItem.hasMention + badge.value: draggable.chatListItem.badge.value + selected: draggable.chatListItem.selected + + icon.color: draggable.chatListItem.icon.color + image.isIdenticon: draggable.chatListItem.image.isIdenticon + image.source: draggable.chatListItem.image.source } - onUnmute: statusChatList.chatItemUnmuted(model.chatId || model.id) } } + } + } - DropArea { - id: dropArea - width: dragSensor.active ? 0 : parent.width - height: dragSensor.active ? 0 : parent.height - keys: ["chat-item-category-" + statusChatListItem.categoryId] + Loader { + id: popupMenuSlot + active: !!statusChatList.popupMenu + } - onEntered: reorderDelay.start() - onDropped: statusChatList.chatItemReordered(statusChatListItem.chatId, drag.source.originalOrder, statusChatListItem.DelegateModel.itemsIndex) + Loader { + id: emptyListDropAreaLoader + active: statusChatListItemsWrapper.implicitHeight == 0 + width: parent.width + height: active ? 8 : 0 + sourceComponent: Item { + width: statusChatList.width + height: 8 + + StatusChatListDropIndicator { + visible: emptyListDropArea.containsDrag + opacity: 0.5 + anchors.bottom: statusChatList.categoryId == "" ? parent.bottom : undefined + anchors.top: statusChatList.categoryId !== "" ? parent.top : undefined + } + DropArea { + id: emptyListDropArea + anchors.fill: parent + visible: parent.visible + keys: ["chat-item"] + onEntered: reorderDelay2.start() Timer { - id: reorderDelay + id: reorderDelay2 interval: 100 repeat: false onTriggered: { - if (dropArea.containsDrag) { - dropArea.drag.source.chatListItem.originalOrder = statusChatListItem.originalOrder - delegateModel.items.move(dropArea.drag.source.DelegateModel.itemsIndex, draggable.DelegateModel.itemsIndex) + if (emptyListDropArea.containsDrag) { + emptyListDropArea.drag.source.chatListItem.newOrder = 0 + emptyListDropArea.drag.source.chatListItem.newCategoryId = statusChatList.categoryId } } } } - - Loader { - id: draggedListItemLoader - active: dragSensor.active - sourceComponent: StatusChatListItem { - property var globalPosition: Utils.getAbsolutePosition(draggable) - parent: QC.Overlay.overlay - sensor.cursorShape: dragSensor.cursorShape - Drag.active: dragSensor.active - Drag.hotSpot.x: width / 2 - Drag.hotSpot.y: height / 2 - Drag.keys: ["chat-item-category-" + categoryId] - Drag.source: draggable - - Component.onCompleted: { - x = globalPosition.x - y = globalPosition.y - } - chatId: draggable.chatListItem.chatId - categoryId: draggable.chatListItem.categoryId - name: draggable.chatListItem.name - type: draggable.chatListItem.type - muted: draggable.chatListItem.muted - dragged: true - hasUnreadMessages: draggable.chatListItem.hasUnreadMessages - hasMention: draggable.chatListItem.hasMention - badge.value: draggable.chatListItem.badge.value - selected: draggable.chatListItem.selected - - icon.color: draggable.chatListItem.icon.color - image.isIdenticon: draggable.chatListItem.image.isIdenticon - image.source: draggable.chatListItem.image.source - } - } } } - - Repeater { - id: statusChatListItems - model: delegateModel - } - - Loader { - id: popupMenuSlot - active: !!statusChatList.popupMenu - } } diff --git a/src/StatusQ/Components/StatusChatListAndCategories.qml b/src/StatusQ/Components/StatusChatListAndCategories.qml index 2411fe09..b2086549 100644 --- a/src/StatusQ/Components/StatusChatListAndCategories.qml +++ b/src/StatusQ/Components/StatusChatListAndCategories.qml @@ -142,7 +142,14 @@ Item { chatList.selectedChatId: statusChatListAndCategories.selectedChatId chatList.onChatItemSelected: statusChatListAndCategories.chatItemSelected(id) chatList.onChatItemUnmuted: statusChatListAndCategories.chatItemUnmuted(id) - chatList.onChatItemReordered: statusChatListAndCategories.chatItemReordered(model.categoryId, id, from, to) + chatList.onChatItemReordered: { + console.log("REORDERING CHAT ITEM:") + console.log("FROM CATEGORY: ", model.categoryId) + console.log("TO: ", categoryId) + console.log("FROM POSITION: ", from) + console.log("TO POSITION: ", to) + statusChatListAndCategories.chatItemReordered(categoryId, id, from, to) + } chatList.draggableItems: statusChatListAndCategories.draggableItems popupMenu: statusChatListAndCategories.categoryPopupMenu @@ -155,7 +162,9 @@ Item { height: draggable.chatListCategory.dragActive ? 0 : parent.height keys: ["chat-category"] - onEntered: reorderDelay.start() + onEntered: { + reorderDelay.start() + } onDropped: statusChatListAndCategories.chatListCategoryReordered(statusChatListCategory.categoryId, drag.source.originalOrder, statusChatListCategory.DelegateModel.itemsIndex) Timer { diff --git a/src/StatusQ/Components/StatusChatListCategory.qml b/src/StatusQ/Components/StatusChatListCategory.qml index 806136de..ffbcdc40 100644 --- a/src/StatusQ/Components/StatusChatListCategory.qml +++ b/src/StatusQ/Components/StatusChatListCategory.qml @@ -1,5 +1,6 @@ import QtQuick 2.13 +import StatusQ.Core.Utils 0.1 import StatusQ.Components 0.1 import StatusQ.Popups 0.1 @@ -8,6 +9,7 @@ Column { spacing: 0 opacity: dragged ? 0.5 : 1 + property string uuid: Utils.uuid() objectName: "chatListCategory" property int originalOrder: -1 @@ -64,6 +66,7 @@ Column { StatusChatList { id: statusChatList + uuid: statusChatListCategory.uuid anchors.horizontalCenter: parent.horizontalCenter visible: statusChatListCategory.opened categoryId: statusChatListCategory.categoryId diff --git a/src/StatusQ/Components/StatusChatListDropIndicator.qml b/src/StatusQ/Components/StatusChatListDropIndicator.qml new file mode 100644 index 00000000..84b31aee --- /dev/null +++ b/src/StatusQ/Components/StatusChatListDropIndicator.qml @@ -0,0 +1,11 @@ +import QtQuick 2.13 +import StatusQ.Core.Theme 0.1 + +Rectangle { + id: root + implicitWidth: 288 + height: 4 + color: Theme.palette.successColor1 + radius: 2 +} + diff --git a/src/StatusQ/Components/qmldir b/src/StatusQ/Components/qmldir index 78e2b203..1354b19e 100644 --- a/src/StatusQ/Components/qmldir +++ b/src/StatusQ/Components/qmldir @@ -3,6 +3,7 @@ module StatusQ.Components StatusBadge 0.1 StatusBadge.qml StatusChatInfoToolBar 0.1 StatusChatInfoToolBar.qml StatusChatList 0.1 StatusChatList.qml +StatusChatListDropIndicator 0.1 StatusChatListDropIndicator.qml StatusChatListItem 0.1 StatusChatListItem.qml StatusChatListCategory 0.1 StatusChatListCategory.qml StatusChatListCategoryItem 0.1 StatusChatListCategoryItem.qml diff --git a/statusq.qrc b/statusq.qrc index 01a6b987..358056ee 100644 --- a/statusq.qrc +++ b/statusq.qrc @@ -249,6 +249,7 @@ src/StatusQ/Controls/StatusBaseInput.qml src/StatusQ/Controls/StatusChatListCategoryItemButton.qml src/StatusQ/Components/StatusChatList.qml + src/StatusQ/Components/StatusChatListDropIndicator.qml src/StatusQ/Popups/statusModal/StatusImageWithTitle.qml src/StatusQ/Popups/statusModal/StatusModalFooter.qml src/StatusQ/Popups/statusModal/StatusModalHeader.qml