From 8543125fa656ddc2c114072adfc27e4e7c461695 Mon Sep 17 00:00:00 2001 From: "kali kaneko (leap communications)" Date: Mon, 6 Sep 2021 21:08:14 +0200 Subject: [ui] transient connecting state --- gui/components/Locations.qml | 284 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 239 insertions(+), 45 deletions(-) (limited to 'gui/components/Locations.qml') diff --git a/gui/components/Locations.qml b/gui/components/Locations.qml index d3e0f5a..955da26 100644 --- a/gui/components/Locations.qml +++ b/gui/components/Locations.qml @@ -1,62 +1,256 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.14 +import QtGraphicalEffects 1.0 import "../themes/themes.js" as Theme -Page { +/* TODO + [ ] build a better gateway object list (location, user-friendly, country, bridge, etc) + [ ] ui: mark manual override icon somehow? + [ ] ui: auto radiobutton should use also the bridge icon + [ ] corner case: manual override, not full list yet + [ ] persist bridges + [ ] persist manual selection + [ ] display the location we know + [ ] corner case: user selects bridges with manual selection + (I think the backend should discard any manual selection when selecting bridges... + unless the current selection provides the bridge, in which case we can maintain it) + */ + +ThemedPage { + + id: locationPage title: qsTr("Select Location") - ListView { - id: gwList - focus: true - currentIndex: -1 - anchors.fill: parent - spacing: 1 + // TODO add ScrollIndicator + // https://doc.qt.io/qt-5.12//qml-qtquick-controls2-scrollindicator.html - delegate: ItemDelegate { - id: loc - Rectangle { - width: parent.width - height: 1 - color: Theme.borderColor - } - width: parent.width - text: model.text - highlighted: ListView.isCurrentItem - icon.color: "transparent" - icon.source: model.icon - onClicked: { - model.triggered() - stackView.pop() - } - MouseArea { - property var onMouseAreaClicked: function () { - parent.clicked() - } - id: mouseArea - anchors.fill: loc - cursorShape: Qt.PointingHandCursor - onReleased: { - onMouseAreaClicked() + //: this is in the radio button for the auto selection + property var autoSelectionLabel: qsTr("Automatically use best connection") + //: Location Selection: label for radio buttons that selects manually + property var manualSelectionLabel: qsTr("Manually select") + //: A little display to signal that the clicked gateway is being switched to + property var switchingLocationLabel: qsTr("Switching gateways...") + //: Subtitle to explain that only bridge locations are shown in the selector + property var onlyBridgesWarning: qsTr("Only locations with bridges") + + property bool switching: false + + ButtonGroup { + id: locsel + } + + Rectangle { + id: autoBox + width: root.width * 0.90 + height: 100 + radius: 10 + color: "white" + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + margins: 10 + } + Rectangle { + anchors { + fill: parent + margins: 10 + } + Label { + id: recommendedLabel + //: Location Selection: label for radio button that selects automatically + text: qsTr("Recommended") + font.bold: true + } + WrappedRadioButton { + id: autoRadioButton + anchors.top: recommendedLabel.bottom + text: getAutoLabel() + ButtonGroup.group: locsel + checked: false + onClicked: { + root.selectedGateway = "auto" + console.debug("Selected gateway: auto") + backend.useAutomaticGateway() } } } + } - model: ListModel { - ListElement { - text: qsTr("Paris") - triggered: function () {} - icon: "../resources/reception-4.svg" + Rectangle { + id: manualBox + visible: root.locationsModel.length > 0 + width: root.width * 0.90 + height: getManualBoxHeight() + radius: 10 + color: Theme.fgColor + anchors { + horizontalCenter: parent.horizontalCenter + top: autoBox.bottom + margins: 10 + } + Rectangle { + anchors { + fill: parent + margins: 10 + } + Label { + id: manualLabel + text: manualSelectionLabel + font.bold: true + } + Label { + id: bridgeWarning + text: onlyBridgesWarning + color: "gray" + visible: isBridgeSelected() + wrapMode: Text.Wrap + anchors { + topMargin: 5 + top: manualLabel.bottom + } + font.pixelSize: Theme.fontSize - 3 } - ListElement { - text: qsTr("Montreal") - triggered: function () {} - icon: "../resources/reception-4.svg" + ColumnLayout { + id: gatewayListColumn + width: parent.width + spacing: 1 + anchors.top: getManualAnchor() + + Repeater { + id: gwManualSelectorList + width: parent.width + model: root.locationsModel + + RowLayout { + width: parent.width + WrappedRadioButton { + text: getLocationLabel(modelData) + location: modelData + ButtonGroup.group: locsel + checked: false + enabled: locationPage.switching ? false : true + onClicked: { + if (ctx.status == "on") { + locationPage.switching = true + } + root.selectedGateway = location + backend.useLocation(location) + } + } + Item { + Layout.fillWidth: true + } + Image { + height: 16 + width: 16 + visible: isBridgeSelected() + source: "../resources/bridge.png" + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 10 + } + SignalIcon { + // TODO mocked! + quality: getSignalFor(modelData) + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 20 + } + } + } } - ListElement { - text: qsTr("Seattle") - triggered: function () {} - icon: "../resources/reception-2.svg" + } + } + + StateGroup { + states: [ + State { + when: locationPage.switching && ctx.status != "on" + PropertyChanges { + target: manualLabel + text: switchingLocationLabel + } + }, + State { + when: ctx && ctx.status == "on" + PropertyChanges { + target: manualLabel + text: manualSelectionLabel + } + StateChangeScript { + script: { + locationPage.switching = false + } + } + } + ] + } + + function getAutoLabel() { + let l = autoSelectionLabel + if (ctx && ctx.locations && ctx.bestLocation) { + let best = ctx.locationLabels[ctx.bestLocation] + let label = best[0] + ", " + best[1] + l += " (" + label + ")" + } + return l + } + + function getLocationLabel(location) { + if (!ctx) { return ""} + let l = ctx.locationLabels[location] + return l[0] + ", " + l[1] + } + + function getManualBoxHeight() { + let h = gatewayListColumn.height + manualLabel.height + if (bridgeWarning.visible) { + h += bridgeWarning.height + } + return h + 15 + } + + function getSignalFor(location) { + switch(location) { + case "amsterdam": + case "paris": + return "good" + case "newyork": + return "medium" + case "montreal": + return "medium" + default: + return "low" + } + } + + function isBridgeSelected() { + if (ctx && ctx.transport == "obfs4") { + return true + } else { + return false + } + } + + function getManualAnchor() { + if (isBridgeSelected()) { + return bridgeWarning.bottom + } else { + return manualLabel.bottom + } + } + + Component.onCompleted: { + if (root.selectedGateway == "auto") { + autoRadioButton.checked = true; + } else { + let match = false + for (var i=1; i