blob: e64a639bc1b9e0b7d9cd1e394b598e6fc2429071 [file] [log] [blame]
Isa Nanic6e4a39a2018-12-04 14:26:02 -05001/***************************************************************************
Sébastien Blin68abac92019-01-02 17:41:31 -05002 * Copyright (C) 2019-2019 by Savoir-faire Linux *
Isa Nanic6e4a39a2018-12-04 14:26:02 -05003 * Author: Isa Nanic <isa.nanic@savoirfairelinux.com> *
4 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
Sébastien Blin68abac92019-01-02 17:41:31 -05005 * Author: Anthony L�onard <anthony.leonard@savoirfairelinux.com> *
Isa Nanic6e4a39a2018-12-04 14:26:02 -05006 * Author: Olivier Soldano <olivier.soldano@savoirfairelinux.com> *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 3 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
Andreas Traczykf9465492019-01-09 13:53:37 -050019 * along with this program. If not, see <https://www.gnu.org/licenses/>. *
Isa Nanic6e4a39a2018-12-04 14:26:02 -050020 **************************************************************************/
Andreas Traczykf9465492019-01-09 13:53:37 -050021
22#include "settingswidget.h"
23#include "ui_settingswidget.h"
24
Isa Nanic6e4a39a2018-12-04 14:26:02 -050025#include <QPixmap>
26#include <QTimer>
27#include <QModelIndex>
28#include <QFileDialog>
29#include <QInputDialog>
30#include <QStandardPaths>
31#include <QMessageBox>
Isa Nanic5bafcb92018-12-19 12:54:51 -050032#include <QSettings>
Isa Nanic6e4a39a2018-12-04 14:26:02 -050033
Andreas Traczykf9465492019-01-09 13:53:37 -050034#include "utils.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050035#include "settingsitemwidget.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050036#include "passworddialog.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050037#include "regnamedialog.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050038#include "setavatardialog.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050039#include "deleteaccountdialog.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050040
Andreas Traczykf9465492019-01-09 13:53:37 -050041#include "api/newdevicemodel.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050042#include "media/recordingmodel.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050043#include "audio/settings.h"
44#include "audio/outputdevicemodel.h"
45#include "audio/inputdevicemodel.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050046#include "video/devicemodel.h"
47#include "video/channel.h"
48#include "video/resolution.h"
49#include "video/rate.h"
50#include "video/previewmanager.h"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050051#include "callmodel.h"
52
Sébastien Blind2e8d0e2019-01-10 14:18:36 -050053#ifdef Q_OS_WIN
Andreas Traczykf9465492019-01-09 13:53:37 -050054#include "winsparkle.h"
Sébastien Blind2e8d0e2019-01-10 14:18:36 -050055#endif
Andreas Traczykf9465492019-01-09 13:53:37 -050056
Isa Nanic6e4a39a2018-12-04 14:26:02 -050057SettingsWidget::SettingsWidget(QWidget* parent)
58 : NavWidget(parent),
59 ui(new Ui::SettingsWidget),
Andreas Traczykb81281e2018-12-13 13:13:28 -050060 scrollArea_(new QScrollArea(this)),
Isa Nanic6e4a39a2018-12-04 14:26:02 -050061 deviceModel_(&Video::DeviceModel::instance()),
62 gif(new QMovie(":/images/ajax-loader.gif"))
63{
64 ui->setupUi(this);
65
66 ui->accountSettingsButton->setAutoFillBackground(true);
67 ui->generalSettingsButton->setAutoFillBackground(true);
68 ui->avSettingsButton->setAutoFillBackground(true);
69
70 ui->accountSettingsButton->setChecked(true);
71
72 ui->exitSettingsButton->setIconSize(QSize(24, 24));
73 ui->exitSettingsButton->setIcon(QPixmap(":/images/icons/round-close-24px.svg"));
74
Isa Nanic6e4a39a2018-12-04 14:26:02 -050075 // display name (aka alias)
76 ui->displayNameLineEdit->setAlignment(Qt::AlignHCenter);
77 ui->displayNameLineEdit->setPlaceholderText(tr("Enter the displayed name"));
78
Isa Nanic6e4a39a2018-12-04 14:26:02 -050079 setSelected(Button::accountSettingsButton);
80
81 ui->currentRegisteredID->setReadOnly(true);
82 ui->currentRegisteredID->setStyleSheet("border : 0px;");
83
84 ui->currentRingID->setReadOnly(true);
85
86 avatarSize_ = ui->currentAccountAvatar->width();
87
88 ui->currentAccountAvatar->setIconSize(QSize(avatarSize_, avatarSize_));
89
90 // create ellipse-selectable avatar image
91 QRegion avatarClickableRegion(-1, -1,
92 ui->currentAccountAvatar->width() + 2, ui->currentAccountAvatar->height() + 2, QRegion::Ellipse);
93 ui->currentAccountAvatar->setMask(avatarClickableRegion);
94
95 QString styleS(
Andreas Traczyk2771e2e2019-01-08 17:44:33 -050096 "QPushButton {"
97 " background-color: white;"
98 " border-right: 1px solid #f0f0f0;"
Isa Nanic6e4a39a2018-12-04 14:26:02 -050099 "}"
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500100 " QPushButton:hover {"
101 " background-color: rgb(245, 245, 245);"
102 " border-right: 1px solid #f0f0f0;"
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500103 " }"
104
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500105 "QPushButton:checked {"
106 " background-color: rgb(237, 237, 237);"
107 " border-right: 1px solid #f0f0f0;"
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500108 "}"
109 );
110
111 ui->accountSettingsButton->setStyleSheet(styleS);
112 ui->generalSettingsButton->setStyleSheet(styleS);
113 ui->avSettingsButton->setStyleSheet(styleS);
114
115 ui->advancedAccountSettingsPButton->setIcon(QPixmap(":/images/icons/round-arrow_drop_down-24px.svg"));
116 ui->linkDevPushButton->setIcon(QPixmap(":/images/icons/round-add-24px.svg"));
117 ui->blockedContactsBtn->setIcon(QPixmap(":/images/icons/round-arrow_drop_up-24px.svg"));
118
119 ui->advancedSettingsOffsetLabel->show();
120
Andreas Traczykfc33a492018-12-14 13:53:13 -0500121 auto accountList = LRCInstance::accountModel().getAccountList();
122 if (!accountList.size()) {
Andreas Traczykf9465492019-01-09 13:53:37 -0500123 Utils::oneShotConnect(&LRCInstance::accountModel(),
Andreas Traczykfc33a492018-12-14 13:53:13 -0500124 &lrc::api::NewAccountModel::accountAdded,
Andreas Traczykf9465492019-01-09 13:53:37 -0500125 [this](const std::string& accountId) {
Andreas Traczyk14c3e862018-12-26 14:02:30 -0500126 Q_UNUSED(accountId);
Andreas Traczykfc33a492018-12-14 13:53:13 -0500127 setConnections();
Andreas Traczykfc33a492018-12-14 13:53:13 -0500128 });
129 } else {
130 setConnections();
131 }
132
133 ui->containerWidget->setVisible(false);
134}
135
136void
137SettingsWidget::navigated(bool to)
138{
139 ui->containerWidget->setVisible(to);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500140}
141
Andreas Traczyk6ace34f2018-12-14 14:31:23 -0500142void SettingsWidget::updateCustomUI()
143{
144}
145
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500146void
147SettingsWidget::leaveSettingsSlot()
148{
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500149 if (advancedSettingsDropped_) {
150 toggleAdvancedSettings();
151 }
152
Isa Nanic054ef9a2018-12-11 11:56:28 -0500153 Video::PreviewManager::instance().stopPreview();
154 saveSizeIndex();
155
156 emit NavigationRequested(ScreenEnum::CallScreen);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500157}
158
159SettingsWidget::~SettingsWidget()
160{
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500161 delete ui;
162}
163
164// called at every callwidget -> settingsWidget navigation
165void
166SettingsWidget::updateSettings(int size)
167{
168 setSelected(Button::accountSettingsButton);
169 resize(size);
170 updateAccountInfoDisplayed();
171}
172
173void
174SettingsWidget::resize(int size)
175{
176 ui->rightSettingsWidget->setGeometry(size, 0, this->width() - size, this->height());
177 ui->accountSettingsButton->setFixedWidth(size);
178}
179
180void
181SettingsWidget::setSelected(Button sel)
182{
Isa Nanic054ef9a2018-12-11 11:56:28 -0500183 saveSizeIndex();
184
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500185 switch (sel)
186 {
187 case Button::accountSettingsButton:
Isa Nanic054ef9a2018-12-11 11:56:28 -0500188 Video::PreviewManager::instance().stopPreview();
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500189
Isa Nanic054ef9a2018-12-11 11:56:28 -0500190 if (advancedSettingsDropped_) { toggleAdvancedSettings(); }
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500191 ui->stackedWidget->setCurrentWidget(ui->currentAccountSettingsScrollWidget);
192 if (pastButton_ == Button::generalSettingsButton) {
193 ui->accountSettingsButton->setChecked(true);
194 ui->generalSettingsButton->setChecked(false);
195 break;
196 }
197 else {
198 ui->accountSettingsButton->setChecked(true);
199 ui->avSettingsButton->setChecked(false);
200 break;
201 }
202 case Button::generalSettingsButton:
Isa Nanic054ef9a2018-12-11 11:56:28 -0500203 Video::PreviewManager::instance().stopPreview();
204
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500205 ui->stackedWidget->setCurrentWidget(ui->generalSettings);
206 populateGeneralSettings();
207 if (pastButton_ == Button::avSettingsButton) {
208 ui->generalSettingsButton->setChecked(true);
209 ui->avSettingsButton->setChecked(false);
210 break;
211 }
212 else {
213 ui->generalSettingsButton->setChecked(true);
214 ui->accountSettingsButton->setChecked(false);
215 break;
216 }
217 case Button::avSettingsButton:
218 ui->stackedWidget->setCurrentWidget(ui->avSettings);
Isa Nanic054ef9a2018-12-11 11:56:28 -0500219 populateAVSettings();
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500220 if (pastButton_ == Button::accountSettingsButton) {
221 ui->avSettingsButton->setChecked(true);
222 ui->accountSettingsButton->setChecked(false);
223 break;
224 }
225 else {
226 ui->avSettingsButton->setChecked(true);
227 ui->generalSettingsButton->setChecked(false);
228 break;
229 }
230 }
231 pastButton_ = sel;
232}
233
234// called to update current settings information when navigating to settingsWidget
235void
236SettingsWidget::updateAccountInfoDisplayed()
237{
238 ui->currentRegisteredID->setText(QString::fromStdString(LRCInstance::getCurrentAccountInfo().registeredName));
239 ui->currentRingID->setText(QString::fromStdString(LRCInstance::getCurrentAccountInfo().profileInfo.uri));
240
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500241// if no registered name is found for account
242 if (LRCInstance::getCurrentAccountInfo().registeredName.empty()) {
243 ui->currentRegisteredID->setReadOnly(false);
244 }
245 else {
246 ui->currentRegisteredID->setReadOnly(true);
247 setRegNameUi(RegName::BLANK);
248 }
249
250 ui->currentAccountAvatar->setIcon(LRCInstance::getCurrAccPixmap().
251 scaledToHeight(avatarSize_, Qt::SmoothTransformation));
252
253 ui->accountEnableCheckBox->setChecked(LRCInstance::getCurrentAccountInfo().enabled);
254
255 ui->displayNameLineEdit->setText(QString::fromStdString(LRCInstance::getCurrentAccountInfo().profileInfo.alias));
256
257 updateAndShowDevicesSlot();
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500258 bannedContactsShown_ = false;
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500259 if (!LRCInstance::getCurrentAccountInfo().contactModel->getBannedContacts().size()){
260 ui->blockedContactsBtn->hide();
261 } else {
262 ui->blockedContactsBtn->show();
263 }
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500264}
265
266void
267SettingsWidget::passwordClicked()
268{
269 PasswordDialog passwdDialog(this);
270 passwdDialog.exec();
271}
272
273void
274SettingsWidget::toggleAdvancedSettings()
275{
276 if (advancedSettingsDropped_) {
277 ui->advancedAccountSettingsPButton->setIcon(QPixmap(":/images/icons/round-arrow_drop_down-24px.svg"));
278 ui->currentAccountSettingsScrollLayout->removeWidget(advancedSettingsWidget_);
279 ui->scrollBarLabel->show();
280 ui->advancedSettingsOffsetLabel->hide();
281 delete advancedSettingsWidget_;
282 }
283 else { // will show advanced settings next
284 ui->advancedAccountSettingsPButton->setIcon(QPixmap(":/images/icons/round-arrow_drop_up-24px.svg"));
Andreas Traczykb81281e2018-12-13 13:13:28 -0500285 advancedSettingsWidget_ = new AdvancedSettingsWidget(this);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500286 advancedSettingsWidget_->setMaximumWidth(ui->scrollAreaWidgetContents->width() - 10);
287 ui->currentAccountSettingsScrollLayout->addWidget(advancedSettingsWidget_);
288 ui->advancedSettingsOffsetLabel->show();
289 ui->scrollBarLabel->hide();
290 }
291 advancedSettingsDropped_ = !advancedSettingsDropped_;
292}
293
294void
295SettingsWidget::toggleBannedContacts()
296{
297 if (bannedContactsShown_) { // will show linked devices next
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500298 bannedContactsShown_ = false;
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500299 updateAndShowDevicesSlot();
300 }
301 else { // will show banned contacts next
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500302 bannedContactsShown_ = true;
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500303 updateAndShowBannedContactsSlot();
304 }
305}
306
307void
308SettingsWidget::resizeEvent(QResizeEvent* event)
309{
310 QWidget::resizeEvent(event);
311 scrollArea_->resize(ui->currentAccountSettingsScrollWidget->width(), this->height());
312}
313
314void
315SettingsWidget::avatarClicked()
316{
317 SetAvatarDialog avatarDialog(this);
318
319 // return new avatar pixmap from setAvatarDialog
Andreas Traczyk2771e2e2019-01-08 17:44:33 -0500320 connect(&avatarDialog, &SetAvatarDialog::pixmapSignal,
321 [this](const std::string& pixString) {
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500322 if (!pixString.empty()) {
323 LRCInstance::setCurrAccAvatar(pixString);
324 updateAccountInfoDisplayed();
325 }
326 }
327 );
328 avatarDialog.exec();
329}
330
331void
332SettingsWidget::verifyRegisteredNameSlot()
333{
334 if (!LRCInstance::getCurrentAccountInfo().registeredName.empty()) {
335 setRegNameUi(RegName::BLANK);
336 }
337 else {
338 registeredName_ = ui->currentRegisteredID->text().simplified();
339
340 if (!registeredName_.isEmpty()) {
341 if (validateRegNameForm(registeredName_)) { // name has valid form
342 setRegNameUi(RegName::SEARCHING);
343 QTimer::singleShot(300, this, SLOT(beforeNameLookup()));
344 } else { // name does not have valid form
345 setRegNameUi(RegName::INVALIDFORM);
346 }
347 } else {
348 setRegNameUi(RegName::BLANK);
349 }
350 }
351}
352
353// returns true if name is valid registered name
354bool
355SettingsWidget::validateRegNameForm(const QString& regName)
356{
357 QRegularExpression regExp(" ");
358 if (regName.size() > 2 && !regName.contains(regExp)) {
359 return true;
Isa Nanic26c86612018-12-14 12:21:56 -0500360 } else {
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500361 return false;
362 }
363}
364
365void
366SettingsWidget::receiveRegNameSlot(const std::string& accountID,
367 lrc::api::account::LookupStatus status, const std::string& address, const std::string& name)
368{
369 Q_UNUSED(accountID); Q_UNUSED(address);
370 afterNameLookup(status, name);
371}
372
373void
374SettingsWidget::beforeNameLookup()
375{
376 NameDirectory::instance().lookupName(nullptr, QString(), registeredName_);
377}
378
379void
380SettingsWidget::afterNameLookup(lrc::api::account::LookupStatus status, const std::string& regName)
381{
382 if (registeredName_.toStdString() == regName && regName.length() > 2) {
383 if (status == lrc::api::account::LookupStatus::NOT_FOUND) {
384 setRegNameUi(RegName::FREE);
385 }
386 else {
387 setRegNameUi(RegName::TAKEN);
388 }
389 }
390 else {
391 setRegNameUi(RegName::BLANK);
392 }
393}
394
395void SettingsWidget::setRegNameUi(RegName stat)
396{
397 disconnect(gif, SIGNAL(frameChanged(int)), this, SLOT(setButtonIconSlot(int)));
398 disconnect(ui->regNameButton, &QPushButton::clicked, this, &SettingsWidget::regNameRegisteredSlot);
399
400 switch (stat) {
401 case RegName::BLANK:
402 ui->currentRegisteredID->setStyleSheet("padding-left: 5px; border: 0px; border-radius: 3px; border: 1px solid rgb(245, 245, 245);");
403 regNameBtn_ = false;
404 ui->currentRegisteredID->setToolTip(tr(""));
405 ui->regNameButton->setIcon(QPixmap());
406 ui->regNameButton->setEnabled(false);
407 break;
408
409 case RegName::INVALIDFORM:
410 ui->currentRegisteredID->setStyleSheet("padding-left: 5px; border: 1px solid red; border-radius: 3px;");
411 regNameBtn_ = false;
412 ui->currentRegisteredID->setToolTip(tr("A registered name should not have any spaces and must be at least three letters long"));
413 ui->regNameButton->setIcon(QPixmap(":/images/icons/round-error-24px.svg"));
414 ui->regNameButton->setToolTip(tr("A registered name should not have any spaces and must be at least three letters long"));
415 ui->regNameButton->setEnabled(true);
416 break;
417
418 case RegName::TAKEN:
419 ui->currentRegisteredID->setStyleSheet("padding-left: 5px; border: 1px solid orange; border-radius: 3px;");
420 regNameBtn_ = false;
421 ui->currentRegisteredID->setToolTip(tr("This name is already taken"));
422 ui->regNameButton->setIcon(QPixmap(":/images/icons/round-error-24px.svg"));
423 ui->regNameButton->setToolTip(tr("This registered name is already taken"));
424 ui->regNameButton->setEnabled(true);
425 break;
426
427 case RegName::FREE:
428 ui->currentRegisteredID->setStyleSheet("padding-left: 5px; border: 1px solid green; border-radius: 3px;");
429 regNameBtn_ = true;
430 ui->currentRegisteredID->setToolTip(tr("This name is available"));
431 ui->regNameButton->setIcon(QPixmap(":/images/icons/round-check_circle-24px.svg"));
432 ui->regNameButton->setToolTip(tr("Register this name"));
433 ui->regNameButton->setEnabled(true);
434
435 connect(ui->regNameButton, &QPushButton::clicked, this, &SettingsWidget::regNameRegisteredSlot);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500436 break;
437
438 case RegName::SEARCHING:
439 ui->currentRegisteredID->setStyleSheet("padding-left: 5px; border: 1px solid rgb(2, 187, 213); border-radius: 3px;");
440 regNameBtn_ = false;
441 ui->currentRegisteredID->setToolTip(tr(""));
442
443 connect(gif, SIGNAL(frameChanged(int)), this, SLOT(setButtonIconSlot(int)));
444 gif->start();
445 ui->regNameButton->setEnabled(false);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500446 break;
447 }
448}
Isa Nanic26c86612018-12-14 12:21:56 -0500449
450void
451SettingsWidget::setButtonIconSlot(int frame)
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500452{
453 Q_UNUSED(frame);
454 ui->regNameButton->setIcon(QIcon(gif->currentPixmap()));
455}
456
457void
458SettingsWidget::regNameRegisteredSlot()
459{
460 if (!regNameBtn_) { return; }
461
462 RegNameDialog regNameDialog(registeredName_, this);
Isa Nanic26c86612018-12-14 12:21:56 -0500463 if (regNameDialog.exec() == QDialog::Accepted) {
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500464 ui->currentRegisteredID->setReadOnly(true);
Isa Nanic26c86612018-12-14 12:21:56 -0500465 ui->currentRegisteredID->setText(registeredName_);
466
467 lrc::api::account::ConfProperties_t accountProperties = LRCInstance::accountModel().getAccountConfig(LRCInstance::getCurrAccId());
468 LRCInstance::accountModel().setAccountConfig(LRCInstance::getCurrAccId(), accountProperties);
469 } else {
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500470 ui->currentRegisteredID->setText("");
471 registeredName_ = "";
472 }
473 setRegNameUi(RegName::BLANK);
474}
475
476void
477SettingsWidget::setAccEnableSlot(int state)
478{
479 LRCInstance::editableAccountModel()->enableAccount(LRCInstance::getCurrAccId(), (bool)state);
480
481 auto confProps = LRCInstance::accountModel().getAccountConfig(LRCInstance::getCurrAccId());
482 LRCInstance::editableAccountModel()->setAccountConfig(LRCInstance::getCurrAccId(), confProps);
483}
484
485void
486SettingsWidget::delAccountSlot()
487{
488 DeleteAccountDialog delDialog(this);
Andreas Traczyk39fa4d02019-01-03 17:32:07 -0500489 auto ret = delDialog.exec();
490 if (ret == QDialog::Accepted) {
491 LRCInstance::setSelectedAccountId("");
492 if (!LRCInstance::accountModel().getAccountList().size()) {
493 emit NavigationRequested(ScreenEnum::WizardScreen);
494 } else {
495 LRCInstance::setSelectedConvId("");
496 emit NavigationRequested(ScreenEnum::CallScreen);
497 }
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500498 }
499}
500
501void
502SettingsWidget::removeDeviceSlot(int index)
503{
504 if (!index) { return; }
505
506 auto deviceList = LRCInstance::getCurrentAccountInfo().deviceModel->getAllDevices();
507 auto it = deviceList.begin();
508
509 std::advance(it, index);
510 QString psswd;
511
512 bool ok = false;
513 if (LRCInstance::getCurrAccConfig().archiveHasPassword) {
514 psswd = QInputDialog::getText(this, tr("Remove Device"),
Isa Nanic100368f2018-12-11 16:43:01 -0500515 tr("Enter this account's password to confirm the removal of this device"), QLineEdit::Password,
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500516 QDir::home().dirName(), &ok);
517 }
518 else {
519 psswd = "";
520 QMessageBox devDel;
521 devDel.setText(tr("Please confirm that you wish to remove this device"));
522 devDel.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
523 devDel.setDefaultButton(QMessageBox::Cancel);
524 if (devDel.exec() == QMessageBox::Ok) { goto delete_; }
525 }
526
527 if (ok) {
528 delete_:
529 LRCInstance::getCurrentAccountInfo().deviceModel->revokeDevice(it->id, psswd.toStdString());
530 updateAndShowDevicesSlot();
531 }
532}
533
534void
535SettingsWidget::unban(int index)
536{
537 auto bannedContactList = LRCInstance::getCurrentAccountInfo().contactModel->getBannedContacts();
538 auto it = bannedContactList.begin();
539 std::advance(it, index);
540
541 auto contactInfo = LRCInstance::getCurrentAccountInfo().contactModel->getContact(*it);
542
543 LRCInstance::getCurrentAccountInfo().contactModel->addContact(contactInfo);
544 updateAndShowBannedContactsSlot();
545}
546
547void
548SettingsWidget::exportAccountSlot()
549{
550 QFileDialog dialog(this);
551 QString dir = QFileDialog::getExistingDirectory(this, tr("Export Account Here"),
552 QDir::homePath() + "/Desktop", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
553
554 if (!dir.isEmpty()) {
555 LRCInstance::accountModel().exportToFile(LRCInstance::getCurrAccId(), (dir + "/export.gz").toStdString());
556 }
557}
558
559void
560SettingsWidget::updateAndShowDevicesSlot()
561{
562 ui->settingsListWidget->clear();
563
564 ui->label->setText(tr("Linked Devices"));
565 ui->blockedContactsBtn->setText(tr("Blocked Contacts"));
566 ui->linkDevPushButton->show();
567
568 auto deviceList = LRCInstance::getCurrentAccountInfo().deviceModel->getAllDevices();
569
570 int i = 0;
571
572 for (auto it = deviceList.begin(); it != deviceList.end(); ++it, ++i) {
573 SettingsItemWidget* item = new SettingsItemWidget(itemHeight_, i, false, ui->settingsListWidget);
574 item->setSizeHint(QSize(ui->settingsListWidget->width(), itemHeight_));
575 ui->settingsListWidget->addItem(item);
576
577 if (i) {
578 connect(item->button_, &QPushButton::clicked, [this, i]() {
579 removeDeviceSlot(i);
580 }
581 );
582 }
583 }
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500584}
585
586void
587SettingsWidget::updateAndShowBannedContactsSlot()
588{
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500589 if (bannedContactsShown_) {
590 ui->settingsListWidget->clear();
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500591
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500592 ui->label->setText(tr("Blocked Contacts"));
593 ui->blockedContactsBtn->setText(tr("Linked Devices"));
594 ui->linkDevPushButton->hide();
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500595
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500596 auto bannedContactList = LRCInstance::getCurrentAccountInfo().contactModel->getBannedContacts();
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500597
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500598 int i = 0;
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500599
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500600 for (auto it = bannedContactList.begin(); it != bannedContactList.end(); ++it, ++i) {
601 SettingsItemWidget* item = new SettingsItemWidget(itemHeight_, i, true, ui->settingsListWidget);
602 item->setSizeHint(QSize(ui->settingsListWidget->width(), itemHeight_));
603 ui->settingsListWidget->addItem(item);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500604
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500605 connect(item->button_, &QPushButton::clicked, [this, i]() {
606 unban(i);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500607 }
Isa Nanic3a9c6e22018-12-11 17:07:20 -0500608 );
609 }
610 if (!bannedContactList.size()) { updateAndShowDevicesSlot(); ui->blockedContactsBtn->hide(); }
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500611 }
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500612}
613
614void
615SettingsWidget::showLinkDevSlot()
616{
617 if (!advancedSettingsWidget_) { delete advancedSettingsWidget_; }
618
619 linkDevWidget = new LinkDevWidget(ui->scrollAreaWidgetContents);
620 linkDevWidget->setMinimumWidth(600);
621
622 ui->accountHorLayout->insertWidget(1, linkDevWidget);
623
624 linkDevWidget->show();
625 ui->centralWidget->hide();
626
627 connect(linkDevWidget->cancelBtn(), &QPushButton::clicked, this, &SettingsWidget::showCurrentAccountSlot);
628 connect(linkDevWidget->endCancelBtn(), &QPushButton::clicked, this, &SettingsWidget::showCurrentAccountSlot);
629}
630
631void
632SettingsWidget::showCurrentAccountSlot()
633{
634 disconnect(linkDevWidget);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500635 delete linkDevWidget;
Isa Nanic5bafcb92018-12-19 12:54:51 -0500636
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500637 ui->centralWidget->show();
Isa Nanic5bafcb92018-12-19 12:54:51 -0500638 updateAndShowDevicesSlot();
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500639}
640
641void
642SettingsWidget::setConnections()
643{
644 // exitSettingsButton
645 connect(ui->exitSettingsButton, &QPushButton::clicked, this, &SettingsWidget::leaveSettingsSlot);
646
647 connect(ui->accountSettingsButton, &QPushButton::clicked, [this]() {
648 setSelected(Button::accountSettingsButton); }
649 );
650
651 connect(ui->generalSettingsButton, &QPushButton::clicked, [this]() {
652 setSelected(Button::generalSettingsButton); }
653 );
654
655 connect(ui->avSettingsButton, &QPushButton::clicked, [this]() {
656 setSelected(Button::avSettingsButton); }
657 );
658
659 connect(ui->passwdPushButton, &QPushButton::clicked, [this]() {
660 passwordClicked(); }
661 );
662
663 connect(ui->currentAccountAvatar, &QPushButton::clicked, [this]() {
664 avatarClicked(); }
665 );
666
667 connect(ui->advancedAccountSettingsPButton, &QPushButton::clicked, this, &SettingsWidget::toggleAdvancedSettings);
668
669 connect(ui->currentRegisteredID, &QLineEdit::textChanged, this, &SettingsWidget::verifyRegisteredNameSlot);
670
671 connect(&LRCInstance::accountModel(), &lrc::api::NewAccountModel::registeredNameFound,
672 this, &SettingsWidget::receiveRegNameSlot);
673
674 //connect "export account" button
675 connect(ui->btnExportAccount, &QPushButton::clicked, this, &SettingsWidget::exportAccountSlot);
676
677 // connect "delete account" button
678 connect(ui->btnDeletAccount, &QPushButton::clicked, this, &SettingsWidget::delAccountSlot);
679
680 // connect "banned contacts" button
681 connect(ui->blockedContactsBtn, &QPushButton::clicked, this, &SettingsWidget::toggleBannedContacts);
682
683 // connect "link device" button
684 connect(ui->linkDevPushButton, &QPushButton::clicked, this, &SettingsWidget::showLinkDevSlot);
685
686 // update banned accounts automatically
687 connect(LRCInstance::getCurrentAccountInfo().contactModel.get(), &lrc::api::ContactModel::modelUpdated,
688 this, &SettingsWidget::updateAndShowBannedContactsSlot);
689
690 // update linked devices automatically
691 QObject::connect(LRCInstance::getCurrentAccountInfo().deviceModel.get(), &lrc::api::NewDeviceModel::deviceUpdated,
692 this, &SettingsWidget::updateAndShowDevicesSlot);
693
694 // account settings setters {
Isa Nanic26c86612018-12-14 12:21:56 -0500695 connect(ui->accountEnableCheckBox, &QCheckBox::clicked, this, &SettingsWidget::setAccEnableSlot);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500696
697 connect(ui->displayNameLineEdit, &QLineEdit::textChanged, [this](const QString& displayName) {
698 LRCInstance::setCurrAccDisplayName(displayName.toStdString());
699 }
700 );
701
702 // general settings
703
Isa Nanic100368f2018-12-11 16:43:01 -0500704 connect(ui->notificationCheckBox, &QAbstractButton::clicked, this, &SettingsWidget::setNotificationsSlot);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500705
Isa Nanic100368f2018-12-11 16:43:01 -0500706 connect(ui->closeOrMinCheckBox, &QAbstractButton::clicked, this, &SettingsWidget::setClosedOrMinSlot);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500707
Isa Nanic100368f2018-12-11 16:43:01 -0500708 connect(ui->downloadButton, &QAbstractButton::clicked, this, &SettingsWidget::openDownloadFolderSlot);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500709
Isa Nanic100368f2018-12-11 16:43:01 -0500710 connect(ui->alwaysRecordingCheckBox, &QAbstractButton::clicked, this, &SettingsWidget::setAlwaysRecordingSlot);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500711
Isa Nanic100368f2018-12-11 16:43:01 -0500712 connect(ui->checkUpdateButton, &QAbstractButton::clicked, this, &SettingsWidget::checkForUpdateSlot);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500713
714 connect(ui->intervalUpdateCheckSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &SettingsWidget::setUpdateIntervalSlot);
715
Isa Nanic100368f2018-12-11 16:43:01 -0500716 connect(ui->autoUpdateCheckBox, &QAbstractButton::clicked, this, &SettingsWidget::setUpdateAutomaticSlot);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500717
718 // audio / visual settings
719
720 connect(ui->recordPathButton, &QPushButton::clicked, this, &SettingsWidget::openRecordFolderSlot);
721}
722
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500723// ************************* General Settings *************************
724
725void SettingsWidget::populateGeneralSettings()
726{
Isa Nanic5bafcb92018-12-19 12:54:51 -0500727 QSettings settings;
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500728
729 // settings
Isa Nanic5bafcb92018-12-19 12:54:51 -0500730 ui->downloadButton->setText(QString::fromStdString(LRCInstance::dataTransferModel().downloadDirectory));
731 ui->closeOrMinCheckBox->setChecked(settings.value(SettingsKey::closeOrMinimized).toBool());
732 ui->notificationCheckBox->setChecked(settings.value(SettingsKey::enableNotifications).toBool());
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500733
734 //recordings
735 ui->alwaysRecordingCheckBox->setChecked(media::RecordingModel::instance().isAlwaysRecording());
736
737 if (media::RecordingModel::instance().recordPath().isEmpty()) {
738 QString recordPath = QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
739 media::RecordingModel::instance().setRecordPath(recordPath);
740 }
741 ui->recordPathButton->setText(media::RecordingModel::instance().recordPath());
742
Sébastien Blind2e8d0e2019-01-10 14:18:36 -0500743#ifdef Q_OS_WIN
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500744 ui->autoUpdateCheckBox->setChecked(win_sparkle_get_automatic_check_for_updates());
745 ui->intervalUpdateCheckSpinBox->setValue(win_sparkle_get_update_check_interval() / 86400);
Sébastien Blind2e8d0e2019-01-10 14:18:36 -0500746#endif
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500747}
748
749void
750SettingsWidget::setNotificationsSlot(int state)
751{
Isa Nanic5bafcb92018-12-19 12:54:51 -0500752 QSettings settings;
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500753 if (state == Qt::CheckState::Unchecked) {
Isa Nanic5bafcb92018-12-19 12:54:51 -0500754 settings.setValue(SettingsKey::enableNotifications, false);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500755 } else {
Isa Nanic5bafcb92018-12-19 12:54:51 -0500756 settings.setValue(SettingsKey::enableNotifications, true);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500757 }
758}
759
760void
761SettingsWidget::setClosedOrMinSlot(int state)
762{
Isa Nanic5bafcb92018-12-19 12:54:51 -0500763 QSettings settings;
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500764 if (state == Qt::CheckState::Unchecked) {
Isa Nanic5bafcb92018-12-19 12:54:51 -0500765 settings.setValue(SettingsKey::closeOrMinimized, false);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500766 }
767 else {
Isa Nanic5bafcb92018-12-19 12:54:51 -0500768 settings.setValue(SettingsKey::closeOrMinimized, true);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500769 }
770}
771
772void
773SettingsWidget::checkForUpdateSlot()
774{
Sébastien Blind2e8d0e2019-01-10 14:18:36 -0500775#ifdef Q_OS_WIN
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500776 win_sparkle_check_update_with_ui();
Sébastien Blind2e8d0e2019-01-10 14:18:36 -0500777#endif
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500778}
779
780void
781SettingsWidget::setUpdateIntervalSlot(int value)
782{
Sébastien Blind2e8d0e2019-01-10 14:18:36 -0500783#ifdef Q_OS_WIN
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500784 win_sparkle_set_update_check_interval(value * 86400);
Sébastien Blind2e8d0e2019-01-10 14:18:36 -0500785#endif
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500786}
787
788void
789SettingsWidget::setUpdateAutomaticSlot(int state)
790{
Sébastien Blind2e8d0e2019-01-10 14:18:36 -0500791#ifdef Q_OS_WIN
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500792 if (state == Qt::CheckState::Unchecked) {
793 win_sparkle_set_automatic_check_for_updates(false);
794 ui->intervalUpdateCheckSpinBox->setEnabled(false);
795 } else {
796 win_sparkle_set_automatic_check_for_updates(true);
797 ui->intervalUpdateCheckSpinBox->setEnabled(true);
798 }
Sébastien Blind2e8d0e2019-01-10 14:18:36 -0500799#endif
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500800}
801
802void
803SettingsWidget::openDownloadFolderSlot()
804{
Isa Nanic5bafcb92018-12-19 12:54:51 -0500805 QSettings settings;
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500806 QString dir = QFileDialog::getExistingDirectory(this, tr("Select A Folder For Your Downloads"),
807 QStandardPaths::writableLocation(QStandardPaths::DownloadLocation), QFileDialog::ShowDirsOnly
808 | QFileDialog::DontResolveSymlinks);
809
810 if (!dir.isEmpty()) {
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500811 ui->downloadButton->setText(dir);
Isa Nanic5bafcb92018-12-19 12:54:51 -0500812 settings.setValue(SettingsKey::downloadPath, dir);
Isa Nanic7a3dfa42018-12-12 12:34:42 -0500813 LRCInstance::editableDataTransferModel()->downloadDirectory = dir.toStdString() + "/";
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500814 }
815}
816
817void
818SettingsWidget::setAlwaysRecordingSlot(int state)
819{
820 if (state == Qt::CheckState::Unchecked) {
821 media::RecordingModel::instance().setAlwaysRecording(false);
822 } else {
823 media::RecordingModel::instance().setAlwaysRecording(true);
824 }
825}
826
827void
828SettingsWidget::openRecordFolderSlot()
829{
830 QString dir = QFileDialog::getExistingDirectory(this, tr("Select A Folder For Your Recordings"),
831 media::RecordingModel::instance().recordPath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
832
833 if (!dir.isEmpty()) {
834 media::RecordingModel::instance().setRecordPath(dir);
835 ui->recordPathButton->setText(media::RecordingModel::instance().recordPath());
836 }
837}
838
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500839// ************************* Audio/Visual Settings *************************
840
841void
842SettingsWidget::populateAVSettings()
843{
844 ui->deviceBox->setModel(deviceModel_);
845 connect(deviceModel_, SIGNAL(currentIndexChanged(int)),
846 this, SLOT(deviceIndexChanged(int)));
847
Isa Nanic9316d472018-12-18 16:27:13 -0500848 // Audio settings
849 auto inputModel = Audio::Settings::instance().inputDeviceModel();
850 auto outputModel = Audio::Settings::instance().outputDeviceModel();
851
852 ui->inputComboBox->setModel(inputModel);
853 ui->outputComboBox->setModel(outputModel);
854
855 auto inputIndex = inputModel->selectionModel()->currentIndex();
856 auto outputIndex = outputModel->selectionModel()->currentIndex();
857
858 ui->inputComboBox->setCurrentIndex(inputIndex.row());
859 ui->outputComboBox->setCurrentIndex(outputIndex.row());
860
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500861 if (ui->deviceBox->count() > 0) {
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500862 deviceBoxCurrentIndexChangedSlot(0);
863 }
864
Isa Nanic054ef9a2018-12-11 11:56:28 -0500865 if (currentResIndex >= 0) {
866 ui->sizeBox->setCurrentIndex(currentResIndex);
867 }
868
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500869 connect(ui->outputComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
870 this, &SettingsWidget::outputDevIndexChangedSlot);
871 connect(ui->inputComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
872 this, &SettingsWidget::inputdevIndexChangedSlot);
873
874 connect(ui->deviceBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
875 &SettingsWidget::deviceBoxCurrentIndexChangedSlot);
876 connect(ui->sizeBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
877 &SettingsWidget::sizeBoxCurrentIndexChangedSlot);
Isa Nanic9316d472018-12-18 16:27:13 -0500878
879 showPreview();
Isa Nanic054ef9a2018-12-11 11:56:28 -0500880}
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500881
Isa Nanic054ef9a2018-12-11 11:56:28 -0500882void
883SettingsWidget::saveSizeIndex()
884{
885 currentResIndex = ui->sizeBox->currentIndex();
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500886}
887
888void
889SettingsWidget::showPreview()
890{
891 if (!CallModel::instance().getActiveCalls().size()) {
892 ui->previewUnavailableLabel->hide();
893 ui->videoWidget->show();
Isa Nanic054ef9a2018-12-11 11:56:28 -0500894 startVideo();
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500895 ui->videoWidget->setIsFullPreview(true);
896 } else {
897 ui->previewUnavailableLabel->show();
898 ui->videoWidget->hide();
899 }
900}
901
902void
903SettingsWidget::deviceBoxCurrentIndexChangedSlot(int index)
904{
905 if (index < 0) {
906 return;
907 }
908
Isa Nanic9316d472018-12-18 16:27:13 -0500909 deviceModel_->setActive(index);
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500910
911 auto device = deviceModel_->activeDevice();
912
913 ui->sizeBox->clear();
914
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500915 if (device->channelList().size() > 0) {
916 for (auto resolution : device->channelList()[0]->validResolutions()) {
917 ui->sizeBox->addItem(resolution->name());
918 }
919 }
920 ui->sizeBox->setCurrentIndex(
921 device->channelList()[0]->activeResolution()->relativeIndex());
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500922}
923
924void
925SettingsWidget::sizeBoxCurrentIndexChangedSlot(int index)
926{
Isa Nanic9316d472018-12-18 16:27:13 -0500927 if (index < 0) {
928 return;
929 }
930
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500931 auto device = deviceModel_->activeDevice();
932
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500933 device->channelList()[0]->setActiveResolution(device->channelList()[0]->validResolutions()[index]);
Isa Nanic9316d472018-12-18 16:27:13 -0500934
935 QTimer::singleShot(200, this, [this]() {
936 deviceModel_->setActive(ui->deviceBox->currentIndex());
937 });
Isa Nanic6e4a39a2018-12-04 14:26:02 -0500938}
939
940void
941SettingsWidget::deviceIndexChanged(int index)
942{
943 ui->deviceBox->setCurrentIndex(index);
944
945 ui->videoLayout->update();
946}
947
948void
949SettingsWidget::outputDevIndexChangedSlot(int index)
950{
951 auto outputModel = Audio::Settings::instance().outputDeviceModel();
952 outputModel->selectionModel()->setCurrentIndex(outputModel->index(index), QItemSelectionModel::ClearAndSelect);
953}
954
955void
956SettingsWidget::inputdevIndexChangedSlot(int index)
957{
958 auto inputModel = Audio::Settings::instance().inputDeviceModel();
959 inputModel->selectionModel()->setCurrentIndex(inputModel->index(index), QItemSelectionModel::ClearAndSelect);
Isa Nanic054ef9a2018-12-11 11:56:28 -0500960}
961
962void
963SettingsWidget::startVideo()
964{
965 Video::PreviewManager::instance().stopPreview();
966 Video::PreviewManager::instance().startPreview();
967}
968
969void
970SettingsWidget::stopVideo()
971{
972 Video::PreviewManager::instance().stopPreview();
Isa Nanic26c86612018-12-14 12:21:56 -0500973}