Make Window transparent (TODO? remove border and implement custom top bar and resize/move managment).
Add Javascript utils.
This commit is contained in:
parent
5a0dd7216e
commit
bfbc6e7a0c
6 changed files with 1003 additions and 239 deletions
|
|
@ -8,10 +8,11 @@ import QtQuick.Controls 2.2 as Control
|
||||||
|
|
||||||
import Linphone
|
import Linphone
|
||||||
|
|
||||||
Item {
|
Rectangle {
|
||||||
id: mainItem
|
id: mainItem
|
||||||
property alias titleContent : titleLayout.children
|
property alias titleContent : titleLayout.children
|
||||||
property alias centerContent : centerLayout.children
|
property alias centerContent : centerLayout.children
|
||||||
|
color: DefaultStyle.grey_0
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.rightMargin: 40 * DefaultStyle.dp
|
anchors.rightMargin: 40 * DefaultStyle.dp
|
||||||
anchors.leftMargin: 119 * DefaultStyle.dp
|
anchors.leftMargin: 119 * DefaultStyle.dp
|
||||||
|
|
|
||||||
|
|
@ -80,248 +80,274 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Item{
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
// spacing: 30
|
ColumnLayout{
|
||||||
anchors.topMargin: 18 * DefaultStyle.dp
|
anchors.fill: parent
|
||||||
VerticalTabBar {
|
spacing: 0
|
||||||
id: tabbar
|
Rectangle{
|
||||||
Layout.fillHeight: true
|
Layout.fillWidth: true
|
||||||
model: [
|
Layout.fillHeight: true
|
||||||
{icon: AppIcons.phone, selectedIcon: AppIcons.phoneSelected, label: qsTr("Appels")},
|
color: DefaultStyle.grey_0
|
||||||
{icon: AppIcons.adressBook, selectedIcon: AppIcons.adressBookSelected, label: qsTr("Contacts")},
|
}
|
||||||
{icon: AppIcons.chatTeardropText, selectedIcon: AppIcons.chatTeardropTextSelected, label: qsTr("Conversations")},
|
RowLayout{
|
||||||
{icon: AppIcons.usersThree, selectedIcon: AppIcons.usersThreeSelected, label: qsTr("Réunions")}
|
Layout.fillWidth: true
|
||||||
]
|
Layout.preferredHeight: tabbar.cornerRadius
|
||||||
}
|
spacing: 0
|
||||||
ColumnLayout {
|
Item{// Transparent corner
|
||||||
Layout.fillWidth: true
|
Layout.fillHeight: true
|
||||||
Layout.fillHeight: true
|
Layout.preferredWidth: tabbar.cornerRadius
|
||||||
RowLayout {
|
}
|
||||||
id: topRow
|
Rectangle{
|
||||||
Layout.leftMargin: 25 * DefaultStyle.dp
|
|
||||||
Layout.rightMargin: 41 * DefaultStyle.dp
|
|
||||||
SearchBar {
|
|
||||||
id: magicSearchBar
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
placeholderText: qsTr("Rechercher un contact, appeler ou envoyer un message...")
|
Layout.fillHeight: true
|
||||||
onTextChanged: {
|
color: DefaultStyle.grey_0
|
||||||
if (text.length != 0) listPopup.open()
|
|
||||||
else listPopup.close()
|
|
||||||
}
|
|
||||||
Popup {
|
|
||||||
id: listPopup
|
|
||||||
width: magicSearchList.width + listPopup.rightPadding + listPopup.leftPadding
|
|
||||||
height: Math.min(magicSearchList.contentHeight, 300 * DefaultStyle.dp + topPadding + bottomPadding)
|
|
||||||
y: magicSearchBar.height
|
|
||||||
closePolicy: Popup.NoAutoClose
|
|
||||||
topPadding: 10 * DefaultStyle.dp
|
|
||||||
bottomPadding: 10 * DefaultStyle.dp
|
|
||||||
rightPadding: 10 * DefaultStyle.dp
|
|
||||||
leftPadding: 10 * DefaultStyle.dp
|
|
||||||
background: Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
Rectangle {
|
|
||||||
id: popupBg
|
|
||||||
radius: 15 * DefaultStyle.dp
|
|
||||||
anchors.fill: parent
|
|
||||||
// width: magicSearchList.width + listPopup.rightPadding + listPopup.leftPadding
|
|
||||||
}
|
|
||||||
MultiEffect {
|
|
||||||
source: popupBg
|
|
||||||
anchors.fill: popupBg
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowBlur: 1
|
|
||||||
shadowColor: DefaultStyle.grey_1000
|
|
||||||
shadowOpacity: 0.1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Control.ScrollBar {
|
|
||||||
id: scrollbar
|
|
||||||
height: parent.height
|
|
||||||
anchors.right: parent.right
|
|
||||||
}
|
|
||||||
ContactsList {
|
|
||||||
id: magicSearchList
|
|
||||||
visible: magicSearchBar.text.length != 0
|
|
||||||
height: Math.min(contentHeight, listPopup.height)
|
|
||||||
width: magicSearchBar.width
|
|
||||||
initialHeadersVisible: false
|
|
||||||
contactMenuVisible: false
|
|
||||||
model: MagicSearchProxy {
|
|
||||||
searchText: magicSearchBar.text.length === 0 ? "*" : magicSearchBar.text
|
|
||||||
aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
|
|
||||||
}
|
|
||||||
Control.ScrollBar.vertical: scrollbar
|
|
||||||
header: ColumnLayout {
|
|
||||||
width: magicSearchList.width
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 10 * DefaultStyle.dp
|
|
||||||
Avatar {
|
|
||||||
Layout.preferredWidth: 45 * DefaultStyle.dp
|
|
||||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
|
||||||
address: sipAddr.text
|
|
||||||
}
|
|
||||||
ColumnLayout {
|
|
||||||
Text {
|
|
||||||
text: magicSearchBar.text
|
|
||||||
font {
|
|
||||||
pixelSize: 14 * DefaultStyle.dp
|
|
||||||
weight: 700 * DefaultStyle.dp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
id: sipAddr
|
|
||||||
text: UtilsCpp.generateLinphoneSipAddress(magicSearchBar.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
background: Item{}
|
|
||||||
Layout.preferredWidth: 24 * DefaultStyle.dp
|
|
||||||
Layout.preferredHeight: 24 * DefaultStyle.dp
|
|
||||||
contentItem: Image {
|
|
||||||
width: 24 * DefaultStyle.dp
|
|
||||||
height: 24 * DefaultStyle.dp
|
|
||||||
source: AppIcons.phone
|
|
||||||
}
|
|
||||||
onClicked: {
|
|
||||||
var callObj = UtilsCpp.createCall(sipAddr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: DefaultStyle.main2_200
|
|
||||||
pressedColor: DefaultStyle.main2_400
|
|
||||||
background: Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: DefaultStyle.main2_200
|
|
||||||
}
|
|
||||||
contentItem: RowLayout {
|
|
||||||
spacing: 10 * DefaultStyle.dp
|
|
||||||
Image {
|
|
||||||
source: AppIcons.userPlus
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: qsTr("Ajouter ce contact")
|
|
||||||
font {
|
|
||||||
pixelSize: 14 * DefaultStyle.dp
|
|
||||||
weight: 700 * DefaultStyle.dp
|
|
||||||
capitalization: Font.AllUppercase
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {Layout.fillWidth: true}
|
|
||||||
}
|
|
||||||
onClicked: {
|
|
||||||
var currentItem = mainStackLayout.children[mainStackLayout.currentIndex]
|
|
||||||
listPopup.close()
|
|
||||||
currentItem.createContact(magicSearchBar.text, sipAddr.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delegateButtons: Button {
|
|
||||||
Layout.preferredWidth: 24 * DefaultStyle.dp
|
|
||||||
Layout.preferredHeight: 24 * DefaultStyle.dp
|
|
||||||
background: Item{}
|
|
||||||
contentItem: Image {
|
|
||||||
anchors.fill: parent
|
|
||||||
width: 24 * DefaultStyle.dp
|
|
||||||
height: 24 * DefaultStyle.dp
|
|
||||||
source: AppIcons.phone
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PopupButton {
|
|
||||||
id: avatarButton
|
|
||||||
AccountProxy{
|
|
||||||
id: accountProxy
|
|
||||||
//property bool haveAvatar: defaultAccount && defaultAccount.core.pictureUri || false
|
|
||||||
}
|
|
||||||
background.visible: false
|
|
||||||
Layout.preferredWidth: 54 * DefaultStyle.dp
|
|
||||||
Layout.preferredHeight: width
|
|
||||||
contentItem: Avatar {
|
|
||||||
id: avatar
|
|
||||||
height: avatarButton.height
|
|
||||||
width: avatarButton.width
|
|
||||||
account: accountProxy.defaultAccount
|
|
||||||
}
|
|
||||||
popup.x: width - popup.width
|
|
||||||
popup.padding: 0
|
|
||||||
popup.contentItem: ColumnLayout {
|
|
||||||
Accounts {
|
|
||||||
id: accounts
|
|
||||||
onAddAccountRequest: mainItem.addAccountRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PopupButton {
|
|
||||||
id: settingsButton
|
|
||||||
Layout.preferredWidth: 24 * DefaultStyle.dp
|
|
||||||
Layout.preferredHeight: 24 * DefaultStyle.dp
|
|
||||||
popup.x: width - popup.width
|
|
||||||
popup.width: 271 * DefaultStyle.dp
|
|
||||||
popup.contentItem: ColumnLayout {
|
|
||||||
spacing: 20 * DefaultStyle.dp
|
|
||||||
IconLabelButton {
|
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
|
||||||
iconSize: 32 * DefaultStyle.dp
|
|
||||||
text: qsTr("Mon compte")
|
|
||||||
iconSource: AppIcons.manageProfile
|
|
||||||
onClicked: console.log("TODO : manage profile")
|
|
||||||
}
|
|
||||||
IconLabelButton {
|
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
|
||||||
iconSize: 32 * DefaultStyle.dp
|
|
||||||
text: qsTr("Paramètres")
|
|
||||||
iconSource: AppIcons.settings
|
|
||||||
}
|
|
||||||
IconLabelButton {
|
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
|
||||||
iconSize: 32 * DefaultStyle.dp
|
|
||||||
text: qsTr("Enregistrements")
|
|
||||||
iconSource: AppIcons.micro
|
|
||||||
}
|
|
||||||
IconLabelButton {
|
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
|
||||||
iconSize: 32 * DefaultStyle.dp
|
|
||||||
text: qsTr("Aide")
|
|
||||||
iconSource: AppIcons.question
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 1 * DefaultStyle.dp
|
|
||||||
color: DefaultStyle.main2_400
|
|
||||||
}
|
|
||||||
IconLabelButton {
|
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
|
||||||
iconSize: 32 * DefaultStyle.dp
|
|
||||||
text: qsTr("Ajouter un compte")
|
|
||||||
iconSource: AppIcons.plusCircle
|
|
||||||
onClicked: mainItem.addAccountRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StackLayout {
|
}
|
||||||
// width: parent.width
|
|
||||||
// height: parent.height
|
RowLayout {
|
||||||
id: mainStackLayout
|
anchors.fill: parent
|
||||||
currentIndex: tabbar.currentIndex
|
// spacing: 30
|
||||||
|
anchors.topMargin: 18 * DefaultStyle.dp
|
||||||
CallPage {
|
VerticalTabBar {
|
||||||
id: callPage
|
id: tabbar
|
||||||
|
Layout.fillHeight: true
|
||||||
|
model: [
|
||||||
|
{icon: AppIcons.phone, selectedIcon: AppIcons.phoneSelected, label: qsTr("Appels")},
|
||||||
|
{icon: AppIcons.adressBook, selectedIcon: AppIcons.adressBookSelected, label: qsTr("Contacts")},
|
||||||
|
{icon: AppIcons.chatTeardropText, selectedIcon: AppIcons.chatTeardropTextSelected, label: qsTr("Conversations")},
|
||||||
|
{icon: AppIcons.usersThree, selectedIcon: AppIcons.usersThreeSelected, label: qsTr("Réunions")}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
RowLayout {
|
||||||
|
id: topRow
|
||||||
|
Layout.leftMargin: 25 * DefaultStyle.dp
|
||||||
|
Layout.rightMargin: 41 * DefaultStyle.dp
|
||||||
|
SearchBar {
|
||||||
|
id: magicSearchBar
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: qsTr("Rechercher un contact, appeler ou envoyer un message...")
|
||||||
|
onTextChanged: {
|
||||||
|
if (text.length != 0) listPopup.open()
|
||||||
|
else listPopup.close()
|
||||||
|
}
|
||||||
|
Popup {
|
||||||
|
id: listPopup
|
||||||
|
width: magicSearchList.width + listPopup.rightPadding + listPopup.leftPadding
|
||||||
|
height: Math.min(magicSearchList.contentHeight, 300 * DefaultStyle.dp + topPadding + bottomPadding)
|
||||||
|
y: magicSearchBar.height
|
||||||
|
closePolicy: Popup.NoAutoClose
|
||||||
|
topPadding: 10 * DefaultStyle.dp
|
||||||
|
bottomPadding: 10 * DefaultStyle.dp
|
||||||
|
rightPadding: 10 * DefaultStyle.dp
|
||||||
|
leftPadding: 10 * DefaultStyle.dp
|
||||||
|
background: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
Rectangle {
|
||||||
|
id: popupBg
|
||||||
|
radius: 15 * DefaultStyle.dp
|
||||||
|
anchors.fill: parent
|
||||||
|
// width: magicSearchList.width + listPopup.rightPadding + listPopup.leftPadding
|
||||||
|
}
|
||||||
|
MultiEffect {
|
||||||
|
source: popupBg
|
||||||
|
anchors.fill: popupBg
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowBlur: 1
|
||||||
|
shadowColor: DefaultStyle.grey_1000
|
||||||
|
shadowOpacity: 0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Control.ScrollBar {
|
||||||
|
id: scrollbar
|
||||||
|
height: parent.height
|
||||||
|
anchors.right: parent.right
|
||||||
|
}
|
||||||
|
ContactsList {
|
||||||
|
id: magicSearchList
|
||||||
|
visible: magicSearchBar.text.length != 0
|
||||||
|
height: Math.min(contentHeight, listPopup.height)
|
||||||
|
width: magicSearchBar.width
|
||||||
|
initialHeadersVisible: false
|
||||||
|
contactMenuVisible: false
|
||||||
|
model: MagicSearchProxy {
|
||||||
|
searchText: magicSearchBar.text.length === 0 ? "*" : magicSearchBar.text
|
||||||
|
aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
|
||||||
|
}
|
||||||
|
Control.ScrollBar.vertical: scrollbar
|
||||||
|
header: ColumnLayout {
|
||||||
|
width: magicSearchList.width
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10 * DefaultStyle.dp
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: 45 * DefaultStyle.dp
|
||||||
|
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||||
|
address: sipAddr.text
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Text {
|
||||||
|
text: magicSearchBar.text
|
||||||
|
font {
|
||||||
|
pixelSize: 14 * DefaultStyle.dp
|
||||||
|
weight: 700 * DefaultStyle.dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
id: sipAddr
|
||||||
|
text: UtilsCpp.generateLinphoneSipAddress(magicSearchBar.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
background: Item{}
|
||||||
|
Layout.preferredWidth: 24 * DefaultStyle.dp
|
||||||
|
Layout.preferredHeight: 24 * DefaultStyle.dp
|
||||||
|
contentItem: Image {
|
||||||
|
width: 24 * DefaultStyle.dp
|
||||||
|
height: 24 * DefaultStyle.dp
|
||||||
|
source: AppIcons.phone
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
var callObj = UtilsCpp.createCall(sipAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: DefaultStyle.main2_200
|
||||||
|
pressedColor: DefaultStyle.main2_400
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: DefaultStyle.main2_200
|
||||||
|
}
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: 10 * DefaultStyle.dp
|
||||||
|
Image {
|
||||||
|
source: AppIcons.userPlus
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: qsTr("Ajouter ce contact")
|
||||||
|
font {
|
||||||
|
pixelSize: 14 * DefaultStyle.dp
|
||||||
|
weight: 700 * DefaultStyle.dp
|
||||||
|
capitalization: Font.AllUppercase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {Layout.fillWidth: true}
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
var currentItem = mainStackLayout.children[mainStackLayout.currentIndex]
|
||||||
|
listPopup.close()
|
||||||
|
currentItem.createContact(magicSearchBar.text, sipAddr.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegateButtons: Button {
|
||||||
|
Layout.preferredWidth: 24 * DefaultStyle.dp
|
||||||
|
Layout.preferredHeight: 24 * DefaultStyle.dp
|
||||||
|
background: Item{}
|
||||||
|
contentItem: Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
width: 24 * DefaultStyle.dp
|
||||||
|
height: 24 * DefaultStyle.dp
|
||||||
|
source: AppIcons.phone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PopupButton {
|
||||||
|
id: avatarButton
|
||||||
|
AccountProxy{
|
||||||
|
id: accountProxy
|
||||||
|
//property bool haveAvatar: defaultAccount && defaultAccount.core.pictureUri || false
|
||||||
|
}
|
||||||
|
background.visible: false
|
||||||
|
Layout.preferredWidth: 54 * DefaultStyle.dp
|
||||||
|
Layout.preferredHeight: width
|
||||||
|
contentItem: Avatar {
|
||||||
|
id: avatar
|
||||||
|
height: avatarButton.height
|
||||||
|
width: avatarButton.width
|
||||||
|
account: accountProxy.defaultAccount
|
||||||
|
}
|
||||||
|
popup.x: width - popup.width
|
||||||
|
popup.padding: 0
|
||||||
|
popup.contentItem: ColumnLayout {
|
||||||
|
Accounts {
|
||||||
|
id: accounts
|
||||||
|
onAddAccountRequest: mainItem.addAccountRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PopupButton {
|
||||||
|
id: settingsButton
|
||||||
|
Layout.preferredWidth: 24 * DefaultStyle.dp
|
||||||
|
Layout.preferredHeight: 24 * DefaultStyle.dp
|
||||||
|
popup.x: width - popup.width
|
||||||
|
popup.width: 271 * DefaultStyle.dp
|
||||||
|
popup.contentItem: ColumnLayout {
|
||||||
|
spacing: 20 * DefaultStyle.dp
|
||||||
|
IconLabelButton {
|
||||||
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
|
iconSize: 32 * DefaultStyle.dp
|
||||||
|
text: qsTr("Mon compte")
|
||||||
|
iconSource: AppIcons.manageProfile
|
||||||
|
onClicked: console.log("TODO : manage profile")
|
||||||
|
}
|
||||||
|
IconLabelButton {
|
||||||
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
|
iconSize: 32 * DefaultStyle.dp
|
||||||
|
text: qsTr("Paramètres")
|
||||||
|
iconSource: AppIcons.settings
|
||||||
|
}
|
||||||
|
IconLabelButton {
|
||||||
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
|
iconSize: 32 * DefaultStyle.dp
|
||||||
|
text: qsTr("Enregistrements")
|
||||||
|
iconSource: AppIcons.micro
|
||||||
|
}
|
||||||
|
IconLabelButton {
|
||||||
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
|
iconSize: 32 * DefaultStyle.dp
|
||||||
|
text: qsTr("Aide")
|
||||||
|
iconSource: AppIcons.question
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1 * DefaultStyle.dp
|
||||||
|
color: DefaultStyle.main2_400
|
||||||
|
}
|
||||||
|
IconLabelButton {
|
||||||
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
|
iconSize: 32 * DefaultStyle.dp
|
||||||
|
text: qsTr("Ajouter un compte")
|
||||||
|
iconSource: AppIcons.plusCircle
|
||||||
|
onClicked: mainItem.addAccountRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StackLayout {
|
||||||
|
// width: parent.width
|
||||||
|
// height: parent.height
|
||||||
|
id: mainStackLayout
|
||||||
|
currentIndex: tabbar.currentIndex
|
||||||
|
|
||||||
|
CallPage {
|
||||||
|
id: callPage
|
||||||
|
}
|
||||||
|
ContactPage{}
|
||||||
|
//ConversationPage{}
|
||||||
|
//MeetingPage{}
|
||||||
}
|
}
|
||||||
ContactPage{}
|
|
||||||
//ConversationPage{}
|
|
||||||
//MeetingPage{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Window {
|
||||||
// TODO : handle this bool when security mode is implemented
|
// TODO : handle this bool when security mode is implemented
|
||||||
property bool firstConnection: true
|
property bool firstConnection: true
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
function goToNewCall() {
|
function goToNewCall() {
|
||||||
mainWindowStackView.replace(mainPage, StackView.Immediate)
|
mainWindowStackView.replace(mainPage, StackView.Immediate)
|
||||||
mainWindowStackView.currentItem.goToNewCall()
|
mainWindowStackView.currentItem.goToNewCall()
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
|
||||||
view/Page/Main/CallPage.qml
|
view/Page/Main/CallPage.qml
|
||||||
view/Page/Main/ContactPage.qml
|
view/Page/Main/ContactPage.qml
|
||||||
|
|
||||||
|
view/Tool/utils.js
|
||||||
# Prototypes
|
# Prototypes
|
||||||
view/Prototype/PhoneNumberPrototype.qml
|
view/Prototype/PhoneNumberPrototype.qml
|
||||||
view/Prototype/AccountsPrototype.qml
|
view/Prototype/AccountsPrototype.qml
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ Control.TabBar {
|
||||||
topPadding: 20 * DefaultStyle.dp
|
topPadding: 20 * DefaultStyle.dp
|
||||||
|
|
||||||
property var model
|
property var model
|
||||||
|
readonly property alias cornerRadius: bottomLeftCorner.radius
|
||||||
|
|
||||||
contentItem: ListView {
|
contentItem: ListView {
|
||||||
model: mainItem.contentModel
|
model: mainItem.contentModel
|
||||||
|
|
@ -32,6 +33,7 @@ Control.TabBar {
|
||||||
id: background
|
id: background
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
id: bottomLeftCorner
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: DefaultStyle.main1_500_main
|
color: DefaultStyle.main1_500_main
|
||||||
radius: 25 * DefaultStyle.dp
|
radius: 25 * DefaultStyle.dp
|
||||||
|
|
|
||||||
734
Linphone/view/Tool/utils.js
Normal file
734
Linphone/view/Tool/utils.js
Normal file
|
|
@ -0,0 +1,734 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||||
|
*
|
||||||
|
* This file is part of linphone-desktop
|
||||||
|
* (see https://www.linphone.org).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
// =============================================================================
|
||||||
|
// Contains many common helpers.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.pragma library
|
||||||
|
|
||||||
|
.import QtQuick as QtQuick
|
||||||
|
.import Linphone as Linphone
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Constants.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
var SCHEME_REGEX = new RegExp('^[^:]+:')
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// QML helpers.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
function buildCommonDialogUri (component) {
|
||||||
|
return 'qrc:/ui/modules/Common/Dialog/' + component + '.qml'
|
||||||
|
}
|
||||||
|
function buildCommonUri (component, subComponent) {
|
||||||
|
return 'qrc:/ui/modules/Common/'+subComponent+'/' + component + '.qml'
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLinphoneDialogUri (component) {
|
||||||
|
return 'qrc:/ui/modules/Linphone/Dialog/' + component + '.qml'
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAppDialogUri (component) {
|
||||||
|
return 'qrc:/ui/views/App/Dialog/' + component + '.qml'
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Destroy timeout.
|
||||||
|
function clearTimeout (timer) {
|
||||||
|
timer.stop() // NECESSARY.
|
||||||
|
timer.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function createObject (source, parent, options) {
|
||||||
|
if (options && options.isString) {
|
||||||
|
var object = Qt.createQmlObject(source, parent)
|
||||||
|
|
||||||
|
var properties = options && options.properties
|
||||||
|
if (properties) {
|
||||||
|
for (var key in properties) {
|
||||||
|
object[key] = properties[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
var component = Qt.createComponent(source)
|
||||||
|
if (component.status !== QtQuick.Component.Ready) {
|
||||||
|
console.debug('Component not ready.')
|
||||||
|
if (component.status === QtQuick.Component.Error) {
|
||||||
|
console.debug('Error: ' + component.errorString())
|
||||||
|
}
|
||||||
|
return // Error.
|
||||||
|
}
|
||||||
|
|
||||||
|
var object = component.createObject(parent, (options && options.properties) || {})
|
||||||
|
if (!object) {
|
||||||
|
console.debug('Error: unable to create dynamic object.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function getSystemPathFromUri (uri) {
|
||||||
|
var str = uri.toString()
|
||||||
|
if (startsWith(str, 'file://')) {
|
||||||
|
str = str.substring(7)
|
||||||
|
|
||||||
|
// Absolute path.
|
||||||
|
if (str.charAt(0) === '/') {
|
||||||
|
return runOnWindows() ? str.substring(1) : str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUriFromSystemPath (path) {
|
||||||
|
if (path.startsWith('file://')) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runOnWindows()) {
|
||||||
|
return 'file://' + (/^\w:/.exec(path) ? '/' : '') + path
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'file://' + path
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Returns the top (root) parent of one object.
|
||||||
|
function getTopParent (object, useFakeParent) {
|
||||||
|
function _getTopParent (object, useFakeParent) {
|
||||||
|
return (useFakeParent && object.$parent) || object.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = _getTopParent(object, useFakeParent)
|
||||||
|
var p
|
||||||
|
while ((p = _getTopParent(parent, useFakeParent)) != null) {
|
||||||
|
parent = p
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Load by default a window in the ui/views folder.
|
||||||
|
// If options.isString is equals to true, a marshalling component can
|
||||||
|
// be used.
|
||||||
|
//
|
||||||
|
// Supported options: isString, exitHandler, properties.
|
||||||
|
//
|
||||||
|
// If exitHandler is used, window must implement exitStatus signal.
|
||||||
|
function openWindow (window, parent, options, fullscreen) {
|
||||||
|
var object = createObject(window, parent, options)
|
||||||
|
|
||||||
|
object.closing.connect(object.destroy.bind(object))
|
||||||
|
|
||||||
|
if (options && options.exitHandler) {
|
||||||
|
object.exitStatus.connect(
|
||||||
|
// Bind to access parent properties.
|
||||||
|
options.exitHandler.bind(parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if( runOnWindows()){
|
||||||
|
object.show() // Needed for Windows : Show the window in all case. Allow to graphically locate the window before going to fullscreen.
|
||||||
|
if(fullscreen)
|
||||||
|
object.showFullScreen()// Should be equivalent to changing visibility
|
||||||
|
}else if(fullscreen)
|
||||||
|
object.showFullScreen()// Should be equivalent to changing visibility
|
||||||
|
else
|
||||||
|
object.show()
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function resolveImageUri (name) {
|
||||||
|
return name
|
||||||
|
//? 'image://internal/' + removeScheme(Qt.resolvedUrl('/assets/images/' + name + '.svg'))
|
||||||
|
? 'image://internal/' + name
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
function runOnWindows () {
|
||||||
|
var os = Qt.platform.os
|
||||||
|
return os === 'windows' || os === 'winrt'
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Test if a point is in a item.
|
||||||
|
//
|
||||||
|
// `source` is the item that generated the point.
|
||||||
|
// `target` is the item to test.
|
||||||
|
// `point` is the point to test.
|
||||||
|
function pointIsInItem (source, target, point) {
|
||||||
|
point = source.mapToItem(target.parent, point.x, point.y)
|
||||||
|
|
||||||
|
return (
|
||||||
|
point.x >= target.x &&
|
||||||
|
point.y >= target.y &&
|
||||||
|
point.x < target.x + target.width &&
|
||||||
|
point.y < target.y + target.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Test the type of a qml object.
|
||||||
|
// Warning: this function is probably not portable
|
||||||
|
// on new versions of Qt.
|
||||||
|
//
|
||||||
|
// So, if you want to use it on a specific `className`, please to add
|
||||||
|
// a test in `test_qmlTypeof_data` of `utils.spec.qml`.
|
||||||
|
function qmlTypeof (object, className) {
|
||||||
|
var str = object.toString()
|
||||||
|
|
||||||
|
return (
|
||||||
|
str.indexOf(className + '(') == 0 ||
|
||||||
|
str.indexOf(className + '_QML') == 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function removeScheme (url) {
|
||||||
|
return url.toString().replace(SCHEME_REGEX, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// A copy of `Window.setTimeout` from js.
|
||||||
|
// delay is in milliseconds.
|
||||||
|
function setTimeout (parent, delay, cb) {
|
||||||
|
var timer = new (function (parent) {
|
||||||
|
return Qt.createQmlObject('import QtQml 2.2; Timer { }', parent)
|
||||||
|
})(parent)
|
||||||
|
|
||||||
|
timer.interval = delay
|
||||||
|
timer.repeat = false
|
||||||
|
timer.triggered.connect(cb)
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
return timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// GENERIC.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
function _computeOptimizedCb (func, context) {
|
||||||
|
return context
|
||||||
|
? (function () {
|
||||||
|
return func.apply(context, arguments)
|
||||||
|
}) : func
|
||||||
|
}
|
||||||
|
|
||||||
|
function _indexFinder (array, cb, context) {
|
||||||
|
var length = array.length
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
if (cb( (array.getAt ? array.getAt(i) : array[i]), i, array)) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function _keyFinder (obj, cb, context) {
|
||||||
|
var keys = Object.keys(obj)
|
||||||
|
var length = keys.length
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
var key = keys[i]
|
||||||
|
if (cb(obj[key], key, obj)) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Basic assert function.
|
||||||
|
function assert (condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error('Assert: ' + message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function basename (str) {
|
||||||
|
if (!str) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runOnWindows()) {
|
||||||
|
str = str.replace(/\\/g, '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
var str2 = str
|
||||||
|
var length = str2.length - 1
|
||||||
|
|
||||||
|
if (str2[length] === '/') {
|
||||||
|
str2 = str2.substring(0, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
return str2.slice(str2.lastIndexOf('/') + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function capitalizeFirstLetter (str) {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function dirname (str) {
|
||||||
|
if (!str) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runOnWindows()) {
|
||||||
|
str = str.replace(/\\/g, '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
var str2 = str
|
||||||
|
var length = str2.length - 1
|
||||||
|
|
||||||
|
if (str2[length] === '/') {
|
||||||
|
str2 = str2.substring(0, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
return str2.slice(0, str2.lastIndexOf('/') + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function extractProperties (obj, pattern) {
|
||||||
|
if (!pattern) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj2 = {}
|
||||||
|
pattern.forEach(function (property) {
|
||||||
|
obj2[property] = obj[property]
|
||||||
|
})
|
||||||
|
|
||||||
|
return obj2
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Returns an array from an `object` or `array` argument.
|
||||||
|
function ensureArray (obj) {
|
||||||
|
if (isArray(obj)) {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = Object.keys(obj)
|
||||||
|
var length = keys.length
|
||||||
|
var values = Array(length)
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
values[i] = obj[keys[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function escapeQuotes (str) {
|
||||||
|
return str != null
|
||||||
|
? str.replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, '$1\\\'')
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function decode(str){
|
||||||
|
return decodeURIComponent(escape(str.replace(/%([0-9A-Fa-f]{2})/g, function() {
|
||||||
|
return String.fromCharCode(parseInt(arguments[1], 16));
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Get the first matching value in a array or object.
|
||||||
|
// The matching value is obtained if `cb` returns true.
|
||||||
|
function find (obj, cb, context) {
|
||||||
|
cb = _computeOptimizedCb(cb, context)
|
||||||
|
|
||||||
|
var finder = isArray(obj) ? _indexFinder : _keyFinder
|
||||||
|
var key = finder(obj, cb, context)
|
||||||
|
|
||||||
|
return key != null && key !== -1 ? obj[key] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function findIndex (array, cb, context) {
|
||||||
|
cb = _computeOptimizedCb(cb, context)
|
||||||
|
|
||||||
|
var key = _indexFinder(array, cb, context)
|
||||||
|
return key != null ? key : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
function formatElapsedTime (seconds) {
|
||||||
|
seconds = parseInt(seconds, 10)
|
||||||
|
//s, m, h, d, W, M, Y
|
||||||
|
//1, 60, 3600, 86400, 604800, 2592000, 31104000
|
||||||
|
var y = Math.floor(seconds / 31104000)
|
||||||
|
if(y > 0)
|
||||||
|
return y+ ' years'
|
||||||
|
var M = Math.floor(seconds / 2592000)
|
||||||
|
if(M > 0)
|
||||||
|
return M+' months'
|
||||||
|
var w = Math.floor(seconds / 604800)
|
||||||
|
if(w>0)
|
||||||
|
return w+' week';
|
||||||
|
var d = Math.floor(seconds / 86400)
|
||||||
|
if(d>0)
|
||||||
|
return d+' days'
|
||||||
|
var h = Math.floor(seconds / 3600)
|
||||||
|
var m = Math.floor((seconds - h * 3600) / 60)
|
||||||
|
var s = seconds - h * 3600 - m * 60
|
||||||
|
|
||||||
|
if (h < 10 && h > 0) {
|
||||||
|
h = '0' + h
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m < 10) {
|
||||||
|
m = '0' + m
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s < 10) {
|
||||||
|
s = '0' + s
|
||||||
|
}
|
||||||
|
|
||||||
|
return (h === 0 ? '' : h + ':') + m + ':' + s
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration (seconds) {
|
||||||
|
seconds = parseInt(seconds, 10)
|
||||||
|
//s, m, h, d, W, M, Y
|
||||||
|
//1, 60, 3600, 86400, 604800, 2592000, 31104000
|
||||||
|
var y = Math.floor(seconds / 31104000)
|
||||||
|
if(y > 0)
|
||||||
|
//: '%1 year'
|
||||||
|
return qsTr('formatYears', '', y).arg(y)
|
||||||
|
var M = Math.floor(seconds / 2592000)
|
||||||
|
if(M > 0)
|
||||||
|
//: '%1 month'
|
||||||
|
return qsTr('formatMonths', '', M).arg(M)
|
||||||
|
var w = Math.floor(seconds / 604800)
|
||||||
|
if(w>0)
|
||||||
|
//: '%1 week'
|
||||||
|
return qsTr('formatWeeks', '', w).arg(w)
|
||||||
|
var d = Math.floor(seconds / 86400)
|
||||||
|
if(d>0)
|
||||||
|
//: '%1 day'
|
||||||
|
return qsTr('formatDays', '', d).arg(d)
|
||||||
|
var h = Math.floor(seconds / 3600)
|
||||||
|
var m = Math.floor((seconds - h * 3600) / 60)
|
||||||
|
var s = seconds - h * 3600 - m * 60
|
||||||
|
|
||||||
|
//: '%1 hour'
|
||||||
|
return (h > 0 ? qsTr('formatHours', '', h).arg(h): '')
|
||||||
|
//: '%1 minute'
|
||||||
|
+ (m > 0 ? (h > 0 ? ', ' : '') +qsTr('formatMinutes', '', m).arg(m): '')
|
||||||
|
//: '%1 second'
|
||||||
|
+ (s > 0 ? (h> 0 || m > 0 ? ', ' : '') +qsTr('formatSeconds', '', s).arg(s): '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDate(date, time){
|
||||||
|
var dateTime = new Date()
|
||||||
|
dateTime.setFullYear(date.getFullYear(), date.getMonth(), date.getDate())
|
||||||
|
dateTime.setHours(time.getHours())
|
||||||
|
dateTime.setMinutes(time.getMinutes())
|
||||||
|
dateTime.setSeconds(time.getSeconds())
|
||||||
|
return dateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
function equalDate(date1, date2){
|
||||||
|
return date1.getFullYear() == date2.getFullYear() && date1.getMonth() == date2.getMonth() && date1.getDate() == date2.getDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromUTC(date){
|
||||||
|
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(),
|
||||||
|
date.getUTCDate(), date.getUTCHours(),
|
||||||
|
date.getUTCMinutes(), date.getUTCSeconds()));
|
||||||
|
}
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function formatSize (size) {
|
||||||
|
var units = ['KB', 'MB', 'GB', 'TB']
|
||||||
|
var unit = 'B'
|
||||||
|
|
||||||
|
if (size == null) {
|
||||||
|
size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var length = units.length
|
||||||
|
for (var i = 0; size >= 1024 && i < length; i++) {
|
||||||
|
unit = units[i]
|
||||||
|
size /= 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFloat(size.toFixed(2)) + unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Generate a random number in the [min, max[ interval.
|
||||||
|
// Uniform distrib.
|
||||||
|
function genRandomNumber (min, max) {
|
||||||
|
return Math.random() * (max - min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
function genRandomColor(){
|
||||||
|
return '#'+ Math.floor(Math.random()*255).toString(16)
|
||||||
|
+Math.floor(Math.random()*255).toString(16)
|
||||||
|
+Math.floor(Math.random()*255).toString(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Generate a random number between a set of intervals.
|
||||||
|
// The `intervals` param must be orderer like this:
|
||||||
|
// `[ [ 1, 4 ], [ 8, 16 ], [ 22, 25 ] ]`
|
||||||
|
function genRandomNumberBetweenIntervals (intervals) {
|
||||||
|
if (intervals.length === 1) {
|
||||||
|
return genRandomNumber(intervals[0][0], intervals[0][1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the intervals size.
|
||||||
|
var size = 0
|
||||||
|
intervals.forEach(function (interval) {
|
||||||
|
size += interval[1] - interval[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Generate a value in the interval: `[0, size[`
|
||||||
|
var n = genRandomNumber(0, size)
|
||||||
|
|
||||||
|
// Map the value in the right interval.
|
||||||
|
n += intervals[0][0]
|
||||||
|
for (var i = 0; i < intervals.length - 1; i++) {
|
||||||
|
if (n < intervals[i][1]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
n += intervals[i + 1][0] - intervals[i][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Returns the extension of a filename.
|
||||||
|
function getExtension (str) {
|
||||||
|
var index = str.lastIndexOf('.')
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.slice(index + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Test if a value is included in an array or object.
|
||||||
|
function includes (obj, value, startIndex) {
|
||||||
|
obj = ensureArray(obj)
|
||||||
|
if (startIndex == null) {
|
||||||
|
startIndex = 0
|
||||||
|
}
|
||||||
|
var length = obj.length
|
||||||
|
|
||||||
|
for (var i = startIndex; i < length; i++) {
|
||||||
|
if (
|
||||||
|
value == obj[i] ||
|
||||||
|
// Check `NaN`.
|
||||||
|
(value !== value && obj[i] !== obj[i])
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function isArray (array) {
|
||||||
|
return (array instanceof Array)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function isFunction (func) {
|
||||||
|
return typeof func === 'function'
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function isInteger (integer) {
|
||||||
|
return integer === parseInt(integer, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function isObject (object) {
|
||||||
|
return object !== null && typeof object === 'object'
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function isString (string) {
|
||||||
|
return typeof string === 'string' || string instanceof String
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Convert a snake_case string to a lowerCamelCase string.
|
||||||
|
function snakeToCamel (s) {
|
||||||
|
return s.replace(/(\_\w)/g, function (matches) {
|
||||||
|
return matches[1].toUpperCase()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Test if a string starts by a given string.
|
||||||
|
function startsWith (str, searchStr) {
|
||||||
|
if (searchStr == null) {
|
||||||
|
searchStr = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.slice(0, searchStr.length) === searchStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Invoke a `cb` function with each value of the interval: `[0, n[`.
|
||||||
|
// Return a mapped array created with the returned values of `cb`.
|
||||||
|
function times (n, cb, context) {
|
||||||
|
var arr = Array(Math.max(0, n))
|
||||||
|
cb = _computeOptimizedCb(cb, context, 1)
|
||||||
|
|
||||||
|
for (var i = 0; i < n; i++) {
|
||||||
|
arr[i] = cb(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function unscapeHtml (str) {
|
||||||
|
return str.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '\u2063<')
|
||||||
|
.replace(/>/g, '\u2063>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function write (fileName, text) {
|
||||||
|
// TODO: Deal with async.
|
||||||
|
var request = new XMLHttpRequest()
|
||||||
|
request.open('PUT', getUriFromSystemPath(fileName), false)
|
||||||
|
request.send(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeAvatarSize (container, maxSize, ratio) {
|
||||||
|
var height = container.height * ratio
|
||||||
|
var width = container.width * ratio
|
||||||
|
var size = height < maxSize && height > 0 ? height : maxSize
|
||||||
|
return size < width ? size : width
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function openCodecOnlineInstallerDialog (window, codecInfo, cb) {
|
||||||
|
var VideoCodecsModel = Linphone.VideoCodecsModel
|
||||||
|
window.attachVirtualWindow(buildCommonDialogUri('ConfirmDialog'), {
|
||||||
|
descriptionText: qsTr('downloadCodecDescription')
|
||||||
|
.replace('%1', codecInfo.mime)
|
||||||
|
.replace('%2', codecInfo.encoderDescription)
|
||||||
|
}, function (status) {
|
||||||
|
if (status) {
|
||||||
|
window.attachVirtualWindow(buildLinphoneDialogUri('OnlineInstallerDialog'), {
|
||||||
|
downloadUrl: codecInfo.downloadUrl,
|
||||||
|
extract: true,
|
||||||
|
installFolder: VideoCodecsModel.codecsFolder,
|
||||||
|
installName: codecInfo.installName,
|
||||||
|
mime: codecInfo.mime,
|
||||||
|
checksum: codecInfo.checksum
|
||||||
|
}, function (status) {
|
||||||
|
if (status) {
|
||||||
|
VideoCodecsModel.reload()
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(window)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (cb) {
|
||||||
|
cb(window)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function printObject(o) {
|
||||||
|
var out = '';
|
||||||
|
for (var p in o) {
|
||||||
|
out += p + ': ' + o[p] + '\n';
|
||||||
|
}
|
||||||
|
if(!o)
|
||||||
|
return 'Empty'
|
||||||
|
else
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function infoDialog(window, message) {
|
||||||
|
window.attachVirtualWindow(buildCommonDialogUri('ConfirmDialog'), {
|
||||||
|
buttonTexts : ['',qsTr('okButton')],
|
||||||
|
descriptionText: message,
|
||||||
|
showButtonOnly: 1
|
||||||
|
}, function (status) {})
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue