Allow delete / forward selected in shared media.

Also use PeerListBox with a chats list with global search controller
instead of HistoryHider for forward / share contact.
This commit is contained in:
John Preston 2017-10-20 19:19:42 +03:00
parent 7b69282c7e
commit be5f4c9a71
24 changed files with 508 additions and 47 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

View File

@ -100,6 +100,7 @@ void PeerListBox::prepare() {
setDimensions(st::boxWideWidth, st::boxMaxListHeight);
if (_select) {
_select->finishAnimating();
myEnsureResized(_select);
_scrollBottomFixed = true;
onScrollToY(0);
}

View File

@ -80,12 +80,31 @@ infoTopBarTitle: FlatLabel(defaultFlatLabel) {
linkFontOver: font(14px semibold);
}
}
infoTopBarClose: IconButton(infoTopBarBack) {
icon: icon {{ "info_close", boxTitleCloseFg }};
iconOver: icon {{ "info_close", boxTitleCloseFgOver }};
}
infoTopBarForward: IconButton(infoTopBarBack) {
width: 46px;
icon: icon {{ "info_media_forward", boxTitleCloseFg }};
iconOver: icon {{ "info_media_forward", boxTitleCloseFgOver }};
iconPosition: point(6px, -1px);
rippleAreaPosition: point(1px, 6px);
}
infoTopBarDelete: IconButton(infoTopBarForward) {
icon: icon {{ "info_media_delete", boxTitleCloseFg }};
iconOver: icon {{ "info_media_delete", boxTitleCloseFgOver }};
}
infoTopBar: InfoTopBar {
height: infoTopBarHeight;
back: infoTopBarBack;
title: infoTopBarTitle;
titlePosition: point(23px, 18px);
bg: windowBg;
mediaCancel: infoTopBarClose;
mediaActionsSkip: 4px;
mediaForward: infoTopBarForward;
mediaDelete: infoTopBarDelete;
}
infoLayerTopBarHeight: boxLayerTitleHeight;
@ -107,12 +126,27 @@ infoLayerTopBarClose: IconButton(infoLayerTopBarBack) {
icon: infoLayerTopBarCloseIcon;
iconOver: infoLayerTopBarCloseIconOver;
}
infoLayerTopBarForward: IconButton(infoLayerTopBarBack) {
width: 45px;
icon: icon {{ "info_media_forward", boxTitleCloseFg }};
iconOver: icon {{ "info_media_forward", boxTitleCloseFgOver }};
iconPosition: point(6px, -1px);
rippleAreaPosition: point(1px, 6px);
}
infoLayerTopBarDelete: IconButton(infoLayerTopBarForward) {
icon: icon {{ "info_media_delete", boxTitleCloseFg }};
iconOver: icon {{ "info_media_delete", boxTitleCloseFgOver }};
}
infoLayerTopBar: InfoTopBar {
height: infoLayerTopBarHeight;
back: infoLayerTopBarBack;
title: boxTitle;
titlePosition: boxLayerTitlePosition;
bg: boxBg;
mediaCancel: infoLayerTopBarClose;
mediaActionsSkip: 6px;
mediaForward: infoLayerTopBarForward;
mediaDelete: infoLayerTopBarDelete;
}
infoMinimalWidth: 324px;

View File

@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include <rpl/never.h>
#include <rpl/combine.h>
#include <rpl/range.h>
#include "window/window_controller.h"
#include "ui/widgets/scroll_area.h"
#include "lang/lang_keys.h"
@ -156,4 +157,8 @@ QRect ContentWidget::rectForFloatPlayer() const {
return mapToGlobal(_scroll->geometry());
}
rpl::producer<SelectedItems> ContentWidget::selectedListValue() const {
return rpl::single(SelectedItems(Storage::SharedMediaType::Photo));
}
} // namespace Info

View File

