Improve sessions list design.
After Width: | Height: | Size: 757 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/settings/devices/device_other.png
Normal file
After Width: | Height: | Size: 414 B |
BIN
Telegram/Resources/icons/settings/devices/device_other@2x.png
Normal file
After Width: | Height: | Size: 662 B |
BIN
Telegram/Resources/icons/settings/devices/device_other@3x.png
Normal file
After Width: | Height: | Size: 984 B |
BIN
Telegram/Resources/icons/settings/devices/device_other_large.png
Normal file
After Width: | Height: | Size: 611 B |
After Width: | Height: | Size: 1,011 B |
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/settings/devices/device_web_other.png
Normal file
After Width: | Height: | Size: 821 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.9 KiB |
BIN
Telegram/Resources/icons/settings/devices/terminate_all.png
Normal file
After Width: | Height: | Size: 699 B |
BIN
Telegram/Resources/icons/settings/devices/terminate_all@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/settings/devices/terminate_all@3x.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Entry>;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Ui::GenericBox*> 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<int>;
|
||||
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<Type> {
|
||||
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<Type> {
|
||||
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<Window::SessionController*> controller)
|
||||
|
@ -475,17 +633,22 @@ void SessionsContent::Inner::setupContent() {
|
|||
Ui::show(Box(RenameBox), Ui::LayerOption::KeepOther);
|
||||
});
|
||||
|
||||
_current = content->add(object_ptr<List>(content));
|
||||
_current = content->add(
|
||||
object_ptr<List>(content),
|
||||
style::margins{ 0, 0, 0, st::sessionCurrentSkip });
|
||||
const auto terminateWrap = content->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
content,
|
||||
object_ptr<Ui::VerticalLayout>(content)))->setDuration(0);
|
||||
const auto terminateInner = terminateWrap->entity();
|
||||
_terminateAll = terminateInner->add(
|
||||
object_ptr<Ui::SettingsButton>(
|
||||
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<Ui::VerticalLayout>(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<List>(incompleteInner));
|
||||
AddSkip(incompleteInner);
|
||||
|
@ -505,19 +668,18 @@ void SessionsContent::Inner::setupContent() {
|
|||
content,
|
||||
object_ptr<Ui::VerticalLayout>(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<List>(listInner));
|
||||
AddSkip(listInner);
|
||||
AddDividerText(listInner, tr::lng_sessions_about_apps());
|
||||
|
||||
const auto ttlWrap = content->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
content,
|
||||
object_ptr<Ui::VerticalLayout>(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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|