error text display
show welcome page on first launch only
try to fix crash in variant object (to fix properly)
forbid connection if account already connected
contacts list first row size + contact selected signal
accounts layout
rm layout rearrange warning
login error messages layout
trust circle avatar
no error message on status ok
busy indicator on login
This commit is contained in:
Gaelle Braud 2024-01-10 17:39:21 +01:00
parent f9abfb9fbc
commit 517c6b96a5
23 changed files with 304 additions and 144 deletions

View file

@ -81,6 +81,7 @@ void App::init() {
// Core. Manage the logger so it must be instantiate at first. // Core. Manage the logger so it must be instantiate at first.
auto coreModel = CoreModel::create("", mLinphoneThread); auto coreModel = CoreModel::create("", mLinphoneThread);
connect(mLinphoneThread, &QThread::started, coreModel.get(), &CoreModel::start); connect(mLinphoneThread, &QThread::started, coreModel.get(), &CoreModel::start);
mFirstLaunch = mSettings.value("firstLaunch", 1).toInt();
// Console Commands // Console Commands
createCommandParser(); createCommandParser();
mParser->parse(this->arguments()); mParser->parse(this->arguments());
@ -264,6 +265,17 @@ void App::closeCallsWindow() {
} }
} }
void App::setFirstLaunch(bool first) {
if (mFirstLaunch != first) {
mFirstLaunch = first;
mSettings.setValue("firstLaunch", first);
}
}
bool App::getFirstLaunch() const {
return mFirstLaunch;
}
QQuickWindow *App::getMainWindow() { QQuickWindow *App::getMainWindow() {
return mMainWindow; return mMainWindow;
} }

View file

@ -20,6 +20,7 @@
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QSettings>
#include <QSharedPointer> #include <QSharedPointer>
#include "core/singleapplication/singleapplication.h" #include "core/singleapplication/singleapplication.h"
@ -93,6 +94,9 @@ public:
QQuickWindow *getCallsWindow(QVariant callGui); QQuickWindow *getCallsWindow(QVariant callGui);
void closeCallsWindow(); void closeCallsWindow();
bool getFirstLaunch() const;
void setFirstLaunch(bool first);
QQuickWindow *getMainWindow(); QQuickWindow *getMainWindow();
QQmlApplicationEngine *mEngine = nullptr; QQmlApplicationEngine *mEngine = nullptr;
@ -108,6 +112,8 @@ private:
Notifier *mNotifier = nullptr; Notifier *mNotifier = nullptr;
QQuickWindow *mMainWindow = nullptr; QQuickWindow *mMainWindow = nullptr;
QQuickWindow *mCallsWindow = nullptr; QQuickWindow *mCallsWindow = nullptr;
QSettings mSettings;
bool mFirstLaunch = true;
// TODO : changer ce count lorsqu'on aura liste d'appels // TODO : changer ce count lorsqu'on aura liste d'appels
int callsCount = 0; int callsCount = 0;

View file