@ -79,6 +79,10 @@ public:
bool wheelEventFromFloatPlayer(QEvent *e);
QRect rectForFloatPlayer() const;
virtual rpl::producer<SelectedItems> selectedListValue() const;
virtual void cancelSelection() {
}
protected:
template <typename Widget>
Widget *setInnerWidget(

View File

@ -0,0 +1,176 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "info/info_top_bar_override.h"
#include <rpl/merge.h>
#include "styles/style_info.h"
#include "lang/lang_keys.h"
#include "info/info_wrap_widget.h"
#include "storage/storage_shared_media.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/widgets/shadow.h"
#include "mainwidget.h"
#include "boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
namespace Info {
ChooseRecipientBoxController::ChooseRecipientBoxController(
base::lambda<void(not_null<PeerData*>)> callback)
: _callback(std::move(callback)) {
}
void ChooseRecipientBoxController::prepareViewHook() {
delegate()->peerListSetTitle(langFactory(lng_forward_choose));
}
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
_callback(row->peer());
}
auto ChooseRecipientBoxController::createRow(
not_null<History*> history) -> std::unique_ptr<Row> {
return std::make_unique<Row>(history);
}
TopBarOverride::TopBarOverride(
QWidget *parent,
const style::InfoTopBar &st,
SelectedItems &&items)
: RpWidget(parent)
, _st(st)
, _items(std::move(items))
, _canDelete(computeCanDelete())
, _cancel(this, _st.mediaCancel)
, _text(this, generateText(), Ui::FlatLabel::InitType::Simple, _st.title)
, _forward(this, _st.mediaForward)
, _delete(this, _st.mediaDelete) {
setAttribute(Qt::WA_OpaquePaintEvent);
updateControlsVisibility();
_forward->addClickHandler([this] { performForward(); });
_delete->addClickHandler([this] { performDelete(); });
}
QString TopBarOverride::generateText() const {
using Type = Storage::SharedMediaType;
auto phrase = [&] {
switch (_items.type) {
case Type::Photo: return lng_profile_photos;
case Type::Video: return lng_profile_videos;
case Type::File: return lng_profile_files;
case Type::MusicFile: return lng_profile_songs;
case Type::Link: return lng_profile_shared_links;
case Type::VoiceFile: return lng_profile_audios;
case Type::RoundFile: return lng_profile_rounds;
}
Unexpected("Type in TopBarOverride::generateText()");
}();
return phrase(lt_count, _items.list.size());
}
bool TopBarOverride::computeCanDelete() const {
return base::find_if(_items.list, [](const SelectedItem &item) {
return !item.canDelete;
}) == _items.list.end();
}
void TopBarOverride::setItems(SelectedItems &&items) {
_items = std::move(items);
_canDelete = computeCanDelete();
_text->setText(generateText());
updateControlsVisibility();
updateControlsGeometry(width());
}
rpl::producer<> TopBarOverride::cancelRequests() const {
return rpl::merge(
_cancel->clicks(),
_correctionCancelRequests.events());
}
int TopBarOverride::resizeGetHeight(int newWidth) {
updateControlsGeometry(newWidth);
return _st.height;
}
void TopBarOverride::updateControlsGeometry(int newWidth) {
auto right = _st.mediaActionsSkip;
if (_canDelete) {
_delete->moveToRight(right, 0, newWidth);
right += _delete->width();
}
_forward->moveToRight(right, 0, newWidth);
_cancel->moveToLeft(0, 0);
_text->moveToLeft(_cancel->width(), _st.titlePosition.y());
}
void TopBarOverride::updateControlsVisibility() {
_delete->setVisible(_canDelete);
}
void TopBarOverride::paintEvent(QPaintEvent *e) {
Painter p(this);
p.fillRect(e->rect(), _st.bg);
}
SelectedItemSet TopBarOverride::collectItems() const {
auto result = SelectedItemSet();
for (auto value : _items.list) {
if (auto item = App::histItemById(value.msgId)) {
result.insert(result.size(), item);
}
}
return result;
}
void TopBarOverride::performForward() {
auto items = collectItems();
if (items.empty()) {
_correctionCancelRequests.fire({});
return;
}
auto callback = [items = std::move(items)](not_null<PeerData*> peer) {
App::main()->setForwardDraft(peer->id, items);
};
Ui::show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(std::move(callback)),
[](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_cancel), [box] {
box->closeBox();
});
}));
}
void TopBarOverride::performDelete() {
auto items = collectItems();
if (items.empty()) {
_correctionCancelRequests.fire({});
} else {
Ui::show(Box<DeleteMessagesBox>(items));
}
}
} // namespace Info

