ChatListView.qml (11759B)
1 import QtQuick 2 import QtQuick.Layouts 3 import "../components" 4 import "../models" 5 import "../styles" 6 7 FocusScope { 8 id: root 9 focus: true 10 11 property bool isLoading: false 12 property bool gPressed: false 13 property bool searchActive: false 14 property bool renameActive: false 15 property int renameChatId: -1 16 17 Component.onCompleted: forceActiveFocus() 18 19 function activateSearch() { 20 searchActive = true 21 searchInput.forceActiveFocus() 22 } 23 24 function deactivateSearch() { 25 searchActive = false 26 chatListModel.filter = "" 27 searchInput.text = "" 28 root.forceActiveFocus() 29 } 30 31 function activateRename() { 32 var model = chatListView.model 33 if (chatListView.currentIndex >= 0 && chatListView.currentIndex < model.count) { 34 var chat = model.get(chatListView.currentIndex) 35 renameChatId = chat.id 36 renameInput.text = chat.title 37 renameActive = true 38 renameInput.forceActiveFocus() 39 renameInput.selectAll() 40 } 41 } 42 43 function deactivateRename() { 44 renameActive = false 45 renameChatId = -1 46 renameInput.text = "" 47 root.forceActiveFocus() 48 } 49 50 function submitRename() { 51 if (renameChatId > 0 && renameInput.text.trim().length > 0) { 52 NetworkManager.updateChat(renameChatId, renameInput.text.trim()) 53 } 54 deactivateRename() 55 } 56 57 Keys.onPressed: function(event) { 58 // Handle Escape for search/rename 59 if (searchActive) { 60 if (event.key === Qt.Key_Escape) { 61 deactivateSearch() 62 event.accepted = true 63 } 64 return 65 } 66 if (renameActive) { 67 if (event.key === Qt.Key_Escape) { 68 deactivateRename() 69 event.accepted = true 70 } 71 return 72 } 73 74 var listView = chatListView 75 var model = listView.model 76 77 if (event.key === Qt.Key_Slash) { 78 activateSearch() 79 event.accepted = true 80 } else if (event.key === Qt.Key_R) { 81 activateRename() 82 event.accepted = true 83 } else if (event.key === Qt.Key_J || (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier)) { 84 if (listView.currentIndex < model.count - 1) { 85 listView.currentIndex++ 86 } 87 gPressed = false 88 event.accepted = true 89 } else if (event.key === Qt.Key_K || (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier)) { 90 if (listView.currentIndex > 0) { 91 listView.currentIndex-- 92 } 93 gPressed = false 94 event.accepted = true 95 } else if (event.key === Qt.Key_G && event.modifiers & Qt.ShiftModifier) { 96 listView.currentIndex = model.count - 1 97 gPressed = false 98 event.accepted = true 99 } else if (event.key === Qt.Key_G && !event.modifiers) { 100 if (gPressed) { 101 listView.currentIndex = 0 102 gPressed = false 103 } else { 104 gPressed = true 105 } 106 event.accepted = true 107 } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { 108 chatListView.openCurrentChat() 109 gPressed = false 110 event.accepted = true 111 } else { 112 gPressed = false 113 } 114 } 115 116 // Dynamic chat list model 117 ChatListModel { 118 id: chatListModel 119 } 120 121 // Handle network responses 122 Connections { 123 target: NetworkManager 124 function onChatCreated(chatJson) { 125 // ChatListModel handles adding the chat - just reset loading state 126 root.isLoading = false 127 } 128 function onRequestFailed(error) { 129 // Error already logged in Rust - just update UI state 130 root.isLoading = false 131 } 132 } 133 134 ColumnLayout { 135 anchors.fill: parent 136 spacing: 0 137 138 // Top bar 139 TopBar { 140 Layout.fillWidth: true 141 title: "Telegram" 142 showBackButton: false 143 showMenuButton: true 144 showAddButton: true 145 enabled: !root.isLoading 146 onMenuClicked: { 147 // Navigate to settings 148 if (typeof stackView !== 'undefined' && stackView) { 149 stackView.push(Qt.resolvedUrl("SettingsView.qml")) 150 } 151 } 152 onAddClicked: { 153 root.isLoading = true 154 NetworkManager.postChat("New Chat") 155 } 156 } 157 158 // Search bar 159 Rectangle { 160 Layout.fillWidth: true 161 height: searchActive ? 48 : 0 162 visible: searchActive 163 color: "white" 164 border.color: TelegramStyle.divider 165 border.width: 1 166 167 Behavior on height { 168 NumberAnimation { duration: 150 } 169 } 170 171 RowLayout { 172 anchors.fill: parent 173 anchors.margins: TelegramStyle.smallMargin 174 spacing: TelegramStyle.smallMargin 175 176 Text { 177 text: "/" 178 font.pixelSize: TelegramStyle.fontSizeNormal 179 color: TelegramStyle.textSecondary 180 } 181 182 TextInput { 183 id: searchInput 184 Layout.fillWidth: true 185 font.pixelSize: TelegramStyle.fontSizeNormal 186 color: TelegramStyle.textPrimary 187 clip: true 188 189 onTextChanged: chatListModel.filter = text 190 191 Keys.onEscapePressed: root.deactivateSearch() 192 Keys.onReturnPressed: { 193 // Open first matching chat 194 if (chatListView.count > 0) { 195 chatListView.currentIndex = 0 196 chatListView.openCurrentChat() 197 root.deactivateSearch() 198 } 199 } 200 201 Text { 202 visible: searchInput.text.length === 0 203 anchors.verticalCenter: parent.verticalCenter 204 text: "Search chats..." 205 color: TelegramStyle.textSecondary 206 font.pixelSize: TelegramStyle.fontSizeNormal 207 } 208 } 209 210 Text { 211 text: "Esc" 212 font.pixelSize: TelegramStyle.fontSizeSmall 213 color: TelegramStyle.textSecondary 214 215 MouseArea { 216 anchors.fill: parent 217 cursorShape: Qt.PointingHandCursor 218 onClicked: root.deactivateSearch() 219 } 220 } 221 } 222 } 223 224 // Rename bar 225 Rectangle { 226 Layout.fillWidth: true 227 height: renameActive ? 48 : 0 228 visible: renameActive 229 color: "white" 230 border.color: TelegramStyle.primaryColor 231 border.width: 2 232 233 Behavior on height { 234 NumberAnimation { duration: 150 } 235 } 236 237 RowLayout { 238 anchors.fill: parent 239 anchors.margins: TelegramStyle.smallMargin 240 spacing: TelegramStyle.smallMargin 241 242 Text { 243 text: "Rename:" 244 font.pixelSize: TelegramStyle.fontSizeNormal 245 color: TelegramStyle.textSecondary 246 } 247 248 TextInput { 249 id: renameInput 250 Layout.fillWidth: true 251 font.pixelSize: TelegramStyle.fontSizeNormal 252 color: TelegramStyle.textPrimary 253 clip: true 254 255 Keys.onEscapePressed: root.deactivateRename() 256 Keys.onReturnPressed: root.submitRename() 257 258 Text { 259 visible: renameInput.text.length === 0 260 anchors.verticalCenter: parent.verticalCenter 261 text: "Enter new name..." 262 color: TelegramStyle.textSecondary 263 font.pixelSize: TelegramStyle.fontSizeNormal 264 } 265 } 266 267 Text { 268 text: "Enter" 269 font.pixelSize: TelegramStyle.fontSizeSmall 270 color: TelegramStyle.primaryColor 271 272 MouseArea { 273 anchors.fill: parent 274 cursorShape: Qt.PointingHandCursor 275 onClicked: root.submitRename() 276 } 277 } 278 279 Text { 280 text: "Esc" 281 font.pixelSize: TelegramStyle.fontSizeSmall 282 color: TelegramStyle.textSecondary 283 284 MouseArea { 285 anchors.fill: parent 286 cursorShape: Qt.PointingHandCursor 287 onClicked: root.deactivateRename() 288 } 289 } 290 } 291 } 292 293 // Chat list 294 Rectangle { 295 Layout.fillWidth: true 296 Layout.fillHeight: true 297 color: TelegramStyle.chatListBg 298 299 ListView { 300 id: chatListView 301 anchors.fill: parent 302 model: chatListModel.filteredModel 303 clip: true 304 keyNavigationEnabled: false 305 currentIndex: 0 306 highlight: null 307 highlightFollowsCurrentItem: false 308 309 function openCurrentChat() { 310 if (currentIndex >= 0 && currentIndex < model.count) { 311 var chatId = model.get(currentIndex).id 312 if (typeof stackView !== 'undefined' && stackView) { 313 stackView.push(Qt.resolvedUrl("MessageThreadView.qml"), { chatId: chatId }) 314 } 315 } 316 } 317 318 // Detect when user scrolls to bottom 319 onAtYEndChanged: { 320 // Only trigger if we have items in the list (avoid triggering on empty initial load) 321 if (atYEnd && model.count > 0 && chatListModel.hasMore && !chatListModel.isLoadingMore) { 322 chatListModel.loadMore() 323 } 324 } 325 326 delegate: ChatListItem { 327 width: chatListView.width 328 chatData: model 329 isSelected: chatListView.currentIndex === index 330 onClicked: { 331 chatListView.currentIndex = index 332 // Navigate to message thread 333 if (typeof stackView !== 'undefined' && stackView) { 334 stackView.push(Qt.resolvedUrl("MessageThreadView.qml"), { chatId: model.id }) 335 } 336 } 337 } 338 339 // Loading indicator at bottom 340 footer: Item { 341 width: chatListView.width 342 height: chatListModel.isLoadingMore ? 60 : 0 343 visible: chatListModel.isLoadingMore 344 345 Text { 346 anchors.centerIn: parent 347 text: "Loading more chats..." 348 color: TelegramStyle.textSecondary 349 font.pixelSize: TelegramStyle.fontSizeSmall 350 } 351 } 352 } 353 } 354 } 355 }