mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Moved settings of blocked peers to section.
This commit is contained in:
parent
639ed8b973
commit
1349989494
15 changed files with 284 additions and 13 deletions
|
@ -1067,6 +1067,8 @@ PRIVATE
|
||||||
profile/profile_cover_drop_area.h
|
profile/profile_cover_drop_area.h
|
||||||
settings/settings_advanced.cpp
|
settings/settings_advanced.cpp
|
||||||
settings/settings_advanced.h
|
settings/settings_advanced.h
|
||||||
|
settings/settings_blocked_peers.cpp
|
||||||
|
settings/settings_blocked_peers.h
|
||||||
settings/settings_chat.cpp
|
settings/settings_chat.cpp
|
||||||
settings/settings_chat.h
|
settings/settings_chat.h
|
||||||
settings/settings_calls.cpp
|
settings/settings_calls.cpp
|
||||||
|
|
BIN
Telegram/Resources/animations/blocked_peers_empty.tgs
Normal file
BIN
Telegram/Resources/animations/blocked_peers_empty.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/icons/settings/blocked.png
Normal file
BIN
Telegram/Resources/icons/settings/blocked.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 476 B |
BIN
Telegram/Resources/icons/settings/blocked@2x.png
Normal file
BIN
Telegram/Resources/icons/settings/blocked@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 789 B |
BIN
Telegram/Resources/icons/settings/blocked@3x.png
Normal file
BIN
Telegram/Resources/icons/settings/blocked@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -799,6 +799,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_blocked_list_confirm_text" = "Do you want to block {name} from messaging and calling you on Telegram?";
|
"lng_blocked_list_confirm_text" = "Do you want to block {name} from messaging and calling you on Telegram?";
|
||||||
"lng_blocked_list_confirm_clear" = "Delete this chat";
|
"lng_blocked_list_confirm_clear" = "Delete this chat";
|
||||||
"lng_blocked_list_confirm_ok" = "Block";
|
"lng_blocked_list_confirm_ok" = "Block";
|
||||||
|
"lng_blocked_list_empty_title" = "No blocked users";
|
||||||
|
"lng_blocked_list_empty_description" = "You haven't blocked anyone yet.";
|
||||||
|
"lng_blocked_list_subtitle#one" = "{count} blocked user";
|
||||||
|
"lng_blocked_list_subtitle#other" = "{count} blocked users";
|
||||||
|
|
||||||
"lng_edit_privacy_everyone" = "Everybody";
|
"lng_edit_privacy_everyone" = "Everybody";
|
||||||
"lng_edit_privacy_contacts" = "My contacts";
|
"lng_edit_privacy_contacts" = "My contacts";
|
||||||
|
|
|
@ -2,4 +2,7 @@
|
||||||
<qresource prefix="/animations">
|
<qresource prefix="/animations">
|
||||||
<file alias="change_number.tgs">../../animations/change_number.tgs</file>
|
<file alias="change_number.tgs">../../animations/change_number.tgs</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
<qresource prefix="/animations">
|
||||||
|
<file alias="blocked_peers_empty.tgs">../../animations/blocked_peers_empty.tgs</file>
|
||||||
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -602,6 +602,9 @@ changePhoneError: FlatLabel(changePhoneLabel) {
|
||||||
textFg: boxTextFgError;
|
textFg: boxTextFgError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockedUsersListSubtitleAddPadding: margins(0px, 1px, 0px, -14px);
|
||||||
|
blockedUsersListIconPadding: margins(0px, 34px, 0px, 5px);
|
||||||
|
|
||||||
adminLogFilterUserpicLeft: 15px;
|
adminLogFilterUserpicLeft: 15px;
|
||||||
adminLogFilterLittleSkip: 16px;
|
adminLogFilterLittleSkip: 16px;
|
||||||
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {
|
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||||
|
|
|
@ -90,6 +90,7 @@ settingsIconPosition: icon {{ "settings/position", settingsIconFg }};
|
||||||
settingsIconPin: icon {{ "settings/pin", settingsIconFg }};
|
settingsIconPin: icon {{ "settings/pin", settingsIconFg }};
|
||||||
settingsIconDownload: icon {{ "settings/download", settingsIconFg }};
|
settingsIconDownload: icon {{ "settings/download", settingsIconFg }};
|
||||||
settingsIconMention: icon {{ "settings/mention", settingsIconFg }};
|
settingsIconMention: icon {{ "settings/mention", settingsIconFg }};
|
||||||
|
settingsIconBlocked: icon {{ "settings/blocked", settingsIconFg }};
|
||||||
|
|
||||||
settingsCheckbox: Checkbox(defaultBoxCheckbox) {
|
settingsCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||||
textPosition: point(15px, 1px);
|
textPosition: point(15px, 1px);
|
||||||
|
|
211
Telegram/SourceFiles/settings/settings_blocked_peers.cpp
Normal file
211
Telegram/SourceFiles/settings/settings_blocked_peers.cpp
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "settings/settings_blocked_peers.h"
|
||||||
|
|
||||||
|
#include "api/api_blocked_peers.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_peer.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "lottie/lottie_icon.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "settings/settings_privacy_controllers.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/wrap/padding_wrap.h"
|
||||||
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
|
#include "styles/style_settings.h"
|
||||||
|
#include "styles/style_boxes.h"
|
||||||
|
|
||||||
|
namespace Settings {
|
||||||
|
|
||||||
|
Blocked::Blocked(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Window::SessionController*> controller)
|
||||||
|
: Section(parent)
|
||||||
|
, _controller(controller) {
|
||||||
|
|
||||||
|
setupContent();
|
||||||
|
|
||||||
|
{
|
||||||
|
auto padding = st::changePhoneIconPadding;
|
||||||
|
padding.setBottom(padding.top());
|
||||||
|
_loading = base::make_unique_q<Ui::CenterWrap<>>(
|
||||||
|
this,
|
||||||
|
object_ptr<Ui::PaddingWrap<>>(
|
||||||
|
this,
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
this,
|
||||||
|
tr::lng_contacts_loading(),
|
||||||
|
st::changePhoneDescription),
|
||||||
|
std::move(padding)));
|
||||||
|
Ui::ResizeFitChild(this, _loading.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
_controller->session().api().blockedPeers().slice(
|
||||||
|
) | rpl::start_with_next([=](const Api::BlockedPeers::Slice &slice) {
|
||||||
|
checkTotal(slice.total);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_controller->session().changes().peerUpdates(
|
||||||
|
Data::PeerUpdate::Flag::IsBlocked
|
||||||
|
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||||
|
if (update.peer->isBlocked()) {
|
||||||
|
checkTotal(1);
|
||||||
|
}
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> Blocked::title() {
|
||||||
|
return tr::lng_settings_blocked_users();
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointer<Ui::RpWidget> Blocked::createPinnedToTop(not_null<QWidget*> parent) {
|
||||||
|
const auto content = Ui::CreateChild<Ui::VerticalLayout>(parent.get());
|
||||||
|
|
||||||
|
AddSkip(content);
|
||||||
|
|
||||||
|
AddButton(
|
||||||
|
content,
|
||||||
|
tr::lng_blocked_list_add(),
|
||||||
|
st::settingsButton,
|
||||||
|
{ &st::settingsIconBlocked, kIconLightBlue }
|
||||||
|
)->addClickHandler([=] {
|
||||||
|
BlockedBoxController::BlockNewPeer(_controller);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddSkip(content);
|
||||||
|
AddDividerText(content, tr::lng_blocked_list_about());
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto subtitle = content->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
content,
|
||||||
|
object_ptr<Ui::VerticalLayout>(content)))->setDuration(0);
|
||||||
|
AddSkip(subtitle->entity());
|
||||||
|
auto subtitleText = _countBlocked.value(
|
||||||
|
) | rpl::map([=](int count) {
|
||||||
|
return tr::lng_blocked_list_subtitle(tr::now, lt_count, count);
|
||||||
|
});
|
||||||
|
AddSubsectionTitle(
|
||||||
|
subtitle->entity(),
|
||||||
|
rpl::duplicate(subtitleText),
|
||||||
|
st::blockedUsersListSubtitleAddPadding);
|
||||||
|
subtitle->toggleOn(
|
||||||
|
rpl::merge(
|
||||||
|
_emptinessChanges.events() | rpl::map(!rpl::mappers::_1),
|
||||||
|
_countBlocked.value() | rpl::map(rpl::mappers::_1 > 0)
|
||||||
|
) | rpl::distinct_until_changed());
|
||||||
|
|
||||||
|
// Workaround.
|
||||||
|
std::move(
|
||||||
|
subtitleText
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
subtitle->entity()->resizeToWidth(content->width());
|
||||||
|
}, subtitle->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ui::MakeWeak(not_null<Ui::RpWidget*>{ content });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Blocked::setupContent() {
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
const auto container = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||||
|
|
||||||
|
const auto listWrap = container->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
container,
|
||||||
|
object_ptr<Ui::VerticalLayout>(container)));
|
||||||
|
listWrap->toggleOn(
|
||||||
|
_emptinessChanges.events_starting_with(true) | rpl::map(!_1),
|
||||||
|
anim::type::instant);
|
||||||
|
|
||||||
|
{
|
||||||
|
struct State {
|
||||||
|
std::unique_ptr<BlockedBoxController> controller;
|
||||||
|
std::unique_ptr<PeerListContentDelegateSimple> delegate;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto controller = std::make_unique<BlockedBoxController>(_controller);
|
||||||
|
const auto content = listWrap->entity()->add(
|
||||||
|
object_ptr<PeerListContent>(this, controller.get()));
|
||||||
|
|
||||||
|
const auto state = content->lifetime().make_state<State>();
|
||||||
|
state->controller = std::move(controller);
|
||||||
|
state->delegate = std::make_unique<PeerListContentDelegateSimple>();
|
||||||
|
|
||||||
|
state->delegate->setContent(content);
|
||||||
|
state->controller->setDelegate(state->delegate.get());
|
||||||
|
|
||||||
|
state->controller->rowsCountChanges(
|
||||||
|
) | rpl::start_with_next([=](int total) {
|
||||||
|
_countBlocked = total;
|
||||||
|
checkTotal(total);
|
||||||
|
}, content->lifetime());
|
||||||
|
_countBlocked = content->fullRowsCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto emptyWrap = container->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
container,
|
||||||
|
object_ptr<Ui::VerticalLayout>(container)));
|
||||||
|
emptyWrap->toggleOn(
|
||||||
|
_emptinessChanges.events_starting_with(false),
|
||||||
|
anim::type::instant);
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto content = emptyWrap->entity();
|
||||||
|
auto icon = CreateLottieIcon(content, {
|
||||||
|
.name = u"blocked_peers_empty"_q,
|
||||||
|
.sizeOverride = {
|
||||||
|
st::changePhoneIconSize,
|
||||||
|
st::changePhoneIconSize,
|
||||||
|
},
|
||||||
|
}, st::blockedUsersListIconPadding);
|
||||||
|
content->add(std::move(icon.widget));
|
||||||
|
|
||||||
|
_showFinished.events(
|
||||||
|
) | rpl::start_with_next([animate = std::move(icon.animate)] {
|
||||||
|
animate();
|
||||||
|
}, content->lifetime());
|
||||||
|
|
||||||
|
content->add(
|
||||||
|
object_ptr<Ui::CenterWrap<>>(
|
||||||
|
content,
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
content,
|
||||||
|
tr::lng_blocked_list_empty_title(),
|
||||||
|
st::changePhoneTitle)),
|
||||||
|
st::changePhoneTitlePadding);
|
||||||
|
|
||||||
|
content->add(
|
||||||
|
object_ptr<Ui::CenterWrap<>>(
|
||||||
|
content,
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
content,
|
||||||
|
tr::lng_blocked_list_empty_description(),
|
||||||
|
st::changePhoneDescription)),
|
||||||
|
st::changePhoneDescriptionPadding);
|
||||||
|
|
||||||
|
AddSkip(content, st::blockedUsersListIconPadding.top());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ui::ResizeFitChild(this, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Blocked::checkTotal(int total) {
|
||||||
|
_loading = nullptr;
|
||||||
|
_emptinessChanges.fire(total <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Blocked::showFinished() {
|
||||||
|
_showFinished.fire({});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Settings
|
46
Telegram/SourceFiles/settings/settings_blocked_peers.h
Normal file
46
Telegram/SourceFiles/settings/settings_blocked_peers.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "settings/settings_common.h"
|
||||||
|
|
||||||
|
namespace Window {
|
||||||
|
class Controller;
|
||||||
|
} // namespace Window
|
||||||
|
|
||||||
|
namespace Settings {
|
||||||
|
|
||||||
|
class Blocked : public Section<Blocked> {
|
||||||
|
public:
|
||||||
|
Blocked(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
|
void showFinished() override;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<QString> title() override;
|
||||||
|
|
||||||
|
[[nodiscard]] QPointer<Ui::RpWidget> createPinnedToTop(
|
||||||
|
not_null<QWidget*> parent) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupContent();
|
||||||
|
void checkTotal(int total);
|
||||||
|
|
||||||
|
const not_null<Window::SessionController*> _controller;
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::RpWidget> _loading;
|
||||||
|
|
||||||
|
rpl::variable<int> _countBlocked;
|
||||||
|
|
||||||
|
rpl::event_stream<> _showFinished;
|
||||||
|
rpl::event_stream<bool> _emptinessChanges;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Settings
|
|
@ -264,7 +264,7 @@ LottieIcon CreateLottieIcon(
|
||||||
raw->lifetime().add([kept = std::move(owned)]{});
|
raw->lifetime().add([kept = std::move(owned)]{});
|
||||||
|
|
||||||
const auto animate = [=] {
|
const auto animate = [=] {
|
||||||
icon->animate([=] { raw->update(); }, 0, icon->framesCount());
|
icon->animate([=] { raw->update(); }, 0, icon->framesCount() - 1);
|
||||||
};
|
};
|
||||||
raw->paintRequest(
|
raw->paintRequest(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
|
|
|
@ -383,6 +383,7 @@ void BlockedBoxController::handleBlockedEvent(not_null<PeerData*> user) {
|
||||||
} else if (auto row = delegate()->peerListFindRow(user->id.value)) {
|
} else if (auto row = delegate()->peerListFindRow(user->id.value)) {
|
||||||
delegate()->peerListRemoveRow(row);
|
delegate()->peerListRemoveRow(row);
|
||||||
delegate()->peerListRefreshRows();
|
delegate()->peerListRefreshRows();
|
||||||
|
_rowsCountChanges.fire(delegate()->peerListFullRowsCount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,6 +409,7 @@ bool BlockedBoxController::appendRow(not_null<PeerData*> peer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delegate()->peerListAppendRow(createRow(peer));
|
delegate()->peerListAppendRow(createRow(peer));
|
||||||
|
_rowsCountChanges.fire(delegate()->peerListFullRowsCount());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,6 +418,7 @@ bool BlockedBoxController::prependRow(not_null<PeerData*> peer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delegate()->peerListPrependRow(createRow(peer));
|
delegate()->peerListPrependRow(createRow(peer));
|
||||||
|
_rowsCountChanges.fire(delegate()->peerListFullRowsCount());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,6 +443,10 @@ std::unique_ptr<PeerListRow> BlockedBoxController::createRow(
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> BlockedBoxController::rowsCountChanges() const {
|
||||||
|
return _rowsCountChanges.events();
|
||||||
|
}
|
||||||
|
|
||||||
PhoneNumberPrivacyController::PhoneNumberPrivacyController(
|
PhoneNumberPrivacyController::PhoneNumberPrivacyController(
|
||||||
not_null<Window::SessionController*> controller)
|
not_null<Window::SessionController*> controller)
|
||||||
: _controller(controller) {
|
: _controller(controller) {
|
||||||
|
|
|
@ -33,6 +33,8 @@ public:
|
||||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||||
void loadMoreRows() override;
|
void loadMoreRows() override;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<int> rowsCountChanges() const;
|
||||||
|
|
||||||
static void BlockNewPeer(not_null<Window::SessionController*> window);
|
static void BlockNewPeer(not_null<Window::SessionController*> window);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -50,6 +52,8 @@ private:
|
||||||
|
|
||||||
base::has_weak_ptr _guard;
|
base::has_weak_ptr _guard;
|
||||||
|
|
||||||
|
rpl::event_stream<int> _rowsCountChanges;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PhoneNumberPrivacyController : public EditPrivacyController {
|
class PhoneNumberPrivacyController : public EditPrivacyController {
|
||||||
|
|
|
@ -13,11 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_self_destruct.h"
|
#include "api/api_self_destruct.h"
|
||||||
#include "api/api_sensitive_content.h"
|
#include "api/api_sensitive_content.h"
|
||||||
#include "api/api_global_privacy.h"
|
#include "api/api_global_privacy.h"
|
||||||
|
#include "settings/settings_blocked_peers.h"
|
||||||
#include "settings/settings_common.h"
|
#include "settings/settings_common.h"
|
||||||
#include "settings/settings_privacy_controllers.h"
|
#include "settings/settings_privacy_controllers.h"
|
||||||
#include "base/timer_rpl.h"
|
#include "base/timer_rpl.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "boxes/peer_list_box.h"
|
|
||||||
#include "boxes/edit_privacy_box.h"
|
#include "boxes/edit_privacy_box.h"
|
||||||
#include "boxes/passcode_box.h"
|
#include "boxes/passcode_box.h"
|
||||||
#include "boxes/auto_lock_box.h"
|
#include "boxes/auto_lock_box.h"
|
||||||
|
@ -772,17 +772,7 @@ void SetupBlockedList(
|
||||||
st::settingsButton,
|
st::settingsButton,
|
||||||
{ &st::settingsIconMinus, kIconRed });
|
{ &st::settingsIconMinus, kIconRed });
|
||||||
blockedPeers->addClickHandler([=] {
|
blockedPeers->addClickHandler([=] {
|
||||||
const auto initBox = [=](not_null<PeerListBox*> box) {
|
showOther(Blocked::Id());
|
||||||
box->addButton(tr::lng_close(), [=] {
|
|
||||||
box->closeBox();
|
|
||||||
});
|
|
||||||
box->addLeftButton(tr::lng_blocked_list_add(), [=] {
|
|
||||||
BlockedBoxController::BlockNewPeer(controller);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
controller->show(Box<PeerListBox>(
|
|
||||||
std::make_unique<BlockedBoxController>(controller),
|
|
||||||
initBox));
|
|
||||||
});
|
});
|
||||||
std::move(
|
std::move(
|
||||||
updateTrigger
|
updateTrigger
|
||||||
|
|
Loading…
Add table
Reference in a new issue