Improve sessions list design.

This commit is contained in:
John Preston 2021-11-30 12:36:12 +04:00
parent 94bec3b574
commit 47074b48d6
25 changed files with 231 additions and 41 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -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";

View File

@ -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;

View File

@ -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>;

View File

@ -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;

View File

@ -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);
}

View File

@ -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);