Show session details on click.
This commit is contained in:
parent
f3e1aef264
commit
598cec8a9d
|
@ -723,6 +723,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_sessions_terminate_all_about" = "Logs out all devices except for this one.";
|
"lng_sessions_terminate_all_about" = "Logs out all devices except for this one.";
|
||||||
"lng_sessions_incomplete" = "Incomplete login attempts";
|
"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_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_application" = "Application";
|
||||||
|
"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_blocked_list_title" = "Blocked users";
|
"lng_blocked_list_title" = "Blocked users";
|
||||||
"lng_blocked_list_unknown_phone" = "unknown phone number";
|
"lng_blocked_list_unknown_phone" = "unknown phone number";
|
||||||
|
|
|
@ -58,7 +58,7 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) {
|
||||||
//if (j != countries.cend()) {
|
//if (j != countries.cend()) {
|
||||||
// country = QString::fromUtf8(j.value()->name);
|
// country = QString::fromUtf8(j.value()->name);
|
||||||
//}
|
//}
|
||||||
|
result.system = qs(data.vsystem_version());
|
||||||
result.activeTime = data.vdate_active().v
|
result.activeTime = data.vdate_active().v
|
||||||
? data.vdate_active().v
|
? data.vdate_active().v
|
||||||
: data.vdate_created().v;
|
: data.vdate_created().v;
|
||||||
|
@ -82,10 +82,7 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) {
|
||||||
result.active = lastDate.toString(cDateFormat());
|
result.active = lastDate.toString(cDateFormat());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.location = (country.isEmpty() ? result.ip : country)
|
result.location = country;
|
||||||
+ (result.hash
|
|
||||||
? (QString::fromUtf8(" \xe2\x80\x93 ") + result.active)
|
|
||||||
: QString());
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ public:
|
||||||
|
|
||||||
bool incomplete = false;
|
bool incomplete = false;
|
||||||
TimeId activeTime = 0;
|
TimeId activeTime = 0;
|
||||||
QString name, active, info, ip, location;
|
QString name, active, info, ip, location, system;
|
||||||
};
|
};
|
||||||
using List = std::vector<Entry>;
|
using List = std::vector<Entry>;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,8 @@ namespace {
|
||||||
constexpr auto kSessionsShortPollTimeout = 60 * crl::time(1000);
|
constexpr auto kSessionsShortPollTimeout = 60 * crl::time(1000);
|
||||||
constexpr auto kMaxDeviceModelLength = 32;
|
constexpr auto kMaxDeviceModelLength = 32;
|
||||||
|
|
||||||
|
using EntryData = Api::Authorizations::Entry;
|
||||||
|
|
||||||
void RenameBox(not_null<Ui::GenericBox*> box) {
|
void RenameBox(not_null<Ui::GenericBox*> box) {
|
||||||
box->setTitle(tr::lng_settings_rename_device_title());
|
box->setTitle(tr::lng_settings_rename_device_title());
|
||||||
|
|
||||||
|
@ -74,6 +76,63 @@ void RenameBox(not_null<Ui::GenericBox*> box) {
|
||||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SessionInfoBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
const EntryData &data,
|
||||||
|
Fn<void(uint64)> terminate) {
|
||||||
|
box->setTitle(rpl::single(data.name));
|
||||||
|
box->setWidth(st::boxWidth);
|
||||||
|
|
||||||
|
const auto skips = style::margins(0, 0, 0, st::settingsSectionSkip);
|
||||||
|
const auto date = base::unixtime::parse(data.activeTime);
|
||||||
|
box->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
rpl::single(langDateTimeFull(date)),
|
||||||
|
st::boxDividerLabel),
|
||||||
|
st::boxRowPadding + skips);
|
||||||
|
|
||||||
|
const auto add = [&](rpl::producer<QString> label, QString value) {
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Settings::AddSubsectionTitle(
|
||||||
|
box->verticalLayout(),
|
||||||
|
std::move(label));
|
||||||
|
box->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
rpl::single(value),
|
||||||
|
st::boxDividerLabel),
|
||||||
|
st::boxRowPadding + skips);
|
||||||
|
};
|
||||||
|
add(tr::lng_sessions_application(), data.info);
|
||||||
|
add(tr::lng_sessions_system(), data.system);
|
||||||
|
add(tr::lng_sessions_ip(), data.ip);
|
||||||
|
add(tr::lng_sessions_location(), data.location);
|
||||||
|
if (!data.location.isEmpty()) {
|
||||||
|
Settings::AddDividerText(
|
||||||
|
box->verticalLayout(),
|
||||||
|
tr::lng_sessions_location_about());
|
||||||
|
}
|
||||||
|
|
||||||
|
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
|
||||||
|
box->addLeftButton(tr::lng_sessions_terminate(), [=, hash = data.hash] {
|
||||||
|
const auto weak = Ui::MakeWeak(box.get());
|
||||||
|
terminate(hash);
|
||||||
|
if (weak) {
|
||||||
|
box->closeBox();
|
||||||
|
}
|
||||||
|
}, st::attentionBoxButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString LocationAndDate(const EntryData &entry) {
|
||||||
|
return (entry.location.isEmpty() ? entry.ip : entry.location)
|
||||||
|
+ (entry.hash
|
||||||
|
? (QString::fromUtf8(" \xe2\x80\x93 ") + entry.active)
|
||||||
|
: QString());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class SessionsContent : public Ui::RpWidget {
|
class SessionsContent : public Ui::RpWidget {
|
||||||
|
@ -91,20 +150,20 @@ protected:
|
||||||
private:
|
private:
|
||||||
struct Entry {
|
struct Entry {
|
||||||
Entry() = default;
|
Entry() = default;
|
||||||
Entry(const Api::Authorizations::Entry &entry)
|
Entry(const EntryData &entry)
|
||||||
: hash(entry.hash)
|
: data(entry)
|
||||||
, incomplete(entry.incomplete)
|
, incomplete(entry.incomplete)
|
||||||
, activeTime(entry.activeTime)
|
, activeTime(entry.activeTime)
|
||||||
, name(st::sessionNameStyle, entry.name)
|
, name(st::sessionNameStyle, entry.name)
|
||||||
, info(st::sessionInfoStyle, entry.info)
|
, info(st::sessionInfoStyle, entry.info)
|
||||||
, ip(st::sessionInfoStyle, entry.location) {
|
, location(st::sessionInfoStyle, LocationAndDate(entry)) {
|
||||||
};
|
};
|
||||||
|
|
||||||
uint64 hash = 0;
|
EntryData data;
|
||||||
|
|
||||||
bool incomplete = false;
|
bool incomplete = false;
|
||||||
TimeId activeTime = 0;
|
TimeId activeTime = 0;
|
||||||
Ui::Text::String name, info, ip;
|
Ui::Text::String name, info, location;
|
||||||
};
|
};
|
||||||
struct Full {
|
struct Full {
|
||||||
Entry current;
|
Entry current;
|
||||||
|
@ -121,6 +180,7 @@ private:
|
||||||
void terminateOne(uint64 hash);
|
void terminateOne(uint64 hash);
|
||||||
void terminateAll();
|
void terminateAll();
|
||||||
|
|
||||||
|
const not_null<Window::SessionController*> _controller;
|
||||||
const not_null<Api::Authorizations*> _authorizations;
|
const not_null<Api::Authorizations*> _authorizations;
|
||||||
|
|
||||||
rpl::variable<bool> _loading = false;
|
rpl::variable<bool> _loading = false;
|
||||||
|
@ -139,7 +199,8 @@ public:
|
||||||
|
|
||||||
void showData(gsl::span<const Entry> items);
|
void showData(gsl::span<const Entry> items);
|
||||||
rpl::producer<int> itemsCount() const;
|
rpl::producer<int> itemsCount() const;
|
||||||
rpl::producer<uint64> terminate() const;
|
rpl::producer<uint64> terminateRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<EntryData> showRequests() const;
|
||||||
|
|
||||||
void terminating(uint64 hash, bool terminating);
|
void terminating(uint64 hash, bool terminating);
|
||||||
|
|
||||||
|
@ -147,6 +208,9 @@ protected:
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
int resizeGetHeight(int newWidth) override;
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -161,8 +225,11 @@ private:
|
||||||
RowWidth _rowWidth;
|
RowWidth _rowWidth;
|
||||||
std::vector<Entry> _items;
|
std::vector<Entry> _items;
|
||||||
std::map<uint64, std::unique_ptr<Ui::IconButton>> _terminateButtons;
|
std::map<uint64, std::unique_ptr<Ui::IconButton>> _terminateButtons;
|
||||||
rpl::event_stream<uint64> _terminate;
|
rpl::event_stream<uint64> _terminateRequests;
|
||||||
rpl::event_stream<int> _itemsCount;
|
rpl::event_stream<int> _itemsCount;
|
||||||
|
rpl::event_stream<EntryData> _showRequests;
|
||||||
|
|
||||||
|
int _pressed = -1;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -174,9 +241,10 @@ public:
|
||||||
rpl::producer<int> ttlDays);
|
rpl::producer<int> ttlDays);
|
||||||
|
|
||||||
void showData(const Full &data);
|
void showData(const Full &data);
|
||||||
rpl::producer<uint64> terminateOne() const;
|
[[nodiscard]] rpl::producer<EntryData> showRequests() const;
|
||||||
rpl::producer<> terminateAll() const;
|
[[nodiscard]] rpl::producer<uint64> terminateOne() const;
|
||||||
rpl::producer<> renameCurrentRequests() const;
|
[[nodiscard]] rpl::producer<> terminateAll() const;
|
||||||
|
[[nodiscard]] rpl::producer<> renameCurrentRequests() const;
|
||||||
|
|
||||||
void terminatingOne(uint64 hash, bool terminating);
|
void terminatingOne(uint64 hash, bool terminating);
|
||||||
|
|
||||||
|
@ -195,7 +263,8 @@ private:
|
||||||
SessionsContent::SessionsContent(
|
SessionsContent::SessionsContent(
|
||||||
QWidget*,
|
QWidget*,
|
||||||
not_null<Window::SessionController*> controller)
|
not_null<Window::SessionController*> controller)
|
||||||
: _authorizations(&controller->session().api().authorizations())
|
: _controller(controller)
|
||||||
|
, _authorizations(&controller->session().api().authorizations())
|
||||||
, _inner(this, controller, _authorizations->ttlDays())
|
, _inner(this, controller, _authorizations->ttlDays())
|
||||||
, _shortPollTimer([=] { shortPollSessions(); }) {
|
, _shortPollTimer([=] { shortPollSessions(); }) {
|
||||||
}
|
}
|
||||||
|
@ -209,6 +278,14 @@ void SessionsContent::setupContent() {
|
||||||
resize(width(), height);
|
resize(width(), height);
|
||||||
}, _inner->lifetime());
|
}, _inner->lifetime());
|
||||||
|
|
||||||
|
_inner->showRequests(
|
||||||
|
) | rpl::start_with_next([=](const EntryData &data) {
|
||||||
|
_controller->show(Box(
|
||||||
|
SessionInfoBox,
|
||||||
|
data,
|
||||||
|
[=](uint64 hash) { terminateOne(hash); }));
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
_inner->terminateOne(
|
_inner->terminateOne(
|
||||||
) | rpl::start_with_next([=](uint64 hash) {
|
) | rpl::start_with_next([=](uint64 hash) {
|
||||||
terminateOne(hash);
|
terminateOne(hash);
|
||||||
|
@ -240,7 +317,7 @@ void SessionsContent::parse(const Api::Authorizations::List &list) {
|
||||||
_data = Full();
|
_data = Full();
|
||||||
for (const auto &auth : list) {
|
for (const auto &auth : list) {
|
||||||
auto entry = Entry(auth);
|
auto entry = Entry(auth);
|
||||||
if (!entry.hash) {
|
if (!entry.data.hash) {
|
||||||
_data.current = std::move(entry);
|
_data.current = std::move(entry);
|
||||||
} else if (entry.incomplete) {
|
} else if (entry.incomplete) {
|
||||||
_data.incomplete.push_back(std::move(entry));
|
_data.incomplete.push_back(std::move(entry));
|
||||||
|
@ -323,7 +400,10 @@ void SessionsContent::terminateOne(uint64 hash) {
|
||||||
_inner->terminatingOne(hash, false);
|
_inner->terminatingOne(hash, false);
|
||||||
const auto removeByHash = [&](std::vector<Entry> &list) {
|
const auto removeByHash = [&](std::vector<Entry> &list) {
|
||||||
list.erase(
|
list.erase(
|
||||||
ranges::remove(list, hash, &Entry::hash),
|
ranges::remove(
|
||||||
|
list,
|
||||||
|
hash,
|
||||||
|
[](const Entry &entry) { return entry.data.hash; }),
|
||||||
end(list));
|
end(list));
|
||||||
};
|
};
|
||||||
removeByHash(_data.incomplete);
|
removeByHash(_data.incomplete);
|
||||||
|
@ -489,8 +569,14 @@ rpl::producer<> SessionsContent::Inner::terminateAll() const {
|
||||||
|
|
||||||
rpl::producer<uint64> SessionsContent::Inner::terminateOne() const {
|
rpl::producer<uint64> SessionsContent::Inner::terminateOne() const {
|
||||||
return rpl::merge(
|
return rpl::merge(
|
||||||
_incomplete->terminate(),
|
_incomplete->terminateRequests(),
|
||||||
_list->terminate());
|
_list->terminateRequests());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<EntryData> SessionsContent::Inner::showRequests() const {
|
||||||
|
return rpl::merge(
|
||||||
|
_incomplete->showRequests(),
|
||||||
|
_list->showRequests());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionsContent::Inner::terminatingOne(uint64 hash, bool terminating) {
|
void SessionsContent::Inner::terminatingOne(uint64 hash, bool terminating) {
|
||||||
|
@ -512,7 +598,7 @@ void SessionsContent::List::subscribeToCustomDeviceModel() {
|
||||||
Core::App().settings().deviceModelChanges(
|
Core::App().settings().deviceModelChanges(
|
||||||
) | rpl::start_with_next([=](const QString &model) {
|
) | rpl::start_with_next([=](const QString &model) {
|
||||||
for (auto &entry : _items) {
|
for (auto &entry : _items) {
|
||||||
if (!entry.hash) {
|
if (!entry.data.hash) {
|
||||||
entry.name.setText(st::sessionNameStyle, model);
|
entry.name.setText(st::sessionNameStyle, model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -527,7 +613,7 @@ void SessionsContent::List::showData(gsl::span<const Entry> items) {
|
||||||
_items.clear();
|
_items.clear();
|
||||||
_items.insert(begin(_items), items.begin(), items.end());
|
_items.insert(begin(_items), items.begin(), items.end());
|
||||||
for (const auto &entry : _items) {
|
for (const auto &entry : _items) {
|
||||||
const auto hash = entry.hash;
|
const auto hash = entry.data.hash;
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -542,7 +628,7 @@ void SessionsContent::List::showData(gsl::span<const Entry> items) {
|
||||||
st::sessionTerminate))).first->second.get();
|
st::sessionTerminate))).first->second.get();
|
||||||
}();
|
}();
|
||||||
button->setClickedCallback([=] {
|
button->setClickedCallback([=] {
|
||||||
_terminate.fire_copy(hash);
|
_terminateRequests.fire_copy(hash);
|
||||||
});
|
});
|
||||||
button->show();
|
button->show();
|
||||||
const auto number = _terminateButtons.size() - 1;
|
const auto number = _terminateButtons.size() - 1;
|
||||||
|
@ -557,12 +643,16 @@ void SessionsContent::List::showData(gsl::span<const Entry> items) {
|
||||||
_itemsCount.fire(_items.size());
|
_itemsCount.fire(_items.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<EntryData> SessionsContent::List::showRequests() const {
|
||||||
|
return _showRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
rpl::producer<int> SessionsContent::List::itemsCount() const {
|
rpl::producer<int> SessionsContent::List::itemsCount() const {
|
||||||
return _itemsCount.events_starting_with(_items.size());
|
return _itemsCount.events_starting_with(_items.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<uint64> SessionsContent::List::terminate() const {
|
rpl::producer<uint64> SessionsContent::List::terminateRequests() const {
|
||||||
return _terminate.events();
|
return _terminateRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionsContent::List::terminating(uint64 hash, bool terminating) {
|
void SessionsContent::List::terminating(uint64 hash, bool terminating) {
|
||||||
|
@ -617,7 +707,7 @@ void SessionsContent::List::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
const auto nameW = _rowWidth.info;
|
const auto nameW = _rowWidth.info;
|
||||||
const auto nameH = entry.name.style()->font->height;
|
const auto nameH = entry.name.style()->font->height;
|
||||||
const auto infoW = entry.hash ? _rowWidth.info : available;
|
const auto infoW = entry.data.hash ? _rowWidth.info : available;
|
||||||
const auto infoH = entry.info.style()->font->height;
|
const auto infoH = entry.info.style()->font->height;
|
||||||
|
|
||||||
p.setPen(st::sessionNameFg);
|
p.setPen(st::sessionNameFg);
|
||||||
|
@ -627,12 +717,26 @@ void SessionsContent::List::paintEvent(QPaintEvent *e) {
|
||||||
entry.info.drawLeftElided(p, x, y + nameH, infoW, w);
|
entry.info.drawLeftElided(p, x, y + nameH, infoW, w);
|
||||||
|
|
||||||
p.setPen(st::sessionInfoFg);
|
p.setPen(st::sessionInfoFg);
|
||||||
entry.ip.drawLeftElided(p, x, y + nameH + infoH, available, w);
|
entry.location.drawLeftElided(p, x, y + nameH + infoH, available, w);
|
||||||
|
|
||||||
p.translate(0, st::sessionHeight);
|
p.translate(0, st::sessionHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SessionsContent::List::mousePressEvent(QMouseEvent *e) {
|
||||||
|
const auto index = e->pos().y() / st::sessionHeight;
|
||||||
|
_pressed = (index >= 0 && index < _items.size()) ? index : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionsContent::List::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
const auto index = e->pos().y() / st::sessionHeight;
|
||||||
|
const auto released = (index >= 0 && index < _items.size()) ? index : -1;
|
||||||
|
if (released == _pressed && released >= 0) {
|
||||||
|
_showRequests.fire_copy(_items[released].data);
|
||||||
|
}
|
||||||
|
_pressed = -1;
|
||||||
|
}
|
||||||
|
|
||||||
SessionsBox::SessionsBox(
|
SessionsBox::SessionsBox(
|
||||||
QWidget*,
|
QWidget*,
|
||||||
not_null<Window::SessionController*> controller)
|
not_null<Window::SessionController*> controller)
|
||||||
|
|
Loading…
Reference in New Issue