Skip to content

Commit

Permalink
VPN-6021: Improve "App Exculsions" screen load time by switching from…
Browse files Browse the repository at this point in the history
… Repeater to ListView (#9131)

* Quick and dirty

* Use ListView header and footer

* Implement header and footer

* Initial tabbing behavior

* Use FocusScope and MZList

* Fix issues

* Focus fixes

* Fixes

* fixes

* Fix test, scrolling workaround

* Fix tests

* Nits

* Fix margins
  • Loading branch information
vinocher authored Apr 9, 2024
1 parent 3e9a7d7 commit 0a0ffb6
Show file tree
Hide file tree
Showing 6 changed files with 460 additions and 145 deletions.
1 change: 1 addition & 0 deletions nebula/ui/components/MZCheckBox.qml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "qrc:/nebula/utils/MZUiUtils.js" as MZUiUtils
CheckBox {
property var uiState: MZTheme.theme.uiState
property string accessibleName
property alias checkBoxActiveFocusOnTab: checkBox.activeFocusOnTab

id: checkBox

Expand Down
106 changes: 102 additions & 4 deletions nebula/ui/components/MZList.qml
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,117 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import QtQuick 2.5
import QtQuick.Controls

import Mozilla.Shared 1.0

// ListView with scroll bar and an ensureVisible() method that scrolls an item into the
// ListView's visible viewport. (ensureVisible() is used by scrollToComponent())

ListView {
id: list

property var listName
property var _accessibleName: ""

height:contentHeight
Accessible.role: Accessible.List
Accessible.name: listName
Accessible.name: _accessibleName
Accessible.ignored: !visible
activeFocusOnTab: true
interactive: false // disable scrolling on list since the entire window is scrollable
boundsBehavior: Flickable.StopAtBounds
highlightFollowsCurrentItem: true
Keys.onDownPressed: list.incrementCurrentIndex()
Keys.onUpPressed: list.decrementCurrentIndex()

ScrollBar.vertical: ScrollBar {
readonly property real scrollBarWidth: Qt.platform.os === "osx" ? 6 : 10
id: scrollBar

Accessible.ignored: true
hoverEnabled: true
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.rightMargin: Qt.platform.os === "osx" ? 2 : 0

background: Rectangle {
color: MZTheme.theme.transparent
anchors.fill: parent
}

contentItem: Rectangle {
color: MZTheme.colors.grey40
width: scrollBar.scrollBarWidth
implicitWidth: scrollBar.scrollBarWidth
radius: scrollBar.scrollBarWidth

opacity: scrollBar.pressed ? 0.5 :
scrollBar.interactive && scrollBar.hovered ? 0.4 : 0.3
Behavior on opacity {
PropertyAnimation {
duration: 100
}
}
}

font.pixelSize: 0 // QTBUG-96733 workaround
implicitWidth: scrollBarWidth
width: scrollBarWidth
minimumSize: 0

visible: list.interactive

Behavior on opacity {
PropertyAnimation {
duration: 100
}
}
}

// Scroll item into ListView's visible viewport region, if necessary
function ensureVisible(item) {
if(ensureVisAnimation.running) {
ensureVisAnimation.stop();
}

const itemY = item.mapToItem(list.contentItem, 0, 0).y;

if (item.skipEnsureVisible || itemY < list.originY) {
return;
}

const isItemBeyondNavBarTop = (item.mapToItem(window.contentItem, 0, 0).y + item.height) > (window.height - MZTheme.theme.navBarHeightWithMargins);
const isItemBeyondWindowHeight = (item.mapToItem(window.contentItem, 0, 0).y + item.height) > window.height;
const bottomMargin = (navbar.visible && isItemBeyondNavBarTop) ? MZTheme.theme.navBarHeightWithMargins : MZTheme.theme.contentBottomMargin;
const itemHeight = item.height + bottomMargin;
let scrollY;

// Scroll the item upwards into view. Occurs if the item is beyond the navbar's top while navbar is visible. Or if the
// item is beyond the window height while navbar is not visible.
if((navbar.visible && isItemBeyondNavBarTop) || (!navbar.visible && isItemBeyondWindowHeight)) {
const scrollDistance = item.mapToItem(window.contentItem, 0, 0).y + item.height - (window.height - bottomMargin);

scrollY = list.contentY + scrollDistance;
}
else {
// Scroll the item downwards into view
if (itemY < list.contentY) {
scrollY = itemY - bottomMargin;
}
}

if (typeof(scrollY) === "undefined") {
return;
}

// Animate scroll to destination
ensureVisAnimation.to = scrollY;
ensureVisAnimation.start();
}

NumberAnimation on contentY {
id: ensureVisAnimation

duration: 300
easing.type: Easing.OutQuad
}
}
5 changes: 3 additions & 2 deletions nebula/ui/components/MZViewBase.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Item {
property string _menuTitle: ""
property string _accessibleName: ""
property var _menuOnBackClicked
property bool _useMargins: true
property alias _viewContentData: viewContent.data
property alias _interactive: vpnFlickable.interactive
property alias _contentHeight: vpnFlickable.contentHeight
Expand Down Expand Up @@ -45,8 +46,8 @@ Item {
top: parent.top
left: parent.left
right: parent.right
topMargin: MZTheme.theme.viewBaseTopMargin
bottomMargin: navbar.visible ? 0 : MZTheme.theme.rowHeight
topMargin: _useMargins ? MZTheme.theme.viewBaseTopMargin : 0
bottomMargin: (_useMargins && !navbar.visible) ? MZTheme.theme.rowHeight : 0
}
}
}
Expand Down
114 changes: 67 additions & 47 deletions nebula/ui/components/forms/MZSearchBar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -10,71 +10,91 @@ import Mozilla.VPN 1.0
import components 0.1
import components.forms 0.1

ColumnLayout {
FocusScope {
property var _filterProxyCallback: () => {}
property var _sortProxyCallback: () => {}
property var _editCallback: () => {}
property alias _filterProxySource: model.source
property alias _searchBarPlaceholderText: searchBar._placeholderText
property bool _searchBarHasError: false
readonly property bool isEmpty: searchBar.length === 0
property var _getNextTabItem: () => { return null; }

spacing: MZTheme.theme.windowMargin / 2
implicitHeight: searchBarColumn.implicitHeight
implicitWidth: searchBarColumn.implicitWidth

MZTextField {
id: searchBar
objectName: "searchBarTextField"
ColumnLayout {
id: searchBarColumn

Accessible.editable: false
Accessible.searchEdit: true
Layout.fillWidth: true
anchors.fill: parent
spacing: MZTheme.theme.windowMargin / 2

background: MZInputBackground {}
leftInset: MZTheme.theme.windowMargin * 3
leftPadding: MZTheme.theme.windowMargin * 3
rightPadding: MZTheme.theme.windowMargin * 3
rightInset: MZTheme.theme.windowMargin * 3
hasError: _searchBarHasError
MZTextField {
id: searchBar
objectName: "searchBarTextField"

onLengthChanged: text => model.invalidate()
onTextChanged: {
if (focus) {
_editCallback();
Accessible.editable: false
Accessible.searchEdit: true
Layout.fillWidth: true
focus: true

background: MZInputBackground {}
leftInset: MZTheme.theme.windowMargin * 3
leftPadding: MZTheme.theme.windowMargin * 3
rightPadding: MZTheme.theme.windowMargin * 3
rightInset: MZTheme.theme.windowMargin * 3
hasError: _searchBarHasError

onLengthChanged: text => model.invalidate()
onTextChanged: {
if (activeFocus) {
_editCallback();
}
}
}

MZIcon {
anchors {
left: parent.left
leftMargin: MZTheme.theme.hSpacing
verticalCenter: parent.verticalCenter
function handleTabPressed() {
let nextTabItem = _getNextTabItem();
if (!nextTabItem) {
nextTabItem = searchBar.nextItemInFocusChain();
}
nextTabItem.forceActiveFocus(Qt.TabFocusReason);
}
source: "qrc:/nebula/resources/search.svg"
sourceSize.height: MZTheme.theme.windowMargin
sourceSize.width: MZTheme.theme.windowMargin
opacity: parent.focus ? 1 : 0.8
}
}

MZContextualAlerts {
id: searchWarning
objectName: "searchBarError"
Layout.fillWidth: true
visible: _searchBarHasError

messages: [
{
type: "error",
message: MZI18n.ServersViewSearchNoResultsLabel,
visible: searchBar.hasError
Keys.onTabPressed: handleTabPressed()

MZIcon {
anchors {
left: parent.left
leftMargin: MZTheme.theme.hSpacing
verticalCenter: parent.verticalCenter
}
source: "qrc:/nebula/resources/search.svg"
sourceSize.height: MZTheme.theme.windowMargin
sourceSize.width: MZTheme.theme.windowMargin
opacity: parent.focus ? 1 : 0.8
}
]
}
}

MZContextualAlerts {
id: searchWarning
objectName: "searchBarError"
Layout.fillWidth: true
visible: _searchBarHasError

MZFilterProxyModel {
id: model
filterCallback: _filterProxyCallback
sortCallback: _sortProxyCallback
messages: [
{
type: "error",
message: MZI18n.ServersViewSearchNoResultsLabel,
visible: searchBar.hasError
}
]
}

MZFilterProxyModel {
id: model
filterCallback: _filterProxyCallback
sortCallback: _sortProxyCallback
}
}

function getProxyModel() {
Expand Down
Loading

0 comments on commit 0a0ffb6

Please sign in to comment.