qt-chat-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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 }