Do not focus on the first element after opening the settings menu.
Fix settings menu navigation. Update SDK for screensharing. Fix account list object selection.
This commit is contained in:
parent
c908f0d42c
commit
017971c886
8 changed files with 82 additions and 65 deletions
|
|
@ -60,7 +60,7 @@ void AccountList::setSelf(QSharedPointer<AccountList> me) {
|
||||||
QSharedPointer<AccountCore> defaultAccountCore;
|
QSharedPointer<AccountCore> defaultAccountCore;
|
||||||
for (auto it : linphoneAccounts) {
|
for (auto it : linphoneAccounts) {
|
||||||
auto model = AccountCore::create(it);
|
auto model = AccountCore::create(it);
|
||||||
if (it == defaultAccount) defaultAccountCore = AccountCore::create(defaultAccount);
|
if (it == defaultAccount) defaultAccountCore = model;
|
||||||
accounts->push_back(model);
|
accounts->push_back(model);
|
||||||
}
|
}
|
||||||
mModelConnection->invokeToCore([this, accounts, defaultAccountCore, isInitialization]() {
|
mModelConnection->invokeToCore([this, accounts, defaultAccountCore, isInitialization]() {
|
||||||
|
|
@ -76,8 +76,10 @@ void AccountList::setSelf(QSharedPointer<AccountList> me) {
|
||||||
mModelConnection->makeConnectToModel(
|
mModelConnection->makeConnectToModel(
|
||||||
&CoreModel::defaultAccountChanged,
|
&CoreModel::defaultAccountChanged,
|
||||||
[this](const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account) {
|
[this](const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account) {
|
||||||
if (account) {
|
if (account && account->getParams()->getIdentityAddress()) {
|
||||||
auto model = AccountCore::create(account);
|
auto address =
|
||||||
|
Utils::coreStringToAppString(account->getParams()->getIdentityAddress()->asStringUriOnly());
|
||||||
|
auto model = findAccountByAddress(address);
|
||||||
mModelConnection->invokeToCore([this, model]() { setDefaultAccount(model); });
|
mModelConnection->invokeToCore([this, model]() { setDefaultAccount(model); });
|
||||||
} else mModelConnection->invokeToCore([this]() { setDefaultAccount(nullptr); });
|
} else mModelConnection->invokeToCore([this]() { setDefaultAccount(nullptr); });
|
||||||
});
|
});
|
||||||
|
|
@ -104,12 +106,10 @@ void AccountList::setDefaultAccount(QSharedPointer<AccountCore> account) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountGui *AccountList::findAccountByAddress(const QString &address) {
|
QSharedPointer<AccountCore> AccountList::findAccountByAddress(const QString &address) {
|
||||||
for (auto &item : mList) {
|
for (auto &item : getSharedList<AccountCore>()) {
|
||||||
if (auto isAccount = item.objectCast<AccountCore>()) {
|
if (item->getIdentityAddress() == address) {
|
||||||
if (isAccount->getIdentityAddress() == address) {
|
return item;
|
||||||
return new AccountGui(isAccount);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public:
|
||||||
AccountGui *getDefaultAccount() const;
|
AccountGui *getDefaultAccount() const;
|
||||||
QSharedPointer<AccountCore> getDefaultAccountCore() const;
|
QSharedPointer<AccountCore> getDefaultAccountCore() const;
|
||||||
void setDefaultAccount(QSharedPointer<AccountCore> account);
|
void setDefaultAccount(QSharedPointer<AccountCore> account);
|
||||||
AccountGui *findAccountByAddress(const QString &address);
|
QSharedPointer<AccountCore> findAccountByAddress(const QString &address);
|
||||||
AccountGui *firstAccount();
|
AccountGui *firstAccount();
|
||||||
|
|
||||||
bool getHaveAccount() const;
|
bool getHaveAccount() const;
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,6 @@ AccountGui *AccountProxy::getDefaultAccount() {
|
||||||
return new AccountGui(mDefaultAccount);
|
return new AccountGui(mDefaultAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountProxy::setDefaultAccount(AccountGui *account) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the default account to let UI build its new object if needed.
|
// Reset the default account to let UI build its new object if needed.
|
||||||
void AccountProxy::resetDefaultAccount() {
|
void AccountProxy::resetDefaultAccount() {
|
||||||
mDefaultAccount = nullptr;
|
mDefaultAccount = nullptr;
|
||||||
|
|
@ -50,7 +47,7 @@ void AccountProxy::resetDefaultAccount() {
|
||||||
|
|
||||||
AccountGui *AccountProxy::findAccountByAddress(const QString &address) {
|
AccountGui *AccountProxy::findAccountByAddress(const QString &address) {
|
||||||
auto model = getListModel<AccountList>();
|
auto model = getListModel<AccountList>();
|
||||||
if (model) return model->findAccountByAddress(address);
|
if (model) return new AccountGui(model->findAccountByAddress(address));
|
||||||
else return nullptr;
|
else return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
class AccountProxy : public LimitProxy {
|
class AccountProxy : public LimitProxy {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(AccountGui *defaultAccount READ getDefaultAccount WRITE setDefaultAccount NOTIFY defaultAccountChanged)
|
Q_PROPERTY(AccountGui *defaultAccount READ getDefaultAccount NOTIFY defaultAccountChanged)
|
||||||
Q_PROPERTY(bool haveAccount READ getHaveAccount NOTIFY haveAccountChanged)
|
Q_PROPERTY(bool haveAccount READ getHaveAccount NOTIFY haveAccountChanged)
|
||||||
Q_PROPERTY(bool isInitialized READ isInitialized NOTIFY initializedChanged)
|
Q_PROPERTY(bool isInitialized READ isInitialized NOTIFY initializedChanged)
|
||||||
|
|
||||||
|
|
@ -41,9 +41,8 @@ public:
|
||||||
AccountProxy(QObject *parent = Q_NULLPTR);
|
AccountProxy(QObject *parent = Q_NULLPTR);
|
||||||
~AccountProxy();
|
~AccountProxy();
|
||||||
|
|
||||||
AccountGui *getDefaultAccount(); // Get a new object from List or give the stored one.
|
AccountGui *getDefaultAccount(); // Get a new object from List or give the stored one.
|
||||||
void setDefaultAccount(AccountGui *account); // TODO
|
void resetDefaultAccount(); // Reset the default account to let UI build its new object if needed.
|
||||||
void resetDefaultAccount(); // Reset the default account to let UI build its new object if needed.
|
|
||||||
Q_INVOKABLE AccountGui *findAccountByAddress(const QString &address);
|
Q_INVOKABLE AccountGui *findAccountByAddress(const QString &address);
|
||||||
Q_INVOKABLE AccountGui *firstAccount();
|
Q_INVOKABLE AccountGui *firstAccount();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,53 @@ Button {
|
||||||
popup.open()
|
popup.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isFocusable(item){
|
||||||
|
return item.activeFocusOnTab
|
||||||
|
}
|
||||||
|
function getPreviousItem(index){
|
||||||
|
return _getPreviousItem(popup.contentItem instanceof FocusScope ? popup.contentItem.children[0] : popup.contentItem, index)
|
||||||
|
}
|
||||||
|
function getNextItem(index){
|
||||||
|
return _getNextItem(popup.contentItem instanceof FocusScope ? popup.contentItem.children[0] : popup.contentItem, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getPreviousItem(content, index){
|
||||||
|
if(content.visibleChildren.length == 0) return null
|
||||||
|
--index
|
||||||
|
while(index >= 0){
|
||||||
|
if( isFocusable(content.children[index]) && content.children[index].visible) return content.children[index]
|
||||||
|
--index
|
||||||
|
}
|
||||||
|
return _getPreviousItem(content, content.children.length)
|
||||||
|
}
|
||||||
|
function _getNextItem(content, index){
|
||||||
|
++index
|
||||||
|
while(index < content.children.length){
|
||||||
|
if( isFocusable(content.children[index]) && content.children[index].visible) return content.children[index]
|
||||||
|
++index
|
||||||
|
}
|
||||||
|
return _getNextItem(content, -1)
|
||||||
|
}
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: (event) => {
|
||||||
if(popup.checked && event.key == Qt.Key_Escape){
|
if(mainItem.checked){
|
||||||
mainItem.close()
|
if( event.key == Qt.Key_Escape || event.key == Qt.Key_Left || event.key == Qt.Key_Space){
|
||||||
|
mainItem.close()
|
||||||
|
mainItem.forceActiveFocus()
|
||||||
|
event.accepted = true
|
||||||
|
}else if(event.key == Qt.Key_Up){
|
||||||
|
getPreviousItem(0).forceActiveFocus()
|
||||||
|
event.accepted = true
|
||||||
|
}else if(event.key == Qt.Key_Tab || event.key == Qt.Key_Down){
|
||||||
|
getNextItem(-1).forceActiveFocus()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}else if(event.key == Qt.Key_Space){
|
||||||
|
mainItem.open()
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Item {
|
background: Item {
|
||||||
anchors.fill: mainItem
|
anchors.fill: mainItem
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -84,16 +125,17 @@ Button {
|
||||||
if( y < mainItem.height && y + popupHeight > 0){
|
if( y < mainItem.height && y + popupHeight > 0){
|
||||||
x += mainItem.width
|
x += mainItem.width
|
||||||
}
|
}
|
||||||
popup.contentItem.forceActiveFocus()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onHeightChanged: Qt.callLater(updatePosition)
|
onHeightChanged: Qt.callLater(updatePosition)
|
||||||
onWidthChanged: Qt.callLater(updatePosition)
|
onWidthChanged: Qt.callLater(updatePosition)
|
||||||
|
onVisibleChanged: Qt.callLater(updatePosition)
|
||||||
|
|
||||||
Connections{
|
Connections{
|
||||||
target: mainItem.Window
|
target: mainItem.Window
|
||||||
function onHeightChanged(){ Qt.callLater(popup.updatePosition)}
|
function onHeightChanged(){ Qt.callLater(popup.updatePosition)}
|
||||||
function onWidthChanged(){ Qt.callLater(popup.updatePosition)}
|
function onWidthChanged(){ Qt.callLater(popup.updatePosition)}
|
||||||
}
|
}
|
||||||
onVisibleChanged: Qt.callLater(updatePosition)
|
|
||||||
|
|
||||||
background: Item {
|
background: Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
|
||||||
|
|
@ -371,46 +371,28 @@ Item {
|
||||||
implicitHeight: settingsButtons.implicitHeight
|
implicitHeight: settingsButtons.implicitHeight
|
||||||
implicitWidth: settingsButtons.implicitWidth
|
implicitWidth: settingsButtons.implicitWidth
|
||||||
Keys.onPressed: (event)=> {
|
Keys.onPressed: (event)=> {
|
||||||
if (event.key == Qt.Key_Left || event.key == Qt.Key_Escape) {
|
if (event.key == Qt.Key_Left || event.key == Qt.Key_Escape) {
|
||||||
settingsMenuButton.popup.close()
|
settingsMenuButton.popup.close()
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: settingsButtons
|
id: settingsButtons
|
||||||
|
spacing: 16 * DefaultStyle.dp
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 16 * DefaultStyle.dp
|
|
||||||
|
|
||||||
function getPreviousItem(index){
|
|
||||||
if(visibleChildren.length == 0) return null
|
|
||||||
--index
|
|
||||||
while(index >= 0){
|
|
||||||
if( index!= 4 && children[index].visible) return children[index]
|
|
||||||
--index
|
|
||||||
}
|
|
||||||
return getPreviousItem(children.length)
|
|
||||||
}
|
|
||||||
function getNextItem(index){
|
|
||||||
++index
|
|
||||||
while(index < children.length){
|
|
||||||
if( index!= 4 && children[index].visible) return children[index]
|
|
||||||
++index
|
|
||||||
}
|
|
||||||
return getNextItem(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
IconLabelButton {
|
IconLabelButton {
|
||||||
id: accountButton
|
id: accountButton
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: !SettingsCpp.hideAccountSettings
|
visible: !SettingsCpp.hideAccountSettings
|
||||||
focus: visible
|
|
||||||
iconSize: 32 * DefaultStyle.dp
|
iconSize: 32 * DefaultStyle.dp
|
||||||
text: qsTr("Mon compte")
|
text: qsTr("Mon compte")
|
||||||
iconSource: AppIcons.manageProfile
|
iconSource: AppIcons.manageProfile
|
||||||
onClicked: openAccountSettings(accountProxy.defaultAccount ? accountProxy.defaultAccount : accountProxy.firstAccount())
|
onClicked: openAccountSettings(accountProxy.defaultAccount ? accountProxy.defaultAccount : accountProxy.firstAccount())
|
||||||
KeyNavigation.up: visibleChildren.length != 0 ? settingsButtons.getPreviousItem(0) : null
|
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(0) : null
|
||||||
KeyNavigation.down: visibleChildren.length != 0 ? settingsButtons.getNextItem(0) : null
|
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(0) : null
|
||||||
}
|
}
|
||||||
IconLabelButton {
|
IconLabelButton {
|
||||||
id: dndButton
|
id: dndButton
|
||||||
|
|
@ -423,51 +405,47 @@ Item {
|
||||||
settingsMenuButton.popup.close()
|
settingsMenuButton.popup.close()
|
||||||
SettingsCpp.dnd = !SettingsCpp.dnd
|
SettingsCpp.dnd = !SettingsCpp.dnd
|
||||||
}
|
}
|
||||||
KeyNavigation.up: visibleChildren.length != 0 ? settingsButtons.getPreviousItem(0) : null
|
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(1) : null
|
||||||
KeyNavigation.down: visibleChildren.length != 0 ? settingsButtons.getNextItem(0) : null
|
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(1) : null
|
||||||
}
|
}
|
||||||
IconLabelButton {
|
IconLabelButton {
|
||||||
id: settingsButton
|
id: settingsButton
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: !SettingsCpp.hideSettings
|
visible: !SettingsCpp.hideSettings
|
||||||
focus: !accountButton.visible && visible
|
|
||||||
iconSize: 32 * DefaultStyle.dp
|
iconSize: 32 * DefaultStyle.dp
|
||||||
text: qsTr("Paramètres")
|
text: qsTr("Paramètres")
|
||||||
iconSource: AppIcons.settings
|
iconSource: AppIcons.settings
|
||||||
onClicked: openContextualMenuComponent(settingsPageComponent)
|
onClicked: openContextualMenuComponent(settingsPageComponent)
|
||||||
KeyNavigation.up: visibleChildren.length != 0 ? settingsButtons.getPreviousItem(1) : null
|
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(2) : null
|
||||||
KeyNavigation.down: visibleChildren.length != 0 ? settingsButtons.getNextItem(1) : null
|
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(2) : null
|
||||||
}
|
}
|
||||||
IconLabelButton {
|
IconLabelButton {
|
||||||
id: recordsButton
|
id: recordsButton
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: !SettingsCpp.disableCallRecordings
|
visible: !SettingsCpp.disableCallRecordings
|
||||||
focus: !accountButton.visible && !settingsButton.visible && visible
|
|
||||||
iconSize: 32 * DefaultStyle.dp
|
iconSize: 32 * DefaultStyle.dp
|
||||||
text: qsTr("Enregistrements")
|
text: qsTr("Enregistrements")
|
||||||
iconSource: AppIcons.micro
|
iconSource: AppIcons.micro
|
||||||
KeyNavigation.up: visibleChildren.length != 0 ? settingsButtons.getPreviousItem(2) : null
|
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(3) : null
|
||||||
KeyNavigation.down: visibleChildren.length != 0 ? settingsButtons.getNextItem(2) : null
|
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(3) : null
|
||||||
}
|
}
|
||||||
IconLabelButton {
|
IconLabelButton {
|
||||||
id: helpButton
|
id: helpButton
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
iconSize: 32 * DefaultStyle.dp
|
iconSize: 32 * DefaultStyle.dp
|
||||||
focus: !accountButton.visible && !settingsButton.visible && !recordsButton.visible
|
|
||||||
text: qsTr("Aide")
|
text: qsTr("Aide")
|
||||||
iconSource: AppIcons.question
|
iconSource: AppIcons.question
|
||||||
onClicked: openContextualMenuComponent(helpPageComponent)
|
onClicked: openContextualMenuComponent(helpPageComponent)
|
||||||
KeyNavigation.up: visibleChildren.length != 0 ? settingsButtons.getPreviousItem(3) : null
|
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(4) : null
|
||||||
KeyNavigation.down: visibleChildren.length != 0 ? settingsButtons.getNextItem(3) : null
|
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(4) : null
|
||||||
}
|
}
|
||||||
IconLabelButton {
|
IconLabelButton {
|
||||||
id: quitButton
|
id: quitButton
|
||||||
Layout.preferredHeight: 32 * DefaultStyle.dp
|
Layout.preferredHeight: 32 * DefaultStyle.dp
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
focus: !accountButton.visible && !settingsButton.visible && visible
|
|
||||||
iconSize: 32 * DefaultStyle.dp
|
iconSize: 32 * DefaultStyle.dp
|
||||||
text: qsTr("Quitter Linphone")
|
text: qsTr("Quitter Linphone")
|
||||||
iconSource: AppIcons.power
|
iconSource: AppIcons.power
|
||||||
|
|
@ -484,8 +462,8 @@ Item {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
KeyNavigation.up: visibleChildren.length != 0 ? settingsButtons.getPreviousItem(4) : null
|
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(5) : null
|
||||||
KeyNavigation.down: visibleChildren.length != 0 ? settingsButtons.getNextItem(4) : null
|
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(5) : null
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -502,8 +480,8 @@ Item {
|
||||||
text: qsTr("Ajouter un compte")
|
text: qsTr("Ajouter un compte")
|
||||||
iconSource: AppIcons.plusCircle
|
iconSource: AppIcons.plusCircle
|
||||||
onClicked: mainItem.addAccountRequest()
|
onClicked: mainItem.addAccountRequest()
|
||||||
KeyNavigation.up: visibleChildren.length != 0 ? settingsButtons.getPreviousItem(5) : null
|
KeyNavigation.up: visibleChildren.length != 0 ? settingsMenuButton.getPreviousItem(7) : null
|
||||||
KeyNavigation.down: visibleChildren.length != 0 ? settingsButtons.getNextItem(5) : null
|
KeyNavigation.down: visibleChildren.length != 0 ? settingsMenuButton.getNextItem(7) : null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,20 +38,21 @@ Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: mainItem.spacing
|
spacing: mainItem.spacing
|
||||||
model: AccountProxy {
|
model: AccountProxy {
|
||||||
|
id: accountProxy
|
||||||
sourceModel: AppCpp.accounts
|
sourceModel: AppCpp.accounts
|
||||||
}
|
}
|
||||||
delegate: Contact{
|
delegate: Contact{
|
||||||
id: contactItem
|
id: contactItem
|
||||||
width: list.width
|
width: list.width
|
||||||
account: modelData
|
account: modelData
|
||||||
|
property bool isSelected: modelData && accountProxy.defaultAccount && modelData.core === accountProxy.defaultAccount.core
|
||||||
onAvatarClicked: fileDialog.open()
|
onAvatarClicked: fileDialog.open()
|
||||||
onBackgroundClicked: {
|
onBackgroundClicked: {
|
||||||
list.currentIndex = index
|
|
||||||
modelData.core.lSetDefaultAccount()
|
modelData.core.lSetDefaultAccount()
|
||||||
}
|
}
|
||||||
onEdit: editAccount(modelData)
|
onEdit: editAccount(modelData)
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
backgroundColor: list.currentIndex === index
|
backgroundColor: contactItem.isSelected
|
||||||
? DefaultStyle.grey_200
|
? DefaultStyle.grey_200
|
||||||
: hovered
|
: hovered
|
||||||
? DefaultStyle.main2_100
|
? DefaultStyle.main2_100
|
||||||
|
|
|
||||||
2
external/linphone-sdk
vendored
2
external/linphone-sdk
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 3e3edec2889317585d5267d764885b2c25806aeb
|
Subproject commit 0383755cb03b44baebadb523ff7e44c51d4bd404
|
||||||
Loading…
Reference in a new issue