@ -49,28 +49,48 @@ void LoginPage::setRegistrationState(linphone::RegistrationState status) {
} }
} }
QString LoginPage::getErrorMessage() const {
return mErrorMessage;
}
void LoginPage::setErrorMessage(const QString &error) {
// force signal emission to display the error even if it doesn't change
mErrorMessage = error;
emit errorMessageChanged();
}
void LoginPage::login(const QString &username, const QString &password) { void LoginPage::login(const QString &username, const QString &password) {
App::postModelAsync([=]() { App::postModelAsync([=]() {
QString *error = new QString(tr("Le couple identifiant mot de passe ne correspont pas"));
// Create on Model thread. // Create on Model thread.
AccountManager *accountManager = new AccountManager(); AccountManager *accountManager = new AccountManager();
connect(accountManager, &AccountManager::registrationStateChanged, this, connect(accountManager, &AccountManager::registrationStateChanged, this,
[accountManager, this](linphone::RegistrationState state) mutable { [accountManager, this, error](linphone::RegistrationState state) mutable {
// View thread // View thread
setRegistrationState(state); setRegistrationState(state);
switch (state) { switch (state) {
case linphone::RegistrationState::Ok:
case linphone::RegistrationState::Cleared:
case linphone::RegistrationState::Failed: { case linphone::RegistrationState::Failed: {
emit accountManager->errorMessageChanged(*error);
accountManager->deleteLater(); accountManager->deleteLater();
break; break;
} }
case linphone::RegistrationState::Ok: {
emit accountManager->errorMessageChanged("");
break;
}
case linphone::RegistrationState::Cleared:
case linphone::RegistrationState::None: case linphone::RegistrationState::None:
case linphone::RegistrationState::Progress: case linphone::RegistrationState::Progress:
case linphone::RegistrationState::Refreshing: case linphone::RegistrationState::Refreshing:
break; break;
} }
}); });
if (!accountManager->login(username, password)) { connect(accountManager, &AccountManager::errorMessageChanged, this,
[this](QString errorMessage) { setErrorMessage(errorMessage); });
connect(accountManager, &AccountManager::destroyed, [error]() { delete error; });
if (!accountManager->login(username, password, error)) {
emit accountManager->registrationStateChanged(linphone::RegistrationState::Failed); emit accountManager->registrationStateChanged(linphone::RegistrationState::Failed);
} }
}); });

View file

@ -33,17 +33,23 @@ public:
~LoginPage(); ~LoginPage();
Q_PROPERTY(linphone::RegistrationState registrationState READ getRegistrationState NOTIFY registrationStateChanged) Q_PROPERTY(linphone::RegistrationState registrationState READ getRegistrationState NOTIFY registrationStateChanged)
Q_PROPERTY(QString errorMessage READ getErrorMessage NOTIFY errorMessageChanged)
Q_INVOKABLE void login(const QString &username, const QString &password); Q_INVOKABLE void login(const QString &username, const QString &password);
linphone::RegistrationState getRegistrationState() const; linphone::RegistrationState getRegistrationState() const;
void setRegistrationState(linphone::RegistrationState status); void setRegistrationState(linphone::RegistrationState status);
QString getErrorMessage() const;
void setErrorMessage(const QString &error);
signals: signals:
void registrationStateChanged(); void registrationStateChanged();
void errorMessageChanged();
private: private:
linphone::RegistrationState mRegistrationState = linphone::RegistrationState::None; linphone::RegistrationState mRegistrationState = linphone::RegistrationState::None;
QString mErrorMessage;
DECLARE_ABSTRACT_OBJECT DECLARE_ABSTRACT_OBJECT
}; };

View file

@ -48,7 +48,7 @@ std::shared_ptr<linphone::Account> AccountManager::createAccount(const QString &
return core->createAccount(core->createAccountParams()); return core->createAccount(core->createAccountParams());
} }
bool AccountManager::login(QString username, QString password) { bool AccountManager::login(QString username, QString password, QString *errorMessage) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto core = CoreModel::getInstance()->getCore(); auto core = CoreModel::getInstance()->getCore();
auto factory = linphone::Factory::get(); auto factory = linphone::Factory::get();
@ -56,13 +56,23 @@ bool AccountManager::login(QString username, QString password) {
auto params = account->getParams()->clone(); auto params = account->getParams()->clone();
// Sip address. // Sip address.
auto identity = params->getIdentityAddress()->clone(); auto identity = params->getIdentityAddress()->clone();
if (mAccountModel) return false; if (mAccountModel) return false;
auto accounts = core->getAccountList();
for (auto account : accounts) {
if (account->getParams()->getIdentityAddress()->getUsername() == Utils::appStringToCoreString(username)) {
*errorMessage = tr("Le compte est déjà connecté");
return false;
}
}
identity->setUsername(Utils::appStringToCoreString(username)); identity->setUsername(Utils::appStringToCoreString(username));
if (params->setIdentityAddress(identity)) { if (params->setIdentityAddress(identity)) {
qWarning() << log() qWarning() << log()
.arg(QStringLiteral("Unable to set identity address: `%1`.")) .arg(QStringLiteral("Unable to set identity address: `%1`."))
.arg(Utils::coreStringToAppString(identity->asStringUriOnly())); .arg(Utils::coreStringToAppString(identity->asStringUriOnly()));
*errorMessage =
tr("Unable to set identity address: `%1`.").arg(Utils::coreStringToAppString(identity->asStringUriOnly()));
return false; return false;
} }

View file

@ -33,7 +33,7 @@ public:
AccountManager(QObject *parent = nullptr); AccountManager(QObject *parent = nullptr);
~AccountManager(); ~AccountManager();
bool login(QString username, QString password); bool login(QString username, QString password, QString *errorMessage = nullptr);
std::shared_ptr<linphone::Account> createAccount(const QString &assistantFile); std::shared_ptr<linphone::Account> createAccount(const QString &assistantFile);
@ -42,6 +42,7 @@ public:
const std::string &message); const std::string &message);
signals: signals:
void registrationStateChanged(linphone::RegistrationState state); void registrationStateChanged(linphone::RegistrationState state);
void errorMessageChanged(const QString &errorMessage);
private: private:
std::shared_ptr<AccountModel> mAccountModel; std::shared_ptr<AccountModel> mAccountModel;

View file