View File

@ -0,0 +1,91 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/rp_widget.h"
#include "info/info_wrap_widget.h"
#include "boxes/peer_list_controllers.h"
namespace style {
struct InfoTopBar;
} // namespace style
namespace Ui {
class IconButton;
class FlatLabel;
} // namespace Ui
namespace Info {
class ChooseRecipientBoxController : public ChatsListBoxController {
public:
ChooseRecipientBoxController(
base::lambda<void(not_null<PeerData*>)> callback);
void rowClicked(not_null<PeerListRow*> row) override;
protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(
not_null<History*> history) override;
base::lambda<void(not_null<PeerData*>)> _callback;
};
class TopBarOverride : public Ui::RpWidget {
public:
TopBarOverride(
QWidget *parent,
const style::InfoTopBar &st,
SelectedItems &&items);
void setItems(SelectedItems &&items);
rpl::producer<> cancelRequests() const;
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
private:
void updateControlsVisibility();
void updateControlsGeometry(int newWidth);
QString generateText() const;
[[nodiscard]] bool computeCanDelete() const;
[[nodiscard]] SelectedItemSet collectItems() const;
void performForward();
void performDelete();
const style::InfoTopBar &_st;
SelectedItems _items;
bool _canDelete = false;
object_ptr<Ui::IconButton> _cancel;
object_ptr<Ui::FlatLabel> _text;
object_ptr<Ui::IconButton> _forward;
object_ptr<Ui::IconButton> _delete;
rpl::event_stream<> _correctionCancelRequests;
};
} // namespace Info

View File

