Improve Contacts: sorting, searching and unread

ChatHandler::handleUnreadMsgs(...) deprecate 'id' field
  in favour of 'chat_id'
IdentityHandler::handleWildcard(...) reduce sleep time from 500 to 50 ms
IdentityHandler::handleWildcard(...) expose 'is_contact' to JSON API
Split contacts list delegate into GxsIdentityDelegate.qml
Update to QtQuick.Controls 2.0 because 1.4 is not supported anymore and
  2.1 is not available yet in Qt 5.7.1
ChatView.qml mark messages as read
Set refreshTimer.triggeredOnStart=true to improve reponsiveness of views
Contacts.qml use a Popup that is available in Controls 2.0 instead of a
  castrated Dialog to display full fingerprint
TrustedNodesView.qml check if locations[*].is_online is an array before
  attempting calling reduce
main.qml added menuentry to shutdown de core
main.qml added menuantry to search contact
This commit is contained in:
Gioacchino Mazzurco 2017-03-24 12:02:13 +01:00
parent 34dc1fac37
commit abe84a4f81
17 changed files with 409 additions and 141 deletions

View file

@ -17,8 +17,9 @@
*/
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Controls 2.0
import QtQuick.Dialogs 1.2
import QtQml.Models 2.2
import org.retroshare.qml_components.LibresapiLocalClient 1.0
Item
@ -26,6 +27,8 @@ Item
id: contactsView
property string own_gxs_id: ""
property string own_nick: ""
property bool searching: false
property var unreadMessages: ({})
Component.onCompleted: refreshOwn()
@ -33,7 +36,7 @@ Item
{
function refreshCallback(par)
{
locationsModel.json = par.response
gxsIdsModel.json = par.response
if(contactsView.own_gxs_id == "") refreshOwn()
}
rsApi.request("/identity/*/", "", refreshCallback)
@ -54,113 +57,174 @@ Item
function startChatCallback(par)
{
var chId = JSON.parse(par.response).data.chat_id
stackView.push({
item:"qrc:/qml/ChatView.qml",
properties: {chatId: chId}
})
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(...) */
function getChatId(from_gxs, to_gxs)
{
return from_gxs < to_gxs ? from_gxs + to_gxs : to_gxs + from_gxs
}
onFocusChanged: focus && refreshData()
JSONListModel
{
id: locationsModel
id: gxsIdsModel
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: 300
model: locationsModel.model
delegate: Item
height: contactsView.searching ?
parent.height - searchBox.height : parent.height
model: visualModel
anchors.top: contactsView.searching ? searchBox.bottom : parent.top
}
Rectangle
{
id: searchBox
visible: contactsView.searching
height: searchText.height
width: searchText.width
anchors.right: parent.right
anchors.top: parent.top
Image
{
height: 40
width: parent.width
id: searchIcon
height: searchText.height - 4
width: searchText.height - 4
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/qml/icons/edit-find.png"
}
MouseArea
{
anchors.fill: parent
onClicked:
{
console.log("Contacts view onclicked:", model.name,
model.gxs_id)
if(model.own) contactsView.own_gxs_id = model.gxs_id
else
{
var jsonData = { "own_gxs_hex": contactsView.own_gxs_id,
"remote_gxs_hex": model.gxs_id }
rsApi.request("/chat/initiate_distant_chat",
JSON.stringify(jsonData),
contactsView.startChatCallback)
}
}
Rectangle
{
id: colorHash
height: parent.height - 4
width: height
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 2
color: "white"
property int childHeight : height/2
Rectangle
{
color: '#' + model.gxs_id.substring(1, 9)
height: parent.childHeight
width: height
anchors.top: parent.top
anchors.left: parent.left
}
Rectangle
{
color: '#' + model.gxs_id.substring(9, 17)
height: parent.childHeight
width: height
anchors.top: parent.top
anchors.right: parent.right
}
Rectangle
{
color: '#' + model.gxs_id.substring(17, 25)
height: parent.childHeight
width: height
anchors.bottom: parent.bottom
anchors.left: parent.left
}
Rectangle
{
color: '#' + model.gxs_id.slice(-8)
height: parent.childHeight
width: height
anchors.bottom: parent.bottom
anchors.right: parent.right
}
MouseArea
{
anchors.fill: parent
onPressAndHold:
{
fingerPrintDialog.nick = model.name
fingerPrintDialog.gxs_id = model.gxs_id
fingerPrintDialog.visible = true
}
}
}
Text
{
id: nickText
color: model.own ? "blue" : "black"
text: model.name
anchors.left: colorHash.right
anchors.leftMargin: 5
anchors.verticalCenter: parent.verticalCenter
}
}
TextField
{
id: searchText
anchors.left: searchIcon.right
anchors.verticalCenter: parent.verticalCenter
onTextChanged: visualModel.resetSorting()
}
}
@ -179,11 +243,16 @@ Item
id: refreshTimer
interval: 5000
repeat: true
onTriggered: if(contactsView.visible) contactsView.refreshData()
triggeredOnStart: true
onTriggered:
if(contactsView.visible)
{
contactsView.refreshUnread()
contactsView.refreshData()
}
Component.onCompleted: start()
}
Dialog
{
id: createIdentityDialog
@ -207,14 +276,17 @@ Item
}
}
Dialog
Popup
{
id: fingerPrintDialog
visible: false
property string nick
property string gxs_id
title: nick + " fingerprint:"
standardButtons: StandardButton.NoButton
width: fingerPrintText.contentWidth + 20
height: fingerPrintText.contentHeight + 20
x: parent.x + parent.width/2 - width/2
y: parent.y + parent.height/2 - height/2
Text
{
id: fingerPrintText