Before Width: | Height: | Size: 107 B |
Before Width: | Height: | Size: 126 B |
Before Width: | Height: | Size: 174 B |
BIN
Telegram/Resources/icons/contacts_alphabet.png
Normal file
After Width: | Height: | Size: 448 B |
BIN
Telegram/Resources/icons/contacts_alphabet@2x.png
Normal file
After Width: | Height: | Size: 784 B |
BIN
Telegram/Resources/icons/contacts_alphabet@3x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/contacts_online.png
Normal file
After Width: | Height: | Size: 385 B |
BIN
Telegram/Resources/icons/contacts_online@2x.png
Normal file
After Width: | Height: | Size: 649 B |
BIN
Telegram/Resources/icons/contacts_online@3x.png
Normal file
After Width: | Height: | Size: 1,014 B |
|
@ -160,6 +160,21 @@ contactsAboutFg: windowSubTextFgOver;
|
||||||
contactsAboutTop: 60px;
|
contactsAboutTop: 60px;
|
||||||
contactsAboutBottom: 19px;
|
contactsAboutBottom: 19px;
|
||||||
|
|
||||||
|
contactsSortButton: IconButton(defaultIconButton) {
|
||||||
|
width: 48px;
|
||||||
|
height: 54px;
|
||||||
|
icon: icon{{ "contacts_alphabet", boxTitleCloseFg }};
|
||||||
|
iconOver: icon{{ "contacts_alphabet", boxTitleCloseFgOver }};
|
||||||
|
iconPosition: point(10px, -1px);
|
||||||
|
rippleAreaPosition: point(1px, 6px);
|
||||||
|
rippleAreaSize: 42px;
|
||||||
|
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||||
|
color: windowBgOver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contactsSortOnlineIcon: icon{{ "contacts_online", boxTitleCloseFg }};
|
||||||
|
contactsSortOnlineIconOver: icon{{ "contacts_online", boxTitleCloseFgOver }};
|
||||||
|
|
||||||
contactsMarginTop: 4px;
|
contactsMarginTop: 4px;
|
||||||
contactsMarginBottom: 4px;
|
contactsMarginBottom: 4px;
|
||||||
membersMarginTop: 10px;
|
membersMarginTop: 10px;
|
||||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_folder.h"
|
#include "data/data_folder.h"
|
||||||
#include "data/data_histories.h"
|
#include "data/data_histories.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
@ -26,12 +27,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "dialogs/dialogs_main_list.h"
|
#include "dialogs/dialogs_main_list.h"
|
||||||
#include "window/window_session_controller.h" // showAddContact()
|
#include "window/window_session_controller.h" // showAddContact()
|
||||||
|
#include "base/unixtime.h"
|
||||||
#include "facades.h"
|
#include "facades.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_profile.h"
|
#include "styles/style_profile.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000);
|
||||||
|
|
||||||
void ShareBotGame(not_null<UserData*> bot, not_null<PeerData*> chat) {
|
void ShareBotGame(not_null<UserData*> bot, not_null<PeerData*> chat) {
|
||||||
const auto history = chat->owner().history(chat);
|
const auto history = chat->owner().history(chat);
|
||||||
auto &histories = history->owner().histories();
|
auto &histories = history->owner().histories();
|
||||||
|
@ -110,17 +114,31 @@ void AddBotToGroup(not_null<UserData*> bot, not_null<PeerData*> chat) {
|
||||||
|
|
||||||
object_ptr<Ui::BoxContent> PrepareContactsBox(
|
object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||||
not_null<Window::SessionController*> sessionController) {
|
not_null<Window::SessionController*> sessionController) {
|
||||||
const auto controller = sessionController;
|
using Mode = ContactsBoxController::SortMode;
|
||||||
auto delegate = [=](not_null<PeerListBox*> box) {
|
auto controller = std::make_unique<ContactsBoxController>(
|
||||||
|
&sessionController->session());
|
||||||
|
const auto raw = controller.get();
|
||||||
|
auto init = [=](not_null<PeerListBox*> box) {
|
||||||
|
struct State {
|
||||||
|
QPointer<Ui::IconButton> toggleSort;
|
||||||
|
Mode mode = ContactsBoxController::SortMode::Online;
|
||||||
|
};
|
||||||
|
const auto state = box->lifetime().make_state<State>();
|
||||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||||
box->addLeftButton(
|
box->addLeftButton(
|
||||||
tr::lng_profile_add_contact(),
|
tr::lng_profile_add_contact(),
|
||||||
[=] { controller->showAddContact(); });
|
[=] { sessionController->showAddContact(); });
|
||||||
|
state->toggleSort = box->addTopButton(st::contactsSortButton, [=] {
|
||||||
|
const auto online = (state->mode == Mode::Online);
|
||||||
|
state->mode = online ? Mode::Alphabet : Mode::Online;
|
||||||
|
raw->setSortMode(state->mode);
|
||||||
|
state->toggleSort->setIconOverride(
|
||||||
|
online ? &st::contactsSortOnlineIcon : nullptr,
|
||||||
|
online ? &st::contactsSortOnlineIconOver : nullptr);
|
||||||
|
});
|
||||||
|
raw->setSortMode(Mode::Online);
|
||||||
};
|
};
|
||||||
return Box<PeerListBox>(
|
return Box<PeerListBox>(std::move(controller), std::move(init));
|
||||||
std::make_unique<ContactsBoxController>(
|
|
||||||
&sessionController->session()),
|
|
||||||
std::move(delegate));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListRowWithLink::setActionLink(const QString &action) {
|
void PeerListRowWithLink::setActionLink(const QString &action) {
|
||||||
|
@ -368,7 +386,8 @@ ContactsBoxController::ContactsBoxController(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
std::unique_ptr<PeerListSearchController> searchController)
|
std::unique_ptr<PeerListSearchController> searchController)
|
||||||
: PeerListController(std::move(searchController))
|
: PeerListController(std::move(searchController))
|
||||||
, _session(session) {
|
, _session(session)
|
||||||
|
, _sortByOnlineTimer([=] { sort(); }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Main::Session &ContactsBoxController::session() const {
|
Main::Session &ContactsBoxController::session() const {
|
||||||
|
@ -404,6 +423,7 @@ void ContactsBoxController::rebuildRows() {
|
||||||
};
|
};
|
||||||
appendList(session().data().contactsList());
|
appendList(session().data().contactsList());
|
||||||
checkForEmptyRows();
|
checkForEmptyRows();
|
||||||
|
sort();
|
||||||
delegate()->peerListRefreshRows();
|
delegate()->peerListRefreshRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,6 +447,66 @@ void ContactsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
Ui::showPeerHistory(row->peer(), ShowAtUnreadMsgId);
|
Ui::showPeerHistory(row->peer(), ShowAtUnreadMsgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContactsBoxController::setSortMode(SortMode mode) {
|
||||||
|
if (_sortMode == mode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_sortMode = mode;
|
||||||
|
sort();
|
||||||
|
if (_sortMode == SortMode::Online) {
|
||||||
|
session().changes().peerUpdates(
|
||||||
|
Data::PeerUpdate::Flag::OnlineStatus
|
||||||
|
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||||
|
return !_sortByOnlineTimer.isActive()
|
||||||
|
&& delegate()->peerListFindRow(update.peer->id.value);
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
_sortByOnlineTimer.callOnce(kSortByOnlineThrottle);
|
||||||
|
}, _sortByOnlineLifetime);
|
||||||
|
} else {
|
||||||
|
_sortByOnlineTimer.cancel();
|
||||||
|
_sortByOnlineLifetime.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContactsBoxController::sort() {
|
||||||
|
switch (_sortMode) {
|
||||||
|
case SortMode::Alphabet: sortByName(); break;
|
||||||
|
case SortMode::Online: sortByOnline(); break;
|
||||||
|
default: Unexpected("SortMode in ContactsBoxController.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContactsBoxController::sortByName() {
|
||||||
|
auto keys = base::flat_map<PeerListRowId, QString>();
|
||||||
|
keys.reserve(delegate()->peerListFullRowsCount());
|
||||||
|
const auto key = [&](const PeerListRow &row) {
|
||||||
|
const auto id = row.id();
|
||||||
|
const auto i = keys.find(id);
|
||||||
|
if (i != end(keys)) {
|
||||||
|
return i->second;
|
||||||
|
}
|
||||||
|
const auto peer = row.peer();
|
||||||
|
const auto history = peer->owner().history(peer);
|
||||||
|
return keys.emplace(id, history->chatListNameSortKey()).first->second;
|
||||||
|
};
|
||||||
|
const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
|
||||||
|
return (key(a).compare(key(b)) < 0);
|
||||||
|
};
|
||||||
|
delegate()->peerListSortRows(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContactsBoxController::sortByOnline() {
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
const auto key = [&](const PeerListRow &row) {
|
||||||
|
const auto user = row.peer()->asUser();
|
||||||
|
return user ? (std::min(user->onlineTill, now) + 1) : TimeId();
|
||||||
|
};
|
||||||
|
const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
|
||||||
|
return key(a) > key(b);
|
||||||
|
};
|
||||||
|
delegate()->peerListSortRows(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
bool ContactsBoxController::appendRow(not_null<UserData*> user) {
|
bool ContactsBoxController::appendRow(not_null<UserData*> user) {
|
||||||
if (auto row = delegate()->peerListFindRow(user->id.value)) {
|
if (auto row = delegate()->peerListFindRow(user->id.value)) {
|
||||||
updateRowHook(row);
|
updateRowHook(row);
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/peer_list_box.h"
|
#include "boxes/peer_list_box.h"
|
||||||
#include "base/flat_set.h"
|
#include "base/flat_set.h"
|
||||||
#include "base/weak_ptr.h"
|
#include "base/weak_ptr.h"
|
||||||
|
#include "base/timer.h"
|
||||||
|
|
||||||
// Not used for now.
|
// Not used for now.
|
||||||
//
|
//
|
||||||
|
@ -136,6 +137,12 @@ public:
|
||||||
not_null<PeerData*> peer) override final;
|
not_null<PeerData*> peer) override final;
|
||||||
void rowClicked(not_null<PeerListRow*> row) override;
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
|
||||||
|
enum class SortMode {
|
||||||
|
Alphabet,
|
||||||
|
Online,
|
||||||
|
};
|
||||||
|
void setSortMode(SortMode mode);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user);
|
virtual std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user);
|
||||||
virtual void prepareViewHook() {
|
virtual void prepareViewHook() {
|
||||||
|
@ -144,11 +151,17 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void sort();
|
||||||
|
void sortByName();
|
||||||
|
void sortByOnline();
|
||||||
void rebuildRows();
|
void rebuildRows();
|
||||||
void checkForEmptyRows();
|
void checkForEmptyRows();
|
||||||
bool appendRow(not_null<UserData*> user);
|
bool appendRow(not_null<UserData*> user);
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
|
SortMode _sortMode = SortMode::Alphabet;
|
||||||
|
base::Timer _sortByOnlineTimer;
|
||||||
|
rpl::lifetime _sortByOnlineLifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|