@ -37,10 +37,16 @@ VariantObject::VariantObject(QVariant defaultValue, QObject *parent) {
new SafeConnection<SafeObject, SafeObject>(mCoreObject, mModelObject), &QObject::deleteLater); new SafeConnection<SafeObject, SafeObject>(mCoreObject, mModelObject), &QObject::deleteLater);
mConnection->makeConnectToCore(&SafeObject::setValue, [this](QVariant value) { mConnection->makeConnectToCore(&SafeObject::setValue, [this](QVariant value) {
mConnection->invokeToModel([this, value]() { mModelObject->onSetValue(value); }); mConnection->invokeToModel([this, value]() {
// TODO : fix this properly
if (mModelObject) mModelObject->onSetValue(value);
});
}); });
mConnection->makeConnectToModel(&SafeObject::setValue, [this](QVariant value) { mConnection->makeConnectToModel(&SafeObject::setValue, [this](QVariant value) {
mConnection->invokeToCore([this, value]() { mCoreObject->onSetValue(value); }); mConnection->invokeToCore([this, value]() {
// TODO : fix this properly
if (mCoreObject) mCoreObject->onSetValue(value);
});
}); });
mConnection->makeConnectToModel(&SafeObject::valueChanged, [this](QVariant value) { mConnection->makeConnectToModel(&SafeObject::valueChanged, [this](QVariant value) {
mConnection->invokeToCore([this, value]() { mCoreObject->valueChanged(value); }); mConnection->invokeToCore([this, value]() { mCoreObject->valueChanged(value); });

View file

@ -315,7 +315,6 @@ Q_DECLARE_METATYPE(LinphoneEnums::FriendCapability)
Q_DECLARE_METATYPE(LinphoneEnums::MediaEncryption) Q_DECLARE_METATYPE(LinphoneEnums::MediaEncryption)
Q_DECLARE_METATYPE(LinphoneEnums::ParticipantDeviceState) Q_DECLARE_METATYPE(LinphoneEnums::ParticipantDeviceState)
Q_DECLARE_METATYPE(LinphoneEnums::RecorderState) Q_DECLARE_METATYPE(LinphoneEnums::RecorderState)
Q_DECLARE_METATYPE(LinphoneEnums::RegistrationState)
Q_DECLARE_METATYPE(LinphoneEnums::TunnelMode) Q_DECLARE_METATYPE(LinphoneEnums::TunnelMode)
Q_DECLARE_METATYPE(LinphoneEnums::TransportType) Q_DECLARE_METATYPE(LinphoneEnums::TransportType)
*/ */

View file

@ -98,6 +98,15 @@ VariantObject *Utils::createCall(const QString &sipAddress,
return data; return data;
} }
void Utils::setFirstLaunch(bool first) {
App::getInstance()->setFirstLaunch(first);
}
bool Utils::getFirstLaunch() {
return App::getInstance()->getFirstLaunch();
}
void Utils::openCallsWindow(CallGui *call) { void Utils::openCallsWindow(CallGui *call) {
if (call) App::getInstance()->getCallsWindow(QVariant::fromValue(call))->show(); if (call) App::getInstance()->getCallsWindow(QVariant::fromValue(call))->show();
} }

View file

@ -57,6 +57,8 @@ public:
Q_INVOKABLE static VariantObject *createCall(const QString &sipAddress, Q_INVOKABLE static VariantObject *createCall(const QString &sipAddress,
const QString &prepareTransfertAddress = "", const QString &prepareTransfertAddress = "",
const QHash<QString, QString> &headers = {}); const QHash<QString, QString> &headers = {});
Q_INVOKABLE static void setFirstLaunch(bool first);
Q_INVOKABLE static bool getFirstLaunch();
Q_INVOKABLE static void openCallsWindow(CallGui *call); Q_INVOKABLE static void openCallsWindow(CallGui *call);
Q_INVOKABLE static QQuickWindow *getMainWindow(); Q_INVOKABLE static QQuickWindow *getMainWindow();
Q_INVOKABLE static QQuickWindow *getCallsWindow(CallGui *callGui); Q_INVOKABLE static QQuickWindow *getCallsWindow(CallGui *callGui);

View file

@ -2,7 +2,7 @@ import QtQuick 2.15
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtQuick.Controls import QtQuick.Controls
import Linphone import Linphone
//import UI 1.0 import UtilsCpp 1.0
Window { Window {
id: mainWindow id: mainWindow
@ -10,6 +10,7 @@ Window {
height: 982 * DefaultStyle.dp height: 982 * DefaultStyle.dp
visible: true visible: true
title: qsTr("Linphone") title: qsTr("Linphone")
// TODO : handle this bool when security mode is implemented
property bool firstConnection: true property bool firstConnection: true
function goToNewCall() { function goToNewCall() {
@ -21,25 +22,23 @@ Window {
mainWindowStackView.currentItem.transferCallSucceed() mainWindowStackView.currentItem.transferCallSucceed()
} }
AccountProxy{ AccountProxy {
// TODO : change this so it does not display the main page for one second
// when we fail trying to connect the first account (account is added and
// removed shortly after)
id: accountProxy id: accountProxy
onHaveAccountChanged: {
if(haveAccount)
mainWindowStackView.replace(mainPage, StackView.Immediate)
else
mainWindowStackView.replace(loginPage, StackView.Immediate)
}
} }
StackView { StackView {
id: mainWindowStackView id: mainWindowStackView
anchors.fill: parent anchors.fill: parent
initialItem: accountProxy.haveAccount ? mainPage : welcomePage initialItem: accountProxy.haveAccount ? mainPage : UtilsCpp.getFirstLaunch() ? welcomePage : loginPage
} }
Component { Component {
id: welcomePage id: welcomePage
WelcomePage { WelcomePage {
onStartButtonPressed: { onStartButtonPressed: {
mainWindowStackView.replace(loginPage)// Replacing the first item will destroy the old. mainWindowStackView.replace(loginPage)// Replacing the first item will destroy the old.
UtilsCpp.setFirstLaunch(false)
} }
} }
} }
@ -51,11 +50,7 @@ Window {
onUseSIPButtonClicked: mainWindowStackView.push(sipLoginPage) onUseSIPButtonClicked: mainWindowStackView.push(sipLoginPage)
onGoToRegister: mainWindowStackView.replace(registerPage) onGoToRegister: mainWindowStackView.replace(registerPage)
onConnectionSucceed: { onConnectionSucceed: {
if (mainWindow.firstConnection) { mainWindowStackView.replace(mainPage)
mainWindowStackView.replace(securityModePage)
} else {
mainWindowStackView.replace(mainPage)
}
} }
} }
} }
@ -66,11 +61,7 @@ Window {
onGoToRegister: mainWindowStackView.replace(registerPage) onGoToRegister: mainWindowStackView.replace(registerPage)
onConnectionSucceed: { onConnectionSucceed: {
if (mainWindow.firstConnection) { mainWindowStackView.replace(mainPage)
mainWindowStackView.replace(securityModePage)
} else {
mainWindowStackView.replace(mainPage)
}
} }
} }
} }
@ -92,7 +83,9 @@ Window {
Component { Component {
id: securityModePage id: securityModePage
SecurityModePage { SecurityModePage {
id: securePage
onModeSelected: (index) => { onModeSelected: (index) => {
// TODO : connect to cpp part when ready
var selectedMode = index == 0 ? "chiffrement" : "interoperable" var selectedMode = index == 0 ? "chiffrement" : "interoperable"
console.debug("[SelectMode]User: User selected mode " + selectedMode) console.debug("[SelectMode]User: User selected mode " + selectedMode)
mainWindowStackView.replace(mainPage) mainWindowStackView.replace(mainPage)
@ -103,7 +96,7 @@ Window {
id: mainPage id: mainPage
MainLayout { MainLayout {
onAddAccountRequest: mainWindowStackView.replace(loginPage) onAddAccountRequest: mainWindowStackView.replace(loginPage)
// StackView.onActivated: connectionSecured(0) // TODO : connect to cpp part when ready
} }
} }
} }

View file

@ -11,21 +11,23 @@ Item {
id: mainItem id: mainItem
width: 517 * DefaultStyle.dp width: 517 * DefaultStyle.dp
readonly property int topPadding: 23 * DefaultStyle.dp readonly property int topPadding: 23 * DefaultStyle.dp
readonly property int bottomPadding: 18 * DefaultStyle.dp readonly property int bottomPadding: 23 * DefaultStyle.dp
readonly property int leftPadding: 32 * DefaultStyle.dp readonly property int leftPadding: 32 * DefaultStyle.dp
readonly property int rightPadding: 32 * DefaultStyle.dp readonly property int rightPadding: 32 * DefaultStyle.dp
readonly property int spacing: 16 * DefaultStyle.dp readonly property int spacing: 16 * DefaultStyle.dp
signal addAccountRequest() signal addAccountRequest()
implicitHeight: list.contentHeight + topPadding + bottomPadding + 32 * DefaultStyle.dp + 1 + newAccountArea.height implicitHeight: list.contentHeight + topPadding + bottomPadding + 32 * DefaultStyle.dp + 1 + newAccountArea.height
ColumnLayout{ ColumnLayout{
id: childLayout
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: mainItem.topPadding anchors.topMargin: mainItem.topPadding
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: mainItem.leftPadding anchors.leftMargin: mainItem.leftPadding
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: mainItem.rightPadding anchors.rightMargin: mainItem.rightPadding
anchors.bottom: parent.bottom
anchors.bottomMargin: mainItem.bottomPadding
ListView{ ListView{
id: list id: list
Layout.preferredHeight: contentHeight Layout.preferredHeight: contentHeight
@ -69,8 +71,10 @@ Item {
EffectImage { EffectImage {
id: newAccount id: newAccount
source: AppIcons.plusCircle source: AppIcons.plusCircle
Layout.fillHeight: true width: 32 * DefaultStyle.dp
Layout.preferredWidth: height height: 32 * DefaultStyle.dp
Layout.preferredWidth: 32 * DefaultStyle.dp
Layout.preferredHeight: 32 * DefaultStyle.dp
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
colorizationColor: DefaultStyle.main2_500main colorizationColor: DefaultStyle.main2_500main

View file

@ -12,6 +12,7 @@ Item {
Control.BusyIndicator { Control.BusyIndicator {
id: busyIndicator id: busyIndicator
running: mainItem.visible running: mainItem.visible
anchors.centerIn: mainItem
} }
MultiEffect { MultiEffect {
source: busyIndicator source: busyIndicator

View file

@ -55,9 +55,9 @@ Item {
} }
Repeater { Repeater {
id: adresses id: adresses
model: [{label: "SIP", address: startCallPopup.contact ? startCallPopup.contact.core.address : ""}, model: [{label: "SIP", address: startCallPopup.contact ? startCallPopup.contact.core.address : ""}
{label: "Work", address: "06000000000"}, // {label: "Work", address: "06000000000"},
{label: "Personal", address: "060000000"} // {label: "Personal", address: "060000000"}
] //account.adresses ] //account.adresses
Button { Button {
id: channel id: channel
@ -235,6 +235,10 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
id: contactList id: contactList
searchBarText: searchBar.text searchBarText: searchBar.text
onContactSelected: (contact) => {
startCallPopup.contact = contact
startCallPopup.open()
}
} }
} }
ColumnLayout { ColumnLayout {
@ -254,6 +258,10 @@ Item {
sourceFlags: LinphoneEnums.MagicSearchSource.FavoriteFriends sourceFlags: LinphoneEnums.MagicSearchSource.FavoriteFriends
aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
} }
onContactSelected: (contact) => {
startCallPopup.contact = contact
startCallPopup.open()
}
} }
} }
Item { Item {

View file

@ -28,7 +28,31 @@ StackView{
onHaveAvatarChanged: replace(haveAvatar ? avatar : initials, StackView.Immediate) onHaveAvatarChanged: replace(haveAvatar ? avatar : initials, StackView.Immediate)
property bool secured: false
initialItem: haveAvatar ? avatar : initials initialItem: haveAvatar ? avatar : initials
Rectangle {
visible: mainItem.secured
anchors.fill: mainItem.currentItem
radius: mainItem.width / 2
z: 1
color: "transparent"
border {
width: 3 * DefaultStyle.dp
color: DefaultStyle.info_500_main
}
Image {
source: AppIcons.trusted
x: mainItem.width / 7
width: mainItem.width / 4.5
height: width
sourceSize.width: width
sourceSize.height: height
fillMode: Image.PreserveAspectFit
anchors.bottom: parent.bottom
}
}
Component{ Component{
id: initials id: initials
Rectangle { Rectangle {

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls as Control
import Linphone import Linphone
@ -31,68 +32,76 @@ Rectangle{
onClicked: mainItem.avatarClicked() onClicked: mainItem.avatarClicked()
} }
} }
ContactDescription{ Item {
id: description Layout.preferredWidth: 200 * DefaultStyle.dp
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.leftMargin: 10 * DefaultStyle.dp Layout.leftMargin: 10 * DefaultStyle.dp
account: mainItem.account Layout.rightMargin: 10 * DefaultStyle.dp
} ContactDescription{
Item{ id: description
id: registrationStatusItem anchors.fill: parent
Layout.preferredWidth: 97 * DefaultStyle.dp account: mainItem.account
Layout.fillHeight: true
Rectangle{
id: registrationStatus
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Math.min(text.implicitWidth + (2 * 8 * DefaultStyle.dp), registrationStatusItem.width)
height: 24 * DefaultStyle.dp
color: DefaultStyle.main2_200
radius: 90 * DefaultStyle.dp
Text{
id: text
anchors.fill: parent
anchors.leftMargin: 8 * DefaultStyle.dp
anchors.rightMargin: 8 * DefaultStyle.dp
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
visible: mainItem.account
readonly property int mode : !mainItem.account || mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Ok
? 0
: mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Cleared || mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.None
? 1
: mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Progress || mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Refreshing
? 2
: 3
// Test texts
//Timer{
// running: true
// interval: 1000
// repeat: true
// onTriggered: text.mode = (++text.mode) % 4
//}
font.weight: 300 * DefaultStyle.dp
font.pixelSize: 12 * DefaultStyle.dp
color: mode == 0
? DefaultStyle.success_500main
: mode == 1
? DefaultStyle.warning_600
: mode == 2
? DefaultStyle.main2_500main
: DefaultStyle.danger_500main
text: mode == 0
? 'Connecté'
: mode == 1
? 'Désactivé'
: mode == 2
? 'Connexion...'
: 'Erreur'
}
} }
} }
Control.Control {
id: registrationStatusItem
Layout.minimumWidth: 49 * DefaultStyle.dp
Layout.preferredHeight: 24 * DefaultStyle.dp
topPadding: 4 * DefaultStyle.dp
bottomPadding: 4 * DefaultStyle.dp
leftPadding: 8 * DefaultStyle.dp
rightPadding: 8 * DefaultStyle.dp
Layout.preferredWidth: text.implicitWidth + (2 * 8 * DefaultStyle.dp)
background: Rectangle{
id: registrationStatus
anchors.fill: parent
color: DefaultStyle.main2_200
radius: 90 * DefaultStyle.dp
}
contentItem: Text {
id: text
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
visible: mainItem.account
property int mode : !mainItem.account || mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Ok
? 0
: mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Cleared || mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.None
? 1
: mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Progress || mainItem.account.core.registrationState == LinphoneEnums.RegistrationState.Refreshing
? 2
: 3
// Test texts
// Timer{
// running: true
// interval: 1000
// repeat: true
// onTriggered: text.mode = (++text.mode) % 4
// }
font.weight: 300 * DefaultStyle.dp
font.pixelSize: 12 * DefaultStyle.dp
color: mode == 0
? DefaultStyle.success_500main
: mode == 1
? DefaultStyle.warning_600
: mode == 2
? DefaultStyle.main2_500main
: DefaultStyle.danger_500main
text: mode == 0
? qsTr("Connecté")
: mode == 1
? qsTr("Désactivé")
: mode == 2
? qsTr("Connexion...")
: qsTr("Erreur")
}
}
Item {
Layout.fillWidth: true
}
Item{ Item{
Layout.preferredWidth: 100 * DefaultStyle.dp Layout.preferredWidth: 22 * DefaultStyle.dp
Layout.preferredHeight: 22 * DefaultStyle.dp
Layout.fillHeight: true Layout.fillHeight: true
Rectangle{ Rectangle{
id: unreadNotifications id: unreadNotifications

View file

@ -12,9 +12,9 @@ ColumnLayout{
property string topText: displayName ? displayName.value : '' property string topText: displayName ? displayName.value : ''
property string bottomText: account ? account.core.identityAddress : '' property string bottomText: account ? account.core.identityAddress : ''
spacing: 0 spacing: 0
Text{ width: topTextItem.implicitWidth
Text {
id: topTextItem id: topTextItem
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
verticalAlignment: (bottomTextItem.visible?Text.AlignBottom:Text.AlignVCenter) verticalAlignment: (bottomTextItem.visible?Text.AlignBottom:Text.AlignVCenter)
visible: text != '' visible: text != ''
@ -22,10 +22,13 @@ ColumnLayout{
font.pixelSize: 14 * DefaultStyle.dp font.pixelSize: 14 * DefaultStyle.dp
color: DefaultStyle.main2_700 color: DefaultStyle.main2_700
text: mainItem.topText text: mainItem.topText
width: mainItem.width
Layout.preferredWidth: mainItem.width
wrapMode: Text.WrapAnywhere
maximumLineCount: 1
} }
Text{ Text {
id: bottomTextItem id: bottomTextItem
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
verticalAlignment: (topTextItem.visible?Text.AlignTop:Text.AlignVCenter) verticalAlignment: (topTextItem.visible?Text.AlignTop:Text.AlignVCenter)
visible: text != '' visible: text != ''
@ -33,5 +36,8 @@ ColumnLayout{
font.pixelSize: 12 * DefaultStyle.dp font.pixelSize: 12 * DefaultStyle.dp
color: DefaultStyle.main2_400 color: DefaultStyle.main2_400
text: mainItem.bottomText text: mainItem.bottomText
Layout.preferredWidth: mainItem.width
maximumLineCount: 1
wrapMode: Text.WrapAnywhere
} }
} }

View file

@ -16,6 +16,8 @@ ListView {
property bool contactMenuVisible: true property bool contactMenuVisible: true
property bool initialHeadersVisible: true property bool initialHeadersVisible: true
signal contactSelected(var contact)
model: MagicSearchProxy { model: MagicSearchProxy {
searchText: searchBarText.length === 0 ? "*" : searchBarText searchText: searchBarText.length === 0 ? "*" : searchBarText
} }
@ -40,6 +42,7 @@ ListView {
} }
Item { Item {
width: mainItem.width width: mainItem.width
Layout.preferredWidth: mainItem.width
height: 56 * DefaultStyle.dp height: 56 * DefaultStyle.dp
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
@ -129,9 +132,7 @@ ListView {
visible: contactArea.containsMouse || friendPopup.hovered visible: contactArea.containsMouse || friendPopup.hovered
} }
onClicked: { onClicked: {
startCallPopup.contact = modelData mainItem.contactSelected(modelData)
startCallPopup.open()
// mainItem.callButtonPressed(modelData.core.address)
} }
} }
} }

View file

@ -8,6 +8,12 @@ Text {
id: mainItem id: mainItem
color: DefaultStyle.danger_500main color: DefaultStyle.danger_500main
opacity: 0 opacity: 0
function displayText() {
mainItem.state = "Visible"
}
function hideText() {
mainItem.state = "Invisible"
}
font { font {
pixelSize: 13 * DefaultStyle.dp pixelSize: 13 * DefaultStyle.dp
weight: 600 * DefaultStyle.dp weight: 600 * DefaultStyle.dp
@ -30,10 +36,6 @@ Text {
property: "opacity" property: "opacity"
duration: 1000 duration: 1000
} }
// NumberAnimation {
// property: "visible"
// duration: 1100
// }
} }
] ]
Timer { Timer {
@ -48,7 +50,7 @@ Text {
target: mainItem target: mainItem
onTextChanged: { onTextChanged: {
if (mainItem.text.length > 0) { if (mainItem.text.length > 0) {
errorText.state = "Visible" mainItem.state = "Visible"
} }
} }
} }

View file

@ -1,5 +1,5 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Layouts 1.0 import QtQuick.Layouts
import QtQuick.Controls as Control import QtQuick.Controls as Control
import Linphone import Linphone
@ -24,32 +24,38 @@ ColumnLayout {
value: DefaultStyle.danger_500main value: DefaultStyle.danger_500main
} }
} }
TextInput {
id: password
label: "Password"
mandatory: true
hidden: true
enableErrorText: true
Binding on background.border.color { Item {
when: errorText.opacity != 0 Layout.preferredHeight: password.implicitHeight
value: DefaultStyle.danger_500main TextInput {
} id: password
Binding on textField.color { label: "Password"
when: errorText.opacity != 0 mandatory: true
value: DefaultStyle.danger_500main hidden: true
} enableErrorText: true
}
ErrorText { Binding on background.border.color {
id: errorText when: errorText.opacity != 0
Connections { value: DefaultStyle.danger_500main
target: LoginPageCpp }
onRegistrationStateChanged: { Binding on textField.color {
if (LoginPageCpp.registrationState === LinphoneEnums.RegistrationState.Failed) { when: errorText.opacity != 0
errorText.text = qsTr("Le couple identifiant mot de passe ne correspont pas") value: DefaultStyle.danger_500main
} else if (LoginPageCpp.registrationState === LinphoneEnums.RegistrationState.Ok) { }
mainItem.connectionSucceed() }
ErrorText {
anchors.bottom: password.bottom
id: errorText
Connections {
target: LoginPageCpp
onErrorMessageChanged: {
errorText.text = LoginPageCpp.errorMessage
}
onRegistrationStateChanged: {
if (LoginPageCpp.registrationState === LinphoneEnums.RegistrationState.Ok) {
mainItem.connectionSucceed()
}
} }
} }
} }
@ -58,11 +64,43 @@ ColumnLayout {
RowLayout { RowLayout {
id: lastFormLineLayout id: lastFormLineLayout
Button { Button {
text: qsTr("Connexion") contentItem: StackLayout {
id: connectionButton
currentIndex: 0
Text {
text: qsTr("Connexion")
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font {
pixelSize: 18 * DefaultStyle.dp
weight: 600 * DefaultStyle.dp
}
color: DefaultStyle.grey_0
}
BusyIndicator {
width: parent.height
height: parent.height
Layout.alignment: Qt.AlignCenter
indicatorColor: DefaultStyle.grey_0
}
Connections {
target: LoginPageCpp
onRegistrationStateChanged: {
if (LoginPageCpp.registrationState != LinphoneEnums.RegistrationState.Progress) {
connectionButton.currentIndex = 0
}
}
onErrorMessageChanged: {
connectionButton.currentIndex = 0
}
}
}
Layout.rightMargin: 20 * DefaultStyle.dp Layout.rightMargin: 20 * DefaultStyle.dp
onClicked: { onClicked: {
username.errorMessage = "" username.errorMessage = ""
password.errorMessage = "" password.errorMessage = ""
errorText.text = ""
if (username.text.length == 0 || password.text.length == 0) { if (username.text.length == 0 || password.text.length == 0) {
if (username.text.length == 0) if (username.text.length == 0)
@ -72,6 +110,7 @@ ColumnLayout {
return return
} }
LoginPageCpp.login(username.text, password.text) LoginPageCpp.login(username.text, password.text)
connectionButton.currentIndex = 1
} }
} }
Button { Button {

View file

@ -18,6 +18,7 @@ ColumnLayout {
property string initialText property string initialText
property bool enableErrorText: false property bool enableErrorText: false
property bool errorTextVisible: errorText.opacity > 0
property alias textField: textField property alias textField: textField
property alias background: input property alias background: input
@ -61,11 +62,11 @@ ColumnLayout {
radius: 79 * DefaultStyle.dp radius: 79 * DefaultStyle.dp
color: mainItem.enableBackgroundColors ? DefaultStyle.grey_100 : "transparent" color: mainItem.enableBackgroundColors ? DefaultStyle.grey_100 : "transparent"
border.color: mainItem.enableBackgroundColors border.color: mainItem.enableBackgroundColors
? errorText.opacity === 0 ? mainItem.errorTextVisible
? textField.activeFocus ? DefaultStyle.danger_500main
: textField.activeFocus
? DefaultStyle.main1_500_main ? DefaultStyle.main1_500_main
: DefaultStyle.grey_200 : DefaultStyle.grey_200
: DefaultStyle.danger_500main
: "transparent" : "transparent"
Control.TextField { Control.TextField {
@ -82,7 +83,7 @@ ColumnLayout {
pixelSize: 14 * DefaultStyle.dp pixelSize: 14 * DefaultStyle.dp
weight: 400 * DefaultStyle.dp weight: 400 * DefaultStyle.dp
} }
color: errorText.opacity === 0 ? DefaultStyle.main2_600 : DefaultStyle.danger_500main color: mainItem.errorTextVisible ? DefaultStyle.danger_500main : DefaultStyle.main2_600
selectByMouse: true selectByMouse: true
validator: mainItem.validator validator: mainItem.validator
background: Item { background: Item {
@ -117,6 +118,6 @@ ColumnLayout {
id: errorText id: errorText
visible: mainItem.enableErrorText visible: mainItem.enableErrorText
text: mainItem.errorMessage text: mainItem.errorMessage
Layout.preferredWidth: mainItem.textInputWidth Layout.preferredWidth: implicitWidth
} }
} }

View file

@ -76,7 +76,6 @@ LoginLayout {
required property int index required property int index
Layout.preferredWidth: width Layout.preferredWidth: width
Layout.preferredHeight: height Layout.preferredHeight: height
Component.onCompleted: console.log("completed", width, Layout.preferredWidth)
onTextEdited: { onTextEdited: {
console.log("textfield text", text, index) console.log("textfield text", text, index)
if (text.length > 0 ) { if (text.length > 0 ) {

View file

@ -54,7 +54,7 @@ LoginLayout {
} }
} }
} }
centerContent: ColumnLayout { centerContent: Item {
id: centerLayout id: centerLayout
Layout.bottomMargin: 20 * DefaultStyle.dp Layout.bottomMargin: 20 * DefaultStyle.dp
Layout.fillWidth: false Layout.fillWidth: false
@ -62,6 +62,7 @@ LoginLayout {
Layout.leftMargin: 250 * DefaultStyle.dp Layout.leftMargin: 250 * DefaultStyle.dp
Layout.topMargin: 165 * DefaultStyle.dp Layout.topMargin: 165 * DefaultStyle.dp
RowLayout { RowLayout {
id: carouselLayout
Image { Image {
id: carouselImg id: carouselImg
Layout.rightMargin: 40 * DefaultStyle.dp Layout.rightMargin: 40 * DefaultStyle.dp
@ -104,10 +105,11 @@ LoginLayout {
} }
Button { Button {
Layout.topMargin: 20 * DefaultStyle.dp anchors.top: carouselLayout.bottom
Layout.bottomMargin: 20 * DefaultStyle.dp anchors.right: carouselLayout.right
Layout.leftMargin: (centerLayout.width - width) * DefaultStyle.dp anchors.topMargin: 20 * DefaultStyle.dp
Layout.alignment: Qt.AlignBottom | Qt.AlignRight anchors.bottomMargin: 20 * DefaultStyle.dp
anchors.leftMargin: (centerLayout.width - width) * DefaultStyle.dp
y: centerLayout.implicitWidth - width y: centerLayout.implicitWidth - width
text: carousel.currentIndex < (carousel.itemsCount - 1) ? qsTr("Suivant") : qsTr("Commencer") text: carousel.currentIndex < (carousel.itemsCount - 1) ? qsTr("Suivant") : qsTr("Commencer")
onClicked: { onClicked: {