calls_panel.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "calls/calls_panel.h"
  8. #include "data/data_photo.h"
  9. #include "data/data_session.h"
  10. #include "data/data_user.h"
  11. #include "data/data_file_origin.h"
  12. #include "data/data_photo_media.h"
  13. #include "data/data_cloud_file.h"
  14. #include "data/data_changes.h"
  15. #include "calls/group/calls_group_common.h"
  16. #include "calls/ui/calls_device_menu.h"
  17. #include "calls/calls_emoji_fingerprint.h"
  18. #include "calls/calls_signal_bars.h"
  19. #include "calls/calls_userpic.h"
  20. #include "calls/calls_video_bubble.h"
  21. #include "calls/calls_video_incoming.h"
  22. #include "ui/platform/ui_platform_window_title.h"
  23. #include "ui/widgets/call_button.h"
  24. #include "ui/widgets/buttons.h"
  25. #include "ui/widgets/labels.h"
  26. #include "ui/widgets/popup_menu.h"
  27. #include "ui/widgets/shadow.h"
  28. #include "ui/widgets/rp_window.h"
  29. #include "ui/layers/layer_manager.h"
  30. #include "ui/layers/generic_box.h"
  31. #include "ui/image/image.h"
  32. #include "ui/text/format_values.h"
  33. #include "ui/wrap/fade_wrap.h"
  34. #include "ui/wrap/padding_wrap.h"
  35. #include "ui/platform/ui_platform_utility.h"
  36. #include "ui/gl/gl_surface.h"
  37. #include "ui/gl/gl_shader.h"
  38. #include "ui/toast/toast.h"
  39. #include "ui/empty_userpic.h"
  40. #include "ui/emoji_config.h"
  41. #include "ui/painter.h"
  42. #include "ui/rect.h"
  43. #include "ui/integration.h"
  44. #include "core/application.h"
  45. #include "lang/lang_keys.h"
  46. #include "main/main_session.h"
  47. #include "apiwrap.h"
  48. #include "platform/platform_specific.h"
  49. #include "base/event_filter.h"
  50. #include "base/platform/base_platform_info.h"
  51. #include "base/power_save_blocker.h"
  52. #include "media/streaming/media_streaming_utility.h"
  53. #include "window/main_window.h"
  54. #include "webrtc/webrtc_environment.h"
  55. #include "webrtc/webrtc_video_track.h"
  56. #include "styles/style_calls.h"
  57. #include "styles/style_chat.h"
  58. #include <QtWidgets/QApplication>
  59. #include <QtGui/QWindow>
  60. #include <QtCore/QTimer>
  61. #include <QtSvg/QSvgRenderer>
  62. namespace Calls {
  63. namespace {
  64. constexpr auto kHideControlsTimeout = 5 * crl::time(1000);
  65. constexpr auto kHideControlsQuickTimeout = 2 * crl::time(1000);
  66. [[nodiscard]] QByteArray BatterySvg(
  67. const QSize &s,
  68. const QColor &c) {
  69. const auto color = u"rgb(%1,%2,%3)"_q
  70. .arg(c.red())
  71. .arg(c.green())
  72. .arg(c.blue())
  73. .toUtf8();
  74. const auto width = QString::number(s.width()).toUtf8();
  75. const auto height = QString::number(s.height()).toUtf8();
  76. return R"(
  77. <svg width=")" + width + R"(" height=")" + height
  78. + R"(" viewBox="0 0 )" + width + R"( )" + height + R"(" fill="none">
  79. <rect x="1.33598" y="0.5" width="24" height="12" rx="4" stroke=")" + color + R"("/>
  80. <path
  81. d="M26.836 4.66666V8.66666C27.6407 8.32788 28.164 7.53979 28.164 6.66666C28.164 5.79352 27.6407 5.00543 26.836 4.66666Z"
  82. fill=")" + color + R"("/>
  83. <path
  84. d="M 5.5 3.5 H 5.5 A 0.5 0.5 0 0 1 6 4 V 9 A 0.5 0.5 0 0 1 5.5 9.5 H 5.5 A 0.5 0.5 0 0 1 5 9 V 4 A 0.5 0.5 0 0 1 5.5 3.5 Z M 5 4 V 9 A 0.5 0.5 0 0 0 5.5 9.5 H 5.5 A 0.5 0.5 0 0 0 6 9 V 4 A 0.5 0.5 0 0 0 5.5 3.5 H 5.5 A 0.5 0.5 0 0 0 5 4 Z"
  85. transform="matrix(1, 0, 0, 1, 0, 0)" + ")\" stroke=\"" + color + R"("/>
  86. </svg>)";
  87. }
  88. } // namespace
  89. Panel::Panel(not_null<Call*> call)
  90. : _call(call)
  91. , _user(call->user())
  92. , _layerBg(std::make_unique<Ui::LayerManager>(widget()))
  93. #ifndef Q_OS_MAC
  94. , _controls(Ui::Platform::SetupSeparateTitleControls(
  95. window(),
  96. st::callTitle,
  97. [=](bool maximized) { toggleFullScreen(maximized); }))
  98. #endif // !Q_OS_MAC
  99. , _bodySt(&st::callBodyLayout)
  100. , _answerHangupRedial(widget(), st::callAnswer, &st::callHangup)
  101. , _decline(widget(), object_ptr<Ui::CallButton>(widget(), st::callHangup))
  102. , _cancel(widget(), object_ptr<Ui::CallButton>(widget(), st::callCancel))
  103. , _screencast(
  104. widget(),
  105. object_ptr<Ui::CallButton>(
  106. widget(),
  107. st::callScreencastOn,
  108. &st::callScreencastOff))
  109. , _camera(widget(), st::callCameraMute, &st::callCameraUnmute)
  110. , _mute(
  111. widget(),
  112. object_ptr<Ui::CallButton>(
  113. widget(),
  114. st::callMicrophoneMute,
  115. &st::callMicrophoneUnmute))
  116. , _name(widget(), st::callName)
  117. , _status(widget(), st::callStatus)
  118. , _hideControlsTimer([=] { requestControlsHidden(true); })
  119. , _controlsShownForceTimer([=] { controlsShownForce(false); }) {
  120. _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
  121. _layerBg->setHideByBackgroundClick(true);
  122. _decline->setDuration(st::callPanelDuration);
  123. _decline->entity()->setText(tr::lng_call_decline());
  124. _cancel->setDuration(st::callPanelDuration);
  125. _cancel->entity()->setText(tr::lng_call_cancel());
  126. _screencast->setDuration(st::callPanelDuration);
  127. initWindow();
  128. initWidget();
  129. initControls();
  130. initLayout();
  131. initMediaDeviceToggles();
  132. showAndActivate();
  133. }
  134. Panel::~Panel() = default;
  135. bool Panel::isVisible() const {
  136. return window()->isVisible()
  137. && !(window()->windowState() & Qt::WindowMinimized);
  138. }
  139. bool Panel::isActive() const {
  140. return window()->isActiveWindow() && isVisible();
  141. }
  142. void Panel::showAndActivate() {
  143. if (window()->isHidden()) {
  144. window()->show();
  145. }
  146. const auto state = window()->windowState();
  147. if (state & Qt::WindowMinimized) {
  148. window()->setWindowState(state & ~Qt::WindowMinimized);
  149. }
  150. window()->raise();
  151. window()->activateWindow();
  152. window()->setFocus();
  153. }
  154. void Panel::minimize() {
  155. window()->setWindowState(window()->windowState() | Qt::WindowMinimized);
  156. }
  157. void Panel::toggleFullScreen() {
  158. toggleFullScreen(!window()->isFullScreen());
  159. }
  160. void Panel::replaceCall(not_null<Call*> call) {
  161. reinitWithCall(call);
  162. updateControlsGeometry();
  163. }
  164. void Panel::initWindow() {
  165. window()->setAttribute(Qt::WA_OpaquePaintEvent);
  166. window()->setAttribute(Qt::WA_NoSystemBackground);
  167. window()->setTitle(_user->name());
  168. window()->setTitleStyle(st::callTitle);
  169. base::install_event_filter(window().get(), [=](not_null<QEvent*> e) {
  170. if (e->type() == QEvent::Close && handleClose()) {
  171. e->ignore();
  172. return base::EventFilterResult::Cancel;
  173. } else if (e->type() == QEvent::KeyPress) {
  174. if ((static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Escape)
  175. && window()->isFullScreen()) {
  176. window()->showNormal();
  177. }
  178. } else if (e->type() == QEvent::WindowStateChange) {
  179. const auto state = window()->windowState();
  180. _fullScreenOrMaximized = (state & Qt::WindowFullScreen)
  181. || (state & Qt::WindowMaximized);
  182. } else if (e->type() == QEvent::Enter) {
  183. _mouseInside = true;
  184. Ui::Integration::Instance().registerLeaveSubscription(
  185. window().get());
  186. if (!_fullScreenOrMaximized.current()) {
  187. requestControlsHidden(false);
  188. _hideControlsTimer.cancel();
  189. }
  190. } else if (e->type() == QEvent::Leave) {
  191. _mouseInside = false;
  192. Ui::Integration::Instance().unregisterLeaveSubscription(
  193. window().get());
  194. if (!_fullScreenOrMaximized.current()) {
  195. _hideControlsTimer.callOnce(kHideControlsQuickTimeout);
  196. }
  197. }
  198. return base::EventFilterResult::Continue;
  199. });
  200. window()->setBodyTitleArea([=](QPoint widgetPoint) {
  201. using Flag = Ui::WindowTitleHitTestFlag;
  202. if (!widget()->rect().contains(widgetPoint)) {
  203. return Flag::None | Flag(0);
  204. }
  205. #ifndef Q_OS_MAC
  206. using Result = Ui::Platform::HitTestResult;
  207. const auto windowPoint = widget()->mapTo(window(), widgetPoint);
  208. if (_controls->controls.hitTest(windowPoint) != Result::None) {
  209. return Flag::None | Flag(0);
  210. }
  211. #endif // !Q_OS_MAC
  212. const auto buttonWidth = st::callCancel.button.width;
  213. const auto buttonsWidth = buttonWidth * 4;
  214. const auto inControls = (_fingerprint
  215. && _fingerprint->geometry().contains(widgetPoint))
  216. || QRect(
  217. (widget()->width() - buttonsWidth) / 2,
  218. _answerHangupRedial->y(),
  219. buttonsWidth,
  220. _answerHangupRedial->height()).contains(widgetPoint)
  221. || (!_outgoingPreviewInBody
  222. && _outgoingVideoBubble->geometry().contains(widgetPoint));
  223. if (inControls) {
  224. return Flag::None | Flag(0);
  225. }
  226. const auto shown = _layerBg->topShownLayer();
  227. return (!shown || !shown->geometry().contains(widgetPoint))
  228. ? (Flag::Move | Flag::Menu | Flag::FullScreen)
  229. : Flag::None;
  230. });
  231. // Don't do that, it looks awful :(
  232. //#ifdef Q_OS_WIN
  233. // // On Windows we replace snap-to-top maximizing with fullscreen.
  234. // //
  235. // // We have to switch first to showNormal, so that showFullScreen
  236. // // will remember correct normal window geometry and next showNormal
  237. // // will show it instead of a moving maximized window.
  238. // //
  239. // // We have to do it in InvokeQueued, otherwise it still captures
  240. // // the maximized window geometry and saves it.
  241. // //
  242. // // I couldn't find a less glitchy way to do that *sigh*.
  243. // const auto object = window()->windowHandle();
  244. // const auto signal = &QWindow::windowStateChanged;
  245. // QObject::connect(object, signal, [=](Qt::WindowState state) {
  246. // if (state == Qt::WindowMaximized) {
  247. // InvokeQueued(object, [=] {
  248. // window()->showNormal();
  249. // InvokeQueued(object, [=] {
  250. // window()->showFullScreen();
  251. // });
  252. // });
  253. // }
  254. // });
  255. //#endif // Q_OS_WIN
  256. }
  257. void Panel::initWidget() {
  258. widget()->setMouseTracking(true);
  259. widget()->paintRequest(
  260. ) | rpl::start_with_next([=](QRect clip) {
  261. paint(clip);
  262. }, widget()->lifetime());
  263. widget()->sizeValue(
  264. ) | rpl::skip(1) | rpl::start_with_next([=] {
  265. updateControlsGeometry();
  266. }, widget()->lifetime());
  267. }
  268. void Panel::initControls() {
  269. _hangupShown = (_call->type() == Type::Outgoing);
  270. _mute->entity()->setClickedCallback([=] {
  271. if (_call) {
  272. _call->setMuted(!_call->muted());
  273. }
  274. });
  275. _screencast->entity()->setClickedCallback([=] {
  276. const auto env = &Core::App().mediaDevices();
  277. if (!_call) {
  278. return;
  279. } else if (!env->desktopCaptureAllowed()) {
  280. if (auto box = Group::ScreenSharingPrivacyRequestBox()) {
  281. _layerBg->showBox(std::move(box));
  282. }
  283. } else if (const auto source = env->uniqueDesktopCaptureSource()) {
  284. if (!chooseSourceActiveDeviceId().isEmpty()) {
  285. chooseSourceStop();
  286. } else {
  287. chooseSourceAccepted(*source, false);
  288. }
  289. } else {
  290. Group::Ui::DesktopCapture::ChooseSource(this);
  291. }
  292. });
  293. _camera->setClickedCallback([=] {
  294. if (!_call) {
  295. return;
  296. } else {
  297. _call->toggleCameraSharing(!_call->isSharingCamera());
  298. }
  299. });
  300. _updateDurationTimer.setCallback([this] {
  301. if (_call) {
  302. updateStatusText(_call->state());
  303. }
  304. });
  305. _updateOuterRippleTimer.setCallback([this] {
  306. if (_call) {
  307. _answerHangupRedial->setOuterValue(
  308. _call->getWaitingSoundPeakValue());
  309. } else {
  310. _answerHangupRedial->setOuterValue(0.);
  311. _updateOuterRippleTimer.cancel();
  312. }
  313. });
  314. _answerHangupRedial->setClickedCallback([this] {
  315. if (!_call || _hangupShownProgress.animating()) {
  316. return;
  317. }
  318. auto state = _call->state();
  319. if (state == State::Busy) {
  320. _call->redial();
  321. } else if (_call->isIncomingWaiting()) {
  322. _call->answer();
  323. } else if (state == State::WaitingUserConfirmation) {
  324. _startOutgoingRequests.fire(false);
  325. } else {
  326. _call->hangup();
  327. }
  328. });
  329. auto hangupCallback = [this] {
  330. if (_call) {
  331. _call->hangup();
  332. }
  333. };
  334. _decline->entity()->setClickedCallback(hangupCallback);
  335. _cancel->entity()->setClickedCallback(hangupCallback);
  336. reinitWithCall(_call);
  337. _decline->finishAnimating();
  338. _cancel->finishAnimating();
  339. _screencast->finishAnimating();
  340. }
  341. void Panel::setIncomingSize(QSize size) {
  342. if (_incomingFrameSize == size) {
  343. return;
  344. }
  345. _incomingFrameSize = size;
  346. refreshIncomingGeometry();
  347. showControls();
  348. }
  349. QWidget *Panel::chooseSourceParent() {
  350. return window().get();
  351. }
  352. QString Panel::chooseSourceActiveDeviceId() {
  353. return _call->screenSharingDeviceId();
  354. }
  355. bool Panel::chooseSourceActiveWithAudio() {
  356. return false;// _call->screenSharingWithAudio();
  357. }
  358. bool Panel::chooseSourceWithAudioSupported() {
  359. //#ifdef Q_OS_WIN
  360. // return true;
  361. //#else // Q_OS_WIN
  362. return false;
  363. //#endif // Q_OS_WIN
  364. }
  365. rpl::lifetime &Panel::chooseSourceInstanceLifetime() {
  366. return lifetime();
  367. }
  368. rpl::producer<bool> Panel::startOutgoingRequests() const {
  369. return _startOutgoingRequests.events(
  370. ) | rpl::filter([=] {
  371. return _call && (_call->state() == State::WaitingUserConfirmation);
  372. });
  373. }
  374. void Panel::chooseSourceAccepted(
  375. const QString &deviceId,
  376. bool withAudio) {
  377. _call->toggleScreenSharing(deviceId/*, withAudio*/);
  378. }
  379. void Panel::chooseSourceStop() {
  380. _call->toggleScreenSharing(std::nullopt);
  381. }
  382. void Panel::refreshIncomingGeometry() {
  383. Expects(_call != nullptr);
  384. Expects(_incoming != nullptr);
  385. if (_incomingFrameSize.isEmpty()) {
  386. _incoming->widget()->hide();
  387. return;
  388. }
  389. const auto to = widget()->size();
  390. const auto use = ::Media::Streaming::DecideFrameResize(
  391. to,
  392. _incomingFrameSize
  393. ).result;
  394. const auto pos = QPoint(
  395. (to.width() - use.width()) / 2,
  396. (to.height() - use.height()) / 2);
  397. _incoming->widget()->setGeometry(QRect(pos, use));
  398. _incoming->widget()->show();
  399. }
  400. void Panel::reinitWithCall(Call *call) {
  401. _callLifetime.destroy();
  402. _call = call;
  403. const auto guard = gsl::finally([&] {
  404. updateControlsShown();
  405. });
  406. if (!_call) {
  407. _fingerprint.destroy();
  408. _incoming = nullptr;
  409. _outgoingVideoBubble = nullptr;
  410. _powerSaveBlocker = nullptr;
  411. return;
  412. }
  413. _user = _call->user();
  414. auto remoteMuted = _call->remoteAudioStateValue(
  415. ) | rpl::map(rpl::mappers::_1 == Call::RemoteAudioState::Muted);
  416. rpl::duplicate(
  417. remoteMuted
  418. ) | rpl::start_with_next([=](bool muted) {
  419. if (muted) {
  420. createRemoteAudioMute();
  421. } else {
  422. _remoteAudioMute.destroy();
  423. showRemoteLowBattery();
  424. }
  425. }, _callLifetime);
  426. _call->remoteBatteryStateValue(
  427. ) | rpl::start_with_next([=](Call::RemoteBatteryState state) {
  428. if (state == Call::RemoteBatteryState::Low) {
  429. createRemoteLowBattery();
  430. } else {
  431. _remoteLowBattery.destroy();
  432. }
  433. }, _callLifetime);
  434. _userpic = std::make_unique<Userpic>(
  435. widget(),
  436. _user,
  437. std::move(remoteMuted));
  438. _outgoingVideoBubble = std::make_unique<VideoBubble>(
  439. widget(),
  440. _call->videoOutgoing());
  441. _incoming = std::make_unique<Incoming>(
  442. widget(),
  443. _call->videoIncoming(),
  444. _window.backend());
  445. _incoming->widget()->hide();
  446. _incoming->rp()->shownValue() | rpl::start_with_next([=] {
  447. updateControlsShown();
  448. }, _incoming->rp()->lifetime());
  449. _hideControlsFilter = nullptr;
  450. _fullScreenOrMaximized.value(
  451. ) | rpl::start_with_next([=](bool fullScreenOrMaximized) {
  452. if (fullScreenOrMaximized) {
  453. class Filter final : public QObject {
  454. public:
  455. explicit Filter(Fn<void(QObject*)> moved) : _moved(moved) {
  456. qApp->installEventFilter(this);
  457. }
  458. bool eventFilter(QObject *watched, QEvent *event) {
  459. if (event->type() == QEvent::MouseMove) {
  460. _moved(watched);
  461. }
  462. return false;
  463. }
  464. private:
  465. Fn<void(QObject*)> _moved;
  466. };
  467. _hideControlsFilter.reset(new Filter([=](QObject *what) {
  468. _mouseInside = true;
  469. if (what->isWidgetType()
  470. && window()->isAncestorOf(static_cast<QWidget*>(what))) {
  471. _hideControlsTimer.callOnce(kHideControlsTimeout);
  472. requestControlsHidden(false);
  473. updateControlsShown();
  474. }
  475. }));
  476. _hideControlsTimer.callOnce(kHideControlsTimeout);
  477. } else {
  478. _hideControlsFilter = nullptr;
  479. _hideControlsTimer.cancel();
  480. if (_mouseInside) {
  481. requestControlsHidden(false);
  482. updateControlsShown();
  483. }
  484. }
  485. }, _incoming->rp()->lifetime());
  486. _call->mutedValue(
  487. ) | rpl::start_with_next([=](bool mute) {
  488. _mute->entity()->setProgress(mute ? 1. : 0.);
  489. _mute->entity()->setText(mute
  490. ? tr::lng_call_unmute_audio()
  491. : tr::lng_call_mute_audio());
  492. }, _callLifetime);
  493. _call->videoOutgoing()->stateValue(
  494. ) | rpl::start_with_next([=] {
  495. {
  496. const auto active = _call->isSharingCamera();
  497. _camera->setProgress(active ? 0. : 1.);
  498. _camera->setText(active
  499. ? tr::lng_call_stop_video()
  500. : tr::lng_call_start_video());
  501. }
  502. {
  503. const auto active = _call->isSharingScreen();
  504. _screencast->entity()->setProgress(active ? 0. : 1.);
  505. _screencast->entity()->setText(tr::lng_call_screencast());
  506. _outgoingVideoBubble->setMirrored(!active);
  507. }
  508. }, _callLifetime);
  509. _call->stateValue(
  510. ) | rpl::start_with_next([=](State state) {
  511. stateChanged(state);
  512. }, _callLifetime);
  513. _call->videoIncoming()->renderNextFrame(
  514. ) | rpl::start_with_next([=] {
  515. const auto track = _call->videoIncoming();
  516. setIncomingSize(track->state() == Webrtc::VideoState::Active
  517. ? track->frameSize()
  518. : QSize());
  519. if (_incoming->widget()->isHidden()) {
  520. return;
  521. }
  522. const auto incoming = incomingFrameGeometry();
  523. const auto outgoing = outgoingFrameGeometry();
  524. _incoming->widget()->update();
  525. if (incoming.intersects(outgoing)) {
  526. widget()->update(outgoing);
  527. }
  528. }, _callLifetime);
  529. _call->videoIncoming()->stateValue(
  530. ) | rpl::start_with_next([=](Webrtc::VideoState state) {
  531. setIncomingSize((state == Webrtc::VideoState::Active)
  532. ? _call->videoIncoming()->frameSize()
  533. : QSize());
  534. }, _callLifetime);
  535. _call->videoOutgoing()->renderNextFrame(
  536. ) | rpl::start_with_next([=] {
  537. const auto incoming = incomingFrameGeometry();
  538. const auto outgoing = outgoingFrameGeometry();
  539. widget()->update(outgoing);
  540. if (incoming.intersects(outgoing)) {
  541. _incoming->widget()->update();
  542. }
  543. }, _callLifetime);
  544. rpl::combine(
  545. _call->stateValue(),
  546. rpl::single(
  547. rpl::empty_value()
  548. ) | rpl::then(_call->videoOutgoing()->renderNextFrame())
  549. ) | rpl::start_with_next([=](State state, auto) {
  550. if (state != State::Ended
  551. && state != State::EndedByOtherDevice
  552. && state != State::Failed
  553. && state != State::FailedHangingUp
  554. && state != State::HangingUp) {
  555. refreshOutgoingPreviewInBody(state);
  556. }
  557. }, _callLifetime);
  558. _call->errors(
  559. ) | rpl::start_with_next([=](Error error) {
  560. const auto text = [=] {
  561. switch (error.type) {
  562. case ErrorType::NoCamera:
  563. return tr::lng_call_error_no_camera(tr::now);
  564. case ErrorType::NotVideoCall:
  565. return tr::lng_call_error_camera_outdated(
  566. tr::now,
  567. lt_user,
  568. _user->name());
  569. case ErrorType::NotStartedCall:
  570. return tr::lng_call_error_camera_not_started(tr::now);
  571. //case ErrorType::NoMicrophone:
  572. // return tr::lng_call_error_no_camera(tr::now);
  573. case ErrorType::Unknown:
  574. return Lang::Hard::CallErrorIncompatible();
  575. }
  576. Unexpected("Error type in _call->errors().");
  577. }();
  578. Ui::Toast::Show(widget(), Ui::Toast::Config{
  579. .text = { text },
  580. .st = &st::callErrorToast,
  581. });
  582. }, _callLifetime);
  583. _name->setText(_user->name());
  584. updateStatusText(_call->state());
  585. _answerHangupRedial->raise();
  586. _decline->raise();
  587. _cancel->raise();
  588. _camera->raise();
  589. if (_startVideo) {
  590. _startVideo->raise();
  591. }
  592. _mute->raise();
  593. _powerSaveBlocker = std::make_unique<base::PowerSaveBlocker>(
  594. base::PowerSaveBlockType::PreventDisplaySleep,
  595. u"Video call is active"_q,
  596. window()->windowHandle());
  597. _incoming->widget()->lower();
  598. }
  599. void Panel::createRemoteAudioMute() {
  600. _remoteAudioMute.create(
  601. widget(),
  602. object_ptr<Ui::FlatLabel>(
  603. widget(),
  604. tr::lng_call_microphone_off(
  605. lt_user,
  606. _user->session().changes().peerFlagsValue(
  607. _user,
  608. Data::PeerUpdate::Flag::Name
  609. ) | rpl::map([=] { return _user->shortName(); })),
  610. st::callRemoteAudioMute),
  611. st::callTooltipPadding);
  612. _remoteAudioMute->setAttribute(Qt::WA_TransparentForMouseEvents);
  613. _remoteAudioMute->paintRequest(
  614. ) | rpl::start_with_next([=] {
  615. auto p = QPainter(_remoteAudioMute);
  616. const auto r = _remoteAudioMute->rect();
  617. auto hq = PainterHighQualityEnabler(p);
  618. p.setOpacity(_controlsShownAnimation.value(
  619. _controlsShown ? 1. : 0.));
  620. p.setBrush(st::videoPlayIconBg);
  621. p.setPen(Qt::NoPen);
  622. p.drawRoundedRect(r, r.height() / 2, r.height() / 2);
  623. st::callTooltipMutedIcon.paint(
  624. p,
  625. st::callTooltipMutedIconPosition,
  626. _remoteAudioMute->width());
  627. }, _remoteAudioMute->lifetime());
  628. showControls();
  629. updateControlsGeometry();
  630. }
  631. void Panel::createRemoteLowBattery() {
  632. _remoteLowBattery.create(
  633. widget(),
  634. object_ptr<Ui::FlatLabel>(
  635. widget(),
  636. tr::lng_call_battery_level_low(
  637. lt_user,
  638. _user->session().changes().peerFlagsValue(
  639. _user,
  640. Data::PeerUpdate::Flag::Name
  641. ) | rpl::map([=] { return _user->shortName(); })),
  642. st::callRemoteAudioMute),
  643. st::callTooltipPadding);
  644. _remoteLowBattery->setAttribute(Qt::WA_TransparentForMouseEvents);
  645. style::PaletteChanged(
  646. ) | rpl::start_with_next([=] {
  647. _remoteLowBattery.destroy();
  648. createRemoteLowBattery();
  649. }, _remoteLowBattery->lifetime());
  650. constexpr auto kBatterySize = QSize(29, 13);
  651. const auto icon = [&] {
  652. auto svg = QSvgRenderer(
  653. BatterySvg(kBatterySize, st::videoPlayIconFg->c));
  654. auto image = QImage(
  655. kBatterySize * style::DevicePixelRatio(),
  656. QImage::Format_ARGB32_Premultiplied);
  657. image.setDevicePixelRatio(style::DevicePixelRatio());
  658. image.fill(Qt::transparent);
  659. {
  660. auto p = QPainter(&image);
  661. svg.render(&p, Rect(kBatterySize));
  662. }
  663. return image;
  664. }();
  665. _remoteLowBattery->paintRequest(
  666. ) | rpl::start_with_next([=] {
  667. auto p = QPainter(_remoteLowBattery);
  668. const auto r = _remoteLowBattery->rect();
  669. auto hq = PainterHighQualityEnabler(p);
  670. p.setOpacity(_controlsShownAnimation.value(
  671. _controlsShown ? 1. : 0.));
  672. p.setBrush(st::videoPlayIconBg);
  673. p.setPen(Qt::NoPen);
  674. p.drawRoundedRect(r, r.height() / 2, r.height() / 2);
  675. p.drawImage(
  676. st::callTooltipMutedIconPosition.x(),
  677. (r.height() - kBatterySize.height()) / 2,
  678. icon);
  679. }, _remoteLowBattery->lifetime());
  680. showControls();
  681. updateControlsGeometry();
  682. }
  683. void Panel::showRemoteLowBattery() {
  684. if (_remoteLowBattery) {
  685. _remoteLowBattery->setVisible(!_remoteAudioMute
  686. || _remoteAudioMute->isHidden());
  687. }
  688. }
  689. void Panel::initLayout() {
  690. initGeometry();
  691. _name->setAttribute(Qt::WA_TransparentForMouseEvents);
  692. _status->setAttribute(Qt::WA_TransparentForMouseEvents);
  693. using UpdateFlag = Data::PeerUpdate::Flag;
  694. _user->session().changes().peerUpdates(
  695. UpdateFlag::Name
  696. ) | rpl::filter([=](const Data::PeerUpdate &update) {
  697. // _user may change for the same Panel.
  698. return (_call != nullptr) && (update.peer == _user);
  699. }) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
  700. _name->setText(_call->user()->name());
  701. updateControlsGeometry();
  702. }, widget()->lifetime());
  703. #ifndef Q_OS_MAC
  704. _controls->wrap.raise();
  705. #endif // !Q_OS_MAC
  706. }
  707. void Panel::showControls() {
  708. Expects(_call != nullptr);
  709. widget()->showChildren();
  710. _decline->setVisible(_decline->toggled());
  711. _cancel->setVisible(_cancel->toggled());
  712. _screencast->setVisible(_screencast->toggled());
  713. const auto shown = !_incomingFrameSize.isEmpty();
  714. _incoming->widget()->setVisible(shown);
  715. _name->setVisible(!shown);
  716. _status->setVisible(!shown);
  717. _userpic->setVisible(!shown);
  718. if (_remoteAudioMute) {
  719. _remoteAudioMute->setVisible(shown);
  720. }
  721. showRemoteLowBattery();
  722. }
  723. void Panel::closeBeforeDestroy() {
  724. window()->close();
  725. reinitWithCall(nullptr);
  726. }
  727. rpl::lifetime &Panel::lifetime() {
  728. return window()->lifetime();
  729. }
  730. void Panel::initGeometry() {
  731. const auto center = Core::App().getPointForCallPanelCenter();
  732. const auto initRect = QRect(0, 0, st::callWidth, st::callHeight);
  733. window()->setGeometry(initRect.translated(center - initRect.center()));
  734. window()->setMinimumSize({ st::callWidthMin, st::callHeightMin });
  735. window()->show();
  736. updateControlsGeometry();
  737. }
  738. void Panel::initMediaDeviceToggles() {
  739. _cameraDeviceToggle = _camera->addCornerButton(
  740. st::callCornerButton,
  741. &st::callCornerButtonInactive);
  742. _audioDeviceToggle = _mute->entity()->addCornerButton(
  743. st::callCornerButton,
  744. &st::callCornerButtonInactive);
  745. _cameraDeviceToggle->setClickedCallback([=] {
  746. showDevicesMenu(_cameraDeviceToggle, {
  747. { Webrtc::DeviceType::Camera, _call->cameraDeviceIdValue() },
  748. });
  749. });
  750. _audioDeviceToggle->setClickedCallback([=] {
  751. showDevicesMenu(_audioDeviceToggle, {
  752. { Webrtc::DeviceType::Playback, _call->playbackDeviceIdValue() },
  753. { Webrtc::DeviceType::Capture, _call->captureDeviceIdValue() },
  754. });
  755. });
  756. }
  757. void Panel::showDevicesMenu(
  758. not_null<QWidget*> button,
  759. std::vector<DeviceSelection> types) {
  760. if (!_call || _devicesMenu) {
  761. return;
  762. }
  763. const auto chosen = [=](Webrtc::DeviceType type, QString id) {
  764. switch (type) {
  765. case Webrtc::DeviceType::Playback:
  766. Core::App().settings().setCallPlaybackDeviceId(id);
  767. break;
  768. case Webrtc::DeviceType::Capture:
  769. Core::App().settings().setCallCaptureDeviceId(id);
  770. break;
  771. case Webrtc::DeviceType::Camera:
  772. Core::App().settings().setCameraDeviceId(id);
  773. break;
  774. }
  775. Core::App().saveSettingsDelayed();
  776. };
  777. controlsShownForce(true);
  778. updateControlsShown();
  779. _devicesMenu = MakeDeviceSelectionMenu(
  780. widget(),
  781. &Core::App().mediaDevices(),
  782. std::move(types),
  783. chosen);
  784. _devicesMenu->setForcedVerticalOrigin(
  785. Ui::PopupMenu::VerticalOrigin::Bottom);
  786. _devicesMenu->popup(button->mapToGlobal(QPoint())
  787. - QPoint(st::callDeviceSelectionMenu.menu.widthMin / 2, 0));
  788. QObject::connect(_devicesMenu.get(), &QObject::destroyed, window(), [=] {
  789. _controlsShownForceTimer.callOnce(kHideControlsQuickTimeout);
  790. });
  791. }
  792. void Panel::refreshOutgoingPreviewInBody(State state) {
  793. const auto inBody = (state != State::Established)
  794. && (_call->videoOutgoing()->state() != Webrtc::VideoState::Inactive)
  795. && !_call->videoOutgoing()->frameSize().isEmpty();
  796. if (_outgoingPreviewInBody == inBody) {
  797. return;
  798. }
  799. _outgoingPreviewInBody = inBody;
  800. _bodySt = inBody ? &st::callBodyWithPreview : &st::callBodyLayout;
  801. updateControlsGeometry();
  802. }
  803. void Panel::toggleFullScreen(bool fullscreen) {
  804. if (fullscreen) {
  805. window()->showFullScreen();
  806. } else {
  807. window()->showNormal();
  808. }
  809. }
  810. QRect Panel::incomingFrameGeometry() const {
  811. return (!_incoming || _incoming->widget()->isHidden())
  812. ? QRect()
  813. : _incoming->widget()->geometry();
  814. }
  815. QRect Panel::outgoingFrameGeometry() const {
  816. return _outgoingVideoBubble->geometry();
  817. }
  818. void Panel::requestControlsHidden(bool hidden) {
  819. _hideControlsRequested = hidden;
  820. updateControlsShown();
  821. }
  822. void Panel::controlsShownForce(bool shown) {
  823. _controlsShownForce = shown;
  824. if (shown) {
  825. _controlsShownForceTimer.cancel();
  826. }
  827. updateControlsShown();
  828. }
  829. void Panel::updateControlsShown() {
  830. const auto shown = !_incoming
  831. || _incoming->widget()->isHidden()
  832. || _controlsShownForce
  833. || !_hideControlsRequested;
  834. if (_controlsShown != shown) {
  835. _controlsShown = shown;
  836. _controlsShownAnimation.start([=] {
  837. updateControlsGeometry();
  838. }, shown ? 0. : 1., shown ? 1. : 0., st::slideDuration);
  839. updateControlsGeometry();
  840. }
  841. }
  842. void Panel::updateControlsGeometry() {
  843. if (widget()->size().isEmpty()) {
  844. return;
  845. }
  846. if (_incoming) {
  847. refreshIncomingGeometry();
  848. }
  849. const auto shown = _controlsShownAnimation.value(
  850. _controlsShown ? 1. : 0.);
  851. if (_fingerprint) {
  852. #ifndef Q_OS_MAC
  853. const auto controlsGeometry = _controls->controls.geometry();
  854. const auto halfWidth = widget()->width() / 2;
  855. const auto minLeft = (controlsGeometry.center().x() < halfWidth)
  856. ? (controlsGeometry.width() + st::callFingerprintTop)
  857. : 0;
  858. const auto minRight = (controlsGeometry.center().x() >= halfWidth)
  859. ? (controlsGeometry.width() + st::callFingerprintTop)
  860. : 0;
  861. _incoming->setControlsAlignment(minLeft
  862. ? style::al_left
  863. : style::al_right);
  864. #else // !Q_OS_MAC
  865. const auto minLeft = 0;
  866. const auto minRight = 0;
  867. #endif // _controls
  868. const auto desired = (widget()->width() - _fingerprint->width()) / 2;
  869. const auto top = anim::interpolate(
  870. -_fingerprint->height(),
  871. st::callFingerprintTop,
  872. shown);
  873. if (minLeft) {
  874. _fingerprint->moveToLeft(std::max(desired, minLeft), top);
  875. } else {
  876. _fingerprint->moveToRight(std::max(desired, minRight), top);
  877. }
  878. }
  879. const auto innerHeight = std::max(widget()->height(), st::callHeightMin);
  880. const auto innerWidth = widget()->width() - 2 * st::callInnerPadding;
  881. const auto availableTop = st::callFingerprintTop
  882. + (_fingerprint ? _fingerprint->height() : 0)
  883. + st::callFingerprintBottom;
  884. const auto available = widget()->height()
  885. - st::callBottomControlsHeight
  886. - availableTop;
  887. const auto bodyPreviewSizeMax = st::callOutgoingPreviewMin
  888. + ((st::callOutgoingPreview
  889. - st::callOutgoingPreviewMin)
  890. * (innerHeight - st::callHeightMin)
  891. / (st::callHeight - st::callHeightMin));
  892. const auto bodyPreviewSize = QSize(
  893. std::min(
  894. bodyPreviewSizeMax.width(),
  895. std::min(innerWidth, st::callOutgoingPreviewMax.width())),
  896. std::min(
  897. bodyPreviewSizeMax.height(),
  898. st::callOutgoingPreviewMax.height()));
  899. const auto contentHeight = _bodySt->height
  900. + (_outgoingPreviewInBody ? bodyPreviewSize.height() : 0);
  901. const auto remainingHeight = available - contentHeight;
  902. const auto skipHeight = remainingHeight
  903. / (_outgoingPreviewInBody ? 3 : 2);
  904. _bodyTop = availableTop + skipHeight;
  905. _buttonsTopShown = availableTop + available;
  906. _buttonsTop = anim::interpolate(
  907. widget()->height(),
  908. _buttonsTopShown,
  909. shown);
  910. const auto previewTop = _bodyTop + _bodySt->height + skipHeight;
  911. _userpic->setGeometry(
  912. (widget()->width() - _bodySt->photoSize) / 2,
  913. _bodyTop + _bodySt->photoTop,
  914. _bodySt->photoSize);
  915. _userpic->setMuteLayout(
  916. _bodySt->mutePosition,
  917. _bodySt->muteSize,
  918. _bodySt->muteStroke);
  919. _name->moveToLeft(
  920. (widget()->width() - _name->width()) / 2,
  921. _bodyTop + _bodySt->nameTop);
  922. updateStatusGeometry();
  923. if (_remoteAudioMute) {
  924. _remoteAudioMute->moveToLeft(
  925. (widget()->width() - _remoteAudioMute->width()) / 2,
  926. (_buttonsTop
  927. - st::callRemoteAudioMuteSkip
  928. - _remoteAudioMute->height()));
  929. _remoteAudioMute->update();
  930. _remoteAudioMute->entity()->setOpacity(shown);
  931. }
  932. if (_remoteLowBattery) {
  933. _remoteLowBattery->moveToLeft(
  934. (widget()->width() - _remoteLowBattery->width()) / 2,
  935. (_buttonsTop
  936. - st::callRemoteAudioMuteSkip
  937. - _remoteLowBattery->height()));
  938. _remoteLowBattery->update();
  939. _remoteLowBattery->entity()->setOpacity(shown);
  940. }
  941. if (_outgoingPreviewInBody) {
  942. _outgoingVideoBubble->updateGeometry(
  943. VideoBubble::DragMode::None,
  944. QRect(
  945. (widget()->width() - bodyPreviewSize.width()) / 2,
  946. previewTop,
  947. bodyPreviewSize.width(),
  948. bodyPreviewSize.height()));
  949. } else if (_outgoingVideoBubble) {
  950. updateOutgoingVideoBubbleGeometry();
  951. }
  952. updateHangupGeometry();
  953. }
  954. void Panel::updateOutgoingVideoBubbleGeometry() {
  955. Expects(!_outgoingPreviewInBody);
  956. const auto size = st::callOutgoingDefaultSize;
  957. _outgoingVideoBubble->updateGeometry(
  958. VideoBubble::DragMode::SnapToCorners,
  959. widget()->rect() - Margins(st::callInnerPadding),
  960. size);
  961. }
  962. void Panel::updateHangupGeometry() {
  963. const auto isWaitingUser = (_call
  964. && _call->state() == State::WaitingUserConfirmation);
  965. const auto hangupProgress = isWaitingUser
  966. ? 0.
  967. : _hangupShownProgress.value(_hangupShown ? 1. : 0.);
  968. _answerHangupRedial->setProgress(hangupProgress);
  969. // Screencast - Camera - Cancel/Decline - Answer/Hangup/Redial - Mute.
  970. const auto buttonWidth = st::callCancel.button.width;
  971. const auto cancelWidth = buttonWidth * (1. - hangupProgress);
  972. const auto cancelLeft = (isWaitingUser)
  973. ? ((widget()->width() - buttonWidth) / 2)
  974. : (_mute->animating())
  975. ? ((widget()->width() - cancelWidth) / 2)
  976. : ((widget()->width() / 2) - cancelWidth);
  977. _cancel->moveToLeft(cancelLeft, _buttonsTop);
  978. _decline->moveToLeft(cancelLeft, _buttonsTop);
  979. _camera->moveToLeft(cancelLeft - buttonWidth, _buttonsTop);
  980. _screencast->moveToLeft(_camera->x() - buttonWidth, _buttonsTop);
  981. _answerHangupRedial->moveToLeft(cancelLeft + cancelWidth, _buttonsTop);
  982. _mute->moveToLeft(_answerHangupRedial->x() + buttonWidth, _buttonsTop);
  983. if (_startVideo) {
  984. _startVideo->moveToLeft(_camera->x(), _camera->y());
  985. }
  986. }
  987. void Panel::updateStatusGeometry() {
  988. _status->moveToLeft(
  989. (widget()->width() - _status->width()) / 2,
  990. _bodyTop + _bodySt->statusTop);
  991. }
  992. void Panel::paint(QRect clip) {
  993. auto p = QPainter(widget());
  994. auto region = QRegion(clip);
  995. if (!_incoming->widget()->isHidden()) {
  996. region = region.subtracted(QRegion(_incoming->widget()->geometry()));
  997. }
  998. for (const auto &rect : region) {
  999. p.fillRect(rect, st::callBgOpaque);
  1000. }
  1001. if (_incoming && _incoming->widget()->isHidden()) {
  1002. _call->videoIncoming()->markFrameShown();
  1003. }
  1004. }
  1005. bool Panel::handleClose() const {
  1006. if (_call) {
  1007. if (_call->state() == Call::State::WaitingUserConfirmation
  1008. || _call->state() == Call::State::Busy) {
  1009. _call->hangup();
  1010. } else {
  1011. window()->hide();
  1012. }
  1013. return true;
  1014. }
  1015. return false;
  1016. }
  1017. not_null<Ui::RpWindow*> Panel::window() const {
  1018. return _window.window();
  1019. }
  1020. not_null<Ui::RpWidget*> Panel::widget() const {
  1021. return _window.widget();
  1022. }
  1023. void Panel::stateChanged(State state) {
  1024. Expects(_call != nullptr);
  1025. updateStatusText(state);
  1026. if ((state != State::HangingUp)
  1027. && (state != State::Ended)
  1028. && (state != State::EndedByOtherDevice)
  1029. && (state != State::FailedHangingUp)
  1030. && (state != State::Failed)) {
  1031. const auto isBusy = (state == State::Busy);
  1032. const auto isWaitingUser = (state == State::WaitingUserConfirmation);
  1033. if (isBusy) {
  1034. _powerSaveBlocker = nullptr;
  1035. }
  1036. if (_startVideo && !isWaitingUser) {
  1037. _startVideo = nullptr;
  1038. } else if (!_startVideo && isWaitingUser) {
  1039. _startVideo = base::make_unique_q<Ui::CallButton>(
  1040. widget(),
  1041. st::callStartVideo);
  1042. _startVideo->show();
  1043. _startVideo->setText(tr::lng_call_start_video());
  1044. _startVideo->clicks() | rpl::map_to(true) | rpl::start_to_stream(
  1045. _startOutgoingRequests,
  1046. _startVideo->lifetime());
  1047. }
  1048. _camera->setVisible(!_startVideo);
  1049. const auto toggleButton = [&](auto &&button, bool visible) {
  1050. button->toggle(
  1051. visible,
  1052. window()->isHidden()
  1053. ? anim::type::instant
  1054. : anim::type::normal);
  1055. };
  1056. const auto incomingWaiting = _call->isIncomingWaiting();
  1057. if (incomingWaiting) {
  1058. _updateOuterRippleTimer.callEach(Call::kSoundSampleMs);
  1059. }
  1060. toggleButton(_decline, incomingWaiting);
  1061. toggleButton(_cancel, (isBusy || isWaitingUser));
  1062. toggleButton(_mute, !isWaitingUser);
  1063. toggleButton(
  1064. _screencast,
  1065. !(isBusy || isWaitingUser || incomingWaiting));
  1066. const auto hangupShown = !_decline->toggled()
  1067. && !_cancel->toggled();
  1068. if (_hangupShown != hangupShown) {
  1069. _hangupShown = hangupShown;
  1070. _hangupShownProgress.start(
  1071. [this] { updateHangupGeometry(); },
  1072. _hangupShown ? 0. : 1.,
  1073. _hangupShown ? 1. : 0.,
  1074. st::callPanelDuration,
  1075. anim::sineInOut);
  1076. }
  1077. const auto answerHangupRedialState = incomingWaiting
  1078. ? AnswerHangupRedialState::Answer
  1079. : isBusy
  1080. ? AnswerHangupRedialState::Redial
  1081. : isWaitingUser
  1082. ? AnswerHangupRedialState::StartCall
  1083. : AnswerHangupRedialState::Hangup;
  1084. if (_answerHangupRedialState != answerHangupRedialState) {
  1085. _answerHangupRedialState = answerHangupRedialState;
  1086. refreshAnswerHangupRedialLabel();
  1087. }
  1088. if (!_call->isKeyShaForFingerprintReady()) {
  1089. _fingerprint.destroy();
  1090. } else if (!_fingerprint) {
  1091. _fingerprint = CreateFingerprintAndSignalBars(widget(), _call);
  1092. updateControlsGeometry();
  1093. }
  1094. }
  1095. }
  1096. void Panel::refreshAnswerHangupRedialLabel() {
  1097. Expects(_answerHangupRedialState.has_value());
  1098. _answerHangupRedial->setText([&] {
  1099. switch (*_answerHangupRedialState) {
  1100. case AnswerHangupRedialState::Answer: return tr::lng_call_accept();
  1101. case AnswerHangupRedialState::Hangup: return tr::lng_call_end_call();
  1102. case AnswerHangupRedialState::Redial: return tr::lng_call_redial();
  1103. case AnswerHangupRedialState::StartCall: return tr::lng_call_start();
  1104. }
  1105. Unexpected("AnswerHangupRedialState value.");
  1106. }());
  1107. }
  1108. void Panel::updateStatusText(State state) {
  1109. auto statusText = [this, state]() -> QString {
  1110. switch (state) {
  1111. case State::Starting:
  1112. case State::WaitingInit:
  1113. case State::WaitingInitAck: return tr::lng_call_status_connecting(tr::now);
  1114. case State::Established: {
  1115. if (_call) {
  1116. auto durationMs = _call->getDurationMs();
  1117. auto durationSeconds = durationMs / 1000;
  1118. startDurationUpdateTimer(durationMs);
  1119. return Ui::FormatDurationText(durationSeconds);
  1120. }
  1121. return tr::lng_call_status_ended(tr::now);
  1122. } break;
  1123. case State::FailedHangingUp:
  1124. case State::Failed: return tr::lng_call_status_failed(tr::now);
  1125. case State::HangingUp: return tr::lng_call_status_hanging(tr::now);
  1126. case State::Ended:
  1127. case State::EndedByOtherDevice: return tr::lng_call_status_ended(tr::now);
  1128. case State::ExchangingKeys: return tr::lng_call_status_exchanging(tr::now);
  1129. case State::Waiting: return tr::lng_call_status_waiting(tr::now);
  1130. case State::Requesting: return tr::lng_call_status_requesting(tr::now);
  1131. case State::WaitingIncoming: return tr::lng_call_status_incoming(tr::now);
  1132. case State::Ringing: return tr::lng_call_status_ringing(tr::now);
  1133. case State::Busy: return tr::lng_call_status_busy(tr::now);
  1134. case State::WaitingUserConfirmation: return tr::lng_call_status_sure(tr::now);
  1135. }
  1136. Unexpected("State in stateChanged()");
  1137. };
  1138. _status->setText(statusText());
  1139. updateStatusGeometry();
  1140. }
  1141. void Panel::startDurationUpdateTimer(crl::time currentDuration) {
  1142. auto msTillNextSecond = 1000 - (currentDuration % 1000);
  1143. _updateDurationTimer.callOnce(msTillNextSecond + 5);
  1144. }
  1145. } // namespace Calls