diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 24f2743b8..07cb596cb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -761,6 +761,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_action_took_screenshot" = "{from} took a screenshot!"; "lng_action_you_took_screenshot" = "You took a screenshot!"; +"lng_ttl_photo_received" = "{from} sent you a self-destructing photo. Please view it on your mobile."; +"lng_ttl_photo_sent" = "You sent a self-destructing photo."; +"lng_ttl_photo_expired" = "Photo has expired"; +"lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile."; +"lng_ttl_video_sent" = "You sent a self-destructing video."; +"lng_ttl_video_expired" = "Video has expired"; + "lng_profile_migrate_reached#one" = "{count} member limit reached"; "lng_profile_migrate_reached#other" = "{count} members limit reached"; "lng_profile_migrate_body" = "To get over this limit, you can upgrade your group to a supergroup."; diff --git a/Telegram/SourceFiles/base/runtime_composer.h b/Telegram/SourceFiles/base/runtime_composer.h index febf011ad..9da21e4e4 100644 --- a/Telegram/SourceFiles/base/runtime_composer.h +++ b/Telegram/SourceFiles/base/runtime_composer.h @@ -26,8 +26,8 @@ typedef void(*RuntimeComponentDestruct)(void *location); typedef void(*RuntimeComponentMove)(void *location, void *waslocation); struct RuntimeComponentWrapStruct { - // don't init any fields, because it is only created in - // global scope, so it will be filled by zeros from the start + // Don't init any fields, because it is only created in + // global scope, so it will be filled by zeros from the start. RuntimeComponentWrapStruct() = default; RuntimeComponentWrapStruct(std::size_t size, std::size_t align, RuntimeComponentConstruct construct, RuntimeComponentDestruct destruct, RuntimeComponentMove move) : Size(size) @@ -54,7 +54,8 @@ extern QAtomicInt RuntimeComponentIndexLast; template struct RuntimeComponent { RuntimeComponent() { - static_assert(alignof(Type) <= alignof(SmallestSizeType), "Components should align to a pointer!"); + // While there is no std::aligned_alloc(). + static_assert(alignof(Type) <= alignof(std::max_align_t), "Components should align to std::max_align_t!"); } RuntimeComponent(const RuntimeComponent &other) = delete; RuntimeComponent &operator=(const RuntimeComponent &other) = delete; @@ -62,17 +63,17 @@ struct RuntimeComponent { RuntimeComponent &operator=(RuntimeComponent &&other) = default; static int Index() { - static QAtomicInt _index(0); - if (int index = _index.loadAcquire()) { + static QAtomicInt MyIndex(0); + if (auto index = MyIndex.loadAcquire()) { return index - 1; } while (true) { - int last = RuntimeComponentIndexLast.loadAcquire(); + auto last = RuntimeComponentIndexLast.loadAcquire(); if (RuntimeComponentIndexLast.testAndSetOrdered(last, last + 1)) { t_assert(last < 64); - if (_index.testAndSetOrdered(0, last + 1)) { + if (MyIndex.testAndSetOrdered(0, last + 1)) { RuntimeComponentWraps[last] = RuntimeComponentWrapStruct( - CeilDivideMinimumOne::Result * sizeof(SmallestSizeType), + sizeof(Type), alignof(Type), Type::RuntimeComponentConstruct, Type::RuntimeComponentDestruct, @@ -81,15 +82,13 @@ struct RuntimeComponent { break; } } - return _index.loadAcquire() - 1; + return MyIndex.loadAcquire() - 1; } static uint64 Bit() { return (1ULL << Index()); } protected: - using SmallestSizeType = void*; - static void RuntimeComponentConstruct(void *location, RuntimeComposer *composer) { new (location) Type(); } @@ -104,30 +103,32 @@ protected: class RuntimeComposerMetadata { public: - RuntimeComposerMetadata(uint64 mask) : size(0), last(64), _mask(mask) { - for (int i = 0; i < 64; ++i) { - uint64 m = (1ULL << i); - if (_mask & m) { - int s = RuntimeComponentWraps[i].Size; - if (s) { + RuntimeComposerMetadata(uint64 mask) : _mask(mask) { + for (int i = 0; i != 64; ++i) { + auto componentBit = (1ULL << i); + if (_mask & componentBit) { + auto componentSize = RuntimeComponentWraps[i].Size; + if (componentSize) { + auto componentAlign = RuntimeComponentWraps[i].Align; + if (auto badAlign = (size % componentAlign)) { + size += (componentAlign - badAlign); + } offsets[i] = size; - size += s; - } else { - offsets[i] = -1; + size += componentSize; + accumulate_max(align, componentAlign); } - } else if (_mask < m) { + } else if (_mask < componentBit) { last = i; - for (; i < 64; ++i) { - offsets[i] = -1; - } - } else { - offsets[i] = -1; + break; } } } - int size, last; - int offsets[64]; + // Meta pointer in the start. + std::size_t size = sizeof(const RuntimeComposerMetadata*); + std::size_t align = alignof(const RuntimeComposerMetadata*); + std::size_t offsets[64] = { 0 }; + int last = 64; bool equals(uint64 mask) const { return _mask == mask; @@ -150,28 +151,28 @@ class RuntimeComposer { public: RuntimeComposer(uint64 mask = 0) : _data(zerodata()) { if (mask) { - const RuntimeComposerMetadata *meta = GetRuntimeComposerMetadata(mask); - int size = sizeof(meta) + meta->size; + auto meta = GetRuntimeComposerMetadata(mask); - auto data = operator new(size); + auto data = operator new(meta->size); t_assert(data != nullptr); _data = data; _meta() = meta; for (int i = 0; i < meta->last; ++i) { - int offset = meta->offsets[i]; - if (offset >= 0) { + auto offset = meta->offsets[i]; + if (offset >= sizeof(_meta())) { try { auto constructAt = _dataptrunsafe(offset); auto space = RuntimeComponentWraps[i].Size; - auto alignedAt = std::align(RuntimeComponentWraps[i].Align, space, constructAt, space); + auto alignedAt = constructAt; + std::align(RuntimeComponentWraps[i].Align, space, alignedAt, space); t_assert(alignedAt == constructAt); RuntimeComponentWraps[i].Construct(constructAt, this); } catch (...) { while (i > 0) { --i; offset = meta->offsets[--i]; - if (offset >= 0) { + if (offset >= sizeof(_meta())) { RuntimeComponentWraps[i].Destruct(_dataptrunsafe(offset)); } } @@ -187,8 +188,8 @@ public: if (_data != zerodata()) { auto meta = _meta(); for (int i = 0; i < meta->last; ++i) { - int offset = meta->offsets[i]; - if (offset >= 0) { + auto offset = meta->offsets[i]; + if (offset >= sizeof(_meta())) { RuntimeComponentWraps[i].Destruct(_dataptrunsafe(offset)); } } @@ -198,7 +199,7 @@ public: template bool Has() const { - return (_meta()->offsets[Type::Index()] >= 0); + return (_meta()->offsets[Type::Index()] >= sizeof(_meta())); } template @@ -218,8 +219,9 @@ protected: if (_data != zerodata() && tmp._data != zerodata()) { auto meta = _meta(), wasmeta = tmp._meta(); for (int i = 0; i < meta->last; ++i) { - int offset = meta->offsets[i], wasoffset = wasmeta->offsets[i]; - if (offset >= 0 && wasoffset >= 0) { + auto offset = meta->offsets[i]; + auto wasoffset = wasmeta->offsets[i]; + if (offset >= sizeof(_meta()) && wasoffset >= sizeof(_meta())) { RuntimeComponentWraps[i].Move(_dataptrunsafe(offset), tmp._dataptrunsafe(wasoffset)); } } @@ -240,15 +242,15 @@ private: } void *_dataptrunsafe(int skip) const { - return (char*)_data + sizeof(_meta()) + skip; + return (char*)_data + skip; } void *_dataptr(int skip) const { - return (skip >= 0) ? _dataptrunsafe(skip) : 0; + return (skip >= sizeof(_meta())) ? _dataptrunsafe(skip) : nullptr; } const RuntimeComposerMetadata *&_meta() const { return *static_cast(_data); } - void *_data; + void *_data = nullptr; void swap(RuntimeComposer &other) { std::swap(_data, other._data); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 1b3212b28..5fb7a3296 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -764,6 +764,37 @@ void Histories::savePinnedToServer() const { MTP::send(MTPmessages_ReorderPinnedDialogs(MTP_flags(flags), MTP_vector(peers))); } +void Histories::selfDestructIn(gsl::not_null item, TimeMs delay) { + _selfDestructItems.push_back(item->fullId()); + if (!_selfDestructTimer.isActive() || _selfDestructTimer.remainingTime() > delay) { + _selfDestructTimer.callOnce(delay); + } +} + +void Histories::checkSelfDestructItems() { + auto now = getms(true); + auto nextDestructIn = TimeMs(0); + for (auto i = _selfDestructItems.begin(); i != _selfDestructItems.cend();) { + if (auto item = App::histItemById(*i)) { + if (auto destructIn = item->getSelfDestructIn(now)) { + if (nextDestructIn > 0) { + accumulate_min(nextDestructIn, destructIn); + } else { + nextDestructIn = destructIn; + } + ++i; + } else { + i = _selfDestructItems.erase(i); + } + } else { + i = _selfDestructItems.erase(i); + } + } + if (nextDestructIn > 0) { + _selfDestructTimer.callOnce(nextDestructIn); + } +} + HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) { auto msgId = MsgId(0); switch (msg.type()) { @@ -799,7 +830,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, Good, Unsupported, Empty, - HasTTL, + HasTimeToLive, }; auto badMedia = MediaCheckResult::Good; if (m.has_media()) switch (m.vmedia.type()) { @@ -822,7 +853,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, case mtpc_messageMediaPhoto: { auto &photo = m.vmedia.c_messageMediaPhoto(); if (photo.has_ttl_seconds()) { - badMedia = MediaCheckResult::HasTTL; + badMedia = MediaCheckResult::HasTimeToLive; } else if (!photo.has_photo()) { badMedia = MediaCheckResult::Empty; } else { @@ -836,7 +867,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, case mtpc_messageMediaDocument: { auto &document = m.vmedia.c_messageMediaDocument(); if (document.has_ttl_seconds()) { - badMedia = MediaCheckResult::HasTTL; + badMedia = MediaCheckResult::HasTimeToLive; } else if (!document.has_document()) { badMedia = MediaCheckResult::Empty; } else { @@ -872,9 +903,8 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } else if (badMedia == MediaCheckResult::Empty) { auto message = HistoryService::PreparedText { lang(lng_message_empty) }; result = HistoryService::create(this, m.vid.v, date(m.vdate), message, m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0); - } else if (badMedia == MediaCheckResult::HasTTL) { - auto message = HistoryService::PreparedText { qsl("Self-destruct media, see mobile") }; - result = HistoryService::create(this, m.vid.v, date(m.vdate), message, m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0); + } else if (badMedia == MediaCheckResult::HasTimeToLive) { + result = HistoryService::create(this, m); } else { result = HistoryMessage::create(this, m); } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 25907c353..e1004d8ea 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_common.h" #include "ui/effects/send_action_animations.h" #include "base/observer.h" +#include "base/timer.h" void HistoryInit(); @@ -43,6 +44,7 @@ public: Map map; Histories() : _a_typings(animation(this, &Histories::step_typings)) { + _selfDestructTimer.setCallback([this] { checkSelfDestructItems(); }); } void regSendAction(History *history, UserData *user, const MTPSendMessageAction &action, TimeId when); @@ -95,13 +97,19 @@ public: base::Observable &sendActionAnimationUpdated() { return _sendActionAnimationUpdated; } + void selfDestructIn(gsl::not_null item, TimeMs delay); private: + void checkSelfDestructItems(); + int _unreadFull = 0; int _unreadMuted = 0; base::Observable _sendActionAnimationUpdated; OrderedSet _pinnedDialogs; + base::Timer _selfDestructTimer; + std::vector _selfDestructItems; + }; class HistoryBlock; diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index c0337d689..67447c3a1 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -579,7 +579,14 @@ public: } void markMediaRead() { _flags &= ~MTPDmessage::Flag::f_media_unread; + markMediaAsReadHook(); } + + // Zero result means this message is not self-destructing right now. + virtual TimeMs getSelfDestructIn(TimeMs now) { + return 0; + } + bool definesReplyKeyboard() const { if (auto markup = Get()) { if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) { @@ -918,13 +925,16 @@ public: protected: HistoryItem(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime msgDate, int32 from); - // to completely create history item we need to call - // a virtual method, it can not be done from constructor + // To completely create history item we need to call + // a virtual method, it can not be done from constructor. virtual void finishCreate(); - // called from resizeGetHeight() when MTPDmessage_ClientFlag::f_pending_init_dimensions is set + // Called from resizeGetHeight() when MTPDmessage_ClientFlag::f_pending_init_dimensions is set. virtual void initDimensions() = 0; + virtual void markMediaAsReadHook() { + } + virtual int resizeContentGetHeight() = 0; void finishEdition(int oldKeyboardTop); diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index cb4128a63..2d8a8e23a 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -220,6 +220,13 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } } +void HistoryService::setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds) { + UpdateComponents(HistoryServiceSelfDestruct::Bit()); + auto selfdestruct = Get(); + selfdestruct->timeToLive = ttlSeconds * 1000LL; + selfdestruct->type = type; +} + bool HistoryService::updateDependent(bool force) { auto dependent = GetDependentData(); t_assert(dependent != nullptr); @@ -393,10 +400,14 @@ HistoryService::PreparedText HistoryService::preparePaymentSentText() { return result; } +HistoryService::HistoryService(gsl::not_null history, const MTPDmessage &message) : + HistoryItem(history, message.vid.v, message.vflags.v, ::date(message.vdate), message.has_from_id() ? message.vfrom_id.v : 0) { + createFromMtp(message); +} + HistoryService::HistoryService(gsl::not_null history, const MTPDmessageService &message) : HistoryItem(history, message.vid.v, mtpCastFlags(message.vflags.v), ::date(message.vdate), message.has_from_id() ? message.vfrom_id.v : 0) { createFromMtp(message); - setMessageByAction(message.vaction); } HistoryService::HistoryService(gsl::not_null history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags, int32 from, PhotoData *photo) : @@ -521,6 +532,35 @@ int HistoryService::resizeContentGetHeight() { return _height; } +void HistoryService::markMediaAsReadHook() { + if (auto selfdestruct = Get()) { + if (!selfdestruct->destructAt) { + selfdestruct->destructAt = getms(true) + selfdestruct->timeToLive; + App::histories().selfDestructIn(this, selfdestruct->timeToLive); + } + } +} + +TimeMs HistoryService::getSelfDestructIn(TimeMs now) { + if (auto selfdestruct = Get()) { + if (selfdestruct->destructAt > 0) { + if (selfdestruct->destructAt <= now) { + auto text = [selfdestruct] { + switch (selfdestruct->type) { + case HistoryServiceSelfDestruct::Type::Photo: return lang(lng_ttl_photo_expired); + case HistoryServiceSelfDestruct::Type::Video: return lang(lng_ttl_video_expired); + } + Unexpected("Type in HistoryServiceSelfDestruct::Type"); + }; + setServiceText({ text() }); + return 0; + } + return selfdestruct->destructAt - now; + } + } + return 0; +} + bool HistoryService::hasPoint(QPoint point) const { auto g = countGeometry(); if (g.width() < 1) { @@ -580,6 +620,48 @@ HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest requ return result; } +void HistoryService::createFromMtp(const MTPDmessage &message) { + auto mediaType = message.vmedia.type(); + switch (mediaType) { + case mtpc_messageMediaPhoto: { + if (message.is_media_unread()) { + auto &photo = message.vmedia.c_messageMediaPhoto(); + t_assert(photo.has_ttl_seconds()); + setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, photo.vttl_seconds.v); + if (out()) { + setServiceText({ lang(lng_ttl_photo_sent) }); + } else { + auto result = PreparedText(); + result.links.push_back(fromLink()); + result.text = lng_ttl_photo_received(lt_from, fromLinkText()); + setServiceText(std::move(result)); + } + } else { + setServiceText({ lang(lng_ttl_photo_expired) }); + } + } break; + case mtpc_messageMediaDocument: { + if (message.is_media_unread()) { + auto &document = message.vmedia.c_messageMediaDocument(); + t_assert(document.has_ttl_seconds()); + setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, document.vttl_seconds.v); + if (out()) { + setServiceText({ lang(lng_ttl_video_sent) }); + } else { + auto result = PreparedText(); + result.links.push_back(fromLink()); + result.text = lng_ttl_video_received(lt_from, fromLinkText()); + setServiceText(std::move(result)); + } + } else { + setServiceText({ lang(lng_ttl_video_expired) }); + } + } break; + + default: Unexpected("Media type in HistoryService::createFromMtp()"); + } +} + void HistoryService::createFromMtp(const MTPDmessageService &message) { if (message.vaction.type() == mtpc_messageActionGameScore) { UpdateComponents(HistoryServiceGameScore::Bit()); diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h index be1db8145..500080f7f 100644 --- a/Telegram/SourceFiles/history/history_service.h +++ b/Telegram/SourceFiles/history/history_service.h @@ -37,6 +37,16 @@ struct HistoryServicePayment : public RuntimeComponent, p QString amount; }; +struct HistoryServiceSelfDestruct : public RuntimeComponent { + enum class Type { + Photo, + Video, + }; + Type type = Type::Photo; + TimeMs timeToLive = 0; + TimeMs destructAt = 0; +}; + namespace HistoryLayout { class ServiceMessagePainter; } // namespace HistoryLayout @@ -48,6 +58,9 @@ public: QList links; }; + static gsl::not_null create(gsl::not_null history, const MTPDmessage &message) { + return _create(history, message); + } static gsl::not_null create(gsl::not_null history, const MTPDmessageService &message) { return _create(history, message); } @@ -83,6 +96,7 @@ public: void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; void applyEdition(const MTPDmessageService &message) override; + TimeMs getSelfDestructIn(TimeMs now) override; int32 addToOverview(AddToOverviewMethod method) override; void eraseFromOverview() override; @@ -102,6 +116,7 @@ public: protected: friend class HistoryLayout::ServiceMessagePainter; + HistoryService(gsl::not_null history, const MTPDmessage &message); HistoryService(gsl::not_null history, const MTPDmessageService &message); HistoryService(gsl::not_null history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = 0); friend class HistoryItemInstantiated; @@ -109,6 +124,8 @@ protected: void initDimensions() override; int resizeContentGetHeight() override; + void markMediaAsReadHook() override; + void setServiceText(const PreparedText &prepared); QString fromLinkText() const { @@ -138,8 +155,10 @@ private: void updateDependentText(); void clearDependency(); + void createFromMtp(const MTPDmessage &message); void createFromMtp(const MTPDmessageService &message); void setMessageByAction(const MTPmessageAction &action); + void setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds); PreparedText preparePinnedText(); PreparedText prepareGameScoreText();