Commit e601e3aa authored by Ronan's avatar Ronan

feat(ui): create a generic `SipAddressesView` component

parent f18b3f40
......@@ -325,13 +325,14 @@
<file>ui/modules/Linphone/Styles/Notifications/NotificationReceivedFileMessageStyle.qml</file>
<file>ui/modules/Linphone/Styles/Notifications/NotificationReceivedMessageStyle.qml</file>
<file>ui/modules/Linphone/Styles/qmldir</file>
<file>ui/modules/Linphone/Styles/SmartSearchBar/SmartSearchBarStyle.qml</file>
<file>ui/modules/Linphone/Styles/TelKeypad/TelKeypadStyle.qml</file>
<file>ui/modules/Linphone/Styles/Timeline/TimelineStyle.qml</file>
<file>ui/modules/Linphone/Styles/View/SipAddressesViewStyle.qml</file>
<file>ui/modules/Linphone/TelKeypad/TelKeypadButton.qml</file>
<file>ui/modules/Linphone/TelKeypad/TelKeypad.qml</file>
<file>ui/modules/Linphone/Timeline/Timeline.js</file>
<file>ui/modules/Linphone/Timeline/Timeline.qml</file>
<file>ui/modules/Linphone/View/SipAddressesView.qml</file>
<file>ui/scripts/LinphoneUtils/linphone-utils.js</file>
<file>ui/scripts/LinphoneUtils/qmldir</file>
<file>ui/scripts/Utils/port-tools.js</file>
......
......@@ -14,14 +14,14 @@ Item {
readonly property alias filter: searchField.text
property alias delegate: list.delegate
property alias header: list.header
property alias entryHeight: menu.entryHeight
property alias maxMenuHeight: menu.maxMenuHeight
property alias model: list.model
property alias placeholderText: searchField.placeholderText
default property alias _content: menu._content
readonly property var view: _content[0]
property bool _isOpen: false
// ---------------------------------------------------------------------------
......@@ -50,6 +50,7 @@ Item {
}
function _filter (text) {
var model = searchBox.view.model
Utils.assert(model.setFilter != null, '`model.setFilter` must be defined.')
model.setFilter(text)
}
......@@ -97,13 +98,18 @@ Item {
Keys.forwardTo: searchField
onClosed: searchBox.closeMenu()
}
ScrollableListView {
id: list
Binding {
target: searchBox.view
property: 'width'
value: searchField.width
}
headerPositioning: header ? ListView.OverlayHeader : ListView.InlineFooter
width: searchField.width
}
Binding {
target: searchBox.view
property: 'headerPositioning'
value: searchBox.view.header ? ListView.OverlayHeader : ListView.InlineFooter
}
}
......
......@@ -42,10 +42,6 @@ Item {
var list = _content[0]
Utils.assert(list != null, 'No list found.')
Utils.assert(
Utils.qmlTypeof(list, 'QQuickListView') || Utils.qmlTypeof(list, 'ScrollableListView'),
'No list view parameter.'
)
var height = list.count * entryHeight
......
import QtQuick 2.7
import QtQuick.Layouts 1.3
import Common 1.0
import Linphone 1.0
import Linphone.Styles 1.0
// =============================================================================
......@@ -12,10 +10,6 @@ SearchBox {
// ---------------------------------------------------------------------------
readonly property string interpretableSipAddress: SipAddressesModel.interpretUrl(
searchBox.filter
)
readonly property alias isOpen: searchBox._isOpen
// ---------------------------------------------------------------------------
......@@ -29,256 +23,49 @@ SearchBox {
// ---------------------------------------------------------------------------
onEnterPressed: interpretableSipAddress.length > 0 && searchBox.launchCall(interpretableSipAddress)
entryHeight: SipAddressesViewStyle.entry.height
// ---------------------------------------------------------------------------
// Header.
// ---------------------------------------------------------------------------
header: MouseArea {
height: {
var height = SmartSearchBarStyle.header.addButtonHeight
return defaultContact.visible ? height + searchBox.entryHeight : height
}
width: parent.width
// Workaround to handle mouse.
// Without it, the mouse can be given to items list when mouse is hover header.
hoverEnabled: true
Column {
anchors.fill: parent
spacing: 0
// -----------------------------------------------------------------------
// Default contact.
// -----------------------------------------------------------------------
Loader {
id: defaultContact
height: searchBox.entryHeight
width: parent.width
visible: interpretableSipAddress.length > 0
sourceComponent: Rectangle {
anchors.fill: parent
color: SmartSearchBarStyle.entry.color.normal
RowLayout {
anchors {
fill: parent
rightMargin: SmartSearchBarStyle.entry.rightMargin
}
spacing: 0
onEnterPressed: view.interpretableSipAddress.length > 0 && searchBox.launchCall(interpretableSipAddress)
Contact {
id: contact
Layout.fillHeight: true
Layout.fillWidth: true
entry: ({
sipAddress: interpretableSipAddress
})
}
ActionBar {
iconSize: SmartSearchBarStyle.entry.iconSize
ActionButton {
icon: 'video_call'
onClicked: {
searchBox.closeMenu()
searchBox.launchVideoCall(interpretableSipAddress)
}
}
// ---------------------------------------------------------------------------
ActionButton {
icon: 'call'
onClicked: {
searchBox.closeMenu()
searchBox.launchCall(interpretableSipAddress)
}
}
SipAddressesView {
id: view
ActionButton {
icon: 'chat'
onClicked: {
searchBox.closeMenu()
searchBox.launchChat(interpretableSipAddress)
}
}
}
}
}
actions: [{
icon: 'video_call',
handler: function (entry) {
searchBox.closeMenu()
searchBox.launchVideoCall(entry.sipAddress)
}
// -----------------------------------------------------------------------
// Add contact button.
// -----------------------------------------------------------------------
MouseArea {
id: addContactButton
height: SmartSearchBarStyle.header.addButtonHeight
width: parent.width
onClicked: {
searchBox.closeMenu()
searchBox.addContact(interpretableSipAddress)
}
Rectangle {
anchors.fill: parent
color: parent.pressed
? SmartSearchBarStyle.header.color.pressed
: SmartSearchBarStyle.header.color.normal
Text {
anchors {
left: parent.left
leftMargin: SmartSearchBarStyle.header.leftMargin
verticalCenter: parent.verticalCenter
}
font {
bold: true
pointSize: SmartSearchBarStyle.header.text.fontSize
}
color: addContactButton.pressed
? SmartSearchBarStyle.header.text.color.pressed
: SmartSearchBarStyle.header.text.color.normal
text: qsTr('addContact')
}
Icon {
anchors {
right: parent.right
rightMargin: SmartSearchBarStyle.header.rightMargin
verticalCenter: parent.verticalCenter
}
icon: 'contact_add'
iconSize: SmartSearchBarStyle.header.iconSize
}
}
}, {
icon: 'call',
handler: function (entry) {
searchBox.closeMenu()
searchBox.launchCall(entry.sipAddress)
}
}
}
// ---------------------------------------------------------------------------
// Entries.
// ---------------------------------------------------------------------------
delegate: Rectangle {
id: sipAddressEntry
color: SmartSearchBarStyle.entry.color.normal
height: searchBox.entryHeight
width: parent ? parent.width : 0
Rectangle {
id: indicator
anchors.left: parent.left
color: 'transparent'
height: parent.height
width: SmartSearchBarStyle.entry.indicator.width
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
RowLayout {
anchors {
fill: parent
rightMargin: SmartSearchBarStyle.entry.rightMargin
}
spacing: 0
// -------------------------------------------------------------------
// Contact or address info.
// -------------------------------------------------------------------
Contact {
Layout.fillHeight: true
Layout.fillWidth: true
entry: $sipAddress
MouseArea {
anchors.fill: parent
cursorShape: containsMouse
? Qt.PointingHandCursor
: Qt.ArrowCursor
hoverEnabled: true
onClicked: {
searchBox.closeMenu()
searchBox.entryClicked($sipAddress)
}
}
}
// -------------------------------------------------------------------
// Actions
// -------------------------------------------------------------------
ActionBar {
iconSize: SmartSearchBarStyle.entry.iconSize
ActionButton {
icon: 'video_call'
onClicked: {
searchBox.closeMenu()
searchBox.launchVideoCall($sipAddress.sipAddress)
}
}
ActionButton {
icon: 'call'
onClicked: {
searchBox.closeMenu()
searchBox.launchCall($sipAddress.sipAddress)
}
}
ActionButton {
icon: 'chat'
onClicked: {
searchBox.closeMenu()
searchBox.launchChat($sipAddress.sipAddress)
}
}
}
}, {
icon: 'chat',
handler: function (entry) {
searchBox.closeMenu()
searchBox.launchChat(entry.sipAddress)
}
}
// Separator.
Rectangle {
color: SmartSearchBarStyle.entry.separator.color
height: SmartSearchBarStyle.entry.separator.height
width: parent.width
}
}]
// -------------------------------------------------------------------------
headerButtonDescription: qsTr('addContact')
headerButtonIcon: 'contact_add'
headerButtonAction: (function (sipAddress) {
searchBox.closeMenu()
searchBox.addContact(sipAddress)
})
states: State {
when: mouseArea.containsMouse
genSipAddress: searchBox.filter
PropertyChanges {
color: SmartSearchBarStyle.entry.color.hovered
target: sipAddressEntry
}
PropertyChanges {
color: SmartSearchBarStyle.entry.indicator.color
target: indicator
}
onEntryClicked: {
searchBox.closeMenu()
searchBox.entryClicked(entry)
}
}
}
......@@ -7,12 +7,13 @@ import Common 1.0
QtObject {
property QtObject entry: QtObject {
property int rightMargin: 10
property int height: 50
property int iconSize: 36
property int rightMargin: 10
property QtObject color: QtObject {
property color normal: Colors.k
property color hovered: Colors.y
property color normal: Colors.k
}
property QtObject indicator: QtObject {
......@@ -27,11 +28,14 @@ QtObject {
}
property QtObject header: QtObject {
property int addButtonHeight: 40
property int iconSize: 22
property int leftMargin: 20
property int rightMargin: 10
property QtObject button: QtObject {
property int height: 40
}
property QtObject color: QtObject {
property color normal: Colors.j
property color pressed: Colors.i
......
......@@ -28,8 +28,8 @@ singleton NotificationReceivedCallStyle 1.0 Notifications/NotificationRec
singleton NotificationReceivedMessageStyle 1.0 Notifications/NotificationReceivedMessageStyle.qml
singleton NotificationReceivedFileMessageStyle 1.0 Notifications/NotificationReceivedFileMessageStyle.qml
singleton SmartSearchBarStyle 1.0 SmartSearchBar/SmartSearchBarStyle.qml
singleton TelKeypadStyle 1.0 TelKeypad/TelKeypadStyle.qml
singleton TimelineStyle 1.0 Timeline/TimelineStyle.qml
singleton SipAddressesViewStyle 1.0 View/SipAddressesViewStyle.qml
import QtQuick 2.7
import QtQuick.Layouts 1.3
import Common 1.0
import Linphone 1.0
import Linphone.Styles 1.0
// =============================================================================
ScrollableListView {
id: sipAddressesView
// ---------------------------------------------------------------------------
// Contains a list of: {
// icon: 'string',
// handler: function () { ... }
// }
property var actions: []
property string genSipAddress
// Optional parameters.
property string headerButtonDescription
property string headerButtonIcon
property var headerButtonAction
readonly property string interpretableSipAddress: SipAddressesModel.interpretUrl(
genSipAddress
)
// ---------------------------------------------------------------------------
signal entryClicked (var entry)
// ---------------------------------------------------------------------------
// Header.
// ---------------------------------------------------------------------------
header: MouseArea {
height: {
var height = headerButton.visible ? SipAddressesViewStyle.header.button.height : 0
if (defaultContact.visible) {
height += SipAddressesViewStyle.entry.height
}
return height
}
width: parent.width
// Workaround to handle mouse.
// Without it, the mouse can be given to items list when mouse is hover header.
hoverEnabled: true
Column {
anchors.fill: parent
spacing: 0
// -----------------------------------------------------------------------
// Default contact.
// -----------------------------------------------------------------------
Loader {
id: defaultContact
height: SipAddressesViewStyle.entry.height
width: parent.width
visible: sipAddressesView.interpretableSipAddress.length > 0
sourceComponent: Rectangle {
anchors.fill: parent
color: SipAddressesViewStyle.entry.color.normal
RowLayout {
anchors {
fill: parent
rightMargin: SipAddressesViewStyle.entry.rightMargin
}
spacing: 0
Contact {
id: contact
Layout.fillHeight: true
Layout.fillWidth: true
entry: ({
sipAddress: sipAddressesView.interpretableSipAddress
})
}
ActionBar {
iconSize: SipAddressesViewStyle.entry.iconSize
Repeater {
model: sipAddressesView.actions
ActionButton {
icon: modelData.icon
onClicked: sipAddressesView.actions[index].handler({
sipAddress: sipAddressesView.interpretableSipAddress
})
}
}
}
}
}
}
// -----------------------------------------------------------------------
// Header button.
// -----------------------------------------------------------------------
MouseArea {
id: headerButton
height: SipAddressesViewStyle.header.button.height
width: parent.width
visible: !!sipAddressesView.headerButtonAction
onClicked: sipAddressesView.headerButtonAction(sipAddressesView.interpretableSipAddress)
Rectangle {
anchors.fill: parent
color: parent.pressed
? SipAddressesViewStyle.header.color.pressed
: SipAddressesViewStyle.header.color.normal
Text {
anchors {
left: parent.left
leftMargin: SipAddressesViewStyle.header.leftMargin
verticalCenter: parent.verticalCenter
}
font {
bold: true
pointSize: SipAddressesViewStyle.header.text.fontSize
}
color: headerButton.pressed
? SipAddressesViewStyle.header.text.color.pressed
: SipAddressesViewStyle.header.text.color.normal
text: sipAddressesView.headerButtonDescription
}
Icon {
anchors {
right: parent.right
rightMargin: SipAddressesViewStyle.header.rightMargin
verticalCenter: parent.verticalCenter
}
icon: sipAddressesView.headerButtonIcon
iconSize: SipAddressesViewStyle.header.iconSize
visible: icon.length > 0
}
}
}
}
}
// ---------------------------------------------------------------------------
// Entries.
// ---------------------------------------------------------------------------
delegate: Rectangle {
id: sipAddressEntry
color: SipAddressesViewStyle.entry.color.normal
height: SipAddressesViewStyle.entry.height
width: parent ? parent.width : 0
Rectangle {
id: indicator
anchors.left: parent.left
color: 'transparent'
height: parent.height
width: SipAddressesViewStyle.entry.indicator.width
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
RowLayout {
anchors {
fill: parent
rightMargin: SipAddressesViewStyle.entry.rightMargin
}
spacing: 0
// ---------------------------------------------------------------------
// Contact or address info.
// ---------------------------------------------------------------------
Contact {
Layout.fillHeight: true
Layout.fillWidth: true
entry: $sipAddress
MouseArea {
anchors.fill: parent
cursorShape: containsMouse
? Qt.PointingHandCursor
: Qt.ArrowCursor
hoverEnabled: true
onClicked: sipAddressesView.entryClicked($sipAddress)
}
}
// ---------------------------------------------------------------------
// Actions
// ---------------------------------------------------------------------
ActionBar {
iconSize: SipAddressesViewStyle.entry.iconSize
Repeater {
model: sipAddressesView.actions
ActionButton {
icon: modelData.icon
onClicked: sipAddressesView.actions[index].handler($sipAddress)
}
}
}
}
}
// Separator.
Rectangle {
color: SipAddressesViewStyle.entry.separator.color
height: SipAddressesViewStyle.entry.separator.height
width: parent.width
}
// -------------------------------------------------------------------------
states: State {
when: mouseArea.containsMouse
PropertyChanges {
color: SipAddressesViewStyle.entry.color.hovered
target: sipAddressEntry
}
PropertyChanges {
color: SipAddressesViewStyle.entry.indicator.color
target: indicator
}
}
}
// ---------------------------------------------------------------------------
// Model.
// ---------------------------------------------------------------------------
model: SipAddressesProxyModel {}
}
......@@ -150,12 +150,9 @@ ApplicationWindow {
Layout.fillWidth: true
entryHeight: MainWindowStyle.searchBox.entryHeight
maxMenuHeight: MainWindowStyle.searchBox.maxHeight
placeholderText: qsTr('mainSearchBarPlaceholder')