diff --git a/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.lottie b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.lottie new file mode 100644 index 0000000000..b4f5567192 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.lottie differ diff --git a/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.png b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.png new file mode 100644 index 0000000000..8cb5249534 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@2x.png b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@2x.png new file mode 100644 index 0000000000..37a44106e3 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@3x.png b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@3x.png new file mode 100644 index 0000000000..8544938c11 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other.png b/Telegram/Resources/icons/settings/devices/device_other.png new file mode 100644 index 0000000000..ad8bce06a0 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other@2x.png b/Telegram/Resources/icons/settings/devices/device_other@2x.png new file mode 100644 index 0000000000..db27d3482c Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other@3x.png b/Telegram/Resources/icons/settings/devices/device_other@3x.png new file mode 100644 index 0000000000..9ec10ee96f Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other_large.png b/Telegram/Resources/icons/settings/devices/device_other_large.png new file mode 100644 index 0000000000..0f47128db6 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other_large.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other_large@2x.png b/Telegram/Resources/icons/settings/devices/device_other_large@2x.png new file mode 100644 index 0000000000..ee45e7b9a1 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other_large@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other_large@3x.png b/Telegram/Resources/icons/settings/devices/device_other_large@3x.png new file mode 100644 index 0000000000..5b617f8336 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other_large@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other.png b/Telegram/Resources/icons/settings/devices/device_web_other.png new file mode 100644 index 0000000000..9b777f53fe Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other@2x.png b/Telegram/Resources/icons/settings/devices/device_web_other@2x.png new file mode 100644 index 0000000000..ce90741f83 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other@3x.png b/Telegram/Resources/icons/settings/devices/device_web_other@3x.png new file mode 100644 index 0000000000..b6740fc967 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other_large.png b/Telegram/Resources/icons/settings/devices/device_web_other_large.png new file mode 100644 index 0000000000..200206a6e9 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other_large.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other_large@2x.png b/Telegram/Resources/icons/settings/devices/device_web_other_large@2x.png new file mode 100644 index 0000000000..740152c945 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other_large@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other_large@3x.png b/Telegram/Resources/icons/settings/devices/device_web_other_large@3x.png new file mode 100644 index 0000000000..6463c3a343 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other_large@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/terminate_all.png b/Telegram/Resources/icons/settings/devices/terminate_all.png new file mode 100644 index 0000000000..ccf538575e Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/terminate_all.png differ diff --git a/Telegram/Resources/icons/settings/devices/terminate_all@2x.png b/Telegram/Resources/icons/settings/devices/terminate_all@2x.png new file mode 100644 index 0000000000..8720d7f238 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/terminate_all@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/terminate_all@3x.png b/Telegram/Resources/icons/settings/devices/terminate_all@3x.png new file mode 100644 index 0000000000..d4da46f9fe Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/terminate_all@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ea9c3e095f..ab5fd1b85e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -723,12 +723,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sessions_terminate_all_about" = "Logs out all devices except for this one."; "lng_sessions_incomplete" = "Incomplete login attempts"; "lng_sessions_incomplete_about" = "The devices above have no access to your messages. The code was entered correctly, but no correct password was given."; -"lng_sessions_terminate" = "Terminate"; +"lng_sessions_info" = "Info"; +"lng_sessions_terminate" = "Terminate Session"; "lng_sessions_application" = "Application"; -"lng_sessions_system" = "System Version"; -"lng_sessions_ip" = "IP Address"; +"lng_sessions_system" = "System version"; +"lng_sessions_ip" = "IP address"; "lng_sessions_location" = "Location"; -"lng_sessions_location_about" = "This location estimate is based on the IP address and may not always be accurate."; +"lng_sessions_location_about" = "This location is based only on the IP address and may not always be accurate."; +"lng_sessions_about_apps" = "The official Telegram app is available for Android, iPhone, iPad, Windows, macOS and Linux."; "lng_blocked_list_title" = "Blocked users"; "lng_blocked_list_unknown_phone" = "unknown phone number"; diff --git a/Telegram/SourceFiles/api/api_authorizations.cpp b/Telegram/SourceFiles/api/api_authorizations.cpp index 0f1f457f81..050fc458b8 100644 --- a/Telegram/SourceFiles/api/api_authorizations.cpp +++ b/Telegram/SourceFiles/api/api_authorizations.cpp @@ -17,6 +17,7 @@ namespace Api { namespace { constexpr auto TestApiId = 17349; +constexpr auto SnapApiId = 611335; constexpr auto DesktopApiId = 2040; Authorizations::Entry ParseEntry(const MTPDauthorization &data) { @@ -25,9 +26,11 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { result.hash = data.is_current() ? 0 : data.vhash().v; result.incomplete = data.is_password_pending(); - const auto apiId = data.vapi_id().v; + const auto apiId = result.apiId = data.vapi_id().v; const auto isTest = (apiId == TestApiId); - const auto isDesktop = (apiId == DesktopApiId) || isTest; + const auto isDesktop = (apiId == DesktopApiId) + || (apiId == SnapApiId) + || isTest; const auto appName = isDesktop ? QString("Telegram Desktop%1").arg(isTest ? " (GitHub)" : QString()) @@ -59,6 +62,7 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { // country = QString::fromUtf8(j.value()->name); //} result.system = qs(data.vsystem_version()); + result.platform = qs(data.vplatform()); result.activeTime = data.vdate_active().v ? data.vdate_active().v : data.vdate_created().v; diff --git a/Telegram/SourceFiles/api/api_authorizations.h b/Telegram/SourceFiles/api/api_authorizations.h index 45092eca28..f789740fc3 100644 --- a/Telegram/SourceFiles/api/api_authorizations.h +++ b/Telegram/SourceFiles/api/api_authorizations.h @@ -21,8 +21,9 @@ public: uint64 hash = 0; bool incomplete = false; + int apiId = 0; TimeId activeTime = 0; - QString name, active, info, ip, location, system; + QString name, active, info, ip, location, system, platform; }; using List = std::vector; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 6ee4529a0e..5dd5c1894b 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -281,10 +281,21 @@ membersAbout: FlatLabel(defaultFlatLabel) { sessionsScroll: boxScroll; sessionsHeight: 350px; -sessionHeight: 70px; -sessionCurrentPadding: margins(0px, 7px, 0px, 4px); -sessionCurrentHeight: 118px; -sessionPadding: margins(22px, 10px, 22px, 0px); +sessionsTerminateAll: SettingsButton(defaultSettingsButton) { + textFg: attentionButtonFg; + textFgOver: attentionButtonFgOver; + font: font(boxFontSize semibold); + height: 20px; + padding: margins(77px, 12px, 22px, 10px); +} +sessionsTerminateAllIcon: icon {{ "settings/devices/terminate_all", attentionButtonFg }}; +sessionsTerminateAllIconLeft: 30px; +sessionHeight: 84px; +sessionInfoTop: 21px; +sessionLocationTop: 43px; +sessionCurrentSkip: 8px; +sessionSubtitleSkip: 14px; +sessionPadding: margins(77px, 11px, 22px, 0px); sessionNameFont: msgNameFont; sessionNameFg: boxTextFg; sessionWhenFont: msgDateFont; @@ -293,6 +304,8 @@ sessionInfoFont: msgFont; sessionInfoFg: windowSubTextFg; sessionTerminateTop: 9px; sessionTerminateSkip: 22px; +sessionUserpicSize: 42px; +sessionUserpicPosition: point(21px, 10px); sessionNamePadding: margins(0px, 0px, 5px, 0px); sessionTerminate: IconButton { width: 20px; @@ -308,10 +321,6 @@ sessionTerminate: IconButton { color: windowBgOver; } } -sessionTerminateAllButton: LinkButton(boxLinkButton) { - color: attentionButtonFg; - overColor: attentionButtonFg; -} sessionNameStyle: TextStyle(defaultTextStyle) { font: sessionNameFont; } @@ -321,6 +330,19 @@ sessionWhenStyle: TextStyle(defaultTextStyle) { sessionInfoStyle: TextStyle(defaultTextStyle) { font: sessionInfoFont; } +sessionIconWindows: icon{{ "settings/devices/device_desktop_win", historyPeerUserpicFg }}; +sessionIconMac: icon{{ "settings/devices/device_desktop_mac", historyPeerUserpicFg }}; +sessionIconUbuntu: icon{{ "settings/devices/device_linux_ubuntu", historyPeerUserpicFg }}; +sessionIconLinux: icon{{ "settings/devices/device_linux", historyPeerUserpicFg }}; +sessionIconiPhone: icon{{ "settings/devices/device_phone_ios", historyPeerUserpicFg }}; +sessionIconiPad: icon{{ "settings/devices/device_tablet_ios", historyPeerUserpicFg }}; +sessionIconAndroid: icon{{ "settings/devices/device_phone_android", historyPeerUserpicFg }}; +sessionIconWeb: icon{{ "settings/devices/device_web_other", historyPeerUserpicFg }}; +sessionIconChrome: icon{{ "settings/devices/device_web_chrome", historyPeerUserpicFg }}; +sessionIconEdge: icon{{ "settings/devices/device_web_edge", historyPeerUserpicFg }}; +sessionIconFirefox: icon{{ "settings/devices/device_web_firefox", historyPeerUserpicFg }}; +sessionIconSafari: icon{{ "settings/devices/device_web_safari", historyPeerUserpicFg }}; +sessionIconOther: icon{{ "settings/devices/device_other", historyPeerUserpicFg }}; passcodeHeaderFont: font(19px); passcodeHeaderHeight: 80px; diff --git a/Telegram/SourceFiles/boxes/sessions_box.cpp b/Telegram/SourceFiles/boxes/sessions_box.cpp index 5ede4940da..bab50931fc 100644 --- a/Telegram/SourceFiles/boxes/sessions_box.cpp +++ b/Telegram/SourceFiles/boxes/sessions_box.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/layers/generic_box.h" +#include "lottie/lottie_icon.h" #include "core/application.h" #include "core/core_settings.h" #include "window/window_session_controller.h" @@ -39,6 +40,22 @@ constexpr auto kMaxDeviceModelLength = 32; using EntryData = Api::Authorizations::Entry; +enum class Type { + Windows, + Mac, + Ubuntu, + Linux, + iPhone, + iPad, + Android, + Web, + Chrome, + Edge, + Firefox, + Safari, + Other, +}; + void RenameBox(not_null box) { box->setTitle(tr::lng_settings_rename_device_title()); @@ -133,6 +150,141 @@ void SessionInfoBox( : QString()); } +[[nodiscard]] Type TypeFromEntry(const EntryData &entry) { + using List = std::vector; + const auto platform = entry.platform.toLower(); + const auto device = entry.name.toLower(); + const auto system = entry.system.toLower(); + const auto apiId = entry.apiId; + const auto kDesktop = std::array{ 2040, 17349, 611335 }; + const auto kMac = std::array{ 2834 }; + const auto kAndroid + = std::array{ 5, 6, 24, 1026, 1083, 2458, 2521, 21724 }; + const auto kiOS = std::array{ 1, 7, 10840, 16352 }; + const auto kWeb = std::array{ 2496, 739222, 1025907 }; + + const auto detectBrowser = [&]() -> std::optional { + if (device.contains("edg/") + || device.contains("edgios/") + || device.contains("edga/")) { + return Type::Edge; + } else if (device.contains("chrome")) { + return Type::Chrome; + } else if (device.contains("safari")) { + return Type::Safari; + } else if (device.contains("firefox")) { + return Type::Firefox; + } + return {}; + }; + const auto detectDesktop = [&]() -> std::optional { + if (platform.contains("windows") || system.contains("windows")) { + return Type::Windows; + } else if (platform.contains("macos") || system.contains("macos")) { + return Type::Mac; + } else if (platform.contains("ubuntu") + || system.contains("ubuntu") + || platform.contains("unity") + || system.contains("unity")) { + return Type::Ubuntu; + } else if (platform.contains("linux") || system.contains("linux")) { + return Type::Linux; + } + return {}; + }; + + if (ranges::contains(kAndroid, apiId)) { + return Type::Android; + } else if (ranges::contains(kDesktop, apiId)) { + return detectDesktop().value_or(Type::Linux); + } else if (ranges::contains(kMac, apiId)) { + return Type::Mac; + } else if (ranges::contains(kWeb, apiId)) { + return detectBrowser().value_or(Type::Web); + } else if (device.contains("chromebook")) { + return Type::Other; + } else if (const auto browser = detectBrowser()) { + return *browser; + } else if (device.contains("iphone")) { + return Type::iPhone; + } else if (device.contains("ipad")) { + return Type::iPad; + } else if (ranges::contains(kiOS, apiId)) { + return Type::iPhone; + } else if (const auto desktop = detectDesktop()) { + return *desktop; + } else if (platform.contains("android") || system.contains("android")) { + return Type::Android; + } else if (platform.contains("ios") || system.contains("ios")) { + return Type::iPhone; + } + return Type::Other; +} + +[[nodiscard]] style::color ColorForType(Type type) { + switch (type) { + case Type::Windows: + case Type::Mac: + case Type::Other: + return st::historyPeer4UserpicBg; // blue + case Type::Ubuntu: + return st::historyPeer8UserpicBg; // orange + case Type::Linux: + return st::historyPeer5UserpicBg; // purple + case Type::iPhone: + case Type::iPad: + return st::historyPeer7UserpicBg; // sea + case Type::Android: + return st::historyPeer2UserpicBg; // green + case Type::Web: + case Type::Chrome: + case Type::Edge: + case Type::Firefox: + case Type::Safari: + return st::historyPeer6UserpicBg; // pink + } + Unexpected("Type in ColorForType."); +} + +[[nodiscard]] const style::icon &IconForType(Type type) { + switch (type) { + case Type::Windows: return st::sessionIconWindows; + case Type::Mac: return st::sessionIconMac; + case Type::Ubuntu: return st::sessionIconUbuntu; + case Type::Linux: return st::sessionIconLinux; + case Type::iPhone: return st::sessionIconiPhone; + case Type::iPad: return st::sessionIconiPad; + case Type::Android: return st::sessionIconAndroid; + case Type::Web: return st::sessionIconWeb; + case Type::Chrome: return st::sessionIconChrome; + case Type::Edge: return st::sessionIconEdge; + case Type::Firefox: return st::sessionIconFirefox; + case Type::Safari: return st::sessionIconSafari; + case Type::Other: return st::sessionIconOther; + } + Unexpected("Type in IconForType."); +} + +[[nodiscard]] QImage GenerateUserpic(Type type) { + const auto size = st::sessionUserpicSize; + const auto full = size * style::DevicePixelRatio(); + const auto rect = QRect(0, 0, size, size); + + auto result = QImage(full, full, QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + result.setDevicePixelRatio(style::DevicePixelRatio()); + + auto p = QPainter(&result); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(ColorForType(type)); + p.setPen(Qt::NoPen); + p.drawEllipse(rect); + IconForType(type).paintInCenter(p, rect); + p.end(); + + return result; +} + } // namespace class SessionsContent : public Ui::RpWidget { @@ -150,20 +302,15 @@ protected: private: struct Entry { Entry() = default; - Entry(const EntryData &entry) - : data(entry) - , incomplete(entry.incomplete) - , activeTime(entry.activeTime) - , name(st::sessionNameStyle, entry.name) - , info(st::sessionInfoStyle, entry.info) - , location(st::sessionInfoStyle, LocationAndDate(entry)) { - }; + explicit Entry(const EntryData &entry); EntryData data; bool incomplete = false; + Type type = Type::Other; TimeId activeTime = 0; Ui::Text::String name, info, location; + QImage userpic; }; struct Full { Entry current; @@ -260,6 +407,17 @@ private: }; +SessionsContent::Entry::Entry(const EntryData &entry) +: data(entry) +, incomplete(entry.incomplete) +, type(TypeFromEntry(entry)) +, activeTime(entry.activeTime) +, name(st::sessionNameStyle, entry.name) +, info(st::sessionInfoStyle, entry.info) +, location(st::sessionInfoStyle, LocationAndDate(entry)) +, userpic(GenerateUserpic(type)) { +}; + SessionsContent::SessionsContent( QWidget*, not_null controller) @@ -475,17 +633,22 @@ void SessionsContent::Inner::setupContent() { Ui::show(Box(RenameBox), Ui::LayerOption::KeepOther); }); - _current = content->add(object_ptr(content)); + _current = content->add( + object_ptr(content), + style::margins{ 0, 0, 0, st::sessionCurrentSkip }); const auto terminateWrap = content->add( object_ptr>( content, object_ptr(content)))->setDuration(0); const auto terminateInner = terminateWrap->entity(); _terminateAll = terminateInner->add( - object_ptr( + CreateButton( terminateInner, tr::lng_sessions_terminate_all(), - st::terminateSessionsButton)); + st::sessionsTerminateAll, + &st::sessionsTerminateAllIcon, + st::sessionsTerminateAllIconLeft, + &st::attentionButtonFg)); AddSkip(terminateInner); AddDividerText(terminateInner, tr::lng_sessions_terminate_all_about()); @@ -494,7 +657,7 @@ void SessionsContent::Inner::setupContent() { content, object_ptr(content)))->setDuration(0); const auto incompleteInner = incompleteWrap->entity(); - AddSkip(incompleteInner); + AddSkip(incompleteInner, st::sessionSubtitleSkip); AddSubsectionTitle(incompleteInner, tr::lng_sessions_incomplete()); _incomplete = incompleteInner->add(object_ptr(incompleteInner)); AddSkip(incompleteInner); @@ -505,19 +668,18 @@ void SessionsContent::Inner::setupContent() { content, object_ptr(content)))->setDuration(0); const auto listInner = listWrap->entity(); - AddSkip(listInner); + AddSkip(listInner, st::sessionSubtitleSkip); AddSubsectionTitle(listInner, tr::lng_sessions_other_header()); _list = listInner->add(object_ptr(listInner)); AddSkip(listInner); + AddDividerText(listInner, tr::lng_sessions_about_apps()); const auto ttlWrap = content->add( object_ptr>( content, object_ptr(content)))->setDuration(0); const auto ttlInner = ttlWrap->entity(); - AddDivider(ttlInner); - AddSkip(ttlInner); - + AddSkip(ttlInner, st::sessionSubtitleSkip); AddSubsectionTitle(ttlInner, tr::lng_settings_terminate_title()); AddButtonWithLabel( @@ -703,19 +865,24 @@ void SessionsContent::List::paintEvent(QPaintEvent *e) { for (auto i = from; i != till; ++i) { const auto &entry = _items[i]; + p.drawImage(st::sessionUserpicPosition, entry.userpic); + const auto nameW = _rowWidth.info; - const auto nameH = entry.name.style()->font->height; const auto infoW = entry.data.hash ? _rowWidth.info : available; - const auto infoH = entry.info.style()->font->height; p.setPen(st::sessionNameFg); entry.name.drawLeftElided(p, x, y, nameW, w); p.setPen(st::boxTextFg); - entry.info.drawLeftElided(p, x, y + nameH, infoW, w); + entry.info.drawLeftElided(p, x, y + st::sessionInfoTop, infoW, w); p.setPen(st::sessionInfoFg); - entry.location.drawLeftElided(p, x, y + nameH + infoH, available, w); + entry.location.drawLeftElided( + p, + x, + y + st::sessionLocationTop, + available, + w); p.translate(0, st::sessionHeight); } diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index a4a00c01d5..93269eba35 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -632,10 +632,6 @@ manageDeleteGroupButton: SettingsCountButton(manageGroupTopButtonWithText) { editPeerSkip: 7px; editPeerHistoryVisibilityMargins: margins(15px, 0px, 20px, 16px); -terminateSessionsButton: SettingsButton(infoBlockButton) { - padding: margins(22px, 12px, 22px, 10px); -} - infoEmptyFg: windowSubTextFg; infoEmptyPhoto: icon {{ "info_media_photo_empty", infoEmptyFg }}; infoEmptyVideo: icon {{ "info_media_video_empty", infoEmptyFg }}; @@ -662,8 +658,6 @@ editPeerTopButtonsLayoutSkipCustomBottom: 11px; editPeerHistoryVisibilityTopSkip: 8px; -editPeerDeleteButtonMargins: margins(25px, 11px, 22px, 16px); -editPeerDeleteButton: sessionTerminateAllButton; editPeerPhotoMargins: margins(22px, 16px, 22px, 8px); editPeerTitle: defaultInputField; editPeerTitleMargins: margins(27px, 21px, 22px, 8px);