@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "info/info_content_widget.h"
#include "info/info_memento.h"
#include "info/info_top_bar.h"
#include "info/info_top_bar_override.h"
#include "ui/widgets/discrete_sliders.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
@ -39,6 +40,15 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_profile.h"
namespace Info {
namespace {
const style::InfoTopBar &TopBarStyle(Wrap wrap) {
return (wrap == Wrap::Layer)
? st::infoLayerTopBar
: st::infoTopBar;
}
} // namespace
struct WrapWidget::StackItem {
std::unique_ptr<ContentMemento> section;
@ -54,6 +64,12 @@ WrapWidget::WrapWidget(
, _wrap(wrap)
, _topShadow(this) {
_topShadow->toggleOn(topShadowToggledValue());
selectedListValue()
| rpl::start_with_next([this](SelectedItems &&items) {
InvokeQueued(this, [this, items = std::move(items)]() mutable {
refreshTopBarOverride(std::move(items));
});
}, lifetime());
showNewContent(memento->content());
}
@ -182,11 +198,7 @@ void WrapWidget::setupTop(
void WrapWidget::createTopBar(
const Section &section,
PeerId peerId) {
_topBar.create(
this,
(wrap() == Wrap::Layer)
? st::infoLayerTopBar
: st::infoTopBar);
_topBar.create(this, TopBarStyle(wrap()));
_topBar->setTitle(TitleValue(
section,
@ -212,6 +224,52 @@ void WrapWidget::createTopBar(
_topBar->show();
}
void WrapWidget::refreshTopBarOverride(SelectedItems &&items) {
if (items.list.empty()) {
destroyTopBarOverride();
} else if (_topBarOverride) {
_topBarOverride->setItems(std::move(items));
} else {
createTopBarOverride(std::move(items));
}
}
void WrapWidget::destroyTopBarOverride() {
if (!_topBarOverride) {
return;
}
auto widget = std::exchange(_topBarOverride, nullptr);
auto handle = weak(widget.data());
_topBarOverrideAnimation.start([this, handle] {
}, 1., 0., st::slideWrapDuration);
widget.destroy();
if (_topTabs) {
_topTabs->show();
} else if (_topBar) {
_topBar->show();
}
}
void WrapWidget::createTopBarOverride(SelectedItems &&items) {
Expects(_topBarOverride == nullptr);
_topBarOverride.create(
this,
TopBarStyle(wrap()),
std::move(items));
if (_topTabs) {
_topTabs->hide();
} else if (_topBar) {
_topBar->hide();
}
_topBarOverride->cancelRequests()
| rpl::start_with_next([this](auto) {
_content->cancelSelection();
}, _topBarOverride->lifetime());
_topBarOverride->moveToLeft(0, 0);
_topBarOverride->resizeToWidth(width());
_topBarOverride->show();
}
void WrapWidget::showBackFromStack() {
auto params = Window::SectionShow(
Window::SectionShow::Way::Backward);
@ -245,6 +303,7 @@ void WrapWidget::finishShowContent() {
updateContentGeometry();
_desiredHeights.fire(desiredHeightForContent());
_desiredShadowVisibilities.fire(_content->desiredShadowVisibility());
_selectedLists.fire(_content->selectedListValue());
_topShadow->raise();
_topShadow->finishAnimating();
if (_topTabs) {
@ -268,6 +327,10 @@ rpl::producer<int> WrapWidget::desiredHeightForContent() const {
$1 + $2);
}
rpl::producer<SelectedItems> WrapWidget::selectedListValue() const {
return _selectedLists.events() | rpl::flatten_latest();
}
object_ptr<ContentWidget> WrapWidget::createContent(Tab tab) {
switch (tab) {
case Tab::Profile: return createProfileWidget();
@ -450,6 +513,9 @@ void WrapWidget::resizeEvent(QResizeEvent *e) {
} else if (_topBar) {
_topBar->resizeToWidth(width());
}
if (_topBarOverride) {
_topBarOverride->resizeToWidth(width());
}
updateContentGeometry();
}

View File

@ -49,6 +49,7 @@ class MoveMemento;
class ContentMemento;
class ContentWidget;
class TopBar;
class TopBarOverride;
enum class Wrap {
Layer,
@ -87,6 +88,25 @@ private:
};
struct SelectedItem {
explicit SelectedItem(FullMsgId msgId) : msgId(msgId) {
}
FullMsgId msgId;
bool canDelete = false;
bool canForward = false;
};
struct SelectedItems {
explicit SelectedItems(Storage::SharedMediaType type)
: type(type) {
}
Storage::SharedMediaType type;
std::vector<SelectedItem> list;
};
class WrapWidget final : public Window::SectionWidget {
public:
WrapWidget(
@ -171,11 +191,18 @@ private:
object_ptr<ContentWidget> createContent(
not_null<ContentMemento*> memento);
rpl::producer<SelectedItems> selectedListValue() const;
void refreshTopBarOverride(SelectedItems &&items);
void createTopBarOverride(SelectedItems &&items);
void destroyTopBarOverride();
rpl::variable<Wrap> _wrap;
object_ptr<ContentWidget> _content = { nullptr };
object_ptr<Ui::PlainShadow> _topTabsBackground = { nullptr };
object_ptr<Ui::SettingsSlider> _topTabs = { nullptr };
object_ptr<TopBar> _topBar = { nullptr };
object_ptr<TopBarOverride> _topBarOverride = { nullptr };
Animation _topBarOverrideAnimation;
object_ptr<Ui::FadeShadow> _topShadow;
Tab _tab = Tab::Profile;
std::unique_ptr<ContentMemento> _anotherTabMemento;
@ -183,6 +210,7 @@ private:
rpl::event_stream<rpl::producer<int>> _desiredHeights;
rpl::event_stream<rpl::producer<bool>> _desiredShadowVisibilities;
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
};

View File

@ -47,7 +47,7 @@ inline auto MediaTextPhrase(Type type) {
case Type::VoiceFile: return lng_profile_audios;
case Type::RoundFile: return lng_profile_rounds;
}
Unexpected("Type in setupSharedMedia()");
Unexpected("Type in MediaTextPhrase()");
};
inline auto MediaText(Type type) {

View File

@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "info/media/info_media_inner_widget.h"
#include <rpl/flatten_latest.h>
#include "boxes/abstract_box.h"
#include "info/media/info_media_list_widget.h"
#include "info/media/info_media_buttons.h"
@ -246,6 +247,7 @@ object_ptr<ListWidget> InnerWidget::setupList(
| rpl::start_to_stream(
_scrollToRequests,
result->lifetime());
_selectedLists.fire(result->selectedListValue());
return result;
}
@ -255,6 +257,15 @@ void InnerWidget::saveState(not_null<Memento*> memento) {
void InnerWidget::restoreState(not_null<Memento*> memento) {
}
rpl::producer<SelectedItems> InnerWidget::selectedListValue() const {
return _selectedLists.events_starting_with(_list->selectedListValue())
| rpl::flatten_latest();
}
void InnerWidget::cancelSelection() {
_list->cancelSelection();
}
int InnerWidget::resizeGetHeight(int newWidth) {
_inResize = true;
auto guard = gsl::finally([this] { _inResize = false; });

View File

@ -55,6 +55,8 @@ public:
rpl::producer<int> scrollToRequests() const {
return _scrollToRequests.events();
}
rpl::producer<SelectedItems> selectedListValue() const;
void cancelSelection();
protected:
int resizeGetHeight(int newWidth) override;
@ -86,6 +88,7 @@ private:
object_ptr<ListWidget> _list = { nullptr };
rpl::event_stream<int> _scrollToRequests;
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
};

View File

@ -159,21 +159,14 @@ bool ListWidget::IsAfter(
bool ListWidget::SkipSelectFromItem(const CursorState &state) {
if (state.cursor.y() >= state.size.height()
&& state.cursor.x() >= 0) {
return true;
} else if (state.cursor.x() >= state.size.width()
&& state.cursor.y() >= 0) {
|| state.cursor.x() >= state.size.width()) {
return true;
}
return false;
}
bool ListWidget::SkipSelectTillItem(const CursorState &state) {
if (state.cursor.y() < state.size.height()
&& state.cursor.x() < 0) {
return true;
} else if (state.cursor.x() < state.size.width()
&& state.cursor.y() < 0) {
if (state.cursor.x() < 0 || state.cursor.y() < 0) {
return true;
}
return false;
@ -600,7 +593,6 @@ void ListWidget::itemRemoved(not_null<const HistoryItem*> item) {
auto i = _selected.find(universalId);
if (i != _selected.cend()) {
removeItemSelection(i);
pushSelectedItems();
}
mouseActionUpdate(_mousePosition);
@ -630,18 +622,20 @@ auto ListWidget::collectSelectedItems() const -> SelectedItems {
auto transformation = [&](const auto &item) {
return convert(item.first, item.second);
};
auto items = SelectedItems();
items.reserve(_selected.size());
std::transform(
_selected.begin(),
_selected.end(),
std::back_inserter(items),
transformation);
auto items = SelectedItems(_type);
if (hasSelectedItems()) {
items.list.reserve(_selected.size());
std::transform(
_selected.begin(),
_selected.end(),
std::back_inserter(items.list),
transformation);
}
return items;
}
void ListWidget::pushSelectedItems() {
_selectedItemsStream.fire(collectSelectedItems());
_selectedListStream.fire(collectSelectedItems());
}
bool ListWidget::hasSelected() const {
@ -661,6 +655,7 @@ void ListWidget::removeItemSelection(
if (_selected.empty()) {
update();
}
pushSelectedItems();
}
bool ListWidget::hasSelectedText() const {
@ -854,8 +849,8 @@ void ListWidget::refreshRows() {
clearStaleLayouts();
resizeToWidth(width());
restoreScrollState();
mouseActionUpdate();
}
void ListWidget::markLayoutsStale() {
@ -1025,7 +1020,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
fromSectionIt,
clip.y() + clip.height());
auto context = Context {
Layout::PaintContext(ms, !_selected.empty()),
Layout::PaintContext(ms, hasSelectedItems()),
&_selected,
&_dragSelected,
_dragSelectAction
@ -1113,6 +1108,7 @@ void ListWidget::applyItemSelection(
universalId,
selection)) {
repaintItem(universalId);
pushSelectedItems();
}
}
@ -1203,10 +1199,12 @@ void ListWidget::clearSelected() {
}
if (hasSelectedText()) {
repaintItem(_selected.begin()->first);
_selected.clear();
} else {
_selected.clear();
pushSelectedItems();
update();
}
_selected.clear();
}
void ListWidget::validateTrippleClickStartTime() {
@ -1246,7 +1244,7 @@ QPoint ListWidget::clampMousePosition(QPoint position) const {
}
void ListWidget::mouseActionUpdate(const QPoint &screenPos) {
if (_sections.empty()) {
if (_sections.empty() || _visibleBottom <= _visibleTop) {
return;
}
@ -1657,6 +1655,7 @@ void ListWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton butt
void ListWidget::applyDragSelection() {
applyDragSelection(_selected);
clearDragSelection();
pushSelectedItems();
}
void ListWidget::applyDragSelection(SelectedMap &applyTo) const {
@ -1693,6 +1692,9 @@ void ListWidget::mouseActionUpdate() {
void ListWidget::clearStaleLayouts() {
for (auto i = _layouts.begin(); i != _layouts.end();) {
if (i->second.stale) {
if (i->second.item.get() == _overLayout) {
_overLayout = nullptr;
}
i = _layouts.erase(i);
} else {
++i;

View File

@ -66,17 +66,12 @@ public:
rpl::producer<int> scrollToRequests() const {
return _scrollToRequests.events();
}
struct SelectedItem {
explicit SelectedItem(FullMsgId msgId) : msgId(msgId) {
}
FullMsgId msgId;
bool canDelete = false;
bool canForward = false;
};
using SelectedItems = std::vector<SelectedItem>;
rpl::producer<SelectedItems> selectedItemsValue() const {
return _selectedItemsStream.events();
rpl::producer<SelectedItems> selectedListValue() const {
return _selectedListStream.events_starting_with(
collectSelectedItems());
}
void cancelSelection() {
clearSelected();
}
~ListWidget();
@ -246,10 +241,6 @@ private:
void updateDragSelection();
void clearDragSelection();
void setDragSelection(
BaseLayout *dragSelectFrom,
BaseLayout *dragSelectTill,
DragSelectAction action);
void trySwitchToWordSelection();
void switchToWordSelection();
@ -285,7 +276,7 @@ private:
bool _pressWasInactive = false;
SelectedMap _selected;
SelectedMap _dragSelected;
rpl::event_stream<SelectedItems> _selectedItemsStream;
rpl::event_stream<SelectedItems> _selectedListStream;
style::cursor _cursor = style::cur_default;
DragSelectAction _dragSelectAction = DragSelectAction::None;
bool _wasSelectedText = false; // was some text selected in current drag action

View File

@ -60,6 +60,14 @@ Widget::Widget(
}, _inner->lifetime());
}
rpl::producer<SelectedItems> Widget::selectedListValue() const {
return _inner->selectedListValue();
}
void Widget::cancelSelection() {
_inner->cancelSelection();
}
Section Widget::section() const {
return Section(type());
}

View File

@ -79,10 +79,13 @@ public:
const QRect &geometry,
not_null<Memento*> memento);
rpl::producer<SelectedItems> selectedListValue() const override;
void cancelSelection() override;
private:
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);
InnerWidget *_inner = nullptr;
};

View File

@ -31,6 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "info/profile/info_profile_icon.h"
#include "info/profile/info_profile_members.h"
#include "info/media/info_media_buttons.h"
#include "info/info_top_bar_override.h"
#include "boxes/abstract_box.h"
#include "boxes/add_contact_box.h"
#include "boxes/confirm_box.h"
@ -351,7 +352,7 @@ object_ptr<Ui::RpWidget> InnerWidget::setupUserActions(
addButton(
Lang::Viewer(lng_profile_share_contact),
CanShareContactValue(user),
[user] { App::main()->shareContactLayer(user); });
[this, user] { shareContact(user); });
addButton(
Lang::Viewer(lng_info_edit_contact),
IsContactValue(user),
@ -422,6 +423,36 @@ object_ptr<Ui::RpWidget> InnerWidget::setupUserActions(
return std::move(result);
}
void InnerWidget::shareContact(not_null<UserData*> user) const {
auto callback = [user](not_null<PeerData*> peer) {
if (!peer->canWrite()) {
Ui::show(Box<InformBox>(
lang(lng_forward_share_cant)),
LayerOption::KeepOther);
return;
}
auto recipient = peer->isUser()
? peer->name
: '\xAB' + peer->name + '\xBB';
Ui::show(Box<ConfirmBox>(
lng_forward_share_contact(lt_recipient, recipient),
lang(lng_forward_send),
[peer, user] {
App::main()->onShareContact(
peer->id,
user);
Ui::hideLayer();
}), LayerOption::KeepOther);
};
Ui::show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(std::move(callback)),
[](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_cancel), [box] {
box->closeBox();
});
}));
}
object_ptr<Ui::RpWidget> InnerWidget::createSkipWidget(
RpWidget *parent) const {
return Ui::CreateSkipWidget(parent, st::infoProfileSkip);

View File

@ -88,6 +88,7 @@ private:
object_ptr<RpWidget> setupUserActions(
RpWidget *parent,
not_null<UserData*> user) const;
void shareContact(not_null<UserData*> user) const;
object_ptr<RpWidget> createSkipWidget(RpWidget *parent) const;
object_ptr<Ui::SlideWrap<RpWidget>> createSlideSkipWidget(

View File

@ -677,7 +677,7 @@ bool MainWidget::setForwardDraft(PeerId peerId, const SelectedItemSet &items) {
auto peer = App::peer(peerId);
auto error = GetErrorTextForForward(peer, items);
if (!error.isEmpty()) {
Ui::show(Box<InformBox>(error));
Ui::show(Box<InformBox>(error), LayerOption::KeepOther);
return false;
}

View File

@ -1117,4 +1117,8 @@ InfoTopBar {
title: FlatLabel;
titlePosition: point;
bg: color;
mediaCancel: IconButton;
mediaActionsSkip: pixels;
mediaForward: IconButton;
mediaDelete: IconButton;
}

View File

@ -219,6 +219,8 @@
<(src_loc)/info/info_section_widget.h
<(src_loc)/info/info_top_bar.cpp
<(src_loc)/info/info_top_bar.h
<(src_loc)/info/info_top_bar_override.cpp
<(src_loc)/info/info_top_bar_override.h
<(src_loc)/info/info_wrap_widget.cpp
<(src_loc)/info/info_wrap_widget.h
<(src_loc)/info/media/info_media_buttons.h