Improve QML app Contacts view performances

Simplified sorting getting rid of complicated DelegateModel
Offload sorting work to another thread via WorkerScript
Get rid of polling and use token system instead
This commit is contained in:
Gioacchino Mazzurco 2017-04-03 21:51:03 +02:00
parent 7d9e89e3d2
commit 8e03fab8da
4 changed files with 166 additions and 154 deletions

View file

@ -28,58 +28,83 @@ Item
property string own_gxs_id: ""
property string own_nick: ""
property bool searching: false
property var unreadMessages: ({})
onSearchingChanged: !searching && contactsSortWorker.sendMessage({})
Component.onCompleted: refreshOwn()
Component.onCompleted: refreshAll()
onFocusChanged: focus && refreshAll()
function refreshData()
WorkerScript
{
function refreshCallback(par)
id: contactsSortWorker
source: "qrc:/qml/ContactSort.js"
onMessage: contactsListModel.json = JSON.stringify(messageObject)
}
function refreshAll()
{
refreshOwn()
refreshContacts()
refreshUnread()
}
function refreshContactsCallback(par)
{
console.log("contactsView.refreshContactsCB()", visible)
if (contactsListModel.model.count < 1)
contactsListModel.json = par.response
var token = JSON.parse(par.response).statetoken
mainWindow.registerToken(token, refreshContacts)
contactsSortWorker.sendMessage(
{'action': 'refreshContacts', 'response': par.response})
}
function refreshContacts()
{
console.log("contactsView.refreshContacts()", visible)
if(!visible) return
rsApi.request("/identity/*/", "", refreshContactsCallback)
}
function refreshOwnCallback(par)
{
console.log("contactsView.refreshOwnCallback(par)", visible)
var json = JSON.parse(par.response)
var token = json.statetoken
mainWindow.registerToken(token, refreshOwn)
if(json.data.length > 0)
{
gxsIdsModel.json = par.response
if(contactsView.own_gxs_id == "") refreshOwn()
contactsView.own_gxs_id = json.data[0].gxs_id
contactsView.own_nick = json.data[0].name
}
rsApi.request("/identity/*/", "", refreshCallback)
else createIdentityDialog.visible = true
}
function refreshOwn()
{
rsApi.request("/identity/own", "", function(par)
{
var json = JSON.parse(par.response)
if(json.data.length > 0)
{
contactsView.own_gxs_id = json.data[0].gxs_id
contactsView.own_nick = json.data[0].name
}
else createIdentityDialog.visible = true
})
console.log("contactsView.refreshOwn()", visible)
rsApi.request("/identity/own", "", refreshOwnCallback)
}
function refreshUnreadCallback(par)
{
console.log("contactsView.refreshUnreadCB()", visible)
var json = JSON.parse(par.response)
mainWindow.registerToken(json.statetoken, refreshUnread)
contactsSortWorker.sendMessage(
{'action': 'refreshUnread', 'response': par.response})
}
function refreshUnread()
{
console.log("contactsView.refreshUnread()", visible)
if(!visible) return
rsApi.request("/chat/unread_msgs", "", refreshUnreadCallback)
}
function startChatCallback(par)
{
var chId = JSON.parse(par.response).data.chat_id
stackView.push("qrc:/qml/ChatView.qml", {'chatId': chId})
}
function refreshUnread()
{
rsApi.request("/chat/unread_msgs", "", function(par)
{
var jsonData = JSON.parse(par.response).data
var dataLen = jsonData.length
if(JSON.stringify(unreadMessages) != JSON.stringify(jsonData))
{
unreadMessages = {}
for ( var i=0; i<dataLen; ++i)
{
var el = jsonData[i]
if(el.is_distant_chat_id)
unreadMessages[el.remote_author_id] = el.unread_count
}
visualModel.resetSorting()
}
})
}
/** This must be equivalent to
p3GxsTunnelService::makeGxsTunnelId(...) */
@ -88,116 +113,21 @@ Item
return from_gxs < to_gxs ? from_gxs + to_gxs : to_gxs + from_gxs
}
onFocusChanged: focus && refreshData()
JSONListModel
{
id: gxsIdsModel
id: contactsListModel
query: "$.data[*]"
}
DelegateModel
{
/* More documentation about this is available at:
* http://doc.qt.io/qt-5/qml-qtqml-models-delegatemodel.html
* http://doc.qt.io/qt-5/qtquick-tutorials-dynamicview-dynamicview4-example.html
* http://imaginativethinking.ca/use-qt-quicks-delegatemodelgroup/
*/
id: visualModel
model: gxsIdsModel.model
property var lessThan:
[
function(left, right)
{
var lfun = unreadMessages.hasOwnProperty(left.gxs_id) ?
unreadMessages[left.gxs_id] : 0
var rgun = unreadMessages.hasOwnProperty(right.gxs_id) ?
unreadMessages[right.gxs_id] : 0
if( lfun !== rgun ) return lfun > rgun
if(left.name !== right.name) return left.name < right.name
return left.gxs_id < right.gxs_id
},
function(left, right)
{
if(searchText.length > 0)
{
var mtc = searchText.text.toLowerCase()
var lfn = left.name.toLowerCase()
var rgn = right.name.toLowerCase()
var lfml = lfn.indexOf(mtc)
var rgml = rgn.indexOf(mtc)
if ( lfml !== rgml )
{
lfml = lfml >= 0 ? lfml : Number.MAX_VALUE
rgml = rgml >= 0 ? rgml : Number.MAX_VALUE
return lfml < rgml
}
}
return lessThan[0](left, right)
}
]
property int sortOrder: contactsView.searching ? 1 : 0
onSortOrderChanged: resetSorting()
property bool isSorting: false
function insertPosition(lessThan, item)
{
var lower = 0
var upper = items.count
while (lower < upper)
{
var middle = Math.floor(lower + (upper - lower) / 2)
var result = lessThan(item.model, items.get(middle).model);
if (result) upper = middle
else lower = middle + 1
}
return lower
}
function resetSorting() { items.setGroups(0, items.count, "unsorted") }
function sort()
{
while (unsortedItems.count > 0)
{
var item = unsortedItems.get(0)
var index = insertPosition(lessThan[visualModel.sortOrder],
item)
item.groups = ["items"]
items.move(item.itemsIndex, index)
}
}
items.includeByDefault: false
groups:
[
DelegateModelGroup
{
id: unsortedItems
name: "unsorted"
includeByDefault: true
onChanged: visualModel.sort()
}
]
delegate: GxsIdentityDelegate {}
}
ListView
{
id: locationsListView
width: parent.width
height: contactsView.searching ?
parent.height - searchBox.height : parent.height
model: visualModel
model: contactsListModel.model
anchors.top: contactsView.searching ? searchBox.bottom : parent.top
delegate: GxsIdentityDelegate {}
}
Rectangle
@ -224,7 +154,9 @@ Item
id: searchText
anchors.left: searchIcon.right
anchors.verticalCenter: parent.verticalCenter
onTextChanged: visualModel.resetSorting()
onTextChanged:
contactsSortWorker.sendMessage(
{'action': 'searchContact', 'sexp': text})
}
}
@ -238,21 +170,6 @@ Item
text: "Open Chat as: " + contactsView.own_nick + " " + contactsView.own_gxs_id
}
Timer
{
id: refreshTimer
interval: 5000
repeat: true
triggeredOnStart: true
onTriggered:
if(contactsView.visible)
{
contactsView.refreshUnread()
contactsView.refreshData()
}
Component.onCompleted: start()
}
Dialog
{
id: createIdentityDialog