1// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
2// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
3// SPDX-License-Identifier: GPL-3.0-only
4
5import QtQuick 2.15
6import QtQuick.Controls 2.15 as QQC2
7import QtQuick.Layouts 1.15
8
9import org.kde.kirigami 2.15 as Kirigami
10
11import org.kde.neochat 1.0
12import NeoChat.Component 1.0
13import NeoChat.Dialog 1.0
14import NeoChat.Page 1.0
15import NeoChat.Panel 1.0
16
17Kirigami.ApplicationWindow {
18    id: root
19
20    property int columnWidth: Kirigami.Units.gridUnit * 13
21
22    minimumWidth: Kirigami.Units.gridUnit * 15
23    minimumHeight: Kirigami.Units.gridUnit * 20
24
25    wideScreen: width > columnWidth * 5
26
27    onClosing: Controller.saveWindowGeometry(root)
28
29    pageStack.initialPage: LoadingPage {}
30
31    property bool roomListLoaded: false
32
33    Connections {
34        target: root.quitAction
35        function onTriggered() {
36            Qt.quit()
37        }
38    }
39
40    // This timer allows to batch update the window size change to reduce
41    // the io load and also work around the fact that x/y/width/height are
42    // changed when loading the page and overwrite the saved geometry from
43    // the previous session.
44    Timer {
45        id: saveWindowGeometryTimer
46        interval: 1000
47        onTriggered: Controller.saveWindowGeometry(root)
48    }
49
50    onWidthChanged: saveWindowGeometryTimer.restart()
51    onHeightChanged: saveWindowGeometryTimer.restart()
52    onXChanged: saveWindowGeometryTimer.restart()
53    onYChanged: saveWindowGeometryTimer.restart()
54
55
56    /// Setup keyboard navigation to the room page.
57    function connectRoomToSignal(item) {
58        if (!roomListLoaded) {
59            console.log("Should not happen: no room list page but room page");
60        }
61        const roomList = pageStack.get(0);
62        item.switchRoomUp.connect(function() {
63            roomList.goToNextRoom();
64        });
65
66        item.switchRoomDown.connect(function() {
67            roomList.goToPreviousRoom();
68        });
69        item.forceActiveFocus();
70        item.KeyNavigation.left = pageStack.get(0);
71    }
72
73    Connections {
74        target: RoomManager
75
76        function onPushRoom(room, event) {
77            const roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml");
78            connectRoomToSignal(roomItem);
79            if (event.length > 0) {
80                roomItem.goToEvent(event);
81            }
82        }
83
84        function onReplaceRoom(room, event) {
85            const roomItem = pageStack.get(pageStack.depth - 1);
86            pageStack.currentIndex = pageStack.depth - 1;
87            connectRoomToSignal(roomItem);
88            if (event.length > 0) {
89                roomItem.goToEvent(event);
90            }
91        }
92
93        function goToEvent(event) {
94            if (event.length > 0) {
95                roomItem.goToEvent(event);
96            }
97            roomItem.forceActiveFocus();
98        }
99
100        function onPushWelcomePage() {
101            // TODO
102        }
103
104        function onOpenRoomInNewWindow(room) {
105            const secondayWindow = roomWindow.createObject(applicationWindow(), {currentRoom: room});
106            secondayWindow.width = root.width - pageStack.get(0).width;
107            secondayWindow.show();
108        }
109
110        function onShowUserDetail(user) {
111            const roomItem = pageStack.get(pageStack.depth - 1);
112            roomItem.showUserDetail(user);
113        }
114
115        function onAskDirectChatConfirmation(user) {
116            askDirectChatConfirmationComponent.createObject(QQC2.ApplicationWindow.overlay, {
117                user: user,
118            }).open();
119        }
120
121        function onWarning(title, message) {
122            if (RoomManager.currentRoom) {
123                const roomItem = pageStack.get(pageStack.depth - 1);
124                roomItem.warning(title, message);
125            } else {
126                showPassiveNotification(i18n("Warning: %1", message));
127            }
128        }
129    }
130
131    function pushReplaceLayer(page, args) {
132        if (pageStack.layers.depth === 2) {
133            pageStack.layers.replace(page, args);
134        } else {
135            pageStack.layers.push(page, args);
136        }
137    }
138
139    function showWindow() {
140        root.show()
141        root.raise()
142        root.requestActivate()
143    }
144
145    contextDrawer: RoomDrawer {
146        id: contextDrawer
147        contentItem.implicitWidth: columnWidth
148        edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
149        modal: !root.wideScreen || !enabled
150        onEnabledChanged: drawerOpen = enabled && !modal
151        onModalChanged: drawerOpen = !modal
152        enabled: RoomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3
153        handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
154    }
155
156    pageStack.columnView.columnWidth: Kirigami.Units.gridUnit * 17
157
158    globalDrawer: Kirigami.GlobalDrawer {
159        property bool hasLayer
160        contentItem.implicitWidth: columnWidth
161        isMenu: true
162        actions: [
163            Kirigami.Action {
164                text: i18n("Explore rooms")
165                icon.name: "compass"
166                onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
167                enabled: pageStack.layers.currentItem.title !== i18n("Explore Rooms") && Controller.accountCount > 0
168            },
169            Kirigami.Action {
170                text: i18n("Start a Chat")
171                icon.name: "irc-join-channel"
172                onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {"connection": Controller.activeConnection})
173                enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
174            },
175            Kirigami.Action {
176                text: i18n("Create a Room")
177                icon.name: "irc-join-channel"
178                onTriggered: {
179                    let dialog = createRoomDialog.createObject(root.overlay);
180                    dialog.open();
181                }
182                shortcut: StandardKey.New
183                enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
184            },
185            Kirigami.Action {
186                text: i18n("Accounts")
187                icon.name: "im-user"
188                onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/AccountsPage.qml")
189                enabled: pageStack.layers.currentItem.title !== i18n("Accounts") && Controller.accountCount > 0
190
191            },
192            Kirigami.Action {
193                text: i18n("Devices")
194                iconName: "network-connect"
195                onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/DevicesPage.qml")
196                enabled: pageStack.layers.currentItem.title !== i18n("Devices") && Controller.accountCount > 0
197            },
198            Kirigami.Action {
199                text: i18n("Settings")
200                icon.name: "settings-configure"
201                onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/SettingsPage.qml")
202                enabled: pageStack.layers.currentItem.title !== i18n("Settings")
203                shortcut: StandardKey.Preferences
204            },
205            Kirigami.Action {
206                text: i18n("About NeoChat")
207                icon.name: "help-about"
208                onTriggered: pushReplaceLayer(aboutPage)
209                enabled: pageStack.layers.currentItem.title !== i18n("About")
210            },
211            Kirigami.Action {
212                text: i18n("Logout")
213                icon.name: "list-remove-user"
214                enabled: Controller.accountCount > 0
215                onTriggered: Controller.logout(Controller.activeConnection, true)
216            },
217            Kirigami.Action {
218                text: i18n("Quit")
219                icon.name: "gtk-quit"
220                shortcut: StandardKey.Quit
221                onTriggered: Qt.quit()
222            }
223        ]
224    }
225
226    Component {
227        id: aboutPage
228        Kirigami.AboutPage {
229            aboutData: Controller.aboutData
230        }
231    }
232
233    Component {
234        id: roomListComponent
235        RoomListPage {
236            id: roomList
237        }
238    }
239    Connections {
240        target: LoginHelper
241        function onInitialSyncFinished() {
242            pageStack.replace(roomListComponent, {
243                activeConnection: Controller.activeConnection
244            });
245            roomListLoaded = true;
246            RoomManager.loadInitialRoom();
247        }
248    }
249
250    Connections {
251        target: Controller
252
253        function onInitiated() {
254            if (RoomManager.hasOpenRoom) {
255                return;
256            }
257            if (Controller.accountCount === 0) {
258                pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml", {});
259            } else {
260                pageStack.replace(roomListComponent, {
261                    activeConnection: Controller.activeConnection
262                });
263                roomListLoaded = true;
264                RoomManager.loadInitialRoom();
265            }
266        }
267
268        function onBusyChanged() {
269            if(!Controller.busy && roomListLoaded === false) {
270                pageStack.replace(roomListComponent);
271                roomListLoaded = true;
272            }
273        }
274
275        function onConnectionDropped() {
276            if (Controller.accountCount === 0) {
277                RoomManager.reset();
278                pageStack.clear();
279                roomListLoaded = false;
280                pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml");
281            }
282        }
283
284        function onGlobalErrorOccured(error, detail) {
285            showPassiveNotification(i18nc("%1: %2", error, detail));
286        }
287
288        function onShowWindow() {
289            root.showWindow()
290        }
291
292        function onUserConsentRequired(url) {
293            consentSheet.url = url
294            consentSheet.open()
295        }
296    }
297
298    Connections {
299        target: Controller.activeConnection
300        onDirectChatAvailable: {
301            RoomManager.enterRoom(Controller.activeConnection.room(directChat.id));
302        }
303    }
304
305    Kirigami.OverlaySheet {
306        id: consentSheet
307
308        property string url: ""
309
310        header: Kirigami.Heading {
311            text: i18n("User consent")
312        }
313
314        QQC2.Label {
315            id: label
316
317            text: i18n("Your homeserver requires you to agree to its terms and conditions before being able to use it. Please click the button below to read them.")
318            wrapMode: Text.WordWrap
319            width: parent.width
320        }
321        footer: QQC2.Button {
322            text: i18n("Open")
323            onClicked: Qt.openUrlExternally(consentSheet.url)
324        }
325    }
326
327    Component {
328        id: createRoomDialog
329
330        CreateRoomDialog {}
331    }
332
333    Component {
334        id: roomWindow
335        RoomWindow {}
336    }
337
338    Component {
339        id: userDialog
340        UserDetailDialog {}
341    }
342
343    Component {
344        id: askDirectChatConfirmationComponent
345
346        Kirigami.OverlaySheet {
347            id: askDirectChatConfirmation
348            required property var user;
349
350            parent: QQC2.ApplicationWindow.overlay
351            header: Kirigami.Heading {
352                text: i18n("Start a chat")
353            }
354            contentItem: QQC2.Label {
355                text: i18n("Do you want to start a chat with %1?", user.displayName)
356                wrapMode: Text.WordWrap
357            }
358            footer: QQC2.DialogButtonBox {
359                standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel
360                onAccepted: {
361                    user.requestDirectChat();
362                    askDirectChatConfirmation.close();
363                }
364                onRejected: askDirectChatConfirmation.close();
365            }
366        }
367    }
368
369    property Item hoverLinkIndicator: QQC2.Control {
370        parent: overlay.parent
371        property alias text: linkText.text
372        opacity: text.length > 0 ? 1 : 0
373
374        z: 20
375        x: 0
376        y: parent.height - implicitHeight
377        contentItem: QQC2.Label {
378            id: linkText
379        }
380        Kirigami.Theme.colorSet: Kirigami.Theme.View
381        background: Rectangle {
382             color: Kirigami.Theme.backgroundColor
383        }
384    }
385}
386