diff --git a/.travis/build.sh b/.travis/build.sh index eea05c509..8ecdfaa96 100755 --- a/.travis/build.sh +++ b/.travis/build.sh @@ -20,13 +20,13 @@ downloadLibs() { info_msg "QT-Version: ${_qtver}, SRC-Dir: ${srcdir}" echo -e "Clone Qt\n" - git clone git://code.qt.io/qt/qt5.git qt5_6_0 - cd qt5_6_0 - git checkout 5.6 + git clone git://code.qt.io/qt/qt5.git qt${_qtver} + cd qt${_qtver} + git checkout $(echo ${_qtver} | sed -e "s/\..$//") perl init-repository --module-subset=qtbase,qtimageformats - git checkout v5.6.0 - cd qtbase && git checkout v5.6.0 && cd .. - cd qtimageformats && git checkout v5.6.0 && cd .. + git checkout v${_qtver} + cd qtbase && git checkout v${_qtver} && cd .. + cd qtimageformats && git checkout v${_qtver} && cd .. cd .. echo -e "Clone Breakpad\n" @@ -48,9 +48,9 @@ prepare() { mkdir -p "$srcdir/Libraries" - ln -s "$srcdir/qt5_6_0" "$srcdir/Libraries/qt5_6_0" - cd "$srcdir/Libraries/qt5_6_0/qtbase" - git apply "$srcdir/tdesktop/Telegram/Patches/qtbase_5_6_0.diff" + ln -s "$srcdir/qt${_qtver}" "$srcdir/Libraries/qt${_qtver}" + cd "$srcdir/Libraries/qt${_qtver}/qtbase" + git apply "$srcdir/tdesktop/Telegram/Patches/qtbase_$(echo ${_qtver} | sed -e "s/\./_/g").diff" if [ ! -h "$srcdir/Libraries/breakpad" ]; then ln -s "$srcdir/breakpad" "$srcdir/Libraries/breakpad" @@ -60,7 +60,6 @@ prepare() { sed -i 's/CUSTOM_API_ID//g' "$srcdir/tdesktop/Telegram/Telegram.pro" sed -i 's,LIBS += /usr/local/lib/libxkbcommon.a,,g' "$srcdir/tdesktop/Telegram/Telegram.pro" sed -i 's,LIBS += /usr/local/lib/libz.a,LIBS += -lz,g' "$srcdir/tdesktop/Telegram/Telegram.pro" - sed -i "s,/usr/local/tdesktop/Qt-5.6.0,$srcdir/qt,g" "$srcdir/tdesktop/Telegram/Telegram.pro" local options="" @@ -98,7 +97,7 @@ build() { info_msg "Build patched Qt" # Build patched Qt - cd "$srcdir/Libraries/qt5_6_0" + cd "$srcdir/Libraries/qt${_qtver}" ./configure -prefix "$srcdir/qt" -release -opensource -confirm-license -qt-zlib \ -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb \ -qt-xkbcommon-x11 -no-opengl -static -nomake examples -nomake tests @@ -117,21 +116,21 @@ build() { # Build codegen_style mkdir -p "$srcdir/tdesktop/Linux/obj/codegen_style/Debug" cd "$srcdir/tdesktop/Linux/obj/codegen_style/Debug" - qmake CONFIG+=debug "../../../../Telegram/build/qmake/codegen_style/codegen_style.pro" + qmake QT_TDESKTOP_PATH=${srcdir}/qt QT_TDESKTOP_VERSION=${_qtver} CONFIG+=debug "../../../../Telegram/build/qmake/codegen_style/codegen_style.pro" make --silent -j4 info_msg "Build codegen_numbers" # Build codegen_numbers mkdir -p "$srcdir/tdesktop/Linux/obj/codegen_numbers/Debug" cd "$srcdir/tdesktop/Linux/obj/codegen_numbers/Debug" - qmake CONFIG+=debug "../../../../Telegram/build/qmake/codegen_numbers/codegen_numbers.pro" + qmake QT_TDESKTOP_PATH=${srcdir}/qt QT_TDESKTOP_VERSION=${_qtver} CONFIG+=debug "../../../../Telegram/build/qmake/codegen_numbers/codegen_numbers.pro" make --silent -j4 info_msg "Build MetaLang" # Build MetaLang mkdir -p "$srcdir/tdesktop/Linux/DebugIntermediateLang" cd "$srcdir/tdesktop/Linux/DebugIntermediateLang" - qmake CONFIG+=debug "../../Telegram/MetaLang.pro" + qmake QT_TDESKTOP_PATH=${srcdir}/qt QT_TDESKTOP_VERSION=${_qtver} CONFIG+=debug "../../Telegram/MetaLang.pro" make --silent -j4 info_msg "Build Telegram Desktop" @@ -142,7 +141,7 @@ build() { ./../codegen/Debug/codegen_style "-I./../../Telegram/Resources" "-I./../../Telegram/SourceFiles" "-o./GeneratedFiles/styles" all_files.style --rebuild ./../codegen/Debug/codegen_numbers "-o./GeneratedFiles" "./../../Telegram/Resources/numbers.txt" ./../DebugLang/MetaLang -lang_in ./../../Telegram/Resources/langs/lang.strings -lang_out ./GeneratedFiles/lang_auto - qmake CONFIG+=debug "../../Telegram/Telegram.pro" + qmake QT_TDESKTOP_PATH=${srcdir}/qt QT_TDESKTOP_VERSION=${_qtver} CONFIG+=debug "../../Telegram/Telegram.pro" make -j4 } diff --git a/.travis/common.sh b/.travis/common.sh index dd3bb1967..ef9da3867 100755 --- a/.travis/common.sh +++ b/.travis/common.sh @@ -12,7 +12,7 @@ Cya='\e[0;36m'; BCya='\e[1;36m'; UCya='\e[4;36m'; ICya='\e[0;96m'; Whi='\e[0;37m'; BWhi='\e[1;37m'; UWhi='\e[4;37m'; IWhi='\e[0;97m'; BIWhi='\e[1;97m'; On_Whi='\e[47m'; On_IWhi='\e[0;107m'; # Set variables -_qtver=5.5.1 +_qtver=5.6.0 srcdir=${PWD} start_msg() { diff --git a/README.md b/README.md index eb79d16a7..c56329b1f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is * Windows XP - Windows 10 (**not** RT) * Mac OS X 10.8 - Mac OS X 10.11 * Mac OS X 10.6 - Mac OS X 10.7 (separate build) -* Ubuntu 12.04 - Ubuntu 15.04 +* Ubuntu 12.04 - Ubuntu 16.04 * Fedora 22 ## Third-party libraries diff --git a/Telegram/MetaEmoji.pro b/Telegram/MetaEmoji.pro index e859c30c8..02c5c7080 100644 --- a/Telegram/MetaEmoji.pro +++ b/Telegram/MetaEmoji.pro @@ -27,7 +27,4 @@ HEADERS += \ ./SourceFiles/_other/memain.h \ ./SourceFiles/_other/genemoji.h \ -INCLUDEPATH += ./../../Libraries/QtStatic/qtbase/include/QtGui/5.5.1/QtGui\ - ./../../Libraries/QtStatic/qtbase/include/QtCore/5.5.1/QtCore\ - ./../../Libraries/QtStatic/qtbase/include\ - +include(qt_static.pri) diff --git a/Telegram/MetaLang.pro b/Telegram/MetaLang.pro index 0dfa29a6f..cb60900f8 100644 --- a/Telegram/MetaLang.pro +++ b/Telegram/MetaLang.pro @@ -27,7 +27,4 @@ HEADERS += \ ./SourceFiles/_other/mlmain.h \ ./SourceFiles/_other/genlang.h \ -INCLUDEPATH += ./../../Libraries/QtStatic/qtbase/include/QtGui/5.5.1/QtGui\ - ./../../Libraries/QtStatic/qtbase/include/QtCore/5.5.1/QtCore\ - ./../../Libraries/QtStatic/qtbase/include\ - +include(qt_static.pri) diff --git a/Telegram/Packer.pro b/Telegram/Packer.pro index 32fc691c3..ee5f36f28 100644 --- a/Telegram/Packer.pro +++ b/Telegram/Packer.pro @@ -35,8 +35,6 @@ unix { } } -INCLUDEPATH += ./../../Libraries/QtStatic/qtbase/include/QtGui/5.5.1/QtGui\ - ./../../Libraries/QtStatic/qtbase/include/QtCore/5.5.1/QtCore\ - ./../../Libraries/QtStatic/qtbase/include +include(qt_static.pri) LIBS += -lcrypto -lssl -lz -llzma diff --git a/Telegram/Resources/all_files.style b/Telegram/Resources/all_files.style index d496505f2..88041eff1 100644 --- a/Telegram/Resources/all_files.style +++ b/Telegram/Resources/all_files.style @@ -24,3 +24,4 @@ using "basic_types.style"; using "basic.style"; using "overview/overview.style"; +using "profile/profile.style"; diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 318c94387..8b8c4547f 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -55,10 +55,15 @@ wndMinWidth: 380px; adaptiveNormalWidth: 640px; adaptiveWideWidth: 1366px; +windowBg: #fff; // fallback for background: white +windowTextFg: #000; // fallback for text color: black +windowSubTextFg: #8a8a8a; // fallback for subtext color: gray +windowActiveTextFg: #1485c2; // fallback for active color: blue online +windowShadowFg: #000; // fallback for shadow color + wndMinHeight: 480px; wndDefWidth: 800px; wndDefHeight: 600px; -wndBG: white; wndShadow: sprite(209px, 46px, 19px, 19px); wndShadowShift: 1px; @@ -69,9 +74,10 @@ overBg: #edf2f5; labelDefFlat: flatLabel { font: font(fsize); - minWidth: 100px; width: 0px; + maxHeight: 0px; align: align(left); + textFg: windowTextFg; } boxBg: white; @@ -140,6 +146,29 @@ boxLabel: flatLabel(labelDefFlat) { align: align(topleft); } +defaultLeftOutlineButton: OutlineButton { + outlineWidth: 3px; + outlineFg: windowBg; + outlineFgOver: #3fb0e4; + + textBg: windowBg; + textBgOver: #f2f7fa; + + textFg: windowActiveTextFg; + textFgOver: windowActiveTextFg; + + font: normalFont; + padding: margins(11px, 6px, 11px, 6px); +} +attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) { + outlineFgOver: #e43f3f; + + textBgOver: #faf2f2; + + textFg: #d15948; + textFgOver: #d15948; +} + defaultInputArea: InputArea { textFg: black; textMargins: margins(5px, 6px, 5px, 4px); @@ -370,9 +399,10 @@ statusFont: font(fsize); versionColor: #777; shadowColor: rgba(0, 0, 0, 24); +shadowToggleDuration: 200; slideDuration: 240; -slideShift: 0.3; +slideShift: 100px; slideFadeOut: 0.3; slideShadow: sprite(348px, 71px, 48px, 1px); slideFunction: transition(easeOutCirc); @@ -1657,18 +1687,18 @@ confirmCompressedSkip: 10px; profileMaxWidth: 410px; profilePadding: margins(28px, 30px, 28px, 0px); -profilePhotoSize: 120px; -profileNameLeft: 21px; -profileNameTop: -1px; -profileNameFont: font(20px); -profileStatusLeft: 22px; -profileStatusTop: 31px; -profileStatusFont: font(fsize); +//profilePhotoSize: 120px; +//profileNameLeft: 21px; +//profileNameTop: -1px; +//profileNameFont: font(20px); +//profileStatusLeft: 22px; +//profileStatusTop: 31px; +//profileStatusFont: font(fsize); profilePhoneLeft: 22px; profilePhoneTop: 62px; profilePhoneFont: font(16px); -profileButtonTop: 18px; -profileButtonSkip: 10px; +//profileButtonTop: 18px; +//profileButtonSkip: 10px; profileHeaderFont: font(20px); profileHeaderColor: black; profileHeaderSkip: 59px; @@ -2444,7 +2474,7 @@ inlineResultsSkip: 3px; inlineMediaHeight: 96px; inlineThumbSize: 64px; inlineThumbSkip: 10px; -inlineDescriptionFg: #8a8a8a; +inlineDescriptionFg: windowSubTextFg; inlineRowMargin: 6px; inlineRowBorder: 1px; inlineRowBorderFg: #eaeaea; diff --git a/Telegram/Resources/basic_types.style b/Telegram/Resources/basic_types.style index 750a52cea..4e2065ec7 100644 --- a/Telegram/Resources/basic_types.style +++ b/Telegram/Resources/basic_types.style @@ -210,9 +210,11 @@ slider { flatLabel { font: font; - minWidth: pixels; + margin: margins; width: pixels; align: align; + textFg: color; + maxHeight: pixels; } switcher { @@ -306,6 +308,8 @@ BoxButton { textTop: pixels; + icon: icon; + font: font; duration: int; } @@ -411,3 +415,18 @@ PeerAvatarButton { size: pixels; photoSize: pixels; } + +OutlineButton { + outlineWidth: pixels; + outlineFg: color; + outlineFgOver: color; + + textBg: color; + textBgOver: color; + + textFg: color; + textFgOver: color; + + font: font; + padding: margins; +} diff --git a/Telegram/Resources/icons/profile_add_member.png b/Telegram/Resources/icons/profile_add_member.png new file mode 100644 index 000000000..828236388 Binary files /dev/null and b/Telegram/Resources/icons/profile_add_member.png differ diff --git a/Telegram/Resources/icons/profile_add_member@2x.png b/Telegram/Resources/icons/profile_add_member@2x.png new file mode 100644 index 000000000..fa6ed0d2c Binary files /dev/null and b/Telegram/Resources/icons/profile_add_member@2x.png differ diff --git a/Telegram/Resources/icons/profile_admin_star.png b/Telegram/Resources/icons/profile_admin_star.png new file mode 100644 index 000000000..0e9d75e97 Binary files /dev/null and b/Telegram/Resources/icons/profile_admin_star.png differ diff --git a/Telegram/Resources/icons/profile_admin_star@2x.png b/Telegram/Resources/icons/profile_admin_star@2x.png new file mode 100644 index 000000000..97ba9c766 Binary files /dev/null and b/Telegram/Resources/icons/profile_admin_star@2x.png differ diff --git a/Telegram/Resources/icons/profile_divider_fill.png b/Telegram/Resources/icons/profile_divider_fill.png new file mode 100644 index 000000000..f212416e7 Binary files /dev/null and b/Telegram/Resources/icons/profile_divider_fill.png differ diff --git a/Telegram/Resources/icons/profile_divider_fill@2x.png b/Telegram/Resources/icons/profile_divider_fill@2x.png new file mode 100644 index 000000000..9d4eaddc3 Binary files /dev/null and b/Telegram/Resources/icons/profile_divider_fill@2x.png differ diff --git a/Telegram/Resources/icons/profile_divider_left.png b/Telegram/Resources/icons/profile_divider_left.png new file mode 100644 index 000000000..c4d4c3aa1 Binary files /dev/null and b/Telegram/Resources/icons/profile_divider_left.png differ diff --git a/Telegram/Resources/icons/profile_divider_left@2x.png b/Telegram/Resources/icons/profile_divider_left@2x.png new file mode 100644 index 000000000..92b5fdae2 Binary files /dev/null and b/Telegram/Resources/icons/profile_divider_left@2x.png differ diff --git a/Telegram/Resources/icons/topbar_back_arrow.png b/Telegram/Resources/icons/topbar_back_arrow.png new file mode 100644 index 000000000..7c62fd73d Binary files /dev/null and b/Telegram/Resources/icons/topbar_back_arrow.png differ diff --git a/Telegram/Resources/icons/topbar_back_arrow@2x.png b/Telegram/Resources/icons/topbar_back_arrow@2x.png new file mode 100644 index 000000000..990a5bb66 Binary files /dev/null and b/Telegram/Resources/icons/topbar_back_arrow@2x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9ffb52302..b7bc6b0ed 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -117,8 +117,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_channel_status" = "channel"; "lng_group_status" = "group"; -"lng_channel_members_link" = "{count:_not_used_|# member|# members} »"; -"lng_channel_admins_link" = "{count:Manage administrators|# administrator|# administrators} »"; +"lng_channel_members_link" = "{count:_not_used_|# member|# members}"; +"lng_channel_admins_link" = "{count:_not_used_|# administrator|# administrators}"; "lng_server_error" = "Internal server error."; "lng_flood_error" = "Too many tries. Please try again later."; @@ -405,12 +405,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_actions_section" = "Actions"; "lng_profile_bot_settings" = "Settings"; "lng_profile_bot_help" = "Help"; +"lng_profile_invite_link_section" = "Invite link"; "lng_profile_create_public_link" = "Create public link"; "lng_profile_edit_public_link" = "Edit public link"; +"lng_profile_manage_admins" = "Manage administrators"; "lng_profile_participants_section" = "Members"; -"lng_profile_info" = "Contact info"; -"lng_profile_group_info" = "Group info"; -"lng_profile_channel_info" = "Channel info"; +"lng_profile_info_section" = "Info"; +"lng_profile_mobile_number" = "Mobile:"; +"lng_profile_username" = "Username:"; +"lng_profile_link" = "Link:"; "lng_profile_add_contact" = "Add Contact"; "lng_profile_edit_contact" = "Edit"; "lng_profile_enable_notifications" = "Notifications"; @@ -421,6 +424,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_delete_channel" = "Delete channel"; "lng_profile_leave_group" = "Leave group"; "lng_profile_delete_group" = "Delete group"; +"lng_profile_report" = "Report"; "lng_profile_search_messages" = "Search for messages"; "lng_profile_block_user" = "Block user"; "lng_profile_unblock_user" = "Unblock user"; @@ -432,6 +436,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_delete_contact" = "Delete"; "lng_profile_set_group_photo" = "Set Photo"; "lng_profile_add_participant" = "Add Members"; +"lng_profile_view_channel" = "View Channel"; +"lng_profile_join_channel" = "Join"; "lng_profile_delete_and_exit" = "Leave"; "lng_profile_kick" = "Remove"; "lng_profile_admin" = "admin"; @@ -441,20 +447,33 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_loading" = "Loading..."; "lng_profile_shared_media" = "Shared media"; "lng_profile_no_media" = "No media in this conversation."; -"lng_profile_photos" = "{count:_not_used_|# photo|# photos} »"; +"lng_profile_photos" = "{count:_not_used_|# photo|# photos}"; "lng_profile_photos_header" = "Photos overview"; -"lng_profile_videos" = "{count:_not_used_|# video file|# video files} »"; +"lng_profile_videos" = "{count:_not_used_|# video file|# video files}"; "lng_profile_videos_header" = "Video files overview"; -"lng_profile_songs" = "{count:_not_used_|# audio file|# audio files} »"; +"lng_profile_songs" = "{count:_not_used_|# audio file|# audio files}"; "lng_profile_songs_header" = "Audio files overview"; -"lng_profile_files" = "{count:_not_used_|# file|# files} »"; +"lng_profile_files" = "{count:_not_used_|# file|# files}"; "lng_profile_files_header" = "Files overview"; -"lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages} »"; +"lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages}"; "lng_profile_audios_header" = "Voice messages overview"; -"lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links} »"; +"lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links}"; "lng_profile_shared_links_header" = "Shared links overview"; "lng_profile_copy_phone" = "Copy phone number"; "lng_profile_copy_fullname" = "Copy name"; +"lng_profile_drop_area_title" = "Drop your image here"; +"lng_profile_drop_area_subtitle" = "to set it as a group photo"; +"lng_profile_drop_area_subtitle_channel" = "to set it as a channel photo"; +"lng_profile_top_bar_share_contact" = "Share"; + +"lng_report_title" = "Report channel"; +"lng_report_reason_spam" = "Spam"; +"lng_report_reason_violence" = "Violence"; +"lng_report_reason_pornography" = "Pornography"; +"lng_report_reason_other" = "Other"; +"lng_report_reason_description" = "Description"; +"lng_report_button" = "Report"; +"lng_report_thanks" = "Thank you! Your report will be reviewed by our team very soon."; "lng_channel_add_admins" = "New administrator"; "lng_channel_add_members" = "Add members"; @@ -550,6 +569,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_action_pinned_media_sticker" = "a sticker"; "lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached"; +"lng_profile_migrate_body" = "To get over this limit, you can upgrade your group to a supergroup."; +"lng_profile_migrate_learn_more" = "Learn more »"; "lng_profile_migrate_about" = "If you'd like to go over this limit, you can upgrade your group to a supergroup. In supergroups:"; "lng_profile_migrate_feature1" = "— The members limit is {count:_not_used_|# user|# users}"; "lng_profile_migrate_feature2" = "— New members see the entire chat history"; @@ -583,7 +604,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_group_invite_create" = "Create an invite link"; "lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link."; "lng_group_invite_create_new" = "Revoke invite link"; -"lng_group_invite_about_new" = "Your previous link will be deactivated\nand we'll generate a new invite link for you."; +"lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you."; "lng_group_invite_copied" = "Invite link copied to clipboard."; "lng_group_invite_no_room" = "Unable to join this group because there are too many members in it already."; @@ -902,6 +923,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org // Not used "lng_topbar_info" = "Info"; +"lng_profile_group_info" = "Group info"; +"lng_profile_channel_info" = "Channel info"; // Wnd specific diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 65d2ba0f4..40a6c2f8f 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,49,0 - PRODUCTVERSION 0,9,49,0 + FILEVERSION 0,9,49,1 + PRODUCTVERSION 0,9,49,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.49.0" + VALUE "FileVersion", "0.9.49.1" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.49.0" + VALUE "ProductVersion", "0.9.49.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 9fc56b8bf..870b47066 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,49,0 - PRODUCTVERSION 0,9,49,0 + FILEVERSION 0,9,49,1 + PRODUCTVERSION 0,9,49,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.9.49.0" + VALUE "FileVersion", "0.9.49.1" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.49.0" + VALUE "ProductVersion", "0.9.49.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 52260bc16..9536c908d 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "apiwrap.h" +#include "observer_peer.h" #include "lang.h" #include "application.h" #include "mainwindow.h" @@ -96,6 +97,10 @@ void ApiWrap::resolveMessageDatas() { } } +void ApiWrap::updatesReceived(const MTPUpdates &updates) { + App::main()->sentUpdatesReceived(updates); +} + void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) { switch (msgs.type()) { case mtpc_messages_messages: { @@ -174,8 +179,8 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt badVersion = (!vc.isEmpty() && vc.at(0).type() == mtpc_channel && vc.at(0).c_channel().vversion.v < peer->asChannel()->version); } - App::feedUsers(d.vusers, false); - App::feedChats(d.vchats, false); + App::feedUsers(d.vusers); + App::feedChats(d.vchats); if (peer->isChat()) { if (d.vfull_chat.type() != mtpc_chatFull) { @@ -206,9 +211,9 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt } else { chat->photoId = 0; } - chat->invitationUrl = (f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString(); + chat->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString()); - App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), f.vnotify_settings); + notifySettingReceived(MTP_inputNotifyPeer(peer->input), f.vnotify_settings); } else if (peer->isChannel()) { if (d.vfull_chat.type() != mtpc_channelFull) { LOG(("MTP Error: bad type in gotChatFull for channel: %1").arg(d.vfull_chat.type())); @@ -217,6 +222,10 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt auto &f(d.vfull_chat.c_channelFull()); PhotoData *photo = App::feedPhoto(f.vchat_photo); ChannelData *channel = peer->asChannel(); + + auto canViewAdmins = channel->canViewAdmins(); + auto canViewMembers = channel->canViewMembers(); + channel->flagsFull = f.vflags.v; if (photo) { channel->photoId = photo->id; @@ -267,17 +276,10 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt } break; } } - channel->about = qs(f.vabout); - int32 newCount = f.has_participants_count() ? f.vparticipants_count.v : 0; - if (newCount != channel->count) { - if (channel->isMegagroup() && !channel->mgInfo->lastParticipants.isEmpty()) { - channel->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; - channel->mgInfo->lastParticipantsCount = channel->count; - } - channel->count = newCount; - } - channel->adminsCount = f.has_admins_count() ? f.vadmins_count.v : 0; - channel->invitationUrl = (f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString(); + channel->setAbout(qs(f.vabout)); + channel->setMembersCount(f.has_participants_count() ? f.vparticipants_count.v : 0); + channel->setAdminsCount(f.has_admins_count() ? f.vadmins_count.v : 0); + channel->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString()); if (History *h = App::historyLoaded(channel->id)) { if (h->inboxReadBefore < f.vread_inbox_max_id.v + 1) { h->setUnreadCount(f.vunread_count.v); @@ -294,7 +296,10 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt } channel->fullUpdated(); - App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), f.vnotify_settings); + if (canViewAdmins != channel->canViewAdmins()) Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::ChannelCanViewAdmins); + if (canViewMembers != channel->canViewMembers()) Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::ChannelCanViewMembers); + + notifySettingReceived(MTP_inputNotifyPeer(peer->input), f.vnotify_settings); } if (req) { @@ -313,18 +318,17 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt } App::clearPeerUpdated(peer); emit fullPeerUpdated(peer); - App::emitPeerUpdated(); } void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req) { const auto &d(result.c_userFull()); - App::feedUsers(MTP_vector(1, d.vuser), false); + App::feedUsers(MTP_vector(1, d.vuser)); if (d.has_profile_photo()) { App::feedPhoto(d.vprofile_photo); } - App::feedUserLink(MTP_int(peerToUser(peer->id)), d.vlink.c_contacts_link().vmy_link, d.vlink.c_contacts_link().vforeign_link, false); + App::feedUserLink(MTP_int(peerToUser(peer->id)), d.vlink.c_contacts_link().vmy_link, d.vlink.c_contacts_link().vforeign_link); if (App::main()) { - App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), d.vnotify_settings); + notifySettingReceived(MTP_inputNotifyPeer(peer->input), d.vnotify_settings); } if (d.has_bot_info()) { @@ -332,8 +336,8 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI } else { peer->asUser()->setBotInfoVersion(-1); } - peer->asUser()->blocked = d.is_blocked() ? UserIsBlocked : UserIsNotBlocked; - peer->asUser()->about = d.has_about() ? qs(d.vabout) : QString(); + peer->asUser()->setBlockStatus(d.is_blocked() ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked); + peer->asUser()->setAbout(d.has_about() ? qs(d.vabout) : QString()); if (req) { QMap::iterator i = _fullPeerRequests.find(peer); @@ -343,7 +347,6 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI } App::clearPeerUpdated(peer); emit fullPeerUpdated(peer); - App::emitPeerUpdated(); } bool ApiWrap::gotPeerFullFailed(PeerData *peer, const RPCError &error) { @@ -538,14 +541,19 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP h->clearLastKeyboard(); if (App::main()) App::main()->updateBotKeyboard(h); } - if (d.vcount.v > peer->count) { - peer->count = d.vcount.v; - } else if (v.count() > peer->count) { - peer->count = v.count(); + int newMembersCount = qMax(d.vcount.v, v.count()); + if (newMembersCount > peer->membersCount()) { + peer->setMembersCount(newMembersCount); } - if (!bots && v.isEmpty()) { - peer->count = peer->mgInfo->lastParticipants.size(); + if (!bots) { + if (v.isEmpty()) { + peer->setMembersCount(peer->mgInfo->lastParticipants.size()); + } + Notify::PeerUpdate update(peer); + update.flags |= Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged; + Notify::peerUpdatedDelayed(update); } + peer->mgInfo->botStatus = botStatus; if (App::main()) emit fullPeerUpdated(peer); } @@ -626,27 +634,33 @@ void ApiWrap::kickParticipant(PeerData *peer, UserData *user) { void ApiWrap::kickParticipantDone(KickRequest kick, const MTPUpdates &result, mtpRequestId req) { _kickRequests.remove(kick); if (kick.first->isMegagroup()) { - int32 i = kick.first->asChannel()->mgInfo->lastParticipants.indexOf(kick.second); + auto channel = kick.first->asChannel(); + auto megagroupInfo = channel->mgInfo; + + int32 i = megagroupInfo->lastParticipants.indexOf(kick.second); if (i >= 0) { - kick.first->asChannel()->mgInfo->lastParticipants.removeAt(i); + megagroupInfo->lastParticipants.removeAt(i); } - if (kick.first->asChannel()->count > 1) { - --kick.first->asChannel()->count; + + if (channel->membersCount() > 1) { + channel->setMembersCount(channel->membersCount() - 1); } else { - kick.first->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; - kick.first->asChannel()->mgInfo->lastParticipantsCount = 0; + megagroupInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; + megagroupInfo->lastParticipantsCount = 0; } - if (kick.first->asChannel()->mgInfo->lastAdmins.contains(kick.second)) { - kick.first->asChannel()->mgInfo->lastAdmins.remove(kick.second); - if (kick.first->asChannel()->adminsCount > 1) { - --kick.first->asChannel()->adminsCount; + if (megagroupInfo->lastAdmins.contains(kick.second)) { + megagroupInfo->lastAdmins.remove(kick.second); + if (channel->adminsCount() > 1) { + channel->setAdminsCount(channel->adminsCount() - 1); } + Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::AdminsChanged); } - kick.first->asChannel()->mgInfo->bots.remove(kick.second); - if (kick.first->asChannel()->mgInfo->bots.isEmpty() && kick.first->asChannel()->mgInfo->botStatus > 0) { - kick.first->asChannel()->mgInfo->botStatus = -1; + megagroupInfo->bots.remove(kick.second); + if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) { + megagroupInfo->botStatus = -1; } } + Notify::peerUpdatedDelayed(kick.first, Notify::PeerUpdate::Flag::MembersChanged); emit fullPeerUpdated(kick.first); } @@ -672,6 +686,156 @@ void ApiWrap::requestStickerSets() { } } +void ApiWrap::joinChannel(ChannelData *channel) { + if (channel->amIn()) { + channelAmInUpdated(channel); + } else if (!_channelAmInRequests.contains(channel)) { + auto requestId = MTP::send(MTPchannels_JoinChannel(channel->inputChannel), rpcDone(&ApiWrap::channelAmInDone, channel), rpcFail(&ApiWrap::channelAmInFail, channel)); + _channelAmInRequests.insert(channel, requestId); + } +} + +void ApiWrap::leaveChannel(ChannelData *channel) { + if (!channel->amIn()) { + channelAmInUpdated(channel); + } else if (!_channelAmInRequests.contains(channel)) { + auto requestId = MTP::send(MTPchannels_LeaveChannel(channel->inputChannel), rpcDone(&ApiWrap::channelAmInDone, channel), rpcFail(&ApiWrap::channelAmInFail, channel)); + _channelAmInRequests.insert(channel, requestId); + } +} + +void ApiWrap::channelAmInUpdated(ChannelData *channel) { + Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::ChannelAmIn); +} + +void ApiWrap::channelAmInDone(ChannelData *channel, const MTPUpdates &updates) { + _channelAmInRequests.remove(channel); + + updatesReceived(updates); +} + +bool ApiWrap::channelAmInFail(ChannelData *channel, const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + _channelAmInRequests.remove(channel); + return true; +} + +void ApiWrap::blockUser(UserData *user) { + if (user->isBlocked()) { + Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsBlocked); + } else if (!_blockRequests.contains(user)) { + auto requestId = MTP::send(MTPcontacts_Block(user->inputUser), rpcDone(&ApiWrap::blockDone, user), rpcFail(&ApiWrap::blockFail, user)); + _blockRequests.insert(user, requestId); + } +} + +void ApiWrap::unblockUser(UserData *user) { + if (!user->isBlocked()) { + Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsBlocked); + } else if (!_blockRequests.contains(user)) { + auto requestId = MTP::send(MTPcontacts_Unblock(user->inputUser), rpcDone(&ApiWrap::unblockDone, user), rpcFail(&ApiWrap::blockFail, user)); + _blockRequests.insert(user, requestId); + } +} + +void ApiWrap::blockDone(UserData *user, const MTPBool &result) { + _blockRequests.remove(user); + user->setBlockStatus(UserData::BlockStatus::Blocked); + emit App::main()->peerUpdated(user); +} + +void ApiWrap::unblockDone(UserData *user, const MTPBool &result) { + _blockRequests.remove(user); + user->setBlockStatus(UserData::BlockStatus::NotBlocked); + emit App::main()->peerUpdated(user); +} + +bool ApiWrap::blockFail(UserData *user, const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + _blockRequests.remove(user); + return true; +} + +void ApiWrap::exportInviteLink(PeerData *peer) { + if (_exportInviteRequests.contains(peer)) { + return; + } + + mtpRequestId request = 0; + if (auto chat = peer->asChat()) { + request = MTP::send(MTPmessages_ExportChatInvite(chat->inputChat), rpcDone(&ApiWrap::exportInviteDone, peer), rpcFail(&ApiWrap::exportInviteFail, peer)); + } else if (auto channel = peer->asChannel()) { + request = MTP::send(MTPchannels_ExportInvite(channel->inputChannel), rpcDone(&ApiWrap::exportInviteDone, peer), rpcFail(&ApiWrap::exportInviteFail, peer)); + } + if (request) { + _exportInviteRequests.insert(peer, request); + } +} + +void ApiWrap::exportInviteDone(PeerData *peer, const MTPExportedChatInvite &result) { + _exportInviteRequests.remove(peer); + if (auto chat = peer->asChat()) { + chat->setInviteLink((result.type() == mtpc_chatInviteExported) ? qs(result.c_chatInviteExported().vlink) : QString()); + } else if (auto channel = peer->asChannel()) { + channel->setInviteLink((result.type() == mtpc_chatInviteExported) ? qs(result.c_chatInviteExported().vlink) : QString()); + } +} + +bool ApiWrap::exportInviteFail(PeerData *peer, const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + _exportInviteRequests.remove(peer); + return true; +} + +void ApiWrap::requestNotifySetting(PeerData *peer) { + if (_notifySettingRequests.contains(peer)) return; + + MTPInputNotifyPeer notifyPeer = MTP_inputNotifyPeer(peer->input); + auto requestId = MTP::send(MTPaccount_GetNotifySettings(notifyPeer), rpcDone(&ApiWrap::notifySettingDone, notifyPeer), rpcFail(&ApiWrap::notifySettingFail, peer)); + _notifySettingRequests.insert(peer, requestId); +} + +void ApiWrap::notifySettingDone(MTPInputNotifyPeer notifyPeer, const MTPPeerNotifySettings &result) { + if (auto requestedPeer = notifySettingReceived(notifyPeer, result)) { + _notifySettingRequests.remove(requestedPeer); + } +} + +PeerData *ApiWrap::notifySettingReceived(MTPInputNotifyPeer notifyPeer, const MTPPeerNotifySettings &settings) { + PeerData *requestedPeer = nullptr; + switch (notifyPeer.type()) { + case mtpc_inputNotifyAll: App::main()->applyNotifySetting(MTP_notifyAll(), settings); break; + case mtpc_inputNotifyUsers: App::main()->applyNotifySetting(MTP_notifyUsers(), settings); break; + case mtpc_inputNotifyChats: App::main()->applyNotifySetting(MTP_notifyChats(), settings); break; + case mtpc_inputNotifyPeer: { + auto &peer = notifyPeer.c_inputNotifyPeer().vpeer; + switch (peer.type()) { + case mtpc_inputPeerEmpty: App::main()->applyNotifySetting(MTP_notifyPeer(MTP_peerUser(MTP_int(0))), settings); break; + case mtpc_inputPeerSelf: requestedPeer = App::self(); break; + case mtpc_inputPeerUser: requestedPeer = App::user(peerFromUser(peer.c_inputPeerUser().vuser_id)); break; + case mtpc_inputPeerChat: requestedPeer = App::chat(peerFromChat(peer.c_inputPeerChat().vchat_id)); break; + case mtpc_inputPeerChannel: requestedPeer = App::channel(peerFromChannel(peer.c_inputPeerChannel().vchannel_id)); break; + } + if (requestedPeer) { + App::main()->applyNotifySetting(MTP_notifyPeer(peerToMTP(requestedPeer->id)), settings); + } + } break; + } + App::wnd()->notifySettingGot(); + return requestedPeer; +} + +bool ApiWrap::notifySettingFail(PeerData *peer, const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + notifySettingReceived(MTP_inputNotifyPeer(peer->input), MTP_peerNotifySettingsEmpty()); + _notifySettingRequests.remove(peer); + return true; +} + void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) { _stickerSetRequests.remove(setId); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 606ef7f14..0469e0139 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -28,7 +28,7 @@ public: ApiWrap(QObject *parent); void init(); - typedef SharedCallback RequestMessageDataCallback; + using RequestMessageDataCallback = SharedCallback; void requestMessageData(ChannelData *channel, MsgId msgId, std_::unique_ptr callback); void requestFullPeer(PeerData *peer); @@ -50,6 +50,15 @@ public: void scheduleStickerSetRequest(uint64 setId, uint64 access); void requestStickerSets(); + void joinChannel(ChannelData *channel); + void leaveChannel(ChannelData *channel); + + void blockUser(UserData *user); + void unblockUser(UserData *user); + + void exportInviteLink(PeerData *peer); + void requestNotifySetting(PeerData *peer); + ~ApiWrap(); signals: @@ -65,6 +74,8 @@ public slots: private: + void updatesReceived(const MTPUpdates &updates); + void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req); struct MessageDataRequest { MessageDataRequest() : req(0) { @@ -120,4 +131,24 @@ private: void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result); bool gotStickerSetFail(uint64 setId, const RPCError &error); + QMap _channelAmInRequests; + void channelAmInUpdated(ChannelData *channel); + void channelAmInDone(ChannelData *channel, const MTPUpdates &updates); + bool channelAmInFail(ChannelData *channel, const RPCError &error); + + QMap _blockRequests; + void blockDone(UserData *user, const MTPBool &result); + void unblockDone(UserData *user, const MTPBool &result); + bool blockFail(UserData *user, const RPCError &error); + + QMap _exportInviteRequests; + void exportInviteDone(PeerData *peer, const MTPExportedChatInvite &result); + bool exportInviteFail(PeerData *peer, const RPCError &error); + + QMap _notifySettingRequests; + void notifySettingDone(MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings); + PeerData *notifySettingReceived(MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings); + bool notifySettingFail(PeerData *peer, const RPCError &error); + + }; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 31c2cf8b7..13f1883c4 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -35,6 +35,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "apiwrap.h" #include "numbers.h" +#include "observer_peer.h" namespace { App::LaunchState _launchState = App::Launched; @@ -222,11 +223,11 @@ namespace { } } - int32 onlineForSort(UserData *user, int32 now) { + TimeId onlineForSort(UserData *user, TimeId now) { if (isServiceUser(user->id) || user->botInfo) { return -1; } - int32 online = user->onlineTill; + TimeId online = user->onlineTill; if (online <= 0) { switch (online) { case 0: @@ -252,11 +253,14 @@ namespace { return online; } - int32 onlineWillChangeIn(UserData *user, int32 now) { + int32 onlineWillChangeIn(UserData *user, TimeId now) { if (isServiceUser(user->id) || user->botInfo) { return 86400; } - int32 online = user->onlineTill; + return onlineWillChangeIn(user->onlineTill, now); + } + + int32 onlineWillChangeIn(TimeId online, TimeId now) { if (online <= 0) { if (-online > now) return -online - now; return 86400; @@ -276,7 +280,7 @@ namespace { return dNow.secsTo(dTomorrow); } - QString onlineText(UserData *user, int32 now, bool precise) { + QString onlineText(UserData *user, TimeId now, bool precise) { if (isNotificationsUser(user->id)) { return lang(lng_status_service_notifications); } else if (isServiceUser(user->id)) { @@ -284,7 +288,10 @@ namespace { } else if (user->botInfo) { return lang(lng_status_bot); } - int32 online = user->onlineTill; + return onlineText(user->onlineTill, now, precise); + } + + QString onlineText(TimeId online, TimeId now, bool precise) { if (online <= 0) { switch (online) { case 0: return lang(lng_status_offline); @@ -346,11 +353,14 @@ namespace { } } - bool onlineColorUse(UserData *user, int32 now) { + bool onlineColorUse(UserData *user, TimeId now) { if (isServiceUser(user->id) || user->botInfo) { return false; } - int32 online = user->onlineTill; + return onlineColorUse(user->onlineTill, now); + } + + bool onlineColorUse(TimeId online, TimeId now) { if (online <= 0) { switch (online) { case 0: @@ -364,21 +374,25 @@ namespace { return (online > now); } - UserData *feedUsers(const MTPVector &users, bool emitPeerUpdated) { - UserData *data = 0; - const auto &v(users.c_vector().v); - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const auto &user(*i); - data = 0; + UserData *feedUsers(const MTPVector &users) { + UserData *result = nullptr; + for_const (auto &user, users.c_vector().v) { + UserData *data = nullptr; bool wasContact = false, minimal = false; const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); + Notify::PeerUpdate update; + using UpdateFlag = Notify::PeerUpdate::Flag; + switch (user.type()) { case mtpc_userEmpty: { const auto &d(user.c_userEmpty()); PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); + auto canShareThisContact = data->canShareThisContactFast(); + wasContact = data->isContact(); + data->input = MTP_inputPeerUser(d.vid, MTP_long(0)); data->inputUser = MTP_inputUser(d.vid, MTP_long(0)); data->setName(lang(lng_deleted), QString(), QString(), QString()); @@ -386,9 +400,11 @@ namespace { data->access = UserNoAccess; data->flags = 0; data->setBotInfoVersion(-1); - wasContact = (data->contact > 0); status = &emptyStatus; data->contact = -1; + + if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact; + if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact; } break; case mtpc_user: { const auto &d(user.c_user()); @@ -396,6 +412,8 @@ namespace { PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); + auto canShareThisContact = data->canShareThisContactFast(); + wasContact = data->isContact(); if (!minimal) { data->flags = d.vflags.v; if (d.is_self()) { @@ -415,7 +433,10 @@ namespace { } } if (d.is_deleted()) { - data->setPhone(QString()); + if (!data->phone().isEmpty()) { + data->setPhone(QString()); + update.flags |= UpdateFlag::UserPhoneChanged; + } data->setName(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); data->access = UserNoAccess; @@ -427,12 +448,14 @@ namespace { QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName; QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName; - QString phone = minimal ? data->phone : (d.has_phone() ? qs(d.vphone) : QString()); + QString phone = minimal ? data->phone() : (d.has_phone() ? qs(d.vphone) : QString()); QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString()); - bool phoneChanged = (data->phone != phone); - if (phoneChanged) data->setPhone(phone); - + bool phoneChanged = (data->phone() != phone); + if (phoneChanged) { + data->setPhone(phone); + update.flags |= UpdateFlag::UserPhoneChanged; + } bool nameChanged = (data->firstName != fname) || (data->lastName != lname); bool showPhone = !isServiceUser(data->id) && !d.is_self() && !d.is_contact() && !d.is_mutual_contact(); @@ -458,7 +481,6 @@ namespace { if (d.has_access_hash()) data->access = d.vaccess_hash.v; status = d.has_status() ? &d.vstatus : &emptyStatus; } - wasContact = (data->contact > 0); if (!minimal) { if (d.has_bot_info_version()) { data->setBotInfoVersion(d.vbot_info_version.v); @@ -468,7 +490,7 @@ namespace { } else { data->setBotInfoVersion(-1); } - data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone.isEmpty() ? -1 : 0); + data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone().isEmpty() ? -1 : 0); if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsHidden) != dbiprsHidden) { cRefReportSpamStatuses().insert(data->id, dbiprsHidden); Local::writeReportSpamStatuses(); @@ -478,6 +500,9 @@ namespace { if (App::wnd()) App::wnd()->updateGlobalMenu(); } } + + if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact; + if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact; } break; } @@ -490,6 +515,8 @@ namespace { } else if (data->loadedStatus != PeerData::FullLoaded) { data->loadedStatus = PeerData::FullLoaded; } + + auto oldOnlineTill = data->onlineTill; if (status && !minimal) switch (status->type()) { case mtpc_userStatusEmpty: data->onlineTill = 0; break; case mtpc_userStatusRecently: @@ -502,8 +529,11 @@ namespace { case mtpc_userStatusOffline: data->onlineTill = status->c_userStatusOffline().vwas_online.v; break; case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break; } + if (oldOnlineTill != data->onlineTill) { + update.flags |= UpdateFlag::UserOnlineChanged; + } - if (data->contact < 0 && !data->phone.isEmpty() && peerToUser(data->id) != MTP::authedId()) { + if (data->contact < 0 && !data->phone().isEmpty() && peerToUser(data->id) != MTP::authedId()) { data->contact = 0; } if (App::main()) { @@ -511,34 +541,42 @@ namespace { Notify::userIsContactChanged(data); } - if (emitPeerUpdated) { - App::main()->peerUpdated(data); - } else { - markPeerUpdated(data); + markPeerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdatedDelayed(update); } } + result = data; } - return data; + return result; } - PeerData *feedChats(const MTPVector &chats, bool emitPeerUpdated) { - PeerData *data = 0; - const auto &v(chats.c_vector().v); - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const auto &chat(*i); - data = 0; + PeerData *feedChats(const MTPVector &chats) { + PeerData *result = nullptr; + for_const (auto &chat, chats.c_vector().v) { + PeerData *data = nullptr; bool minimal = false; + + Notify::PeerUpdate update; + using UpdateFlag = Notify::PeerUpdate::Flag; + switch (chat.type()) { case mtpc_chat: { - const auto &d(chat.c_chat()); + auto &d(chat.c_chat()); data = App::chat(peerFromChat(d.vid.v)); + auto cdata = data->asChat(); + auto canEdit = cdata->canEdit(); + + if (cdata->version < d.vversion.v) { + cdata->version = d.vversion.v; + cdata->invalidateParticipants(); + } + data->input = MTP_inputPeerChat(d.vid); - - data->updateName(qs(d.vtitle), QString(), QString()); - - ChatData *cdata = data->asChat(); + cdata->setName(qs(d.vtitle)); cdata->setPhoto(d.vphoto); cdata->date = d.vdate.v; @@ -571,9 +609,11 @@ namespace { } } Notify::migrateUpdated(channel); + update.flags |= UpdateFlag::MigrationChanged; } if (updatedTo) { Notify::migrateUpdated(cdata); + update.flags |= UpdateFlag::MigrationChanged; } } @@ -584,43 +624,52 @@ namespace { cdata->count = d.vparticipants_count.v; cdata->isForbidden = false; - if (cdata->version < d.vversion.v) { - cdata->version = d.vversion.v; - cdata->invalidateParticipants(); + if (canEdit != cdata->canEdit()) { + update.flags |= UpdateFlag::ChatCanEdit; } } break; case mtpc_chatForbidden: { - const auto &d(chat.c_chatForbidden()); + auto &d(chat.c_chatForbidden()); data = App::chat(peerFromChat(d.vid.v)); + auto cdata = data->asChat(); + auto canEdit = cdata->canEdit(); + data->input = MTP_inputPeerChat(d.vid); - - data->updateName(qs(d.vtitle), QString(), QString()); - - ChatData *cdata = data->asChat(); + cdata->setName(qs(d.vtitle)); cdata->setPhoto(MTP_chatPhotoEmpty()); cdata->date = 0; cdata->count = -1; cdata->invalidateParticipants(); cdata->flags = 0; cdata->isForbidden = true; + if (canEdit != cdata->canEdit()) { + update.flags |= UpdateFlag::ChatCanEdit; + } } break; case mtpc_channel: { - const auto &d(chat.c_channel()); + auto &d(chat.c_channel()); - PeerId peer(peerFromChannel(d.vid.v)); + auto peerId = peerFromChannel(d.vid.v); minimal = d.is_min(); if (minimal) { - data = App::channelLoaded(peer); + data = App::channelLoaded(peerId); if (!data) { continue; // minimal is not loaded, need to make getDifference } } else { - data = App::channel(peer); + data = App::channel(peerId); data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0)); } - ChannelData *cdata = data->asChannel(); + auto cdata = data->asChannel(); + auto wasInChannel = cdata->amIn(); + auto canEditPhoto = cdata->canEditPhoto(); + auto canViewAdmins = cdata->canViewAdmins(); + auto canViewMembers = cdata->canViewMembers(); + auto canAddMembers = cdata->canAddMembers(); + auto wasEditor = cdata->amEditor(); + if (minimal) { auto mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask); @@ -644,15 +693,32 @@ namespace { cdata->isForbidden = false; cdata->flagsUpdated(); cdata->setPhoto(d.vphoto); + + if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn; + if (canEditPhoto != cdata->canEditPhoto()) update.flags |= UpdateFlag::ChannelCanEditPhoto; + if (canViewAdmins != cdata->canViewAdmins()) update.flags |= UpdateFlag::ChannelCanViewAdmins; + if (canViewMembers != cdata->canViewMembers()) update.flags |= UpdateFlag::ChannelCanViewMembers; + if (canAddMembers != cdata->canAddMembers()) update.flags |= UpdateFlag::ChannelCanAddMembers; + if (wasEditor != cdata->amEditor()) { + cdata->selfAdminUpdated(); + update.flags |= (UpdateFlag::ChannelAmEditor | UpdateFlag::AdminsChanged); + } } break; case mtpc_channelForbidden: { - const auto &d(chat.c_channelForbidden()); + auto &d(chat.c_channelForbidden()); - PeerId peer(peerFromChannel(d.vid.v)); - data = App::channel(peer); + auto peerId = peerFromChannel(d.vid.v); + data = App::channel(peerId); data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash); - ChannelData *cdata = data->asChannel(); + auto cdata = data->asChannel(); + auto wasInChannel = cdata->amIn(); + auto canEditPhoto = cdata->canEditPhoto(); + auto canViewAdmins = cdata->canViewAdmins(); + auto canViewMembers = cdata->canViewMembers(); + auto canAddMembers = cdata->canAddMembers(); + auto wasEditor = cdata->amEditor(); + cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); auto mask = mtpCastFlags(MTPDchannelForbidden::Flag::f_broadcast | MTPDchannelForbidden::Flag::f_megagroup); @@ -663,9 +729,19 @@ namespace { cdata->access = d.vaccess_hash.v; cdata->setPhoto(MTP_chatPhotoEmpty()); cdata->date = 0; - cdata->count = 0; + cdata->setMembersCount(0); cdata->isForbidden = true; cdata->flagsUpdated(); + + if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn; + if (canEditPhoto != cdata->canEditPhoto()) update.flags |= UpdateFlag::ChannelCanEditPhoto; + if (canViewAdmins != cdata->canViewAdmins()) update.flags |= UpdateFlag::ChannelCanViewAdmins; + if (canViewMembers != cdata->canViewMembers()) update.flags |= UpdateFlag::ChannelCanViewMembers; + if (canAddMembers != cdata->canAddMembers()) update.flags |= UpdateFlag::ChannelCanAddMembers; + if (wasEditor != cdata->amEditor()) { + cdata->selfAdminUpdated(); + update.flags |= (UpdateFlag::ChannelAmEditor | UpdateFlag::AdminsChanged); + } } break; } if (!data) continue; @@ -678,14 +754,15 @@ namespace { data->loadedStatus = PeerData::FullLoaded; } if (App::main()) { - if (emitPeerUpdated) { - App::main()->peerUpdated(data); - } else { - markPeerUpdated(data); + markPeerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdatedDelayed(update); } } + result = data; } - return data; + return result; } void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated) { @@ -701,6 +778,7 @@ namespace { case mtpc_chatParticipants: { const auto &d(p.c_chatParticipants()); chat = App::chat(d.vchat_id.v); + auto canEdit = chat->canEdit(); if (!requestBotInfos || chat->version <= d.vversion.v) { // !requestBotInfos is true on getFullChat result chat->version = d.vversion.v; const auto &v(d.vparticipants.c_vector().v); @@ -772,8 +850,12 @@ namespace { } } } + if (canEdit != chat->canEdit()) { + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit); + } } break; } + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged); if (chat && App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(chat); @@ -820,6 +902,7 @@ namespace { chat->invalidateParticipants(); chat->count++; } + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged); if (App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(chat); @@ -845,6 +928,7 @@ namespace { } } else if (chat->version <= d.vversion.v && chat->count > 0) { chat->version = d.vversion.v; + auto canEdit = chat->canEdit(); UserData *user = App::userLoaded(d.vuser_id.v); if (user) { if (chat->participants.isEmpty()) { @@ -886,6 +970,10 @@ namespace { chat->invalidateParticipants(); chat->count--; } + if (canEdit != chat->canEdit()) { + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit); + } + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged); if (App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(chat); @@ -906,13 +994,14 @@ namespace { } chat->version = d.vversion.v; if (mtpIsTrue(d.venabled)) { - chat->flags |= MTPDchat::Flag::f_admins_enabled; if (!badVersion) { chat->invalidateParticipants(); } + chat->flags |= MTPDchat::Flag::f_admins_enabled; } else { chat->flags &= ~MTPDchat::Flag::f_admins_enabled; } + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged); if (emitPeerUpdated) { App::main()->peerUpdated(chat); } else { @@ -936,6 +1025,7 @@ namespace { } } else if (chat->version <= d.vversion.v && chat->count > 0) { chat->version = d.vversion.v; + auto canEdit = chat->canEdit(); UserData *user = App::userLoaded(d.vuser_id.v); if (user) { if (mtpIsTrue(d.vis_admin)) { @@ -956,6 +1046,10 @@ namespace { } else { chat->invalidateParticipants(); } + if (canEdit != chat->canEdit()) { + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit); + } + Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged); if (App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(chat); @@ -1163,33 +1257,10 @@ namespace { } } - void feedUserLinks(const MTPVector &links, bool emitPeerUpdated) { - const auto &v(links.c_vector().v); - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const auto &dv(i->c_contacts_link()); - UserData *user = feedUsers(MTP_vector(1, dv.vuser), false); - MTPint userId(MTP_int(0)); - switch (dv.vuser.type()) { - case mtpc_userEmpty: userId = dv.vuser.c_userEmpty().vid; break; - case mtpc_user: userId = dv.vuser.c_user().vid; break; - } - if (userId.v) { - feedUserLink(userId, dv.vmy_link, dv.vforeign_link, false); - } - if (user && App::main()) { - if (emitPeerUpdated) { - App::main()->peerUpdated(user); - } else { - markPeerUpdated(user); - } - } - } - } - - void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink, bool emitPeerUpdated) { + void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink) { UserData *user = userLoaded(userId.v); if (user) { - bool wasContact = (user->contact > 0); + auto wasContact = user->isContact(); bool wasShowPhone = !user->contact; switch (myLink.type()) { case mtpc_contactLinkContact: @@ -1208,10 +1279,14 @@ namespace { break; } if (user->contact < 1) { - if (user->contact < 0 && !user->phone.isEmpty() && peerToUser(user->id) != MTP::authedId()) { + if (user->contact < 0 && !user->phone().isEmpty() && peerToUser(user->id) != MTP::authedId()) { user->contact = 0; } } + + if (wasContact != user->isContact()) { + Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsContact); + } if ((user->contact > 0 && !wasContact) || (wasContact && user->contact < 1)) { Notify::userIsContactChanged(user); } @@ -1219,15 +1294,9 @@ namespace { bool showPhone = !isServiceUser(user->id) && !user->isSelf() && !user->contact; bool showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && ((showPhone && !wasShowPhone) || (!showPhone && wasShowPhone)); if (showPhoneChanged) { - user->setName(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone) : QString(), textOneLine(user->username)); - } - if (App::main()) { - if (emitPeerUpdated) { - App::main()->peerUpdated(user); - } else { - markPeerUpdated(user); - } + user->setName(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone()) : QString(), textOneLine(user->username)); } + markPeerUpdated(user); } } @@ -2272,6 +2341,10 @@ namespace { return result; } + QPixmap pixmapFromImageInPlace(QImage &&image) { + return QPixmap::fromImage(std_::forward(image), Qt::ColorOnly); + } + void regPhotoItem(PhotoData *data, HistoryItem *item) { ::photoItems[data].insert(item, NullType()); } @@ -2317,11 +2390,21 @@ namespace { } void regSharedContactItem(int32 userId, HistoryItem *item) { + auto user = App::userLoaded(userId); + auto canShareThisContact = user ? user->canShareThisContact() : false; ::sharedContactItems[userId].insert(item, NullType()); + if (canShareThisContact != (user ? user->canShareThisContact() : false)) { + Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserCanShareContact); + } } void unregSharedContactItem(int32 userId, HistoryItem *item) { + auto user = App::userLoaded(userId); + auto canShareThisContact = user ? user->canShareThisContact() : false; ::sharedContactItems[userId].remove(item); + if (canShareThisContact != (user ? user->canShareThisContact() : false)) { + Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserCanShareContact); + } } const SharedContactItems &sharedContactItems() { diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 0b24ff72d..3ce9600e0 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -59,13 +59,17 @@ namespace App { QString formatPhone(QString phone); - int32 onlineForSort(UserData *user, int32 now); - int32 onlineWillChangeIn(UserData *user, int32 nowOnServer); - QString onlineText(UserData *user, int32 nowOnServer, bool precise = false); - bool onlineColorUse(UserData *user, int32 now); + TimeId onlineForSort(UserData *user, TimeId now); + int32 onlineWillChangeIn(UserData *user, TimeId now); + int32 onlineWillChangeIn(TimeId online, TimeId now); + QString onlineText(UserData *user, TimeId now, bool precise = false); + QString onlineText(TimeId online, TimeId now, bool precise = false); + bool onlineColorUse(UserData *user, TimeId now); + bool onlineColorUse(TimeId online, TimeId now); + + UserData *feedUsers(const MTPVector &users); // returns last user + PeerData *feedChats(const MTPVector &chats); // returns last chat - UserData *feedUsers(const MTPVector &users, bool emitPeerUpdated = true); // returns last user - PeerData *feedChats(const MTPVector &chats, bool emitPeerUpdated = true); // returns last chat void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated = true); void feedParticipantAdd(const MTPDupdateChatParticipantAdd &d, bool emitPeerUpdated = true); void feedParticipantDelete(const MTPDupdateChatParticipantDelete &d, bool emitPeerUpdated = true); @@ -80,12 +84,10 @@ namespace App { void feedInboxRead(const PeerId &peer, MsgId upTo); void feedOutboxRead(const PeerId &peer, MsgId upTo); void feedWereDeleted(ChannelId channelId, const QVector &msgsIds); - void feedUserLinks(const MTPVector &links, bool emitPeerUpdated = true); - void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink, bool emitPeerUpdated = true); + void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink); void markPeerUpdated(PeerData *data); void clearPeerUpdated(PeerData *data); - void emitPeerUpdated(); ImagePtr image(const MTPPhotoSize &size); StorageImageLocation imageLocation(int32 w, int32 h, const MTPFileLocation &loc); @@ -231,6 +233,7 @@ namespace App { QImage readImage(QByteArray data, QByteArray *format = 0, bool opaque = true, bool *animated = 0); QImage readImage(const QString &file, QByteArray *format = 0, bool opaque = true, bool *animated = 0, QByteArray *content = 0); + QPixmap pixmapFromImageInPlace(QImage &&image); void regPhotoItem(PhotoData *data, HistoryItem *item); void unregPhotoItem(PhotoData *data, HistoryItem *item); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 89950515e..184352a61 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -27,9 +27,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "lang.h" #include "boxes/confirmbox.h" +#include "ui/filedialog.h" #include "langloaderplain.h" #include "localstorage.h" #include "autoupdater.h" +#include "core/observer.h" +#include "observer_peer.h" namespace { void mtpStateChanged(int32 dc, int32 state) { @@ -200,6 +203,7 @@ void Application::singleInstanceChecked() { Logs::multipleInstances(); } + Notify::startObservers(); Sandbox::start(); if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) { @@ -336,6 +340,8 @@ void Application::closeApplication() { if (_updateThread) _updateThread->quit(); _updateThread = 0; #endif + + Notify::finishObservers(); } #ifndef TDESKTOP_DISABLE_AUTOUPDATE @@ -902,6 +908,18 @@ void AppClass::call_handleUnreadCounterUpdate() { } } +void AppClass::call_handleFileDialogQueue() { + while (true) { + if (!FileDialog::processQuery()) { + return; + } + } +} + +void AppClass::call_handleDelayedPeerUpdates() { + Notify::peerUpdatedSendDelayed(); +} + void AppClass::killDownloadSessions() { uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout; for (QMap::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) { diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index f894102fe..b7d595eed 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -203,6 +203,8 @@ public slots: void call_handleHistoryUpdate(); void call_handleUnreadCounterUpdate(); + void call_handleFileDialogQueue(); + void call_handleDelayedPeerUpdates(); private: diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index 0ad979dab..7e64bd52d 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -32,9 +32,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org AboutBox::AboutBox() : AbstractBox(st::aboutWidth) , _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink) -, _text1(this, lang(lng_about_text_1), st::aboutLabel, st::aboutTextStyle) -, _text2(this, lang(lng_about_text_2), st::aboutLabel, st::aboutTextStyle) -, _text3(this, QString(), st::aboutLabel, st::aboutTextStyle) +, _text1(this, lang(lng_about_text_1), FlatLabel::InitType::Rich, st::aboutLabel, st::aboutTextStyle) +, _text2(this, lang(lng_about_text_2), FlatLabel::InitType::Rich, st::aboutLabel, st::aboutTextStyle) +, _text3(this,st::aboutLabel, st::aboutTextStyle) , _done(this, lang(lng_close), st::defaultBoxButton) { _text3.setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]"))); @@ -109,10 +109,9 @@ void AboutBox::paintEvent(QPaintEvent *e) { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS QString _getCrashReportFile(const QMimeData *m) { - if (!m || m->urls().size() != 1) return QString(); + if (!m || m->urls().size() != 1 || !m->urls().at(0).isLocalFile()) return QString(); - QString file(m->urls().at(0).toLocalFile()); - if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file); + auto file = psConvertFileUrl(m->urls().at(0)); return file.endsWith(qstr(".telegramcrash"), Qt::CaseInsensitive) ? file : QString(); } diff --git a/Telegram/SourceFiles/boxes/aboutbox.h b/Telegram/SourceFiles/boxes/aboutbox.h index ceccef089..45af6b009 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.h +++ b/Telegram/SourceFiles/boxes/aboutbox.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "abstractbox.h" +#include "ui/flatlabel.h" class AboutBox : public AbstractBox { Q_OBJECT diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 648e102bc..5b1024489 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -30,18 +30,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" +#include "observer_peer.h" AddContactBox::AddContactBox(QString fname, QString lname, QString phone) : AbstractBox(st::boxWidth) -, _user(0) , _save(this, lang(lng_add_contact), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _retry(this, lang(lng_try_other_contact), st::defaultBoxButton) , _first(this, st::defaultInputField, lang(lng_signup_firstname), fname) , _last(this, st::defaultInputField, lang(lng_signup_lastname), lname) , _phone(this, st::defaultInputField, lang(lng_contact_phone), phone) -, _invertOrder(langFirstNameGoesSecond()) -, _contactId(0) -, _addRequest(0) { +, _invertOrder(langFirstNameGoesSecond()) { if (!phone.isEmpty()) { _phone.setDisabled(true); } @@ -56,10 +54,8 @@ AddContactBox::AddContactBox(UserData *user) : AbstractBox(st::boxWidth) , _retry(this, lang(lng_try_other_contact), st::defaultBoxButton) , _first(this, st::defaultInputField, lang(lng_signup_firstname), user->firstName) , _last(this, st::defaultInputField, lang(lng_signup_lastname), user->lastName) -, _phone(this, st::defaultInputField, lang(lng_contact_phone), user->phone) -, _invertOrder(langFirstNameGoesSecond()) -, _contactId(0) -, _addRequest(0) { +, _phone(this, st::defaultInputField, lang(lng_contact_phone), user->phone()) +, _invertOrder(langFirstNameGoesSecond()) { _phone.setDisabled(true); initBox(); } @@ -190,7 +186,7 @@ void AddContactBox::onSave() { _sentName = firstName; if (_user) { _contactId = rand_value(); - QVector v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone), MTP_string(firstName), MTP_string(lastName))); + QVector v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone()), MTP_string(firstName), MTP_string(lastName))); _addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector(v), MTP_bool(false)), rpcDone(&AddContactBox::onSaveUserDone), rpcFail(&AddContactBox::onSaveUserFail)); } else { _contactId = rand_value(); @@ -206,7 +202,7 @@ bool AddContactBox::onSaveUserFail(const RPCError &error) { QString err(error.type()); QString firstName = _first.getLastText().trimmed(), lastName = _last.getLastText().trimmed(); if (err == "CHAT_TITLE_NOT_MODIFIED") { - _user->updateName(firstName, QString(), QString()); + _user->setName(firstName, lastName, _user->nameOrPhone, _user->username); emit closed(); return true; } else if (err == "NO_CHAT_TITLE") { @@ -548,7 +544,7 @@ bool GroupInfoBox::creationFail(const RPCError &error) { void GroupInfoBox::exportDone(const MTPExportedChatInvite &result) { _creationRequestId = 0; if (result.type() == mtpc_chatInviteExported) { - _createdChannel->invitationUrl = qs(result.c_chatInviteExported().vlink); + _createdChannel->setInviteLink(qs(result.c_chatInviteExported().vlink)); } Ui::showLayer(new SetupChannelBox(_createdChannel)); } @@ -712,7 +708,7 @@ void SetupChannelBox::paintEvent(QPaintEvent *e) { option.setWrapMode(QTextOption::WrapAnywhere); p.setFont(_linkOver ? st::boxTextFont->underline() : st::boxTextFont); p.setPen(st::btnDefLink.color); - p.drawText(_invitationLink, _channel->invitationUrl, option); + p.drawText(_invitationLink, _channel->inviteLink(), option); if (!_goodTextLink.isEmpty() && a_goodOpacity.current() > 0) { p.setOpacity(a_goodOpacity.current()); p.setPen(st::setGoodColor); @@ -753,7 +749,7 @@ void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) { void SetupChannelBox::mousePressEvent(QMouseEvent *e) { mouseMoveEvent(e); if (_linkOver) { - Application::clipboard()->setText(_channel->invitationUrl); + Application::clipboard()->setText(_channel->inviteLink()); _goodTextLink = lang(lng_create_channel_link_copied); a_goodOpacity = anim::fvalue(1, 0); _a_goodFade.start(); @@ -1140,7 +1136,9 @@ bool EditNameTitleBox::onSaveChatFail(const RPCError &error) { _requestId = 0; QString err(error.type()); if (err == qstr("CHAT_TITLE_NOT_MODIFIED") || err == qstr("CHAT_NOT_MODIFIED")) { - _peer->updateName(_sentName, QString(), QString()); + if (auto chatData = _peer->asChat()) { + chatData->setName(_sentName); + } emit closed(); return true; } else if (err == qstr("NO_CHAT_TITLE")) { @@ -1162,7 +1160,7 @@ EditChannelBox::EditChannelBox(ChannelData *channel) : AbstractBox() , _save(this, lang(lng_settings_save), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _title(this, st::defaultInputField, lang(lng_dlg_new_channel_name), _channel->name) -, _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about) +, _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about()) , _sign(this, lang(lng_edit_sign_messages), channel->addsSignature()) , _publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::defaultBoxLinkButton) , _saveTitleRequestId(0) @@ -1303,7 +1301,7 @@ void EditChannelBox::onPublicLink() { } void EditChannelBox::saveDescription() { - if (_sentDescription == _channel->about) { + if (_sentDescription == _channel->about()) { saveSign(); } else { _saveDescriptionRequestId = MTP::send(MTPchannels_EditAbout(_channel->inputChannel, MTP_string(_sentDescription)), rpcDone(&EditChannelBox::onSaveDescriptionDone), rpcFail(&EditChannelBox::onSaveFail)); @@ -1338,9 +1336,10 @@ bool EditChannelBox::onSaveFail(const RPCError &error, mtpRequestId req) { } else if (req == _saveDescriptionRequestId) { _saveDescriptionRequestId = 0; if (err == qstr("CHAT_ABOUT_NOT_MODIFIED")) { - _channel->about = _sentDescription; - if (App::api()) { - emit App::api()->fullPeerUpdated(_channel); + if (_channel->setAbout(_sentDescription)) { + if (App::api()) { + emit App::api()->fullPeerUpdated(_channel); + } } saveSign(); return true; @@ -1367,9 +1366,10 @@ void EditChannelBox::onSaveTitleDone(const MTPUpdates &updates) { void EditChannelBox::onSaveDescriptionDone(const MTPBool &result) { _saveDescriptionRequestId = 0; - _channel->about = _sentDescription; - if (App::api()) { - emit App::api()->fullPeerUpdated(_channel); + if (_channel->setAbout(_sentDescription)) { + if (App::api()) { + emit App::api()->fullPeerUpdated(_channel); + } } saveSign(); } diff --git a/Telegram/SourceFiles/boxes/addcontactbox.h b/Telegram/SourceFiles/boxes/addcontactbox.h index 71dd0f02c..e12ccc3aa 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.h +++ b/Telegram/SourceFiles/boxes/addcontactbox.h @@ -57,7 +57,7 @@ private: void initBox(); - UserData *_user; + UserData *_user = nullptr; QString _boxTitle; BoxButton _save, _cancel, _retry; @@ -66,9 +66,9 @@ private: bool _invertOrder; - uint64 _contactId; + uint64 _contactId = 0; - mtpRequestId _addRequest; + mtpRequestId _addRequest = 0; QString _sentName; }; diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index fcd6b5e1d..3b2dc1cce 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -381,11 +381,10 @@ void ConvertToSupergroupBox::resizeEvent(QResizeEvent *e) { PinMessageBox::PinMessageBox(ChannelData *channel, MsgId msgId) : AbstractBox(st::boxWidth) , _channel(channel) , _msgId(msgId) -, _text(this, lang(lng_pinned_pin_sure), st::boxLabel) +, _text(this, lang(lng_pinned_pin_sure), FlatLabel::InitType::Simple, st::boxLabel) , _notify(this, lang(lng_pinned_notify), true) , _pin(this, lang(lng_pinned_pin), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _requestId(0) { +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { _text.resizeToWidth(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()); setMaxHeight(st::boxPadding.top() + _text.height() + st::boxMediumSkip + _notify.height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _pin.height() + st::boxButtonPadding.bottom()); @@ -441,7 +440,7 @@ RichDeleteMessageBox::RichDeleteMessageBox(ChannelData *channel, UserData *from, , _channel(channel) , _from(from) , _msgId(msgId) -, _text(this, lang(lng_selected_delete_sure_this), st::boxLabel) +, _text(this, lang(lng_selected_delete_sure_this), FlatLabel::InitType::Simple, st::boxLabel) , _banUser(this, lang(lng_ban_user), false) , _reportSpam(this, lang(lng_report_spam), false) , _deleteAll(this, lang(lng_delete_all_from), false) @@ -478,6 +477,7 @@ void RichDeleteMessageBox::onDelete() { if (HistoryItem *item = App::histItemById(_channel ? peerToChannel(_channel->id) : 0, _msgId)) { bool wasLast = (item->history()->lastMsg == item); item->destroy(); + if (_msgId > 0) { App::main()->deleteMessages(_channel, QVector(1, MTP_int(_msgId))); } else if (wasLast) { diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index 097e6f436..e7af61470 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "abstractbox.h" +#include "ui/flatlabel.h" class InformBox; class ConfirmBox : public AbstractBox, public ClickHandlerHost { @@ -216,7 +217,7 @@ private: BoxButton _pin, _cancel; - mtpRequestId _requestId; + mtpRequestId _requestId = 0; }; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 92540758a..10d7da7c4 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -31,6 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/filedialog.h" #include "boxes/photocropbox.h" #include "boxes/confirmbox.h" +#include "observer_peer.h" #include "apiwrap.h" QString cantInviteError() { @@ -201,16 +202,20 @@ void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) { _addAdminRequestId = 0; if (_addAdmin && _channel && _channel->isMegagroup()) { + Notify::PeerUpdate update(_channel); if (_channel->mgInfo->lastParticipants.indexOf(_addAdmin) < 0) { _channel->mgInfo->lastParticipants.push_front(_addAdmin); + update.flags |= Notify::PeerUpdate::Flag::MembersChanged; } _channel->mgInfo->lastAdmins.insert(_addAdmin); + update.flags |= Notify::PeerUpdate::Flag::AdminsChanged; if (_addAdmin->botInfo) { _channel->mgInfo->bots.insert(_addAdmin); if (_channel->mgInfo->botStatus != 0 && _channel->mgInfo->botStatus < 2) { _channel->mgInfo->botStatus = 2; } } + Notify::peerUpdatedDelayed(update); } if (_addAdminBox) _addAdminBox->onClose(); emit adminAdded(); @@ -224,7 +229,7 @@ bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) { _addAdminRequestId = 0; if (_addAdminBox) _addAdminBox->onClose(); if (error.type() == "USERS_TOO_MUCH") { - Ui::showLayer(new MaxInviteBox(_channel->invitationUrl), KeepOtherLayers); + Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers); } else if (error.type() == "ADMINS_TOO_MUCH") { Ui::showLayer(new InformBox(lang(lng_channel_admins_too_much)), KeepOtherLayers); } else if (error.type() == qstr("USER_RESTRICTED")) { @@ -728,7 +733,7 @@ void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) { _checkedContacts.insert(peer, true); ++_selCount; } else if (_channel && !_channel->isMegagroup()) { - Ui::showLayer(new MaxInviteBox(_channel->invitationUrl), KeepOtherLayers); + Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers); } else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) { Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers); } @@ -740,7 +745,7 @@ int32 ContactsInner::selectedCount() const { if (_chat) { result += qMax(_chat->count, 1); } else if (_channel) { - result += qMax(_channel->count, _already.size()); + result += qMax(_channel->membersCount(), _already.size()); } else if (_creating == CreatingGroupGroup) { result += 1; } @@ -1739,7 +1744,7 @@ bool ContactsBox::creationFail(const RPCError &error) { MembersInner::MembersInner(ChannelData *channel, MembersFilter filter) : TWidget() , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _newItemHeight((channel->amCreator() && (channel->count < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilterAdmins)) ? st::contactsNewItemHeight : 0) +, _newItemHeight((channel->amCreator() && (channel->membersCount() < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilterAdmins)) ? st::contactsNewItemHeight : 0) , _newItemSel(false) , _channel(channel) , _filter(filter) @@ -1808,7 +1813,7 @@ void MembersInner::paintEvent(QPaintEvent *e) { paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown); p.translate(0, _rowHeight); } - if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->count || _rows.size() >= Global::ChatSizeMax())) { + if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->membersCount() || _rows.size() >= Global::ChatSizeMax())) { p.setPen(st::stickersReorderFg); _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); } @@ -1976,7 +1981,7 @@ void MembersInner::chooseParticipant() { if (_sel < 0 || _sel >= _rows.size()) return; if (PeerData *peer = _rows[_sel]) { Ui::hideLayer(); - App::main()->showPeerProfile(peer, ShowAtUnreadMsgId); + Ui::showPeerProfile(peer); } } @@ -1987,7 +1992,7 @@ void MembersInner::refresh() { } else { _about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size())); _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - if (_filter != MembersFilterRecent || (_rows.size() >= _channel->count && _rows.size() < Global::ChatSizeMax())) { + if (_filter != MembersFilterRecent || (_rows.size() >= _channel->membersCount() && _rows.size() < Global::ChatSizeMax())) { _aboutHeight = 0; } resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight); @@ -2003,11 +2008,11 @@ MembersFilter MembersInner::filter() const { return _filter; } -QMap MembersInner::already() const { +MembersAlreadyIn MembersInner::already() const { MembersAlreadyIn result; - for (int32 i = 0, l = _rows.size(); i < l; ++i) { - if (_rows.at(i)->isUser()) { - result.insert(_rows.at(i)->asUser(), true); + for_const (auto peer, _rows) { + if (peer->isUser()) { + result.insert(peer->asUser()); } } return result; @@ -2115,14 +2120,16 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result _datas.reserve(v.size()); _dates.reserve(v.size()); _roles.reserve(v.size()); - if (_filter == MembersFilterRecent && _channel->count < d.vcount.v) { - _channel->count = d.vcount.v; + + if (_filter == MembersFilterRecent && _channel->membersCount() < d.vcount.v) { + _channel->setMembersCount(d.vcount.v); if (App::main()) emit App::main()->peerUpdated(_channel); - } else if (_filter == MembersFilterAdmins && _channel->adminsCount < d.vcount.v) { - _channel->adminsCount = d.vcount.v; + } else if (_filter == MembersFilterAdmins && _channel->adminsCount() < d.vcount.v) { + _channel->setAdminsCount(d.vcount.v); if (App::main()) emit App::main()->peerUpdated(_channel); } App::feedUsers(d.vusers); + for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { int32 userId = 0, addedTime = 0; MemberRole role = MemberRoleNone; @@ -2173,6 +2180,8 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result _channel->mgInfo->lastAdmins.insert(_rows.at(i)); } } + + Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged); } } if (_rows.isEmpty()) { @@ -2229,11 +2238,11 @@ void MembersInner::removeKicked() { _dates.removeAt(index); _roles.removeAt(index); clearSel(); - if (_filter == MembersFilterRecent && _channel->count > 1) { - --_channel->count; + if (_filter == MembersFilterRecent && _channel->membersCount() > 1) { + _channel->setMembersCount(_channel->membersCount() - 1); if (App::main()) emit App::main()->peerUpdated(_channel); - } else if (_filter == MembersFilterAdmins && _channel->adminsCount > 1) { - --_channel->adminsCount; + } else if (_filter == MembersFilterAdmins && _channel->adminsCount() > 1) { + _channel->setAdminsCount(_channel->adminsCount() - 1); if (App::main()) emit App::main()->peerUpdated(_channel); } refresh(); @@ -2290,8 +2299,8 @@ void MembersBox::onScroll() { } void MembersBox::onAdd() { - if (_inner.filter() == MembersFilterRecent && _inner.channel()->count >= (_inner.channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { - Ui::showLayer(new MaxInviteBox(_inner.channel()->invitationUrl), KeepOtherLayers); + if (_inner.filter() == MembersFilterRecent && _inner.channel()->membersCount() >= (_inner.channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { + Ui::showLayer(new MaxInviteBox(_inner.channel()->inviteLink()), KeepOtherLayers); return; } ContactsBox *box = new ContactsBox(_inner.channel(), _inner.filter(), _inner.already()); diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index e1bb53673..d40ce5924 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -31,7 +31,7 @@ enum MembersFilter { MembersFilterRecent, MembersFilterAdmins, }; -typedef QMap MembersAlreadyIn; +using MembersAlreadyIn = OrderedSet; QString cantInviteError(); @@ -318,7 +318,7 @@ public: } void clearSel(); - QMap already() const; + MembersAlreadyIn already() const; ~MembersInner(); diff --git a/Telegram/SourceFiles/boxes/report_box.cpp b/Telegram/SourceFiles/boxes/report_box.cpp new file mode 100644 index 000000000..aec3dbc3f --- /dev/null +++ b/Telegram/SourceFiles/boxes/report_box.cpp @@ -0,0 +1,147 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "report_box.h" + +#include "lang.h" +#include "styles/style_profile.h" +#include "boxes/confirmbox.h" + +ReportBox::ReportBox(ChannelData *channel) : AbstractBox(st::boxWidth) +, _channel(channel) +, _reasonSpam(this, qsl("report_reason"), ReasonSpam, lang(lng_report_reason_spam), true) +, _reasonViolence(this, qsl("report_reason"), ReasonViolence, lang(lng_report_reason_violence)) +, _reasonPornography(this, qsl("report_reason"), ReasonPornography, lang(lng_report_reason_pornography)) +, _reasonOther(this, qsl("report_reason"), ReasonOther, lang(lng_report_reason_other)) +, _report(this, lang(lng_report_button), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { + connect(_report, SIGNAL(clicked()), this, SLOT(onReport())); + connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + + connect(_reasonSpam, SIGNAL(changed()), this, SLOT(onChange())); + connect(_reasonViolence, SIGNAL(changed()), this, SLOT(onChange())); + connect(_reasonPornography, SIGNAL(changed()), this, SLOT(onChange())); + connect(_reasonOther, SIGNAL(changed()), this, SLOT(onChange())); + + updateMaxHeight(); + + prepare(); +} + +void ReportBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + paintTitle(p, lang(lng_report_title)); +} + +void ReportBox::resizeEvent(QResizeEvent *e) { + _reasonSpam->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxTitleHeight + st::boxOptionListPadding.top()); + _reasonViolence->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonSpam->y() + _reasonSpam->height() + st::boxOptionListPadding.top()); + _reasonPornography->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonViolence->y() + _reasonViolence->height() + st::boxOptionListPadding.top()); + _reasonOther->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonPornography->y() + _reasonPornography->height() + st::boxOptionListPadding.top()); + + if (_reasonOtherText) { + _reasonOtherText->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() - st::defaultInputField.textMargins.left(), _reasonOther->y() + _reasonOther->height() + st::newGroupDescriptionPadding.top()); + } + + _report->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _report->height()); + _cancel->moveToRight(st::boxButtonPadding.right() + _report->width() + st::boxButtonPadding.left(), _report->y()); +} + +void ReportBox::onChange() { + if (_reasonOther->checked()) { + if (!_reasonOtherText) { + _reasonOtherText = new InputArea(this, st::profileReportReasonOther, lang(lng_report_reason_description)); + _reasonOtherText->show(); + _reasonOtherText->setCtrlEnterSubmit(CtrlEnterSubmitBoth); + _reasonOtherText->setMaxLength(MaxPhotoCaption); + _reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height()); + + updateMaxHeight(); + connect(_reasonOtherText, SIGNAL(resized()), this, SLOT(onDescriptionResized())); + connect(_reasonOtherText, SIGNAL(submitted(bool)), this, SLOT(onReport())); + connect(_reasonOtherText, SIGNAL(cancelled()), this, SLOT(onClose())); + } + } else if (_reasonOtherText) { + _reasonOtherText.destroy(); + updateMaxHeight(); + } + setInnerFocus(); +} + +void ReportBox::setInnerFocus() { + if (_reasonOtherText) { + _reasonOtherText->setFocus(); + } else { + setFocus(); + } +} + +void ReportBox::onDescriptionResized() { + updateMaxHeight(); + update(); +} + +void ReportBox::onReport() { + if (_requestId) return; + + if (_reasonOtherText && _reasonOtherText->getLastText().trimmed().isEmpty()) { + _reasonOtherText->showError(); + return; + } + + auto getReason = [this]() { + if (_reasonViolence->checked()) { + return MTP_inputReportReasonViolence(); + } else if (_reasonPornography->checked()) { + return MTP_inputReportReasonPornography(); + } else if (_reasonOtherText) { + return MTP_inputReportReasonOther(MTP_string(_reasonOtherText->getLastText())); + } else { + return MTP_inputReportReasonSpam(); + } + }; + _requestId = MTP::send(MTPaccount_ReportPeer(_channel->input, getReason()), rpcDone(&ReportBox::reportDone), rpcFail(&ReportBox::reportFail)); +} + +void ReportBox::reportDone(const MTPBool &result) { + _requestId = 0; + Ui::showLayer(new InformBox(lang(lng_report_thanks))); +} + +bool ReportBox::reportFail(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + _requestId = 0; + if (_reasonOtherText) { + _reasonOtherText->showError(); + } + return true; +} + +void ReportBox::updateMaxHeight() { + int32 h = st::boxTitleHeight + 4 * (st::boxOptionListPadding.top() + _reasonSpam->height()) + st::boxButtonPadding.top() + _report->height() + st::boxButtonPadding.bottom(); + if (_reasonOtherText) { + h += st::newGroupDescriptionPadding.top() + _reasonOtherText->height() + st::newGroupDescriptionPadding.bottom(); + } + setMaxHeight(h); +} diff --git a/Telegram/SourceFiles/boxes/report_box.h b/Telegram/SourceFiles/boxes/report_box.h new file mode 100644 index 000000000..d4d08eb16 --- /dev/null +++ b/Telegram/SourceFiles/boxes/report_box.h @@ -0,0 +1,72 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "abstractbox.h" + +class ReportBox : public AbstractBox, public RPCSender { + Q_OBJECT + +public: + ReportBox(ChannelData *channel); + +private slots: + void onReport(); + void onChange(); + void onDescriptionResized(); + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + + void showAll() override { + showChildren(); + } + void hideAll() override { + hideChildren(); + } + void setInnerFocus() override; + +private: + void updateMaxHeight(); + + void reportDone(const MTPBool &result); + bool reportFail(const RPCError &error); + + ChannelData *_channel; + + ChildWidget _reasonSpam; + ChildWidget _reasonViolence; + ChildWidget _reasonPornography; + ChildWidget _reasonOther; + ChildWidget _reasonOtherText = { nullptr }; + + ChildWidget _report, _cancel; + + enum Reason { + ReasonSpam, + ReasonViolence, + ReasonPornography, + ReasonOther, + }; + mtpRequestId _requestId = 0; + +}; diff --git a/Telegram/SourceFiles/codegen/style/generator.cpp b/Telegram/SourceFiles/codegen/style/generator.cpp index 5c6ce69f9..801d5220f 100644 --- a/Telegram/SourceFiles/codegen/style/generator.cpp +++ b/Telegram/SourceFiles/codegen/style/generator.cpp @@ -289,7 +289,7 @@ QString Generator::valueAssignmentCode(structure::Value value) const { } break; case Tag::Icon: { auto v(value.Icon()); - if (v.parts.empty()) return QString(); + if (v.parts.empty()) return QString("{}"); QStringList parts; for (const auto &part : v.parts) { diff --git a/Telegram/SourceFiles/codegen/style/parsed_file.cpp b/Telegram/SourceFiles/codegen/style/parsed_file.cpp index 1a397765f..859a7a428 100644 --- a/Telegram/SourceFiles/codegen/style/parsed_file.cpp +++ b/Telegram/SourceFiles/codegen/style/parsed_file.cpp @@ -535,8 +535,8 @@ structure::Value ParsedFile::readPointValue() { if (tokenValue(font) == "point") { assertNextToken(BasicType::LeftParenthesis); - auto x = readNumericValue(); assertNextToken(BasicType::Comma); - auto y = readNumericValue(); + auto x = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); + auto y = readNumericOrNumericCopyValue(); if (x.type().tag != structure::TypeTag::Pixels || y.type().tag != structure::TypeTag::Pixels) { logErrorTypeMismatch() << "expected two px values for the point"; @@ -581,8 +581,8 @@ structure::Value ParsedFile::readSizeValue() { if (tokenValue(font) == "size") { assertNextToken(BasicType::LeftParenthesis); - auto w = readNumericValue(); assertNextToken(BasicType::Comma); - auto h = readNumericValue(); + auto w = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); + auto h = readNumericOrNumericCopyValue(); if (w.type().tag != structure::TypeTag::Pixels || h.type().tag != structure::TypeTag::Pixels) { logErrorTypeMismatch() << "expected two px values for the size"; @@ -662,10 +662,10 @@ structure::Value ParsedFile::readMarginsValue() { if (tokenValue(font) == "margins") { assertNextToken(BasicType::LeftParenthesis); - auto l = readNumericValue(); assertNextToken(BasicType::Comma); - auto t = readNumericValue(); assertNextToken(BasicType::Comma); - auto r = readNumericValue(); assertNextToken(BasicType::Comma); - auto b = readNumericValue(); + auto l = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); + auto t = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); + auto r = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma); + auto b = readNumericOrNumericCopyValue(); if (l.type().tag != structure::TypeTag::Pixels || t.type().tag != structure::TypeTag::Pixels || r.type().tag != structure::TypeTag::Pixels || @@ -701,18 +701,10 @@ structure::Value ParsedFile::readFontValue() { file_.putBack(); } } - if (auto familyValue = readStringValue()) { + if (auto familyValue = readStringOrStringCopyValue()) { family = familyValue; - } else if (auto sizeValue = readNumericValue()) { + } else if (auto sizeValue = readNumericOrNumericCopyValue()) { size = sizeValue; - } else if (auto copyValue = readCopyValue()) { - if (copyValue.type().tag == structure::TypeTag::String) { - family = copyValue; - } else if (copyValue.type().tag == structure::TypeTag::Pixels) { - size = copyValue; - } else { - logErrorUnexpectedToken() << "font family, font size or ')'"; - } } else if (file_.getToken(BasicType::RightParenthesis)) { break; } else { @@ -776,7 +768,37 @@ structure::Value ParsedFile::readCopyValue() { if (auto variable = module_->findVariable(name)) { return variable->value.makeCopy(variable->name); } - logError(kErrorIdentifierNotFound) << "identifier '" << logFullName(name) << "' not found"; + file_.putBack(); + } + return {}; +} + +structure::Value ParsedFile::readNumericOrNumericCopyValue() { + if (auto result = readNumericValue()) { + return result; + } else if (auto copy = readCopyValue()) { + auto type = copy.type().tag; + if (type == structure::TypeTag::Int + || type == structure::TypeTag::Double + || type == structure::TypeTag::Pixels) { + return copy; + } else { + file_.putBack(); + } + } + return {}; +} + +structure::Value ParsedFile::readStringOrStringCopyValue() { + if (auto result = readStringValue()) { + return result; + } else if (auto copy = readCopyValue()) { + auto type = copy.type().tag; + if (type == structure::TypeTag::String) { + return copy; + } else { + file_.putBack(); + } } return {}; } diff --git a/Telegram/SourceFiles/codegen/style/parsed_file.h b/Telegram/SourceFiles/codegen/style/parsed_file.h index 158782c53..8eea16483 100644 --- a/Telegram/SourceFiles/codegen/style/parsed_file.h +++ b/Telegram/SourceFiles/codegen/style/parsed_file.h @@ -98,6 +98,9 @@ private: structure::Value readIconValue(); structure::Value readCopyValue(); + structure::Value readNumericOrNumericCopyValue(); + structure::Value readStringOrStringCopyValue(); + structure::data::monoicon readMonoIconFields(); QString readMonoIconFilename(); @@ -131,6 +134,7 @@ private: { "align" , { structure::TypeTag::Align } }, { "margins" , { structure::TypeTag::Margins } }, { "font" , { structure::TypeTag::Font } }, + { "icon" , { structure::TypeTag::Icon } }, }; }; diff --git a/Telegram/SourceFiles/codegen/style/processor.cpp b/Telegram/SourceFiles/codegen/style/processor.cpp index b093a8d47..39f02c41e 100644 --- a/Telegram/SourceFiles/codegen/style/processor.cpp +++ b/Telegram/SourceFiles/codegen/style/processor.cpp @@ -74,14 +74,15 @@ bool Processor::write(const structure::Module &module) const { QFileInfo srcFile(module.filepath()); QString dstFilePath = dir.absolutePath() + '/' + destFileBaseName(module); + bool forceReGenerate = false;// !options_.rebuildDependencies; common::ProjectInfo project = { "codegen_style", srcFile.fileName(), "stdafx.h", - false,//!options_.rebuildDependencies, // forceReGenerate + forceReGenerate }; - SpriteGenerator spriteGenerator(module); + SpriteGenerator spriteGenerator(module, forceReGenerate); if (!spriteGenerator.writeSprites()) { return false; } diff --git a/Telegram/SourceFiles/codegen/style/sprite_generator.cpp b/Telegram/SourceFiles/codegen/style/sprite_generator.cpp index cc3c2c1bc..c7d07be97 100644 --- a/Telegram/SourceFiles/codegen/style/sprite_generator.cpp +++ b/Telegram/SourceFiles/codegen/style/sprite_generator.cpp @@ -48,8 +48,9 @@ constexpr int kErrorCouldNotWrite = 845; } // namespace -SpriteGenerator::SpriteGenerator(const structure::Module &module) +SpriteGenerator::SpriteGenerator(const structure::Module &module, bool forceReGenerate) : module_(module) +, forceReGenerate_(forceReGenerate) , basePath_(QFileInfo(module.filepath()).dir().absolutePath()) { } @@ -84,7 +85,7 @@ bool SpriteGenerator::writeSprites() { } } QFile file(filepath); - if (file.open(QIODevice::ReadOnly)) { + if (!forceReGenerate_ && file.open(QIODevice::ReadOnly)) { if (file.readAll() == spriteData) { continue; } diff --git a/Telegram/SourceFiles/codegen/style/sprite_generator.h b/Telegram/SourceFiles/codegen/style/sprite_generator.h index 832a71984..fc1458db3 100644 --- a/Telegram/SourceFiles/codegen/style/sprite_generator.h +++ b/Telegram/SourceFiles/codegen/style/sprite_generator.h @@ -34,7 +34,7 @@ class Module; class SpriteGenerator { public: - SpriteGenerator(const structure::Module &module); + SpriteGenerator(const structure::Module &module, bool forceReGenerate); SpriteGenerator(const SpriteGenerator &other) = delete; SpriteGenerator &operator=(const SpriteGenerator &other) = delete; @@ -46,6 +46,7 @@ private: QImage generateSprite(int scale); // scale = 5 for 125% and 6 for 150%. const structure::Module &module_; + bool forceReGenerate_; QString basePath_; QImage sprite2x_; QList sprites_; diff --git a/Telegram/SourceFiles/core/basic_types.cpp b/Telegram/SourceFiles/core/basic_types.cpp index 2ce138a99..2eaa56db8 100644 --- a/Telegram/SourceFiles/core/basic_types.cpp +++ b/Telegram/SourceFiles/core/basic_types.cpp @@ -44,7 +44,6 @@ uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 }; #include // Base types compile-time check - static_assert(sizeof(char) == 1, "Basic types size check failed"); static_assert(sizeof(uchar) == 1, "Basic types size check failed"); static_assert(sizeof(int16) == 2, "Basic types size check failed"); diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index 9d1bab3a6..f6a57ba4e 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -33,6 +33,13 @@ T *getPointerAndReset(T *&ptr) { return result; } +template +T createAndSwap(T &value) { + T result; + std::swap(result, value); + return result; +} + struct NullType { }; @@ -421,6 +428,24 @@ inline bool operator!=(std::nullptr_t a, const unique_ptr &b) noexcept { return !(a == b); } +using _yes = char(&)[1]; +using _no = char(&)[2]; + +template +struct _host { + operator Base*() const; + operator Derived*(); +}; + +template +struct is_base_of { + template + static _yes check(Derived*, T); + static _no check(Base*, int); + + static constexpr bool value = sizeof(check(_host(), int())) == sizeof(_yes); +}; + } // namespace std_ #include "logs.h" @@ -927,15 +952,6 @@ private: }; -template -inline void destroyImplementation(I *&ptr) { - if (ptr) { - ptr->destroy(); - ptr = 0; - } - deleteAndMark(ptr); -} - class Composer; typedef void(*ComponentConstruct)(void *location, Composer *composer); typedef void(*ComponentDestruct)(void *location); @@ -1170,213 +1186,96 @@ public: virtual R call(Args... args) const = 0; virtual ~SharedCallback() { } - typedef QSharedPointer> Ptr; + using Ptr = QSharedPointer>; + }; -template +template class FunctionImplementation { public: - virtual R call() = 0; + virtual R call(Args... args) = 0; virtual void destroy() { delete this; } virtual ~FunctionImplementation() {} + }; -template -class NullFunctionImplementation : public FunctionImplementation { + +template +class NullFunctionImplementation : public FunctionImplementation { public: - virtual R call() { return R(); } + virtual R call(Args... args) { return R(); } virtual void destroy() {} - static NullFunctionImplementation SharedInstance; + static NullFunctionImplementation SharedInstance; + }; -template -NullFunctionImplementation NullFunctionImplementation::SharedInstance; -template -class FunctionCreator { -public: - FunctionCreator(FunctionImplementation *ptr) : _ptr(ptr) {} - FunctionCreator(const FunctionCreator &other) : _ptr(other.create()) {} - FunctionImplementation *create() const { return getPointerAndReset(_ptr); } - ~FunctionCreator() { destroyImplementation(_ptr); } -private: - FunctionCreator &operator=(const FunctionCreator &other); - mutable FunctionImplementation *_ptr; -}; -template +template +NullFunctionImplementation NullFunctionImplementation::SharedInstance; + +template class Function { public: - typedef FunctionCreator Creator; - static Creator Null() { return Creator(&NullFunctionImplementation::SharedInstance); } - Function(const Creator &creator) : _implementation(creator.create()) {} - R call() { return _implementation->call(); } - ~Function() { destroyImplementation(_implementation); } + Function() : _implementation(nullImpl()) {} + Function(FunctionImplementation *implementation) : _implementation(implementation) {} + Function(const Function &other) = delete; + Function &operator=(const Function &other) = delete; + Function(Function &&other) : _implementation(other._implementation) { + other._implementation = nullImpl(); + } + Function &operator=(Function &&other) { + std::swap(_implementation, other._implementation); + return *this; + } + + bool isNull() const { + return (_implementation == nullImpl()); + } + + R call(Args... args) { return _implementation->call(args...); } + ~Function() { + if (_implementation) { + _implementation->destroy(); + _implementation = nullptr; + } + deleteAndMark(_implementation); + } + private: - Function(const Function &other); - Function &operator=(const Function &other); - FunctionImplementation *_implementation; + static FunctionImplementation *nullImpl() { + return &NullFunctionImplementation::SharedInstance; + } + + FunctionImplementation *_implementation; + }; -template -class WrappedFunction : public FunctionImplementation { +template +class WrappedFunction : public FunctionImplementation { public: - typedef R(*Method)(); + using Method = R(*)(Args... args); WrappedFunction(Method method) : _method(method) {} - virtual R call() { return (*_method)(); } + virtual R call(Args... args) { return (*_method)(args...); } + private: Method _method; + }; -template -inline FunctionCreator func(R(*method)()) { - return FunctionCreator(new WrappedFunction(method)); +template +inline Function func(R(*method)(Args... args)) { + return Function(new WrappedFunction(method)); } -template -class ObjectFunction : public FunctionImplementation { + +template +class ObjectFunction : public FunctionImplementation { public: - typedef R(I::*Method)(); + using Method = R(I::*)(Args... args); ObjectFunction(O *obj, Method method) : _obj(obj), _method(method) {} - virtual R call() { return (_obj->*_method)(); } + virtual R call(Args... args) { return (_obj->*_method)(args...); } + private: O *_obj; Method _method; -}; -template -inline FunctionCreator func(O *obj, R(I::*method)()) { - return FunctionCreator(new ObjectFunction(obj, method)); -} -template -class Function1Implementation { -public: - virtual R call(A1 a1) = 0; - virtual void destroy() { delete this; } - virtual ~Function1Implementation() {} }; -template -class NullFunction1Implementation : public Function1Implementation { -public: - virtual R call(A1 a1) { return R(); } - virtual void destroy() {} - static NullFunction1Implementation SharedInstance; -}; -template -NullFunction1Implementation NullFunction1Implementation::SharedInstance; -template -class Function1Creator { -public: - Function1Creator(Function1Implementation *ptr) : _ptr(ptr) {} - Function1Creator(const Function1Creator &other) : _ptr(other.create()) {} - Function1Implementation *create() const { return getPointerAndReset(_ptr); } - ~Function1Creator() { destroyImplementation(_ptr); } -private: - Function1Creator &operator=(const Function1Creator &other); - mutable Function1Implementation *_ptr; -}; -template -class Function1 { -public: - typedef Function1Creator Creator; - static Creator Null() { return Creator(&NullFunction1Implementation::SharedInstance); } - Function1(const Creator &creator) : _implementation(creator.create()) {} - R call(A1 a1) { return _implementation->call(a1); } - ~Function1() { _implementation->destroy(); } -private: - Function1(const Function1 &other); - Function1 &operator=(const Function1 &other); - Function1Implementation *_implementation; -}; - -template -class WrappedFunction1 : public Function1Implementation { -public: - typedef R(*Method)(A1); - WrappedFunction1(Method method) : _method(method) {} - virtual R call(A1 a1) { return (*_method)(a1); } -private: - Method _method; -}; -template -inline Function1Creator func(R(*method)(A1)) { - return Function1Creator(new WrappedFunction1(method)); -} -template -class ObjectFunction1 : public Function1Implementation { -public: - typedef R(I::*Method)(A1); - ObjectFunction1(O *obj, Method method) : _obj(obj), _method(method) {} - virtual R call(A1 a1) { return (_obj->*_method)(a1); } -private: - O *_obj; - Method _method; -}; -template -Function1Creator func(O *obj, R(I::*method)(A1)) { - return Function1Creator(new ObjectFunction1(obj, method)); -} - -template -class Function2Implementation { -public: - virtual R call(A1 a1, A2 a2) = 0; - virtual void destroy() { delete this; } - virtual ~Function2Implementation() {} -}; -template -class NullFunction2Implementation : public Function2Implementation { -public: - virtual R call(A1 a1, A2 a2) { return R(); } - virtual void destroy() {} - static NullFunction2Implementation SharedInstance; -}; -template -NullFunction2Implementation NullFunction2Implementation::SharedInstance; -template -class Function2Creator { -public: - Function2Creator(Function2Implementation *ptr) : _ptr(ptr) {} - Function2Creator(const Function2Creator &other) : _ptr(other.create()) {} - Function2Implementation *create() const { return getPointerAndReset(_ptr); } - ~Function2Creator() { destroyImplementation(_ptr); } -private: - Function2Creator &operator=(const Function2Creator &other); - mutable Function2Implementation *_ptr; -}; -template -class Function2 { -public: - typedef Function2Creator Creator; - static Creator Null() { return Creator(&NullFunction2Implementation::SharedInstance); } - Function2(const Creator &creator) : _implementation(creator.create()) {} - R call(A1 a1, A2 a2) { return _implementation->call(a1, a2); } - ~Function2() { destroyImplementation(_implementation); } -private: - Function2(const Function2 &other); - Function2 &operator=(const Function2 &other); - Function2Implementation *_implementation; -}; - -template -class WrappedFunction2 : public Function2Implementation { -public: - typedef R(*Method)(A1, A2); - WrappedFunction2(Method method) : _method(method) {} - virtual R call(A1 a1, A2 a2) { return (*_method)(a1, a2); } -private: - Method _method; -}; -template -Function2Creator func(R(*method)(A1, A2)) { - return Function2Creator(new WrappedFunction2(method)); -} - -template -class ObjectFunction2 : public Function2Implementation { -public: - typedef R(I::*Method)(A1, A2); - ObjectFunction2(O *obj, Method method) : _obj(obj), _method(method) {} - virtual R call(A1 a1, A2 a2) { return (_obj->*_method)(a1, a2); } -private: - O *_obj; - Method _method; -}; -template -Function2Creator func(O *obj, R(I::*method)(A1, A2)) { - return Function2Creator(new ObjectFunction2(obj, method)); +template +inline Function func(O *obj, R(I::*method)(Args...)) { + return Function(new ObjectFunction(obj, method)); } diff --git a/Telegram/SourceFiles/core/click_handler.h b/Telegram/SourceFiles/core/click_handler.h index c0199fac9..61d9f3a56 100644 --- a/Telegram/SourceFiles/core/click_handler.h +++ b/Telegram/SourceFiles/core/click_handler.h @@ -27,6 +27,7 @@ enum ExpandLinksMode { ExpandLinksNone, ExpandLinksShortened, ExpandLinksAll, + ExpandLinksUrlOnly, // For custom urls leaves only url instead of text. }; class ClickHandlerHost { @@ -144,9 +145,11 @@ public: } static void hostDestroyed(ClickHandlerHost *host) { if (_activeHost == host) { + if (_active) (*_active).clear(); _activeHost = nullptr; } if (_pressedHost == host) { + if (_pressed) (*_pressed).clear(); _pressedHost = nullptr; } } diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 05043abf2..23b34fb76 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -110,15 +110,22 @@ QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const Q QString result; if (mode == ExpandLinksAll) { result = textPart.toString() + qsl(" (") + url() + ')'; + } else if (mode == ExpandLinksUrlOnly) { + result = url(); } return result; } TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { TextWithEntities result; - result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() }); - if (mode == ExpandLinksAll) { - result.text = textPart.toString() + qsl(" (") + url() + ')'; + if (mode == ExpandLinksUrlOnly) { + result.text = url(); + result.entities.push_back({ EntityInTextUrl, entityOffset, result.text.size() }); + } else { + result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() }); + if (mode == ExpandLinksAll) { + result.text = textPart.toString() + qsl(" (") + url() + ')'; + } } return result; } @@ -174,9 +181,19 @@ TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(ExpandLink return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() }); } +PeerData *BotCommandClickHandler::_peer = nullptr; +UserData *BotCommandClickHandler::_bot = nullptr; void BotCommandClickHandler::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton || button == Qt::MiddleButton) { - if (PeerData *peer = Ui::getPeerForMouseAction()) { + if (auto peer = peerForCommand()) { + if (auto bot = peer->isUser() ? peer->asUser() : botForCommand()) { + Ui::showPeerHistory(peer, ShowAtTheEndMsgId); + App::sendBotCommand(peer, bot, _cmd); + return; + } + } + + if (auto peer = Ui::getPeerForMouseAction()) { // old way UserData *bot = peer->isUser() ? peer->asUser() : nullptr; if (auto item = App::hoveredLinkItem()) { if (!bot) { diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index d25078278..84deafe77 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -193,6 +193,8 @@ private: }; +class PeerData; +class UserData; class BotCommandClickHandler : public TextClickHandler { public: BotCommandClickHandler(const QString &cmd) : _cmd(cmd) { @@ -204,14 +206,30 @@ public: return _cmd; } + static void setPeerForCommand(PeerData *peer) { + _peer = peer; + } + static void setBotForCommand(UserData *bot) { + _bot = bot; + } + TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; protected: QString url() const override { return _cmd; } + static PeerData *peerForCommand() { + return _peer; + } + static UserData *botForCommand() { + return _bot; + } private: QString _cmd; + static PeerData *_peer; + static UserData *_bot; + }; diff --git a/Telegram/SourceFiles/core/observer.cpp b/Telegram/SourceFiles/core/observer.cpp new file mode 100644 index 000000000..6ec33a341 --- /dev/null +++ b/Telegram/SourceFiles/core/observer.cpp @@ -0,0 +1,115 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "core/observer.h" + +namespace Notify { +namespace internal { +namespace { + +struct StartCallbackData { + void *that; + StartCallback call; +}; +struct FinishCallbackData { + void *that; + FinishCallback call; +}; +struct UnregisterCallbackData { + void *that; + UnregisterCallback call; +}; +using StartCallbacksList = QVector; +using FinishCallbacksList = QVector; +NeverFreedPointer StartCallbacks; +NeverFreedPointer FinishCallbacks; +UnregisterCallbackData UnregisterCallbacks[256]/* = { nullptr }*/; + +ObservedEvent LastRegisteredEvent/* = 0*/; + +} // namespace +} // namespace internal + +void startObservers() { + if (!internal::StartCallbacks) return; + + for (auto &callback : *internal::StartCallbacks) { + callback.call(callback.that); + } +} + +void finishObservers() { + if (!internal::FinishCallbacks) return; + + for (auto &callback : *internal::FinishCallbacks) { + callback.call(callback.that); + } + internal::StartCallbacks.clear(); + internal::FinishCallbacks.clear(); +} + +namespace internal { + +BaseObservedEventRegistrator::BaseObservedEventRegistrator(void *that +, StartCallback startCallback +, FinishCallback finishCallback +, UnregisterCallback unregisterCallback) { + _event = LastRegisteredEvent++; + + StartCallbacks.makeIfNull(); + StartCallbacks->push_back({ that, startCallback }); + + FinishCallbacks.makeIfNull(); + FinishCallbacks->push_back({ that, finishCallback }); + + UnregisterCallbacks[_event] = { that, unregisterCallback }; +} + +} // namespace internal + +// Observer base interface. +Observer::~Observer() { + for_const (auto connection, _connections) { + unregisterObserver(connection); + } +} + +void Observer::observerRegistered(ConnectionId connection) { + _connections.push_back(connection); +} + +void unregisterObserver(ConnectionId connection) { + auto event = static_cast(connection >> 24); + auto connectionIndex = int(connection & 0x00FFFFFFU) - 1; + auto &callback = internal::UnregisterCallbacks[event]; + if (connectionIndex >= 0 && callback.call && callback.that) { + callback.call(callback.that, connectionIndex); + } +} + +namespace internal { + +void observerRegisteredDefault(Observer *observer, ConnectionId connection) { + observer->observerRegistered(connection); +} + +} // namespace internal +} // namespace Notify diff --git a/Telegram/SourceFiles/core/observer.h b/Telegram/SourceFiles/core/observer.h new file mode 100644 index 000000000..1530328f8 --- /dev/null +++ b/Telegram/SourceFiles/core/observer.h @@ -0,0 +1,242 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "core/vector_of_moveable.h" + +namespace Notify { + +using ConnectionId = uint32; + +// startObservers() must be called after main() started (not in a global variable constructor). +// finishObservers() must be called before main() finished (not in a global variable destructor). +void startObservers(); +void finishObservers(); + +using StartObservedEventCallback = void(*)(); +using FinishObservedEventCallback = void(*)(); + +namespace internal { + +using ObservedEvent = uchar; +using StartCallback = void(*)(void*); +using FinishCallback = void(*)(void*); +using UnregisterCallback = void(*)(void*,int connectionIndex); + +class BaseObservedEventRegistrator { +public: + BaseObservedEventRegistrator(void *that + , StartCallback startCallback + , FinishCallback finishCallback + , UnregisterCallback unregisterCallback); + +protected: + inline ObservedEvent event() const { + return _event; + } + +private: + ObservedEvent _event; + +}; + +// Handler is one of Function<> instantiations. +template +struct ObserversList { + struct Entry { + Flags flags; + Handler handler; + }; + std_::vector_of_moveable entries; + QVector freeIndices; +}; + +// If no filtering by flags is done, you can use Flags=int and this value. +constexpr int UniversalFlag = 0x01; + +} // namespace internal + +// Objects of this class should be constructed in global scope. +// startCallback will be called from Notify::startObservers(). +// finishCallback will be called from Notify::finishObservers(). +template +class ObservedEventRegistrator : public internal::BaseObservedEventRegistrator { +public: + ObservedEventRegistrator(StartObservedEventCallback startCallback, + FinishObservedEventCallback finishCallback) : internal::BaseObservedEventRegistrator(reinterpret_cast(this), + ObservedEventRegistrator::start, + ObservedEventRegistrator::finish, + ObservedEventRegistrator::unregister) + , _startCallback(startCallback), _finishCallback(finishCallback) { + } + + bool started() const { + return _list != nullptr; + } + + ConnectionId registerObserver(Flags flags, Handler &&handler) { + t_assert(started()); + + int connectionIndex = doRegisterObserver(flags, std_::forward(handler)); + return (static_cast(event()) << 24) | static_cast(connectionIndex + 1); + } + + template + void notify(Flags flags, Args&&... args) { + t_assert(started()); + + for (auto &entry : _list->entries) { + if (!entry.handler.isNull() && (flags & entry.flags)) { + entry.handler.call(std_::forward(args)...); + } + } + } + +private: + using Self = ObservedEventRegistrator; + static void start(void *vthat) { + Self *that = reinterpret_cast(vthat); + + t_assert(!that->started()); + if (that->_startCallback) that->_startCallback(); + that->_list = new internal::ObserversList(); + } + static void finish(void *vthat) { + Self *that = reinterpret_cast(vthat); + + if (that->_finishCallback) that->_finishCallback(); + delete that->_list; + that->_list = nullptr; + } + static void unregister(void *vthat, int connectionIndex) { + Self *that = reinterpret_cast(vthat); + + t_assert(that->started()); + + auto &entries(that->_list->entries); + if (entries.size() <= connectionIndex) return; + + if (entries.size() == connectionIndex + 1) { + for (entries.pop_back(); !entries.isEmpty() && entries.back().handler.isNull();) { + entries.pop_back(); + } + } else { + entries[connectionIndex].handler = Handler(); + that->_list->freeIndices.push_back(connectionIndex); + } + } + + int doRegisterObserver(Flags flags, Handler &&handler) { + while (!_list->freeIndices.isEmpty()) { + auto freeIndex = _list->freeIndices.back(); + _list->freeIndices.pop_back(); + + if (freeIndex < _list->entries.size()) { + _list->entries[freeIndex] = { flags, std_::move(handler) }; + return freeIndex; + } + } + _list->entries.push_back({ flags, std_::move(handler) }); + return _list->entries.size() - 1; + } + + StartObservedEventCallback _startCallback; + FinishObservedEventCallback _finishCallback; + internal::ObserversList *_list = nullptr; + +}; + +// If no filtering of notifications by Flags is intended use this class. +template +class SimpleObservedEventRegistrator { +public: + SimpleObservedEventRegistrator(StartObservedEventCallback startCallback, + FinishObservedEventCallback finishCallback) : _implementation(startCallback, finishCallback) { + } + + bool started() const { + return _implementation.started(); + } + + ConnectionId registerObserver(Handler &&handler) { + return _implementation.registerObserver(internal::UniversalFlag, std_::forward(handler)); + } + + template + void notify(Args&&... args) { + return _implementation.notify(internal::UniversalFlag, std_::forward(args)...); + } + +private: + ObservedEventRegistrator _implementation; + +}; + +// Each observer type should have observerRegistered(Notify::ConnectionId connection) method. +// Usually it is done by deriving the type from the Notify::Observer base class. +// In destructor it should call Notify::unregisterObserver(connection) for all the connections. + +class Observer; +namespace internal { +void observerRegisteredDefault(Observer *observer, ConnectionId connection); +} // namespace internal + +void unregisterObserver(ConnectionId connection); + +class Observer { +public: + virtual ~Observer() = 0; + +private: + void observerRegistered(ConnectionId connection); + friend void internal::observerRegisteredDefault(Observer *observer, ConnectionId connection); + + QVector _connections; + +}; + +namespace internal { + +template +struct ObserverRegisteredGeneric { + static inline void call(ObserverType *observer, ConnectionId connection) { + observer->observerRegistered(connection); + } +}; + +template +struct ObserverRegisteredGeneric { + static inline void call(ObserverType *observer, ConnectionId connection) { + observerRegisteredDefault(observer, connection); + } +}; + +} // namespace internal + +template +inline void observerRegistered(ObserverType *observer, ConnectionId connection) { + // For derivatives of the Observer class we call special friend function observerRegistered(). + // For all other classes we call just a member function observerRegistered(). + using ObserverRegistered = internal::ObserverRegisteredGeneric::value>; + ObserverRegistered::call(observer, connection); +} + +} // namespace Notify diff --git a/Telegram/SourceFiles/core/vector_of_moveable.h b/Telegram/SourceFiles/core/vector_of_moveable.h new file mode 100644 index 000000000..412d87b35 --- /dev/null +++ b/Telegram/SourceFiles/core/vector_of_moveable.h @@ -0,0 +1,153 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +// some minimal implementation of std::vector() for moveable (but not copiable) types. +namespace std_ { + +template +class vector_of_moveable { + typedef vector_of_moveable Self; + int _size = 0, _capacity = 0; + void *_plaindata = nullptr; + +public: + inline T *data() { + return reinterpret_cast(_plaindata); + } + inline const T *data() const { + return reinterpret_cast(_plaindata); + } + + inline bool operator==(const Self &other) const { + if (this == &other) return true; + if (_size != other._size) return false; + for (int i = 0; i < _size; ++i) { + if (data()[i] != other.data()[i]) { + return false; + } + } + return true; + } + inline bool operator!=(const Self &other) const { return !(*this == other); } + inline int size() const { return _size; } + inline bool isEmpty() const { return _size == 0; } + inline void clear() { + for (int i = 0; i < _size; ++i) { + data()[i].~T(); + } + _size = 0; + + operator delete[](_plaindata); + _plaindata = nullptr; + _capacity = 0; + } + + typedef T *iterator; + typedef const T *const_iterator; + + // STL style + inline iterator begin() { return data(); } + inline const_iterator begin() const { return data(); } + inline const_iterator cbegin() const { return data(); } + inline iterator end() { return data() + _size; } + inline const_iterator end() const { return data() + _size; } + inline const_iterator cend() const { return data() + _size; } + inline iterator erase(iterator it) { + T tmp = std_::move(*it); + for (auto next = it + 1, e = end(); next != e; ++next) { + auto prev = next - 1; + *prev = std_::move(*next); + } + --_size; + return it; + } + + inline iterator insert(const_iterator pos, T &&value) { + int insertAtIndex = pos - begin(); + if (_size + 1 > _capacity) { + reallocate(_capacity + (_capacity > 1 ? _capacity / 2 : 1)); + } + auto insertAt = begin() + insertAtIndex, e = end(); + if (insertAt == e) { + new (&(*insertAt)) T(std_::move(value)); + } else { + auto prev = e - 1; + new (&(*e)) T(std_::move(*prev)); + for (auto it = prev; it != insertAt; --it) { + *it = std_::move(*--prev); + } + *insertAt = std_::move(value); + } + ++_size; + return insertAt; + } + inline void push_back(T &&value) { + insert(end(), std_::forward(value)); + } + inline void pop_back() { + erase(end() - 1); + } + inline T &front() { + return *begin(); + } + inline const T &front() const { + return *begin(); + } + inline T &back() { + return *(end() - 1); + } + inline const T &back() const { + return *(end() - 1); + } + inline bool empty() const { return _size == 0; } + + inline T &operator[](int index) { + return data()[index]; + } + inline const T &operator[](int index) const { + return data()[index]; + } + inline const T &at(int index) const { + if (index < 0 || index >= _size) { + throw std::out_of_range(""); + } + return data()[index]; + } + + inline ~vector_of_moveable() { + clear(); + } + +private: + void reallocate(int newCapacity) { + auto newPlainData = operator new[](newCapacity * sizeof(T)); + for (int i = 0; i < _size; ++i) { + *(reinterpret_cast(newPlainData) + i) = std_::move(*(data() + i)); + } + std::swap(_plaindata, newPlainData); + _capacity = newCapacity; + operator delete[](newPlainData); + } + +}; + +} // namespace std_ diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index df192203a..94667764f 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/basic_types.h" -#define BETA_VERSION_MACRO (0ULL) +#define BETA_VERSION_MACRO (9049001ULL) constexpr int AppVersion = 9049; constexpr str_const AppVersionStr = "0.9.49"; diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 2e4ba2522..350f10dab 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -641,7 +641,7 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) { _menu->addAction(lang(lng_profile_clear_history), this, SLOT(onContextClearHistory()))->setEnabled(true); _menu->addAction(lang(lng_profile_delete_conversation), this, SLOT(onContextDeleteAndLeave()))->setEnabled(true); if (_menuPeer->asUser()->access != UserNoAccess && _menuPeer != App::self()) { - _menu->addAction(lang((_menuPeer->asUser()->blocked == UserIsBlocked) ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user)), this, SLOT(onContextToggleBlock()))->setEnabled(true); + _menu->addAction(lang(_menuPeer->asUser()->isBlocked() ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user)), this, SLOT(onContextToggleBlock()))->setEnabled(true); connect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData*))); } } else if (_menuPeer->isChat()) { @@ -662,7 +662,7 @@ bool DialogsInner::menuPeerMuted() { void DialogsInner::onContextProfile() { if (!_menuPeer) return; - App::main()->showPeerProfile(_menuPeer); + Ui::showPeerProfile(_menuPeer); } void DialogsInner::onContextToggleNotifications() { @@ -718,7 +718,7 @@ void DialogsInner::onContextDeleteAndLeaveSure() { void DialogsInner::onContextToggleBlock() { if (!_menuPeer || !_menuPeer->isUser()) return; - if (_menuPeer->asUser()->blocked == UserIsBlocked) { + if (_menuPeer->asUser()->isBlocked()) { MTP::send(MTPcontacts_Unblock(_menuPeer->asUser()->inputUser), rpcDone(&DialogsInner::contextBlockDone, qMakePair(_menuPeer->asUser(), false))); } else { MTP::send(MTPcontacts_Block(_menuPeer->asUser()->inputUser), rpcDone(&DialogsInner::contextBlockDone, qMakePair(_menuPeer->asUser(), true))); @@ -726,7 +726,7 @@ void DialogsInner::onContextToggleBlock() { } void DialogsInner::contextBlockDone(QPair data, const MTPBool &result) { - data.first->blocked = data.second ? UserIsBlocked : UserIsNotBlocked; + data.first->setBlockStatus(data.second ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked); emit App::main()->peerUpdated(data.first); } @@ -935,7 +935,7 @@ void DialogsInner::updateNotifySettings(PeerData *peer) { void DialogsInner::peerUpdated(PeerData *peer) { if (_menu && _menuPeer == peer && _menuPeer->isUser() && _menu->actions().size() > 5) { - _menu->actions().at(5)->setText(lang((_menuPeer->asUser()->blocked == UserIsBlocked) ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user))); + _menu->actions().at(5)->setText(lang(_menuPeer->asUser()->isBlocked() ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user))); } } @@ -1842,27 +1842,32 @@ void DialogsWidget::dialogsToUp() { } } -void DialogsWidget::animShow(const QPixmap &bgAnimCache) { +void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) { if (App::app()) App::app()->mtpPause(); - bool back = true; - (back ? _cacheOver : _cacheUnder) = bgAnimCache; + _cacheUnder = params.oldContentCache; + show(); + _cacheOver = App::main()->grabForShowAnimation(params); _a_show.stop(); - (back ? _cacheUnder : _cacheOver) = myGrab(this); - _scroll.hide(); _filter.hide(); _cancelSearch.hide(); _newGroup.hide(); - a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); - a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); - a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); + int delta = st::slideShift; + if (direction == Window::SlideDirection::FromLeft) { + a_progress = anim::fvalue(1, 0); + std::swap(_cacheUnder, _cacheOver); + a_coordUnder = anim::ivalue(-delta, 0); + a_coordOver = anim::ivalue(0, width()); + } else { + a_progress = anim::fvalue(0, 1); + a_coordUnder = anim::ivalue(0, -delta); + a_coordOver = anim::ivalue(width(), 0); + } _a_show.start(); - - show(); } void DialogsWidget::step_show(float64 ms, bool timer) { @@ -1872,7 +1877,7 @@ void DialogsWidget::step_show(float64 ms, bool timer) { a_coordUnder.finish(); a_coordOver.finish(); - a_shadow.finish(); + a_progress.finish(); _cacheUnder = _cacheOver = QPixmap(); @@ -1887,7 +1892,7 @@ void DialogsWidget::step_show(float64 ms, bool timer) { } else { a_coordUnder.update(dt, st::slideFunction); a_coordOver.update(dt, st::slideFunction); - a_shadow.update(dt, st::slideFunction); + a_progress.update(dt, st::slideFunction); } if (timer) update(); } @@ -2474,15 +2479,16 @@ void DialogsWidget::paintEvent(QPaintEvent *e) { p.setClipRect(r); } if (_a_show.animating()) { + int retina = cIntRetinaFactor(); if (a_coordOver.current() > 0) { - p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * cRetinaFactor(), 0, a_coordOver.current() * cRetinaFactor(), height() * cRetinaFactor())); - p.setOpacity(a_shadow.current() * st::slideFadeOut); - p.fillRect(0, 0, a_coordOver.current(), height(), st::black->b); + p.drawPixmap(QRect(0, 0, a_coordOver.current(), _cacheUnder.height() / retina), _cacheUnder, QRect(-a_coordUnder.current() * retina, 0, a_coordOver.current() * retina, _cacheUnder.height())); + p.setOpacity(a_progress.current() * st::slideFadeOut); + p.fillRect(0, 0, a_coordOver.current(), _cacheUnder.height() / retina, st::black); p.setOpacity(1); } - p.drawPixmap(a_coordOver.current(), 0, _cacheOver); - p.setOpacity(a_shadow.current()); - p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect()); + p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, _cacheOver.height() / retina), _cacheOver, QRect(0, 0, _cacheOver.width(), _cacheOver.height())); + p.setOpacity(a_progress.current()); + p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), _cacheOver.height() / retina), App::sprite(), st::slideShadow.rect()); return; } QRect above(0, 0, width(), _scroll.y()); diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index fe5b33e62..b12e613c0 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "window/section_widget.h" + class MainWidget; namespace Dialogs { class Row; @@ -260,7 +262,10 @@ public: void dialogsToUp(); - void animShow(const QPixmap &bgAnimCache); + bool hasTopBarShadow() const { + return true; + } + void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms); void step_show(float64 ms, bool timer); void destroyData(); @@ -338,7 +343,7 @@ private: Animation _a_show; QPixmap _cacheUnder, _cacheOver; anim::ivalue a_coordUnder, a_coordOver; - anim::fvalue a_shadow; + anim::fvalue a_progress; PeerData *_searchInPeer, *_searchInMigrated; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index e6f6f7758..5480af8d4 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -20,10 +20,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "profile/profile_section_memento.h" +#include "core/vector_of_moveable.h" +#include "core/click_handler_types.h" +#include "observer_peer.h" #include "mainwindow.h" #include "mainwidget.h" #include "application.h" -#include "core/click_handler_types.h" #include "boxes/confirmbox.h" #include "layerwidget.h" #include "lang.h" @@ -237,7 +240,15 @@ void autoplayMediaInlineAsync(const FullMsgId &msgId) { } void showPeerProfile(const PeerId &peer) { - if (MainWidget *m = App::main()) m->showPeerProfile(App::peer(peer)); + if (auto m = App::main()) { + m->showWideSection(Profile::SectionMemento(App::peer(peer))); + } +} + +void showPeerOverview(const PeerId &peer, MediaOverviewType type) { + if (auto m = App::main()) { + m->showMediaOverview(App::peer(peer), type); + } } void showPeerHistory(const PeerId &peer, MsgId msgId, bool back) { @@ -273,6 +284,15 @@ bool hideWindowNoQuit() { return false; } +bool skipPaintEvent(QWidget *widget, QPaintEvent *event) { + if (auto w = App::wnd()) { + if (w->contentOverlapped(widget, event)) { + return true; + } + } + return false; +} + } // namespace Ui namespace Notify { @@ -286,7 +306,10 @@ void userIsContactChanged(UserData *user, bool fromThisApp) { } void botCommandsChanged(UserData *user) { - if (MainWidget *m = App::main()) m->notify_botCommandsChanged(user); + if (MainWidget *m = App::main()) { + m->notify_botCommandsChanged(user); + } + peerUpdatedDelayed(user, PeerUpdate::Flag::BotCommandsChanged); } void inlineBotRequesting(bool requesting) { @@ -500,6 +523,8 @@ struct Data { uint64 LaunchId = 0; SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" }; SingleDelayedCall HandleUnreadCounterUpdate = { App::app(), "call_handleUnreadCounterUpdate" }; + SingleDelayedCall HandleFileDialogQueue = { App::app(), "call_handleFileDialogQueue" }; + SingleDelayedCall HandleDelayedPeerUpdates = { App::app(), "call_handleDelayedPeerUpdates" }; Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout; bool AdaptiveForWide = true; @@ -563,6 +588,8 @@ void finish() { DefineReadOnlyVar(Global, uint64, LaunchId); DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate); DefineRefVar(Global, SingleDelayedCall, HandleUnreadCounterUpdate); +DefineRefVar(Global, SingleDelayedCall, HandleFileDialogQueue); +DefineRefVar(Global, SingleDelayedCall, HandleDelayedPeerUpdates); DefineVar(Global, Adaptive::Layout, AdaptiveLayout); DefineVar(Global, bool, AdaptiveForWide); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 16d10cc20..a368db47c 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -75,6 +75,14 @@ inline void showPeerProfile(const History *history) { showPeerProfile(history->peer->id); } +void showPeerOverview(const PeerId &peer, MediaOverviewType type); +inline void showPeerOverview(const PeerData *peer, MediaOverviewType type) { + showPeerOverview(peer->id, type); +} +inline void showPeerOverview(const History *history, MediaOverviewType type) { + showPeerOverview(history->peer->id, type); +} + void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false); inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) { showPeerHistory(peer->id, msgId, back); @@ -96,6 +104,8 @@ PeerData *getPeerForMouseAction(); bool hideWindowNoQuit(); +bool skipPaintEvent(QWidget *widget, QPaintEvent *event); + } // namespace Ui enum ClipStopperType { @@ -194,6 +204,8 @@ void finish(); DeclareReadOnlyVar(uint64, LaunchId); DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate); DeclareRefVar(SingleDelayedCall, HandleUnreadCounterUpdate); +DeclareRefVar(SingleDelayedCall, HandleFileDialogQueue); +DeclareRefVar(SingleDelayedCall, HandleDelayedPeerUpdates); DeclareVar(Adaptive::Layout, AdaptiveLayout); DeclareVar(bool, AdaptiveForWide); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index c1d09de45..5a8816cd7 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -36,6 +36,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "window/top_bar_widget.h" #include "playerwidget.h" +#include "observer_peer.h" namespace { @@ -751,6 +752,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, if (peer->asChannel()->mgInfo->lastParticipants.indexOf(user) < 0) { peer->asChannel()->mgInfo->lastParticipants.push_front(user); peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); } if (user->botInfo) { peer->asChannel()->mgInfo->bots.insert(user); @@ -769,6 +771,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, if (result->from()->isUser()) { if (peer->asChannel()->mgInfo->lastParticipants.indexOf(result->from()->asUser()) < 0) { peer->asChannel()->mgInfo->lastParticipants.push_front(result->from()->asUser()); + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); } if (result->from()->asUser()->botInfo) { peer->asChannel()->mgInfo->bots.insert(result->from()->asUser()); @@ -793,26 +796,31 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, if (App::main()) App::main()->updateBotKeyboard(this); } if (peer->isMegagroup()) { - if (UserData *user = App::userLoaded(uid)) { - int32 index = peer->asChannel()->mgInfo->lastParticipants.indexOf(user); + if (auto user = App::userLoaded(uid)) { + auto channel = peer->asChannel(); + auto megagroupInfo = channel->mgInfo; + + int32 index = megagroupInfo->lastParticipants.indexOf(user); if (index >= 0) { - peer->asChannel()->mgInfo->lastParticipants.removeAt(index); + megagroupInfo->lastParticipants.removeAt(index); + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); } - if (peer->asChannel()->count > 1) { - --peer->asChannel()->count; + if (peer->asChannel()->membersCount() > 1) { + peer->asChannel()->setMembersCount(channel->membersCount() - 1); } else { - peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; - peer->asChannel()->mgInfo->lastParticipantsCount = 0; + megagroupInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; + megagroupInfo->lastParticipantsCount = 0; } - if (peer->asChannel()->mgInfo->lastAdmins.contains(user)) { - peer->asChannel()->mgInfo->lastAdmins.remove(user); - if (peer->asChannel()->adminsCount > 1) { - --peer->asChannel()->adminsCount; + if (megagroupInfo->lastAdmins.contains(user)) { + megagroupInfo->lastAdmins.remove(user); + if (channel->adminsCount() > 1) { + channel->setAdminsCount(channel->adminsCount() - 1); } + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::AdminsChanged); } - peer->asChannel()->mgInfo->bots.remove(user); - if (peer->asChannel()->mgInfo->bots.isEmpty() && peer->asChannel()->mgInfo->botStatus > 0) { - peer->asChannel()->mgInfo->botStatus = -1; + megagroupInfo->bots.remove(user); + if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) { + megagroupInfo->botStatus = -1; } } } @@ -848,9 +856,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageActionChatEditTitle: { - const auto &d(action.c_messageActionChatEditTitle()); - ChatData *chat = peer->asChat(); - if (chat) chat->updateName(qs(d.vtitle), QString(), QString()); + auto &d(action.c_messageActionChatEditTitle()); + if (auto chat = peer->asChat()) { + chat->setName(qs(d.vtitle)); + } } break; case mtpc_messageActionChatMigrateTo: { @@ -1019,6 +1028,9 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { if (prev) { lastAuthors->push_front(adding->from()->asUser()); } + if (peer->isMegagroup()) { + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); + } } } if (adding->definesReplyKeyboard()) { @@ -1194,6 +1206,7 @@ void History::addOlderSlice(const QVector &slice) { lastAuthors->push_back(item->from()->asUser()); if (peer->isMegagroup()) { peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); } } } @@ -1704,6 +1717,7 @@ void History::getReadyFor(MsgId msgId) { } if (!isReadyFor(msgId)) { clear(true); + if (msgId == ShowAtTheEndMsgId) { newLoaded = true; } @@ -2847,14 +2861,14 @@ HistoryItem::~HistoryItem() { } } -RadialAnimation::RadialAnimation(AnimationCreator creator) +RadialAnimation::RadialAnimation(AnimationCallbacks &&callbacks) : _firstStart(0) , _lastStart(0) , _lastTime(0) , _opacity(0) , a_arcEnd(0, 0) , a_arcStart(0, FullArcLength) -, _animation(creator) { +, _animation(std_::move(callbacks)) { } diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index b2ba3dbea..8582334c7 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -1590,7 +1590,7 @@ protected: class RadialAnimation { public: - RadialAnimation(AnimationCreator creator); + RadialAnimation(AnimationCallbacks &&callbacks); float64 opacity() const { return _opacity; @@ -1811,9 +1811,9 @@ protected: virtual bool dataLoaded() const = 0; struct AnimationData { - AnimationData(AnimationCreator thumbOverCallbacks, AnimationCreator radialCallbacks) : a_thumbOver(0, 0) - , _a_thumbOver(thumbOverCallbacks) - , radial(radialCallbacks) { + AnimationData(AnimationCallbacks &&thumbOverCallbacks, AnimationCallbacks &&radialCallbacks) : a_thumbOver(0, 0) + , _a_thumbOver(std_::move(thumbOverCallbacks)) + , radial(std_::move(radialCallbacks)) { } anim::fvalue a_thumbOver; Animation _a_thumbOver; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 738ab6eac..a423edf68 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -39,6 +39,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "apiwrap.h" #include "window/top_bar_widget.h" +#include "observer_peer.h" #include "playerwidget.h" namespace { @@ -658,7 +659,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt _selected.insert(_dragItem, selStatus); _dragSymbol = dragState.symbol; _dragAction = Selecting; - _dragSelType = TextSelectParagraphs; + _dragSelType = TextSelectType::Paragraphs; dragActionUpdate(_dragPos); _trippleClickTimer.start(QApplication::doubleClickInterval()); } @@ -668,7 +669,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt request.flags = Text::StateRequest::Flag::LookupSymbol; dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); } - if (_dragSelType != TextSelectParagraphs) { + if (_dragSelType != TextSelectType::Paragraphs) { if (App::pressedItem()) { _dragSymbol = dragState.symbol; bool uponSelected = (dragState.cursor == HistoryInTextCursorState); @@ -715,7 +716,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt if (!_dragItem) { _dragAction = NoDrag; } else if (_dragAction == NoDrag) { - _dragItem = 0; + _dragItem = nullptr; } } @@ -776,6 +777,9 @@ void HistoryInner::onDragExec() { mimeData->setData(qsl("application/x-td-forward-selected"), "1"); } drag->setMimeData(mimeData); + + // We don't receive mouseReleaseEvent when drag is finished. + ClickHandler::unpressed(); drag->exec(Qt::CopyAction); if (App::main()) App::main()->updateAfterDrag(); return; @@ -810,6 +814,9 @@ void HistoryInner::onDragExec() { } drag->setMimeData(mimeData); + + // We don't receive mouseReleaseEvent when drag is finished. + ClickHandler::unpressed(); drag->exec(Qt::CopyAction); if (App::main()) App::main()->updateAfterDrag(); return; @@ -906,7 +913,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but } _dragAction = NoDrag; _dragItem = 0; - _dragSelType = TextSelectLetters; + _dragSelType = TextSelectType::Letters; _widget->noSelectingScroll(); _widget->updateTopBarSelection(); } @@ -922,13 +929,13 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) { if (!_history) return; dragActionStart(e->globalPos(), e->button()); - if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _dragSelType == TextSelectLetters && _dragItem) { + if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _dragSelType == TextSelectType::Letters && _dragItem) { HistoryStateRequest request; request.flags |= Text::StateRequest::Flag::LookupSymbol; auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); if (dragState.cursor == HistoryInTextCursorState) { _dragSymbol = dragState.symbol; - _dragSelType = TextSelectWords; + _dragSelType = TextSelectType::Words; if (_dragAction == NoDrag) { _dragAction = Selecting; TextSelection selStatus = { dragState.symbol, dragState.symbol }; @@ -1787,7 +1794,7 @@ void HistoryInner::onUpdateSelected() { bool canSelectMany = (_history != nullptr); if (selectingText) { uint16 second = dragState.symbol; - if (dragState.afterSymbol && _dragSelType == TextSelectLetters) { + if (dragState.afterSymbol && _dragSelType == TextSelectType::Letters) { ++second; } auto selState = _dragItem->adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _dragSelType); @@ -2816,7 +2823,6 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) , _attachDragPhoto(this) , _fileLoader(this, FileLoaderQueueStopTimeout) , _a_show(animation(this, &HistoryWidget::step_show)) -, _sideShadow(this, st::shadowColor) , _topShadow(this, st::shadowColor) { _scroll.setFocusPolicy(Qt::NoFocus); @@ -2937,7 +2943,6 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) _attachDragPhoto.hide(); _topShadow.hide(); - _sideShadow.setVisible(!Adaptive::OneColumn()); connect(&_attachDragDocument, SIGNAL(dropped(const QMimeData*)), this, SLOT(onDocumentDrop(const QMimeData*))); connect(&_attachDragPhoto, SIGNAL(dropped(const QMimeData*)), this, SLOT(onPhotoDrop(const QMimeData*))); @@ -3837,7 +3842,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re if (_channel) { updateNotifySettings(); if (_peer->notify == UnknownNotifySettings) { - App::wnd()->getNotifySetting(MTP_inputNotifyPeer(_peer->input)); + App::api()->requestNotifySetting(_peer); } } @@ -4059,7 +4064,9 @@ bool HistoryWidget::reportSpamSettingFail(const RPCError &error, mtpRequestId re } void HistoryWidget::updateControlsVisibility() { - _topShadow.setVisible(_peer ? true : false); + if (!_a_show.animating()) { + _topShadow.setVisible(_peer ? true : false); + } if (!_history || _a_show.animating()) { _reportSpamPanel.hide(); _scroll.hide(); @@ -4455,6 +4462,7 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages } else if (_migrated) { _migrated->clear(true); } + _delayedShowAtRequest = 0; _history->getReadyFor(_delayedShowAtMsgId); if (_history->isEmpty()) { @@ -4822,7 +4830,7 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) { void HistoryWidget::onUnblock() { if (_unblockRequest) return; - if (!_peer || !_peer->isUser() || _peer->asUser()->blocked != UserIsBlocked) { + if (!_peer || !_peer->isUser() || !_peer->asUser()->isBlocked()) { updateControlsVisibility(); return; } @@ -4833,7 +4841,7 @@ void HistoryWidget::onUnblock() { void HistoryWidget::unblockDone(PeerData *peer, const MTPBool &result, mtpRequestId req) { if (!peer->isUser()) return; if (_unblockRequest == req) _unblockRequest = 0; - peer->asUser()->blocked = UserIsNotBlocked; + peer->asUser()->setBlockStatus(UserData::BlockStatus::NotBlocked); emit App::main()->peerUpdated(peer); } @@ -4847,7 +4855,7 @@ bool HistoryWidget::unblockFail(const RPCError &error, mtpRequestId req) { void HistoryWidget::blockDone(PeerData *peer, const MTPBool &result) { if (!peer->isUser()) return; - peer->asUser()->blocked = UserIsBlocked; + peer->asUser()->setBlockStatus(UserData::BlockStatus::Blocked); emit App::main()->peerUpdated(peer); } @@ -4911,12 +4919,14 @@ void HistoryWidget::onBroadcastSilentChange() { } void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) { - if (!contact || contact->phone.isEmpty()) return; + auto phone = contact->phone(); + if (phone.isEmpty()) phone = App::phoneFromSharedContact(peerToUser(contact->id)); + if (!contact || phone.isEmpty()) return; Ui::showPeerHistory(peer, ShowAtTheEndMsgId); if (!_history) return; - shareContact(peer, contact->phone, contact->firstName, contact->lastName, replyToId(), peerToUser(contact->id)); + shareContact(peer, phone, contact->firstName, contact->lastName, replyToId(), peerToUser(contact->id)); } void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, int32 userId) { @@ -4993,15 +5003,15 @@ MsgId HistoryWidget::msgId() const { return _showAtMsgId; } -void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back) { +void HistoryWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) { if (App::app()) App::app()->mtpPause(); - (back ? _cacheOver : _cacheUnder) = bgAnimCache; - (back ? _cacheTopBarOver : _cacheTopBarUnder) = bgAnimTopBarCache; - (back ? _cacheUnder : _cacheOver) = myGrab(this); - App::main()->topBar()->stopAnim(); - (back ? _cacheTopBarUnder : _cacheTopBarOver) = myGrab(App::main()->topBar()); + _cacheUnder = params.oldContentCache; + show(); + _topShadow.setVisible(params.withTopBarShadow ? false : true); + _cacheOver = App::main()->grabForShowAnimation(params); App::main()->topBar()->startAnim(); + _topShadow.setVisible(params.withTopBarShadow ? true : false); _scroll.hide(); _kbScroll.hide(); @@ -5023,18 +5033,26 @@ void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo _botStart.hide(); _joinChannel.hide(); _muteUnmute.hide(); - _topShadow.hide(); if (_pinnedBar) { _pinnedBar->shadow.hide(); _pinnedBar->cancel.hide(); } - a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); - a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); - a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); + int delta = st::slideShift; + if (direction == Window::SlideDirection::FromLeft) { + a_progress = anim::fvalue(1, 0); + std::swap(_cacheUnder, _cacheOver); + a_coordUnder = anim::ivalue(-delta, 0); + a_coordOver = anim::ivalue(0, width()); + } else { + a_progress = anim::fvalue(0, 1); + a_coordUnder = anim::ivalue(0, -delta); + a_coordOver = anim::ivalue(width(), 0); + } _a_show.start(); App::main()->topBar()->update(); + activate(); } @@ -5042,13 +5060,12 @@ void HistoryWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; if (dt >= 1) { _a_show.stop(); - _sideShadow.setVisible(!Adaptive::OneColumn()); _topShadow.setVisible(_peer ? true : false); a_coordUnder.finish(); a_coordOver.finish(); - a_shadow.finish(); - _cacheUnder = _cacheOver = _cacheTopBarUnder = _cacheTopBarOver = QPixmap(); + a_progress.finish(); + _cacheUnder = _cacheOver = QPixmap(); App::main()->topBar()->stopAnim(); doneShow(); @@ -5056,7 +5073,7 @@ void HistoryWidget::step_show(float64 ms, bool timer) { } else { a_coordUnder.update(dt, st::slideFunction); a_coordOver.update(dt, st::slideFunction); - a_shadow.update(dt, st::slideFunction); + a_progress.update(dt, st::slideFunction); } if (timer) { update(); @@ -5081,14 +5098,12 @@ void HistoryWidget::doneShow() { } void HistoryWidget::updateAdaptiveLayout() { - _sideShadow.setVisible(!Adaptive::OneColumn()); update(); } void HistoryWidget::animStop() { if (!_a_show.animating()) return; _a_show.stop(); - _sideShadow.setVisible(!Adaptive::OneColumn()); _topShadow.setVisible(_peer ? true : false); } @@ -5450,8 +5465,7 @@ DragState HistoryWidget::getDragState(const QMimeData *d) { for (QList::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) { if (!i->isLocalFile()) return DragStateNone; - QString file(i->toLocalFile()); - if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file); + auto file = psConvertFileUrl(*i); QFileInfo info(file); if (info.isDir()) return DragStateNone; @@ -5569,7 +5583,7 @@ bool HistoryWidget::isBotStart() const { } bool HistoryWidget::isBlocked() const { - return _peer && _peer->isUser() && _peer->asUser()->blocked == UserIsBlocked; + return _peer && _peer->isUser() && _peer->asUser()->isBlocked(); } bool HistoryWidget::isJoinChannel() const { @@ -5769,9 +5783,15 @@ void HistoryWidget::onForwardHere() { void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { if (_a_show.animating()) { - p.drawPixmap(a_coordUnder.current(), 0, _cacheTopBarUnder); - p.drawPixmap(a_coordOver.current(), 0, _cacheTopBarOver); - p.setOpacity(a_shadow.current()); + int retina = cIntRetinaFactor(); + if (a_coordOver.current() > 0) { + p.drawPixmap(QRect(0, 0, a_coordOver.current(), st::topBarHeight), _cacheUnder, QRect(-a_coordUnder.current() * retina, 0, a_coordOver.current() * retina, st::topBarHeight * retina)); + p.setOpacity(a_progress.current() * st::slideFadeOut); + p.fillRect(0, 0, a_coordOver.current(), st::topBarHeight, st::black); + p.setOpacity(1); + } + p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, st::topBarHeight), _cacheOver, QRect(0, 0, _cacheOver.width(), st::topBarHeight * retina)); + p.setOpacity(a_progress.current()); p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), st::topBarHeight), App::sprite(), st::slideShadow.rect()); return; } @@ -5806,7 +5826,7 @@ void HistoryWidget::topBarClick() { if (Adaptive::OneColumn()) { Ui::showChatsList(); } else { - if (_history) App::main()->showPeerProfile(_peer); + if (_history) Ui::showPeerProfile(_peer); } } @@ -5839,8 +5859,8 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) { } } } else if (_peer->isChannel()) { - if (_peer->isMegagroup() && _peer->asChannel()->count > 0 && _peer->asChannel()->count <= Global::ChatSizeMax()) { - if (_peer->asChannel()->mgInfo->lastParticipants.size() < _peer->asChannel()->count || _peer->asChannel()->lastParticipantsCountOutdated()) { + if (_peer->isMegagroup() && _peer->asChannel()->membersCount() > 0 && _peer->asChannel()->membersCount() <= Global::ChatSizeMax()) { + if (_peer->asChannel()->mgInfo->lastParticipants.size() < _peer->asChannel()->membersCount() || _peer->asChannel()->lastParticipantsCountOutdated()) { if (App::api()) App::api()->requestLastParticipants(_peer->asChannel()); } int32 onlineCount = 0; @@ -5852,12 +5872,12 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) { } } if (onlineCount && !onlyMe) { - text = lng_chat_status_members_online(lt_count, _peer->asChannel()->count, lt_count_online, onlineCount); + text = lng_chat_status_members_online(lt_count, _peer->asChannel()->membersCount(), lt_count_online, onlineCount); } else { - text = lng_chat_status_members(lt_count, _peer->asChannel()->count); + text = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount()); } } else { - text = _peer->asChannel()->count ? lng_chat_status_members(lt_count, _peer->asChannel()->count) : lang(_peer->isMegagroup() ? lng_group_status : lng_channel_status); + text = _peer->asChannel()->membersCount() ? lng_chat_status_members(lt_count, _peer->asChannel()->membersCount()) : lang(_peer->isMegagroup() ? lng_group_status : lng_channel_status); } } if (_titlePeerText != text) { @@ -6494,8 +6514,6 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) { _topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth); _topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0); - _sideShadow.resize(st::lineWidth, height()); - _sideShadow.moveToLeft(0, 0); } void HistoryWidget::itemRemoved(HistoryItem *item) { @@ -6671,7 +6689,8 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh } } else { } - if (toY > _scroll.scrollTopMax()) toY = _scroll.scrollTopMax(); + auto scrollMax = _scroll.scrollTopMax(); + accumulate_min(toY, scrollMax); if (_scroll.scrollTop() == toY) { visibleAreaUpdated(); } else { @@ -7041,7 +7060,6 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { } connect(&_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide())); _reportSpamPanel.raise(); - _sideShadow.raise(); _topShadow.raise(); updatePinnedBar(); result = true; @@ -7679,7 +7697,7 @@ void HistoryWidget::peerUpdated(PeerData *data) { if (App::api()) { if (data->isChat() && data->asChat()->noParticipantInfo()) { App::api()->requestFullPeer(data); - } else if (data->isUser() && data->asUser()->blocked == UserBlockUnknown) { + } else if (data->isUser() && data->asUser()->blockStatus() == UserData::BlockStatus::Unknown) { App::api()->requestFullPeer(data); } else if (data->isMegagroup() && !data->asChannel()->mgInfo->botStatus) { App::api()->requestBots(data->asChannel()); @@ -7723,6 +7741,7 @@ void HistoryWidget::onDeleteSelected() { } void HistoryWidget::onDeleteSelectedSure() { + Ui::hideLayer(); if (!_list) return; SelectedItemSet sel; @@ -7740,7 +7759,6 @@ void HistoryWidget::onDeleteSelectedSure() { for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) { i.value()->destroy(); } - Ui::hideLayer(); for (QMap >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) { App::main()->deleteMessages(i.key(), i.value()); @@ -7748,6 +7766,8 @@ void HistoryWidget::onDeleteSelectedSure() { } void HistoryWidget::onDeleteContextSure() { + Ui::hideLayer(); + HistoryItem *item = App::contextItem(); if (!item || item->type() != HistoryItemMsg) { return; @@ -7757,12 +7777,11 @@ void HistoryWidget::onDeleteContextSure() { History *h = item->history(); bool wasOnServer = (item->id > 0), wasLast = (h->lastMsg == item); item->destroy(); + if (!wasOnServer && wasLast && !h->lastMsg) { App::main()->checkPeerHistory(h->peer); } - Ui::hideLayer(); - if (wasOnServer) { App::main()->deleteMessages(h->peer, toDelete); } @@ -8117,20 +8136,23 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { if (r != rect()) { p.setClipRect(r); } + bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden(); + if (_a_show.animating()) { + int retina = cIntRetinaFactor(); + int inCacheTop = hasTopBar ? st::topBarHeight : 0; if (a_coordOver.current() > 0) { - p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * cRetinaFactor(), 0, a_coordOver.current() * cRetinaFactor(), height() * cRetinaFactor())); - p.setOpacity(a_shadow.current() * st::slideFadeOut); - p.fillRect(0, 0, a_coordOver.current(), height(), st::black->b); + p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * retina, inCacheTop * retina, a_coordOver.current() * retina, height() * retina)); + p.setOpacity(a_progress.current() * st::slideFadeOut); + p.fillRect(0, 0, a_coordOver.current(), height(), st::black); p.setOpacity(1); } - p.drawPixmap(a_coordOver.current(), 0, _cacheOver); - p.setOpacity(a_shadow.current()); + p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, inCacheTop * retina, _cacheOver.width(), height() * retina)); + p.setOpacity(a_progress.current()); p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect()); return; } - bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden(); QRect fill(0, 0, width(), App::main()->height()); int fromy = (hasTopBar ? (-st::topBarHeight) : 0) + (hasPlayer ? (-st::playerHeight) : 0), x = 0, y = 0; QPixmap cached = App::main()->cachedBackground(fill, x, y); @@ -8208,8 +8230,7 @@ QStringList HistoryWidget::getMediasFromMime(const QMimeData *d) { for (QList::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) { if (!i->isLocalFile()) return QStringList(); - QString file(i->toLocalFile()); - if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file); + auto file = psConvertFileUrl(*i); QFileInfo info(file); uint64 s = info.size(); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index d84f6ec4e..490651c18 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dropdown.h" #include "history/history_common.h" #include "history/field_autocomplete.h" +#include "window/section_widget.h" namespace InlineBots { namespace Layout { @@ -205,7 +206,7 @@ private: Selecting = 0x04, }; DragAction _dragAction = NoDrag; - TextSelectType _dragSelType = TextSelectLetters; + TextSelectType _dragSelType = TextSelectType::Letters; QPoint _dragStartPos, _dragPos; HistoryItem *_dragItem = nullptr; HistoryCursorState _dragCursorState = HistoryDefaultCursorState; @@ -570,7 +571,10 @@ public: void setMsgId(MsgId showAtMsgId); MsgId msgId() const; - void animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false); + bool hasTopBarShadow() const { + return peer() != nullptr; + } + void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms); void step_show(float64 ms, bool timer); void animStop(); @@ -652,14 +656,17 @@ public: bool contentOverlapped(const QRect &globalRect); void grabStart() override { - _sideShadow.hide(); _inGrab = true; resizeEvent(0); } + void grapWithoutTopBarShadow() { + grabStart(); + _topShadow.hide(); + } void grabFinish() override { - _sideShadow.setVisible(!Adaptive::OneColumn()); _inGrab = false; resizeEvent(0); + _topShadow.show(); } bool isItemVisible(HistoryItem *item); @@ -1077,9 +1084,9 @@ private: int32 _titlePeerTextWidth = 0; Animation _a_show; - QPixmap _cacheUnder, _cacheOver, _cacheTopBarUnder, _cacheTopBarOver; + QPixmap _cacheUnder, _cacheOver; anim::ivalue a_coordUnder, a_coordOver; - anim::fvalue a_shadow; + anim::fvalue a_progress; QTimer _scrollTimer; int32 _scrollDelta = 0; @@ -1094,7 +1101,7 @@ private: bool _saveDraftText = false; QTimer _saveDraftTimer, _saveCloudDraftTimer; - PlainShadow _sideShadow, _topShadow; + PlainShadow _topShadow; bool _inGrab = false; }; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 42fd4e156..c3fa01dd6 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -216,9 +216,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (_delete && p == _delete) { bool wasactive = (_state & StateFlag::DeleteOver); if (active != wasactive) { - float64 from = active ? 0 : 1, to = active ? 1 : 0; - EnsureAnimation(_a_deleteOver, from, func(this, &Gif::update)); - _a_deleteOver.start(to, st::stickersRowDuration); + auto from = active ? 0. : 1., to = active ? 1. : 0.; + START_ANIMATION(_a_deleteOver, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear); if (active) { _state |= StateFlag::DeleteOver; } else { @@ -231,9 +230,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (active != wasactive) { if (!getShownDocument()->loaded()) { ensureAnimation(); - float64 from = active ? 0 : 1, to = active ? 1 : 0; - EnsureAnimation(_animation->_a_over, from, func(this, &Gif::update)); - _animation->_a_over.start(to, st::stickersRowDuration); + auto from = active ? 0. : 1., to = active ? 1. : 0.; + START_ANIMATION(_animation->_a_over, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear); } if (active) { _state |= StateFlag::Over; @@ -413,9 +411,8 @@ void Sticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (active != _active) { _active = active; - float64 from = _active ? 0 : 1, to = _active ? 1 : 0; - EnsureAnimation(_a_over, from, func(this, &Sticker::update)); - _a_over.start(to, st::stickersRowDuration); + auto from = active ? 0. : 1., to = active ? 1. : 0.; + START_ANIMATION(_a_over, func(this, &Sticker::update), from, to, st::stickersRowDuration, anim::linear); } } ItemBase::clickHandlerActiveChanged(p, active); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index e36bcc3c5..7095f8828 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -107,9 +107,9 @@ private: void clipCallback(ClipReaderNotification notification); struct AnimationData { - AnimationData(AnimationCreator creator) + AnimationData(AnimationCallbacks &&callbacks) : over(false) - , radial(creator) { + , radial(std_::move(callbacks)) { } bool over; FloatAnimation _a_over; @@ -268,9 +268,9 @@ private: } struct AnimationData { - AnimationData(AnimationCreator thumbOverCallbacks, AnimationCreator radialCallbacks) : a_thumbOver(0, 0) - , _a_thumbOver(thumbOverCallbacks) - , radial(radialCallbacks) { + AnimationData(AnimationCallbacks &&thumbOverCallbacks, AnimationCallbacks &&radialCallbacks) : a_thumbOver(0, 0) + , _a_thumbOver(std_::move(thumbOverCallbacks)) + , radial(std_::move(radialCallbacks)) { } anim::fvalue a_thumbOver; Animation _a_thumbOver; diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index e75b7f4f9..5f4e7c017 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -50,7 +50,7 @@ IntroPhone::IntroPhone(IntroWidget *parent) : IntroStep(parent) , country(this, st::introCountry) , phone(this, st::inpIntroPhone) , code(this, st::inpIntroCountryCode) -, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), st::introErrLabel, st::introErrLabelTextStyle) +, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), FlatLabel::InitType::Rich, st::introErrLabel, st::introErrLabelTextStyle) , _showSignup(false) , sentRequest(0) { setVisible(false); diff --git a/Telegram/SourceFiles/intro/introphone.h b/Telegram/SourceFiles/intro/introphone.h index 8e3bc5e96..8c53bc6b4 100644 --- a/Telegram/SourceFiles/intro/introphone.h +++ b/Telegram/SourceFiles/intro/introphone.h @@ -20,9 +20,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include #include "ui/flatbutton.h" #include "ui/countryinput.h" +#include "ui/flatlabel.h" #include "intro/introwidget.h" class IntroPhone final : public IntroStep { diff --git a/Telegram/SourceFiles/intro/introstart.cpp b/Telegram/SourceFiles/intro/introstart.cpp index 8753300fc..bc3c520ff 100644 --- a/Telegram/SourceFiles/intro/introstart.cpp +++ b/Telegram/SourceFiles/intro/introstart.cpp @@ -27,7 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "langloaderplain.h" IntroStart::IntroStart(IntroWidget *parent) : IntroStep(parent) -, _intro(this, lang(lng_intro), st::introLabel, st::introLabelTextStyle) +, _intro(this, lang(lng_intro), FlatLabel::InitType::Rich, st::introLabel, st::introLabelTextStyle) , _changeLang(this, QString()) , _next(this, lang(lng_start_msgs), st::btnIntroNext) { _changeLang.hide(); diff --git a/Telegram/SourceFiles/intro/introstart.h b/Telegram/SourceFiles/intro/introstart.h index 8bd86075d..c9665476e 100644 --- a/Telegram/SourceFiles/intro/introstart.h +++ b/Telegram/SourceFiles/intro/introstart.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "intro/introwidget.h" +#include "ui/flatlabel.h" class IntroStart final : public IntroStep { public: diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index 8323ba7e0..09caacd8c 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -180,7 +180,7 @@ void IntroWidget::animShow(const QPixmap &bgAnimCache, bool back) { step()->hide(); _back.hide(); - a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); + a_coordUnder = back ? anim::ivalue(-st::slideShift, 0) : anim::ivalue(0, -st::slideShift); a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); _a_show.start(); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index b4962429d..859af987b 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "serialize/serialize_document.h" #include "serialize/serialize_common.h" +#include "observer_peer.h" #include "mainwidget.h" #include "mainwindow.h" #include "lang.h" @@ -3412,7 +3413,7 @@ namespace Local { UserData *user = peer->asUser(); // first + last + phone + username + access - result += Serialize::stringSize(user->firstName) + Serialize::stringSize(user->lastName) + Serialize::stringSize(user->phone) + Serialize::stringSize(user->username) + sizeof(quint64); + result += Serialize::stringSize(user->firstName) + Serialize::stringSize(user->lastName) + Serialize::stringSize(user->phone()) + Serialize::stringSize(user->username) + sizeof(quint64); // flags if (AppVersion >= 9012) { @@ -3424,13 +3425,13 @@ namespace Local { } else if (peer->isChat()) { ChatData *chat = peer->asChat(); - // name + count + date + version + admin + forbidden + left + invitationUrl - result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(chat->invitationUrl); + // name + count + date + version + admin + forbidden + left + inviteLink + result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(chat->inviteLink()); } else if (peer->isChannel()) { ChannelData *channel = peer->asChannel(); - // name + access + date + version + forbidden + flags + invitationUrl - result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(channel->invitationUrl); + // name + access + date + version + forbidden + flags + inviteLink + result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(channel->inviteLink()); } return result; } @@ -3441,7 +3442,7 @@ namespace Local { if (peer->isUser()) { UserData *user = peer->asUser(); - stream << user->firstName << user->lastName << user->phone << user->username << quint64(user->access); + stream << user->firstName << user->lastName << user->phone() << user->username << quint64(user->access); if (AppVersion >= 9012) { stream << qint32(user->flags); } @@ -3455,12 +3456,12 @@ namespace Local { qint32 flagsData = (AppVersion >= 9012) ? chat->flags : (chat->haveLeft() ? 1 : 0); stream << chat->name << qint32(chat->count) << qint32(chat->date) << qint32(chat->version) << qint32(chat->creator); - stream << qint32(chat->isForbidden ? 1 : 0) << qint32(flagsData) << chat->invitationUrl; + stream << qint32(chat->isForbidden ? 1 : 0) << qint32(flagsData) << chat->inviteLink(); } else if (peer->isChannel()) { ChannelData *channel = peer->asChannel(); stream << channel->name << quint64(channel->access) << qint32(channel->date) << qint32(channel->version); - stream << qint32(channel->isForbidden ? 1 : 0) << qint32(channel->flags) << channel->invitationUrl; + stream << qint32(channel->isForbidden ? 1 : 0) << qint32(channel->flags) << channel->inviteLink(); } } @@ -3495,6 +3496,7 @@ namespace Local { QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); if (!wasLoaded) { + user->setPhone(phone); user->setName(first, last, pname, username); user->access = access; @@ -3519,9 +3521,9 @@ namespace Local { } else if (result->isChat()) { ChatData *chat = result->asChat(); - QString name, invitationUrl; + QString name, inviteLink; qint32 count, date, version, creator, forbidden, flagsData, flags; - from.stream >> name >> count >> date >> version >> creator >> forbidden >> flagsData >> invitationUrl; + from.stream >> name >> count >> date >> version >> creator >> forbidden >> flagsData >> inviteLink; if (from.version >= 9012) { flags = flagsData; @@ -3530,14 +3532,14 @@ namespace Local { flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0); } if (!wasLoaded) { - chat->updateName(name, QString(), QString()); + chat->setName(name); chat->count = count; chat->date = date; chat->version = version; chat->creator = creator; chat->isForbidden = (forbidden == 1); chat->flags = MTPDchat::Flags(flags); - chat->invitationUrl = invitationUrl; + chat->setInviteLink(inviteLink); chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); chat->inputChat = MTP_int(peerToChat(chat->id)); @@ -3547,19 +3549,19 @@ namespace Local { } else if (result->isChannel()) { ChannelData *channel = result->asChannel(); - QString name, invitationUrl; + QString name, inviteLink; quint64 access; qint32 date, version, forbidden, flags; - from.stream >> name >> access >> date >> version >> forbidden >> flags >> invitationUrl; + from.stream >> name >> access >> date >> version >> forbidden >> flags >> inviteLink; if (!wasLoaded) { - channel->updateName(name, QString(), QString()); + channel->setName(name, QString()); channel->access = access; channel->date = date; channel->version = version; channel->isForbidden = (forbidden == 1); channel->flags = MTPDchannel::Flags(flags); - channel->invitationUrl = invitationUrl; + channel->setInviteLink(inviteLink); channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); channel->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); @@ -3749,7 +3751,7 @@ namespace Local { cRefSavedPeersByTime().insert(t, peer); peers.push_back(peer); } - App::emitPeerUpdated(); + if (App::api()) App::api()->requestPeers(peers); } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d99c08848..a69a663af 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -22,12 +22,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "ui/buttons/peer_avatar_button.h" +#include "window/section_memento.h" +#include "window/section_widget.h" #include "window/top_bar_widget.h" #include "data/drafts.h" +#include "observer_peer.h" #include "apiwrap.h" #include "dialogswidget.h" #include "historywidget.h" -#include "profilewidget.h" #include "overviewwidget.h" #include "playerwidget.h" #include "lang.h" @@ -45,8 +47,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "shortcuts.h" #include "audio.h" +StackItemSection::StackItemSection(std_::unique_ptr &&memento) : StackItem(nullptr) +, _memento(std_::move(memento)) { +} + +StackItemSection::~StackItemSection() { +} + MainWidget::MainWidget(MainWindow *window) : TWidget(window) , _a_show(animation(this, &MainWidget::step_show)) +, _sideShadow(this, st::shadowColor) , _dialogs(this) , _history(this) , _player(this) @@ -380,7 +390,6 @@ void MainWidget::onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data) void MainWidget::rpcClear() { _history->rpcClear(); _dialogs->rpcClear(); - if (_profile) _profile->rpcClear(); if (_overview) _overview->rpcClear(); if (_api) _api->rpcClear(); RPCSender::rpcClear(); @@ -389,8 +398,8 @@ void MainWidget::rpcClear() { QPixmap MainWidget::grabInner() { if (_overview && !_overview->isHidden()) { return myGrab(_overview); - } else if (_profile && !_profile->isHidden()) { - return myGrab(_profile); + } else if (_wideSection && !_wideSection->isHidden()) { + return myGrab(_wideSection, QRect(0, st::topBarHeight, _history->width(), _history->height() - st::topBarHeight)); } else if (Adaptive::OneColumn() && _history->isHidden()) { return myGrab(_dialogs, QRect(0, st::topBarHeight, _dialogs->width(), _dialogs->height() - st::topBarHeight)); } else if (_history->peer()) { @@ -410,6 +419,8 @@ bool MainWidget::isItemVisible(HistoryItem *item) { QPixmap MainWidget::grabTopBar() { if (!_topBar->isHidden()) { return myGrab(_topBar); + } else if (_wideSection) { + return myGrab(_wideSection, QRect(0, 0, _wideSection->width(), st::topBarHeight)); } else if (Adaptive::OneColumn() && _history->isHidden()) { return myGrab(_dialogs, QRect(0, 0, _dialogs->width(), st::topBarHeight)); } else { @@ -525,18 +536,22 @@ void MainWidget::noHider(HistoryHider *destroyed) { _forwardConfirm = 0; } onHistoryShown(_history->history(), _history->msgId()); - if (_profile || _overview || (_history->peer() && _history->peer()->id)) { - QPixmap animCache = grabInner(), animTopBarCache = grabTopBar(); + if (_wideSection || _overview || (_history->peer() && _history->peer()->id)) { + Window::SectionSlideParams animationParams; + if (_overview) { + animationParams = prepareOverviewAnimation(); + } else if (_wideSection) { + animationParams = prepareWideSectionAnimation(_wideSection); + } else { + animationParams = prepareHistoryAnimation(_history->peer() ? _history->peer()->id : 0); + } _dialogs->hide(); if (_overview) { - _overview->show(); - _overview->animShow(animCache, animTopBarCache); - } else if (_profile) { - _profile->show(); - _profile->animShow(animCache, animTopBarCache); + _overview->showAnimated(Window::SlideDirection::FromRight, animationParams); + } else if (_wideSection) { + _wideSection->showAnimated(Window::SlideDirection::FromRight, animationParams); } else { - _history->show(); - _history->animShow(animCache, animTopBarCache); + _history->showAnimated(Window::SlideDirection::FromRight, animationParams); } } App::wnd()->getTitle()->updateBackButton(); @@ -561,19 +576,19 @@ void MainWidget::hiderLayer(HistoryHider *h) { dialogsToUp(); _hider->hide(); - QPixmap animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + auto animationParams = prepareDialogsAnimation(); onHistoryShown(0, 0); if (_overview) { _overview->hide(); - } else if (_profile) { - _profile->hide(); + } else if (_wideSection) { + _wideSection->hide(); } else { _history->hide(); } _dialogs->show(); resizeEvent(0); - _dialogs->animShow(animCache); + _dialogs->showAnimated(Window::SlideDirection::FromLeft, animationParams); App::wnd()->getTitle()->updateBackButton(); } else { _hider->show(); @@ -683,12 +698,10 @@ void MainWidget::deleteHistoryPart(DeleteHistoryRequest request, const MTPmessag if (peer && peer->isChannel()) { if (peer->asChannel()->ptsUpdated(d.vpts.v, d.vpts_count.v)) { peer->asChannel()->ptsApplySkippedUpdates(); - App::emitPeerUpdated(); } } else { if (ptsUpdated(d.vpts.v, d.vpts_count.v)) { ptsApplySkippedUpdates(); - App::emitPeerUpdated(); } } @@ -716,10 +729,9 @@ void MainWidget::deleteMessages(PeerData *peer, const QVector &ids) { } void MainWidget::deletedContact(UserData *user, const MTPcontacts_Link &result) { - const auto &d(result.c_contacts_link()); - App::feedUsers(MTP_vector(1, d.vuser), false); - App::feedUserLink(MTP_int(peerToUser(user->id)), d.vmy_link, d.vforeign_link, false); - App::emitPeerUpdated(); + auto &d(result.c_contacts_link()); + App::feedUsers(MTP_vector(1, d.vuser)); + App::feedUserLink(MTP_int(peerToUser(user->id)), d.vmy_link, d.vforeign_link); } void MainWidget::removeDialog(History *history) { @@ -755,6 +767,11 @@ void MainWidget::deleteConversation(PeerData *peer, bool deleteHistory) { } } +void MainWidget::deleteAndExit(ChatData *chat) { + PeerData *peer = chat; + MTP::send(MTPmessages_DeleteChatUser(chat->inputChat, App::self()->inputUser), rpcDone(&MainWidget::deleteHistoryAfterLeave, peer), rpcFail(&MainWidget::leaveChatFailed, peer)); +} + void MainWidget::deleteAllFromUser(ChannelData *channel, UserData *from) { t_assert(channel != nullptr && from != nullptr); @@ -780,7 +797,6 @@ void MainWidget::deleteAllFromUserPart(DeleteAllFromUserParams params, const MTP const auto &d(result.c_messages_affectedHistory()); if (params.channel->ptsUpdated(d.vpts.v, d.vpts_count.v)) { params.channel->ptsApplySkippedUpdates(); - App::emitPeerUpdated(); } int32 offset = d.voffset.v; @@ -869,7 +885,6 @@ bool MainWidget::addParticipantsFail(ChannelData *channel, const RPCError &error void MainWidget::kickParticipant(ChatData *chat, UserData *user) { MTP::send(MTPmessages_DeleteChatUser(chat->inputChat, user->inputUser), rpcDone(&MainWidget::sentUpdatesReceived), rpcFail(&MainWidget::kickParticipantFail, chat)); - Ui::hideLayer(); Ui::showPeerHistory(chat->id, ShowAtTheEndMsgId); } @@ -1220,7 +1235,7 @@ bool MainWidget::preloadOverview(PeerData *peer, MediaOverviewType type) { if (type == OverviewCount) return false; History *h = App::history(peer->id); - if (h->overviewCountLoaded(type) || _overviewPreload[type].constFind(peer) != _overviewPreload[type].cend()) { + if (h->overviewCountLoaded(type) || _overviewPreload[type].contains(peer)) { return false; } @@ -1257,11 +1272,10 @@ void MainWidget::overviewPreloaded(PeerData *peer, const MTPmessages_Messages &r App::history(peer->id)->overviewSliceDone(type, result, true); - mediaOverviewUpdated(peer, type); + if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, type); } void MainWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { - if (_profile) _profile->mediaOverviewUpdated(peer, type); if (!_player->isHidden()) _player->mediaOverviewUpdated(peer, type); if (_overview && (_overview->peer() == peer || _overview->peer()->migrateFrom() == peer)) { _overview->mediaOverviewUpdated(peer, type); @@ -1361,15 +1375,6 @@ void MainWidget::loadMediaBack(PeerData *peer, MediaOverviewType type, bool many _overviewLoad[type].insert(peer, MTP::send(MTPmessages_Search(MTP_flags(flags), peer->input, MTPstring(), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(minId), MTP_int(limit)), rpcDone(&MainWidget::overviewLoaded, history))); } -void MainWidget::peerUsernameChanged(PeerData *peer) { - if (_profile && _profile->peer() == peer) { - _profile->peerUsernameChanged(); - } - if (App::settings() && peer == App::self()) { - App::settings()->usernameChanged(); - } -} - void MainWidget::checkLastUpdate(bool afterSleep) { uint64 n = getms(true); if (_lastUpdateTime && n > _lastUpdateTime + (afterSleep ? NoUpdatesAfterSleepTimeout : NoUpdatesTimeout)) { @@ -1443,12 +1448,10 @@ void MainWidget::messagesAffected(PeerData *peer, const MTPmessages_AffectedMess if (peer && peer->isChannel()) { if (peer->asChannel()->ptsUpdated(d.vpts.v, d.vpts_count.v)) { peer->asChannel()->ptsApplySkippedUpdates(); - App::emitPeerUpdated(); } } else { if (ptsUpdated(d.vpts.v, d.vpts_count.v)) { ptsApplySkippedUpdates(); - App::emitPeerUpdated(); } } if (History *h = App::historyLoaded(peer ? peer->id : 0)) { @@ -1671,7 +1674,6 @@ void MainWidget::onParentResize(const QSize &newSize) { void MainWidget::updateOnlineDisplay() { if (this != App::main()) return; _history->updateOnlineDisplay(_history->x(), width() - _history->x() - st::sysBtnDelta * 2 - st::sysCls.img.pxWidth() - st::sysRes.img.pxWidth() - st::sysMin.img.pxWidth()); - if (_profile) _profile->updateOnlineDisplay(); if (App::wnd()->settingsWidget()) App::wnd()->settingsWidget()->updateOnlineDisplay(); } @@ -1855,15 +1857,15 @@ void MainWidget::setInnerFocus() { _hider->setFocus(); } else if (_overview) { _overview->activate(); - } else if (_profile) { - _profile->activate(); + } else if (_wideSection) { + _wideSection->setInnerFocus(); } else { dialogsActivate(); } } else if (_overview) { _overview->activate(); - } else if (_profile) { - _profile->activate(); + } else if (_wideSection) { + _wideSection->setInnerFocus(); } else { _history->setInnerFocus(); } @@ -2003,31 +2005,19 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac _hider = nullptr; } - QPixmap animCache, animTopBarCache; - if (!_a_show.animating() && ((_history->isHidden() && (_profile || _overview)) || (Adaptive::OneColumn() && (_history->isHidden() || !peerId)))) { - if (peerId) { - animCache = grabInner(); - } else if (Adaptive::OneColumn()) { - animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); - } else { - animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); - } - if (peerId || !Adaptive::OneColumn()) { - animTopBarCache = grabTopBar(); - } - _history->show(); + Window::SectionSlideParams animationParams; + if (!_a_show.animating() && ((_history->isHidden() && (_wideSection || _overview)) || (Adaptive::OneColumn() && (_history->isHidden() || !peerId)))) { + animationParams = prepareHistoryAnimation(peerId); } if (_history->peer() && _history->peer()->id != peerId) clearBotStartToken(_history->peer()); _history->showHistory(peerId, showAtMsgId); bool noPeer = (!_history->peer() || !_history->peer()->id), onlyDialogs = noPeer && Adaptive::OneColumn(); - if (_profile || _overview) { - if (_profile) { - _profile->hide(); - _profile->clear(); - _profile->deleteLater(); - _profile->rpcClear(); - _profile = nullptr; + if (_wideSection || _overview) { + if (_wideSection) { + _wideSection->hide(); + _wideSection->deleteLater(); + _wideSection = nullptr; } if (_overview) { _overview->hide(); @@ -2046,9 +2036,10 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac _topBar->hide(); _history->hide(); if (!_a_show.animating()) { - _dialogs->show(); - if (!animCache.isNull()) { - _dialogs->animShow(animCache); + if (!animationParams.oldContentCache.isNull()) { + _dialogs->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams); + } else { + _dialogs->show(); } } } else { @@ -2063,11 +2054,13 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac } if (Adaptive::OneColumn() && !_dialogs->isHidden()) _dialogs->hide(); if (!_a_show.animating()) { - if (_history->isHidden()) _history->show(); - if (!animCache.isNull()) { - _history->animShow(animCache, animTopBarCache, back); - } else if (App::wnd()) { - QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus())); + if (!animationParams.oldContentCache.isNull()) { + _history->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams); + } else { + _history->show(); + if (App::wnd()) { + QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus())); + } } } } @@ -2085,9 +2078,6 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac } PeerData *MainWidget::ui_getPeerForMouseAction() { - if (_profile) { - return _profile->ui_getPeerForMouseAction(); - } return _history->ui_getPeerForMouseAction(); } @@ -2125,10 +2115,6 @@ MsgId MainWidget::activeMsgId() { return _history->peer() ? _history->msgId() : _msgIdInStack; } -PeerData *MainWidget::profilePeer() { - return _profile ? _profile->peer() : 0; -} - PeerData *MainWidget::overviewPeer() { return _overview ? _overview->peer() : 0; } @@ -2159,18 +2145,15 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool return; } - QRect topBarRect = QRect(_topBar->x(), _topBar->y(), _topBar->width(), st::topBarHeight); - QRect historyRect = QRect(_history->x(), topBarRect.y() + topBarRect.height(), _history->width(), _history->y() + _history->height() - topBarRect.y() - topBarRect.height()); - QPixmap animCache, animTopBarCache; - if (!_a_show.animating() && (Adaptive::OneColumn() || _profile || _overview || _history->peer())) { - animCache = grabInner(); - animTopBarCache = grabTopBar(); + Window::SectionSlideParams animationParams; + if (!_a_show.animating() && (Adaptive::OneColumn() || _wideSection || _overview || _history->peer())) { + animationParams = prepareOverviewAnimation(); } if (!back) { if (_overview) { _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); - } else if (_profile) { - _stack.push_back(new StackItemProfile(_profile->peer(), _profile->lastScrollTop())); + } else if (_wideSection) { + _stack.push_back(new StackItemSection(_wideSection->createMemento())); } else if (_history->peer()) { dlgUpdated(); _peerInStack = _history->peer(); @@ -2185,20 +2168,19 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool _overview->deleteLater(); _overview->rpcClear(); } - if (_profile) { - _profile->hide(); - _profile->clear(); - _profile->deleteLater(); - _profile->rpcClear(); - _profile = nullptr; + if (_wideSection) { + _wideSection->hide(); + _wideSection->deleteLater(); + _wideSection = nullptr; } _overview = new OverviewWidget(this, peer, type); _mediaTypeMask = 0; _topBar->show(); resizeEvent(nullptr); mediaOverviewUpdated(peer, type); - if (!animCache.isNull()) { - _overview->animShow(animCache, animTopBarCache, back, lastScrollTop); + _overview->setLastScrollTop(lastScrollTop); + if (!animationParams.oldContentCache.isNull()) { + _overview->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams); } else { _overview->fastShow(); } @@ -2213,28 +2195,88 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool App::wnd()->getTitle()->updateBackButton(); } -void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop) { - if (peer->migrateTo()) { - peer = peer->migrateTo(); - } - +void MainWidget::showWideSection(const Window::SectionMemento &memento) { App::wnd()->hideSettings(); - if (_profile && _profile->peer() == peer) return; - - QPixmap animCache = grabInner(), animTopBarCache = grabTopBar(); - if (!back) { - if (_overview) { - _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); - } else if (_profile) { - _stack.push_back(new StackItemProfile(_profile->peer(), _profile->lastScrollTop())); - } else if (_history->peer()) { - dlgUpdated(); - _peerInStack = _history->peer(); - _msgIdInStack = _history->msgId(); - dlgUpdated(); - _stack.push_back(new StackItemHistory(_peerInStack, _msgIdInStack, _history->replyReturns())); - } + if (_wideSection && _wideSection->showInternal(&memento)) { + return; } + + if (_overview) { + _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); + } else if (_wideSection) { + _stack.push_back(new StackItemSection(_wideSection->createMemento())); + } else if (_history->peer()) { + dlgUpdated(); + _peerInStack = _history->peer(); + _msgIdInStack = _history->msgId(); + dlgUpdated(); + _stack.push_back(new StackItemHistory(_peerInStack, _msgIdInStack, _history->replyReturns())); + } + showWideSectionAnimated(&memento, false); +} + +Window::SectionSlideParams MainWidget::prepareShowAnimation(bool willHaveTopBarShadow) { + Window::SectionSlideParams result; + result.withTopBarShadow = willHaveTopBarShadow; + if (selectingPeer() && Adaptive::OneColumn()) { + result.withTopBarShadow = false; + } else if (_wideSection) { + if (!_wideSection->hasTopBarShadow()) { + result.withTopBarShadow = false; + } + } else if (!_overview && !_history->peer()) { + result.withTopBarShadow = false; + } + + if (selectingPeer() && Adaptive::OneColumn()) { + result.oldContentCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + } else if (_wideSection) { + result.oldContentCache = _wideSection->grabForShowAnimation(result); + } else { + if (result.withTopBarShadow) { + if (_overview) _overview->grapWithoutTopBarShadow(); + _history->grapWithoutTopBarShadow(); + } else { + if (_overview) _overview->grabStart(); + _history->grabStart(); + } + if (Adaptive::OneColumn()) { + result.oldContentCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + } else { + _sideShadow.hide(); + result.oldContentCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); + _sideShadow.show(); + } + if (_overview) _overview->grabFinish(); + _history->grabFinish(); + } + + return result; +} + +Window::SectionSlideParams MainWidget::prepareWideSectionAnimation(Window::SectionWidget *section) { + return prepareShowAnimation(section->hasTopBarShadow()); +} + +Window::SectionSlideParams MainWidget::prepareHistoryAnimation(PeerId historyPeerId) { + return prepareShowAnimation(historyPeerId != 0); +} + +Window::SectionSlideParams MainWidget::prepareOverviewAnimation() { + return prepareShowAnimation(true); +} + +Window::SectionSlideParams MainWidget::prepareDialogsAnimation() { + return prepareShowAnimation(false); +} + +void MainWidget::showWideSectionAnimated(const Window::SectionMemento *memento, bool back) { + QPixmap animCache; + + auto newWideGeometry = QRect(_history->x(), _playerHeight, _history->width(), height() - _playerHeight); + auto newWideSection = memento->createWidget(this, newWideGeometry); + Window::SectionSlideParams animationParams = prepareWideSectionAnimation(newWideSection); + if (_overview) { _overview->hide(); _overview->clear(); @@ -2242,18 +2284,17 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop) _overview->rpcClear(); _overview = nullptr; } - if (_profile) { - _profile->hide(); - _profile->clear(); - _profile->deleteLater(); - _profile->rpcClear(); + if (_wideSection) { + _wideSection->hide(); + _wideSection->deleteLater(); + _wideSection = nullptr; } - _profile = new ProfileWidget(this, peer); - _topBar->show(); + _wideSection = newWideSection; + _topBar->hide(); resizeEvent(0); - _profile->animShow(animCache, animTopBarCache, back, lastScrollTop); + auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight; + _wideSection->showAnimated(direction, animationParams); _history->animStop(); - if (back) clearBotStartToken(_history->peer()); _history->showHistory(0, 0); _history->hide(); if (Adaptive::OneColumn()) _dialogs->hide(); @@ -2272,6 +2313,9 @@ void MainWidget::showBackFromStack() { } StackItem *item = _stack.back(); _stack.pop_back(); + if (auto currentHistoryPeer = _history->peer()) { + clearBotStartToken(currentHistoryPeer); + } if (item->type() == HistoryStackItem) { dlgUpdated(); _peerInStack = 0; @@ -2287,9 +2331,9 @@ void MainWidget::showBackFromStack() { StackItemHistory *histItem = static_cast(item); Ui::showPeerHistory(histItem->peer->id, App::main()->activeMsgId(), true); _history->setReplyReturns(histItem->peer->id, histItem->replyReturns); - } else if (item->type() == ProfileStackItem) { - StackItemProfile *profItem = static_cast(item); - showPeerProfile(profItem->peer, true, profItem->lastScrollTop); + } else if (item->type() == SectionStackItem) { + StackItemSection *sectionItem = static_cast(item); + showWideSectionAnimated(sectionItem->memento(), true); } else if (item->type() == OverviewStackItem) { StackItemOverview *overItem = static_cast(item); showMediaOverview(overItem->peer, overItem->mediaType, true, overItem->lastScrollTop); @@ -2302,6 +2346,7 @@ void MainWidget::orderWidgets() { _player->raise(); _dialogs->raise(); _mediaType->raise(); + _sideShadow.raise(); if (_hider) _hider->raise(); } @@ -2312,6 +2357,19 @@ QRect MainWidget::historyRect() const { return r; } +QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { + _topBar->stopAnim(); + QPixmap result; + if (Adaptive::OneColumn()) { + result = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + } else { + _sideShadow.hide(); + result = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); + _sideShadow.show(); + } + return result; +} + void MainWidget::dlgUpdated() { if (_peerInStack) { _dialogs->dlgUpdated(App::history(_peerInStack->id), _msgIdInStack); @@ -2339,7 +2397,6 @@ void MainWidget::windowShown() { void MainWidget::sentUpdatesReceived(uint64 randomId, const MTPUpdates &result) { feedUpdates(result, randomId); - App::emitPeerUpdated(); } bool MainWidget::deleteChannelFailed(const RPCError &error) { @@ -2394,7 +2451,7 @@ void MainWidget::animShow(const QPixmap &bgAnimCache, bool back) { (back ? _cacheUnder : _cacheOver) = myGrab(this); hideAll(); - a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); + a_coordUnder = back ? anim::ivalue(-st::slideShift, 0) : anim::ivalue(0, -st::slideShift); a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); _a_show.start(); @@ -2449,12 +2506,13 @@ void MainWidget::paintEvent(QPaintEvent *e) { void MainWidget::hideAll() { _dialogs->hide(); _history->hide(); - if (_profile) { - _profile->hide(); + if (_wideSection) { + _wideSection->hide(); } if (_overview) { _overview->hide(); } + _sideShadow.hide(); _topBar->hide(); _mediaType->hide(); if (_player->isOpened() && !_player->isHidden()) { @@ -2468,6 +2526,7 @@ void MainWidget::showAll() { cSetPasswordRecovered(false); Ui::showLayer(new InformBox(lang(lng_signin_password_removed))); } + _sideShadow.show(); if (Adaptive::OneColumn()) { if (_hider) { _hider->hide(); @@ -2482,12 +2541,12 @@ void MainWidget::showAll() { _dialogs->show(); _history->hide(); if (_overview) _overview->hide(); - if (_profile) _profile->hide(); + if (_wideSection) _wideSection->hide(); _topBar->hide(); } else if (_overview) { _overview->show(); - } else if (_profile) { - _profile->show(); + } else if (_wideSection) { + _wideSection->show(); } else if (_history->peer()) { _history->show(); _history->resizeEvent(0); @@ -2495,7 +2554,10 @@ void MainWidget::showAll() { _dialogs->show(); _history->hide(); } - if (!selectingPeer() && (_profile || _overview || _history->peer())) { + if (_wideSection) { + _topBar->hide(); + _dialogs->hide(); + } else if (!selectingPeer() && (_overview || _history->peer())) { _topBar->show(); _dialogs->hide(); } @@ -2510,13 +2572,15 @@ void MainWidget::showAll() { _dialogs->show(); if (_overview) { _overview->show(); - } else if (_profile) { - _profile->show(); + } else if (_wideSection) { + _wideSection->show(); } else { _history->show(); _history->resizeEvent(0); } - if (_profile || _overview || _history->peer()) { + if (_wideSection) { + _topBar->hide(); + } else if (_overview || _history->peer()) { _topBar->show(); } } @@ -2542,6 +2606,8 @@ void MainWidget::resizeEvent(QResizeEvent *e) { _dialogsWidth = chatsListWidth(width()); _dialogs->resize(_dialogsWidth, height()); _dialogs->moveToLeft(0, 0); + _sideShadow.resize(st::lineWidth, height()); + _sideShadow.moveToLeft(_dialogsWidth, 0); _player->resize(width() - _dialogsWidth, _player->height()); _player->moveToLeft(_dialogsWidth, 0); _topBar->resize(width() - _dialogsWidth, st::topBarHeight); @@ -2554,7 +2620,10 @@ void MainWidget::resizeEvent(QResizeEvent *e) { } } _mediaType->moveToLeft(width() - _mediaType->width(), _playerHeight + st::topBarHeight); - if (_profile) _profile->setGeometry(_history->geometry()); + if (_wideSection) { + QRect wideSectionGeometry(_history->x(), _playerHeight, _history->width(), height() - _playerHeight); + _wideSection->setGeometryWithTopMoved(wideSectionGeometry, _contentScrollAddToY); + } if (_overview) _overview->setGeometry(_history->geometry()); _contentScrollAddToY = 0; } @@ -2568,23 +2637,22 @@ void MainWidget::keyPressEvent(QKeyEvent *e) { void MainWidget::updateAdaptiveLayout() { showAll(); + _sideShadow.setVisible(!Adaptive::OneColumn()); + if (_wideSection) { + _wideSection->updateAdaptiveLayout(); + } _topBar->updateAdaptiveLayout(); _history->updateAdaptiveLayout(); - if (_overview) _overview->updateAdaptiveLayout(); - if (_profile) _profile->updateAdaptiveLayout(); - _player->updateAdaptiveLayout(); } bool MainWidget::needBackButton() { - return _overview || _profile || (_history->peer() && _history->peer()->id); + return _overview || _wideSection || _history->peer(); } void MainWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { - if (_profile) { - _profile->paintTopBar(p, over, decreaseWidth); - } else if (_overview) { + if (_overview) { _overview->paintTopBar(p, over, decreaseWidth); - } else { + } else if (!_wideSection) { _history->paintTopBar(p, over, decreaseWidth); } } @@ -2628,17 +2696,15 @@ PlayerWidget *MainWidget::player() { } void MainWidget::onTopBarClick() { - if (_profile) { - _profile->topBarClick(); - } else if (_overview) { + if (_overview) { _overview->topBarClick(); - } else { + } else if (!_wideSection) { _history->topBarClick(); } } void MainWidget::onHistoryShown(History *history, MsgId atMsgId) { - if ((!Adaptive::OneColumn() || !selectingPeer()) && (_profile || _overview || history)) { + if ((!Adaptive::OneColumn() || !selectingPeer()) && (_overview || history)) { _topBar->show(); } else { _topBar->hide(); @@ -2772,7 +2838,7 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha const auto &d(diff.c_updates_channelDifference()); App::feedUsers(d.vusers); - App::feedChats(d.vchats, false); + App::feedChats(d.vchats); _handlingChannelDifference = true; feedMessageIds(d.vother_updates); @@ -2824,8 +2890,6 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha } else if (activePeer() == channel) { channel->ptsWaitingForShortPoll(timeout ? (timeout * 1000) : WaitForChannelGetDifference); } - - App::emitPeerUpdated(); } void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff) { @@ -2852,7 +2916,7 @@ void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_Chann const auto &d(diff.c_updates_channelDifference()); App::feedUsers(d.vusers); - App::feedChats(d.vchats, false); + App::feedChats(d.vchats); _handlingChannelDifference = true; feedMessageIds(d.vother_updates); @@ -2871,8 +2935,6 @@ void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_Chann h->asChannelHistory()->getRangeDifferenceNext(nextRequestPts); } } - - App::emitPeerUpdated(); } bool MainWidget::failChannelDifference(ChannelData *channel, const RPCError &error) { @@ -2893,8 +2955,6 @@ void MainWidget::gotState(const MTPupdates_State &state) { _dialogs->loadDialogs(); updateOnline(); - - App::emitPeerUpdated(); } void MainWidget::gotDifference(const MTPupdates_Difference &diff) { @@ -2909,8 +2969,6 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) { noUpdatesTimer.start(NoUpdatesTimeout); _ptsWaiter.setRequesting(false); - - App::emitPeerUpdated(); } break; case mtpc_updates_differenceSlice: { const auto &d(diff.c_updates_differenceSlice()); @@ -2923,8 +2981,6 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) { MTP_LOG(0, ("getDifference { good - after a slice of difference was received }%1").arg(cTestMode() ? " TESTMODE" : "")); getDifference(); - - App::emitPeerUpdated(); } break; case mtpc_updates_difference: { const auto &d(diff.c_updates_difference()); @@ -3022,8 +3078,8 @@ void MainWidget::ptsApplySkippedUpdates() { void MainWidget::feedDifference(const MTPVector &users, const MTPVector &chats, const MTPVector &msgs, const MTPVector &other) { App::wnd()->checkAutoLock(); - App::feedUsers(users, false); - App::feedChats(chats, false); + App::feedUsers(users); + App::feedChats(chats); feedMessageIds(other); App::feedMsgs(msgs, NewMessageUnread); feedUpdateVector(other, true); @@ -3230,7 +3286,7 @@ void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QStr // Always open bot chats, even from mention links. Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId); } else { - showPeerProfile(peer); + Ui::showPeerProfile(peer); } } else { if (msgId == ShowAtProfileMsgId || !peer->isChannel()) { // show specific posts only in channels / supergroups @@ -3313,7 +3369,7 @@ void MainWidget::usernameResolveDone(QPair msgIdAndStartToken, c // Always open bot chats, even from mention links. Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId); } else { - showPeerProfile(peer); + Ui::showPeerProfile(peer); } } else { if (msgId == ShowAtProfileMsgId || !peer->isChannel()) { // show specific posts only in channels / supergroups @@ -3410,7 +3466,8 @@ void MainWidget::startFull(const MTPVector &users) { } void MainWidget::applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNotifySettings &settings, History *h) { - PeerData *updatePeer = 0; + PeerData *updatePeer = nullptr; + bool changed = false; switch (settings.type()) { case mtpc_peerNotifySettingsEmpty: switch (peer.type()) { @@ -3418,15 +3475,17 @@ void MainWidget::applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNoti case mtpc_notifyUsers: globalNotifyUsersPtr = EmptyNotifySettings; break; case mtpc_notifyChats: globalNotifyChatsPtr = EmptyNotifySettings; break; case mtpc_notifyPeer: { - updatePeer = App::peerLoaded(peerFromMTP(peer.c_notifyPeer().vpeer)); - if (updatePeer && updatePeer->notify != EmptyNotifySettings) { - if (updatePeer->notify != UnknownNotifySettings) { - delete updatePeer->notify; + if ((updatePeer = App::peerLoaded(peerFromMTP(peer.c_notifyPeer().vpeer)))) { + changed = (updatePeer->notify != EmptyNotifySettings); + if (changed) { + if (updatePeer->notify != UnknownNotifySettings) { + delete updatePeer->notify; + } + updatePeer->notify = EmptyNotifySettings; + App::unregMuted(updatePeer); + if (!h) h = App::history(updatePeer->id); + h->setMute(false); } - updatePeer->notify = EmptyNotifySettings; - App::unregMuted(updatePeer); - if (!h) h = App::history(updatePeer->id); - h->setMute(false); } } break; } @@ -3439,29 +3498,32 @@ void MainWidget::applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNoti case mtpc_notifyUsers: setTo = globalNotifyUsersPtr = &globalNotifyUsers; break; case mtpc_notifyChats: setTo = globalNotifyChatsPtr = &globalNotifyChats; break; case mtpc_notifyPeer: { - updatePeer = App::peerLoaded(peerFromMTP(peer.c_notifyPeer().vpeer)); - if (!updatePeer) break; - - if (updatePeer->notify == UnknownNotifySettings || updatePeer->notify == EmptyNotifySettings) { - updatePeer->notify = new NotifySettings(); + if ((updatePeer = App::peerLoaded(peerFromMTP(peer.c_notifyPeer().vpeer)))) { + if (updatePeer->notify == UnknownNotifySettings || updatePeer->notify == EmptyNotifySettings) { + changed = true; + updatePeer->notify = new NotifySettings(); + } + setTo = updatePeer->notify; } - setTo = updatePeer->notify; } break; } if (setTo == UnknownNotifySettings) break; - setTo->flags = d.vflags.v; - setTo->mute = d.vmute_until.v; - setTo->sound = d.vsound.c_string().v; - if (updatePeer) { - if (!h) h = App::history(updatePeer->id); - int32 changeIn = 0; - if (isNotifyMuted(setTo, &changeIn)) { - App::wnd()->notifyClear(h); - h->setMute(true); - App::regMuted(updatePeer, changeIn); - } else { - h->setMute(false); + changed = (setTo->flags != d.vflags.v) || (setTo->mute != d.vmute_until.v) || (setTo->sound != d.vsound.c_string().v); + if (changed) { + setTo->flags = d.vflags.v; + setTo->mute = d.vmute_until.v; + setTo->sound = d.vsound.c_string().v; + if (updatePeer) { + if (!h) h = App::history(updatePeer->id); + int32 changeIn = 0; + if (isNotifyMuted(setTo, &changeIn)) { + App::wnd()->notifyClear(h); + h->setMute(true); + App::regMuted(updatePeer, changeIn); + } else { + h->setMute(false); + } } } } break; @@ -3472,37 +3534,12 @@ void MainWidget::applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNoti _history->updateNotifySettings(); } _dialogs->updateNotifySettings(updatePeer); - if (_profile && _profile->peer() == updatePeer) { - _profile->updateNotifySettings(); + if (changed) { + Notify::peerUpdatedDelayed(updatePeer, Notify::PeerUpdate::Flag::NotificationsEnabled); } } } -void MainWidget::gotNotifySetting(MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings) { - switch (peer.type()) { - case mtpc_inputNotifyAll: applyNotifySetting(MTP_notifyAll(), settings); break; - case mtpc_inputNotifyUsers: applyNotifySetting(MTP_notifyUsers(), settings); break; - case mtpc_inputNotifyChats: applyNotifySetting(MTP_notifyChats(), settings); break; - case mtpc_inputNotifyPeer: - switch (peer.c_inputNotifyPeer().vpeer.type()) { - case mtpc_inputPeerEmpty: applyNotifySetting(MTP_notifyPeer(MTP_peerUser(MTP_int(0))), settings); break; - case mtpc_inputPeerSelf: applyNotifySetting(MTP_notifyPeer(MTP_peerUser(MTP_int(MTP::authedId()))), settings); break; - case mtpc_inputPeerUser: applyNotifySetting(MTP_notifyPeer(MTP_peerUser(peer.c_inputNotifyPeer().vpeer.c_inputPeerUser().vuser_id)), settings); break; - case mtpc_inputPeerChat: applyNotifySetting(MTP_notifyPeer(MTP_peerChat(peer.c_inputNotifyPeer().vpeer.c_inputPeerChat().vchat_id)), settings); break; - case mtpc_inputPeerChannel: applyNotifySetting(MTP_notifyPeer(MTP_peerChannel(peer.c_inputNotifyPeer().vpeer.c_inputPeerChannel().vchannel_id)), settings); break; - } - break; - } - App::wnd()->notifySettingGot(); -} - -bool MainWidget::failNotifySetting(MTPInputNotifyPeer peer, const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - gotNotifySetting(peer, MTP_peerNotifySettingsEmpty()); - return true; -} - void MainWidget::updateNotifySetting(PeerData *peer, NotifySettingStatus notify, SilentNotifiesStatus silent) { if (notify == NotifySettingDontChange && silent == SilentNotifiesDontChange) return; @@ -3612,7 +3649,7 @@ void MainWidget::incrementSticker(DocumentData *sticker) { void MainWidget::activate() { if (_a_show.animating()) return; - if (!_profile && !_overview) { + if (!_wideSection && !_overview) { if (_hider) { if (_hider->wasOffered()) { _hider->setFocus(); @@ -3646,7 +3683,7 @@ bool MainWidget::isActive() const { } bool MainWidget::doWeReadServerHistory() const { - return isActive() && !_profile && !_overview && _history->doWeReadServerHistory(); + return isActive() && !_wideSection && !_overview && _history->doWeReadServerHistory(); } bool MainWidget::lastWasOnline() const { @@ -3701,6 +3738,7 @@ void MainWidget::updateOnline(bool gotOtherOffline) { if (App::self()) { App::self()->onlineTill = unixtime() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1); + Notify::peerUpdatedDelayed(App::self(), Notify::PeerUpdate::Flag::UserOnlineChanged); } if (!isOnline) { // Went offline, so we need to save message draft to the cloud. saveDraftToCloud(); @@ -3804,7 +3842,6 @@ void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) { if (!_ptsWaiter.requesting()) { feedUpdates(updates); } - App::emitPeerUpdated(); } catch (mtpErrorUnexpected &) { // just some other type } } @@ -3924,8 +3961,8 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { } } - App::feedUsers(d.vusers, false); - App::feedChats(d.vchats, false); + App::feedUsers(d.vusers); + App::feedChats(d.vchats); feedUpdateVector(d.vupdates); updSetState(0, d.vdate.v, updQts, d.vseq.v); @@ -3941,8 +3978,8 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { } } - App::feedUsers(d.vusers, false); - App::feedChats(d.vchats, false); + App::feedUsers(d.vusers); + App::feedChats(d.vchats); feedUpdateVector(d.vupdates); updSetState(0, d.vdate.v, updQts, d.vseq.v); @@ -4253,6 +4290,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_userStatusOnline: user->onlineTill = d.vstatus.c_userStatusOnline().vexpires.v; break; } App::markPeerUpdated(user); + Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserOnlineChanged); } if (d.vuser_id.v == MTP::authedId()) { if (d.vstatus.type() == mtpc_userStatusOffline || d.vstatus.type() == mtpc_userStatusEmpty) { @@ -4267,9 +4305,8 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateUserName: { - const auto &d(update.c_updateUserName()); - UserData *user = App::userLoaded(d.vuser_id.v); - if (user) { + auto &d(update.c_updateUserName()); + if (auto user = App::userLoaded(d.vuser_id.v)) { if (user->contact <= 0) { user->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername))); } else { @@ -4314,7 +4351,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateContactLink: { const auto &d(update.c_updateContactLink()); - App::feedUserLink(d.vuser_id, d.vmy_link, d.vforeign_link, false); + App::feedUserLink(d.vuser_id, d.vmy_link, d.vforeign_link); } break; case mtpc_updateNotifySettings: { @@ -4328,12 +4365,16 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateUserPhone: { - const auto &d(update.c_updateUserPhone()); - UserData *user = App::userLoaded(d.vuser_id.v); - if (user) { - user->setPhone(qs(d.vphone)); - user->setName(user->firstName, user->lastName, (user->contact || isServiceUser(user->id) || user->isSelf() || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), user->username); - App::markPeerUpdated(user); + auto &d(update.c_updateUserPhone()); + if (auto user = App::userLoaded(d.vuser_id.v)) { + auto newPhone = qs(d.vphone); + if (newPhone != user->phone()) { + user->setPhone(newPhone); + user->setName(user->firstName, user->lastName, (user->contact || isServiceUser(user->id) || user->isSelf() || user->phone().isEmpty()) ? QString() : App::formatPhone(user->phone()), user->username); + App::markPeerUpdated(user); + + Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserPhoneChanged); + } } } break; @@ -4356,7 +4397,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateUserBlocked: { const auto &d(update.c_updateUserBlocked()); if (UserData *user = App::userLoaded(d.vuser_id.v)) { - user->blocked = mtpIsTrue(d.vblocked) ? UserIsBlocked : UserIsNotBlocked; + user->setBlockStatus(mtpIsTrue(d.vblocked) ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked); App::markPeerUpdated(user); } } break; @@ -4412,7 +4453,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { // Request last active supergroup participants if the 'from' user was not loaded yet. // This will optimize similar getDifference() calls for almost all next messages. if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup() && App::api()) { - if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.isEmpty() || channel->mgInfo->lastParticipants.size() < channel->count)) { + if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.isEmpty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) { App::api()->requestLastParticipants(channel); } } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 0dbebf701..d611867c7 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -33,6 +33,9 @@ class PeerAvatarButton; namespace Window { class TopBarWidget; +class SectionMemento; +class SectionWidget; +struct SectionSlideParams; } // namespace Window class MainWindow; @@ -40,7 +43,6 @@ class ApiWrap; class ConfirmBox; class DialogsWidget; class HistoryWidget; -class ProfileWidget; class OverviewWidget; class PlayerWidget; class HistoryHider; @@ -48,7 +50,7 @@ class Dropdown; enum StackItemType { HistoryStackItem, - ProfileStackItem, + SectionStackItem, OverviewStackItem, }; @@ -64,8 +66,9 @@ public: class StackItemHistory : public StackItem { public: - StackItemHistory(PeerData *peer, MsgId msgId, QList replyReturns) : StackItem(peer), -msgId(msgId), replyReturns(replyReturns) { + StackItemHistory(PeerData *peer, MsgId msgId, QList replyReturns) : StackItem(peer) + , msgId(msgId) + , replyReturns(replyReturns) { } StackItemType type() const { return HistoryStackItem; @@ -74,19 +77,29 @@ msgId(msgId), replyReturns(replyReturns) { QList replyReturns; }; -class StackItemProfile : public StackItem { +class StackItemSection : public StackItem { public: - StackItemProfile(PeerData *peer, int32 lastScrollTop) : StackItem(peer), lastScrollTop(lastScrollTop) { - } + StackItemSection(std_::unique_ptr &&memento); + ~StackItemSection(); + StackItemType type() const { - return ProfileStackItem; + return SectionStackItem; } - int32 lastScrollTop; + Window::SectionMemento *memento() const { + return _memento.get(); + } + +private: + std_::unique_ptr _memento; + }; class StackItemOverview : public StackItem { public: - StackItemOverview(PeerData *peer, MediaOverviewType mediaType, int32 lastWidth, int32 lastScrollTop) : StackItem(peer), mediaType(mediaType), lastWidth(lastWidth), lastScrollTop(lastScrollTop) { + StackItemOverview(PeerData *peer, MediaOverviewType mediaType, int32 lastWidth, int32 lastScrollTop) : StackItem(peer) + , mediaType(mediaType) + , lastWidth(lastWidth) + , lastScrollTop(lastScrollTop) { } StackItemType type() const { return OverviewStackItem; @@ -173,8 +186,6 @@ public: void startFull(const MTPVector &users); bool started(); void applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNotifySettings &settings, History *history = 0); - void gotNotifySetting(MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings); - bool failNotifySetting(MTPInputNotifyPeer peer, const RPCError &error); void updateNotifySetting(PeerData *peer, NotifySettingStatus notify, SilentNotifiesStatus silent = SilentNotifiesDontChange); @@ -210,14 +221,14 @@ public: PeerData *activePeer(); MsgId activeMsgId(); - PeerData *profilePeer(); PeerData *overviewPeer(); bool mediaTypeSwitch(); - void showPeerProfile(PeerData *peer, bool back = false, int32 lastScrollTop = -1); + void showWideSection(const Window::SectionMemento &memento); void showMediaOverview(PeerData *peer, MediaOverviewType type, bool back = false, int32 lastScrollTop = -1); void showBackFromStack(); void orderWidgets(); QRect historyRect() const; + QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms); void onSendFileConfirm(const FileLoadResultPtr &file, bool ctrlShiftEnter); void onSendFileCancel(const FileLoadResultPtr &file); @@ -262,6 +273,7 @@ public: void deleteMessages(PeerData *peer, const QVector &ids); void deletedContact(UserData *user, const MTPcontacts_Link &result); void deleteConversation(PeerData *peer, bool deleteHistory = true); + void deleteAndExit(ChatData *chat); void clearHistory(PeerData *peer); void deleteAllFromUser(ChannelData *channel, UserData *from); @@ -313,7 +325,6 @@ public: void itemEdited(HistoryItem *item); void loadMediaBack(PeerData *peer, MediaOverviewType type, bool many = false); - void peerUsernameChanged(PeerData *peer); void checkLastUpdate(bool afterSleep); void showAddContact(); @@ -483,10 +494,9 @@ public slots: void ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId); private: - void sendReadRequest(PeerData *peer, MsgId upTo); void channelReadDone(PeerData *peer, const MTPBool &result); - void historyReadDone(PeerData *peer, const MTPmessages_AffectedMessages &result); + void historyReadDone(PeerData *peer, const MTPmessages_AffectedMessages &result); bool readRequestFail(PeerData *peer, const RPCError &error); void readRequestDone(PeerData *peer); @@ -496,6 +506,15 @@ private: void saveCloudDraftDone(PeerData *peer, const MTPBool &result, mtpRequestId requestId); bool saveCloudDraftFail(PeerData *peer, const RPCError &error, mtpRequestId requestId); + Window::SectionSlideParams prepareShowAnimation(bool willHaveTopBarShadow); + void showWideSectionAnimated(const Window::SectionMemento *memento, bool back); + + // All this methods use the prepareShowAnimation(). + Window::SectionSlideParams prepareWideSectionAnimation(Window::SectionWidget *section); + Window::SectionSlideParams prepareHistoryAnimation(PeerId historyPeerId); + Window::SectionSlideParams prepareOverviewAnimation(); + Window::SectionSlideParams prepareDialogsAnimation(); + bool _started = false; uint64 failedObjId = 0; @@ -565,9 +584,11 @@ private: int _dialogsWidth = st::dlgMinWidth; + PlainShadow _sideShadow; + ChildWidget _dialogs; ChildWidget _history; - ChildWidget _profile = { nullptr }; + ChildWidget _wideSection = { nullptr }; ChildWidget _overview = { nullptr }; ChildWidget _player; ChildWidget _topBar; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index c6cba8fc2..c50499a6f 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -35,9 +35,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "boxes/contactsbox.h" #include "boxes/addcontactbox.h" +#include "observer_peer.h" #include "autoupdater.h" #include "mediaview.h" #include "localstorage.h" +#include "apiwrap.h" ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : QWidget(parent), _shadow(st::boxShadow), _reconnect(this, QString()) { set(text, reconnect); @@ -435,7 +437,7 @@ void MainWindow::init() { connect(windowHandle(), SIGNAL(activeChanged()), this, SLOT(checkHistoryActivation()), Qt::QueuedConnection); QPalette p(palette()); - p.setColor(QPalette::Window, st::wndBG->c); + p.setColor(QPalette::Window, st::windowBg->c); setPalette(p); title = new TitleWidget(this); @@ -613,10 +615,6 @@ void MainWindow::setupIntro(bool anim) { } } -void MainWindow::getNotifySetting(const MTPInputNotifyPeer &peer, uint32 msWait) { - MTP::send(MTPaccount_GetNotifySettings(peer), main->rpcDone(&MainWidget::gotNotifySetting, peer), main->rpcFail(&MainWidget::failNotifySetting, peer), 0, msWait); -} - void MainWindow::serviceNotification(const QString &msg, const MTPMessageMedia &media, bool force) { History *h = (main && App::userLoaded(ServiceUserId)) ? App::history(ServiceUserId) : 0; if (!h || (!force && h->isEmpty())) { @@ -1374,7 +1372,7 @@ void MainWindow::onClearFailed(int task, void *manager) { } void MainWindow::notifySchedule(History *history, HistoryItem *item) { - if (App::quitting() || !history->currentNotification() || !main) return; + if (App::quitting() || !history->currentNotification() || !App::api()) return; PeerData *notifyByFrom = (!history->peer->isUser() && item->mentionsMe()) ? item->from() : 0; @@ -1394,7 +1392,7 @@ void MainWindow::notifySchedule(History *history, HistoryItem *item) { return; } } else { - App::wnd()->getNotifySetting(MTP_inputNotifyPeer(notifyByFrom->input)); + App::api()->requestNotifySetting(notifyByFrom); } } else { history->popNotification(item); @@ -1403,9 +1401,9 @@ void MainWindow::notifySchedule(History *history, HistoryItem *item) { } } else { if (notifyByFrom && notifyByFrom->notify == UnknownNotifySettings) { - App::wnd()->getNotifySetting(MTP_inputNotifyPeer(notifyByFrom->input), 10); + App::api()->requestNotifySetting(notifyByFrom); } - App::wnd()->getNotifySetting(MTP_inputNotifyPeer(history->peer->input)); + App::api()->requestNotifySetting(history->peer); } if (!item->notificationReady()) { haveSetting = false; @@ -1420,6 +1418,7 @@ void MainWindow::notifySchedule(History *history, HistoryItem *item) { } else if (cOtherOnline() >= t) { delay = Global::NotifyDefaultDelay(); } +// LOG(("Is online: %1, otherOnline: %2, currentTime: %3, otherNotOld: %4, otherLaterThanMe: %5").arg(Logs::b(isOnline)).arg(cOtherOnline()).arg(t).arg(Logs::b(otherNotOld)).arg(Logs::b(otherLaterThanMe))); uint64 when = ms + delay; notifyWhenAlerts[history].insert(when, notifyByFrom); @@ -1882,8 +1881,15 @@ void MainWindow::sendPaths() { void MainWindow::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { if (main) main->mediaOverviewUpdated(peer, type); - if (!_mediaView || _mediaView->isHidden()) return; - _mediaView->mediaOverviewUpdated(peer, type); + if (_mediaView && !_mediaView->isHidden()) { + _mediaView->mediaOverviewUpdated(peer, type); + } + if (type != OverviewCount) { + Notify::PeerUpdate update(peer); + update.flags |= Notify::PeerUpdate::Flag::SharedMediaChanged; + update.mediaTypesMask |= (1 << type); + Notify::peerUpdatedDelayed(update); + } } void MainWindow::documentUpdated(DocumentData *doc) { diff --git a/Telegram/SourceFiles/mainwindow.h b/Telegram/SourceFiles/mainwindow.h index ddac84e02..8c5f19953 100644 --- a/Telegram/SourceFiles/mainwindow.h +++ b/Telegram/SourceFiles/mainwindow.h @@ -156,7 +156,6 @@ public: void checkAutoLockIn(int msec); void setupIntro(bool anim); void setupMain(bool anim, const MTPUser *user = 0); - void getNotifySetting(const MTPInputNotifyPeer &peer, uint32 msWait = 0); void serviceNotification(const QString &msg, const MTPMessageMedia &media = MTP_messageMediaEmpty(), bool force = false); void sendServiceHistoryRequest(); void showDelayedServiceMsgs(); diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 1fdb6711a..d18e231a5 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1875,7 +1875,7 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) { if (_over == OverName && _down == OverName) { if (App::wnd() && _from) { close(); - if (App::main()) App::main()->showPeerProfile(_from); + Ui::showPeerProfile(_from); } } else if (_over == OverDate && _down == OverDate) { onToMessage(); diff --git a/Telegram/SourceFiles/mtproto/facade.h b/Telegram/SourceFiles/mtproto/facade.h index dfd5f22ad..6634fb812 100644 --- a/Telegram/SourceFiles/mtproto/facade.h +++ b/Telegram/SourceFiles/mtproto/facade.h @@ -22,7 +22,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mtproto/core_types.h" #include "mtproto/session.h" -#include "mtproto/file_download.h" namespace MTP { diff --git a/Telegram/SourceFiles/mtproto/file_download.cpp b/Telegram/SourceFiles/mtproto/file_download.cpp index 73d61b131..ab8a25af5 100644 --- a/Telegram/SourceFiles/mtproto/file_download.cpp +++ b/Telegram/SourceFiles/mtproto/file_download.cpp @@ -208,6 +208,7 @@ void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray & } emit App::wnd()->imageLoaded(); emit progress(this); + FileDownload::internal::notifyImageLoaded(); loadNext(); } @@ -549,6 +550,9 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): not done yet, _lastComplete=%2, _size=%3, _nextRequestOffset=%4, _requests=%5").arg(_id).arg(Logs::b(_lastComplete)).arg(_size).arg(_nextRequestOffset).arg(serializereqs(_requests))); } emit progress(this); + if (_complete) { + FileDownload::internal::notifyImageLoaded(); + } loadNext(); } @@ -678,6 +682,7 @@ void webFileLoader::onFinished(const QByteArray &data) { Local::writeWebFile(_url, _data); } emit progress(this); + FileDownload::internal::notifyImageLoaded(); loadNext(); } @@ -1089,3 +1094,25 @@ namespace MTP { ++GlobalPriority; } } + +namespace FileDownload { +namespace { + +using internal::ImageLoadedHandler; + +Notify::SimpleObservedEventRegistrator creator(nullptr, nullptr); + +} // namespace + +namespace internal { + +Notify::ConnectionId plainRegisterImageLoadedObserver(ImageLoadedHandler &&handler) { + return creator.registerObserver(std_::forward(handler)); +} + +void notifyImageLoaded() { + creator.notify(); +} + +} // namespace internal +} \ No newline at end of file diff --git a/Telegram/SourceFiles/mtproto/file_download.h b/Telegram/SourceFiles/mtproto/file_download.h index 35c6a095c..f8a05d7ea 100644 --- a/Telegram/SourceFiles/mtproto/file_download.h +++ b/Telegram/SourceFiles/mtproto/file_download.h @@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "core/observer.h" + namespace MTP { void clearLoaderPriorities(); } @@ -391,3 +393,21 @@ static WebLoadManager * const FinishedWebLoadManager = SharedMemoryLocation; +Notify::ConnectionId plainRegisterImageLoadedObserver(ImageLoadedHandler &&handler); + +void notifyImageLoaded(); + +} // namespace internal + +template +void registerImageLoadedObserver(ObserverType *observer, void (ObserverType::*handler)()) { + auto connection = internal::plainRegisterImageLoadedObserver(func(observer, handler)); + Notify::observerRegistered(observer, connection); +} + +} // namespace FileDownload diff --git a/Telegram/SourceFiles/observer_peer.cpp b/Telegram/SourceFiles/observer_peer.cpp new file mode 100644 index 000000000..12e84f556 --- /dev/null +++ b/Telegram/SourceFiles/observer_peer.cpp @@ -0,0 +1,124 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "observer_peer.h" + +#include "core/observer.h" + +namespace App { +// Temp forward declaration (while all peer updates are not done through observers). +void emitPeerUpdated(); +} // namespace App + +namespace Notify { +namespace { + +using internal::PeerUpdateHandler; + +using SmallUpdatesList = QVector; +NeverFreedPointer SmallUpdates; +using AllUpdatesList = QMap; +NeverFreedPointer AllUpdates; + +void StartCallback() { + SmallUpdates.makeIfNull(); + AllUpdates.makeIfNull(); +} +void FinishCallback() { + SmallUpdates.clear(); + AllUpdates.clear(); +} +ObservedEventRegistrator creator(StartCallback, FinishCallback); + +} // namespace + +namespace internal { + +ConnectionId plainRegisterPeerObserver(PeerUpdate::Flags events, PeerUpdateHandler &&handler) { + return creator.registerObserver(events, std_::forward(handler)); +} + +} // namespace internal + +void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) { + if (!(mergeTo.flags & PeerUpdate::Flag::NameChanged)) { + if (mergeFrom.flags & PeerUpdate::Flag::NameChanged) { + mergeTo.oldNames = mergeFrom.oldNames; + mergeTo.oldNameFirstChars = mergeFrom.oldNameFirstChars; + } + } + if (mergeFrom.flags & PeerUpdate::Flag::SharedMediaChanged) { + mergeTo.mediaTypesMask |= mergeFrom.mediaTypesMask; + } + mergeTo.flags |= mergeFrom.flags; +} + +void peerUpdatedDelayed(const PeerUpdate &update) { + t_assert(creator.started()); + + Global::RefHandleDelayedPeerUpdates().call(); + + int existingUpdatesCount = SmallUpdates->size(); + for (int i = 0; i < existingUpdatesCount; ++i) { + auto &existingUpdate = (*SmallUpdates)[i]; + if (existingUpdate.peer == update.peer) { + mergePeerUpdate(existingUpdate, update); + return; + } + } + if (AllUpdates->isEmpty()) { + if (existingUpdatesCount < 5) { + SmallUpdates->push_back(update); + } else { + AllUpdates->insert(update.peer, update); + } + } else { + auto it = AllUpdates->find(update.peer); + if (it != AllUpdates->cend()) { + mergePeerUpdate(it.value(), update); + return; + } + AllUpdates->insert(update.peer, update); + } +} + +void peerUpdatedSendDelayed() { + if (!creator.started()) return; + + App::emitPeerUpdated(); + + if (SmallUpdates->isEmpty()) return; + + auto smallList = createAndSwap(*SmallUpdates); + auto allList = createAndSwap(*AllUpdates); + for_const (auto &update, smallList) { + creator.notify(update.flags, update); + } + for_const (auto &update, allList) { + creator.notify(update.flags, update); + } + if (SmallUpdates->isEmpty()) { + std::swap(smallList, *SmallUpdates); + SmallUpdates->resize(0); + } +} + +} // namespace Notify diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h new file mode 100644 index 000000000..638938e64 --- /dev/null +++ b/Telegram/SourceFiles/observer_peer.h @@ -0,0 +1,100 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "core/observer.h" + +namespace Notify { + +// Generic notifications about updates of some PeerData. +// You can subscribe to them by Notify::registerPeerObserver(). +// 0x0000FFFFU for general peer updates (valid for any peer). +// 0xFFFF0000U for specific peer updates (valid for user / chat / channel). +struct PeerUpdate { + PeerUpdate(PeerData *updated = nullptr) : peer(updated) { + } + PeerData *peer; + + enum class Flag { + NameChanged = 0x00000001U, + UsernameChanged = 0x00000002U, + PhotoChanged = 0x00000004U, + AboutChanged = 0x00000008U, + NotificationsEnabled = 0x00000010U, + SharedMediaChanged = 0x00000020U, + MigrationChanged = 0x00000040U, + + // For chats and channels + InviteLinkChanged = 0x00000020U, + MembersChanged = 0x00000040U, + AdminsChanged = 0x00000080U, + + UserCanShareContact = 0x00010000U, + UserIsContact = 0x00020000U, + UserPhoneChanged = 0x00040000U, + UserIsBlocked = 0x00080000U, + BotCommandsChanged = 0x00100000U, + UserOnlineChanged = 0x00200000U, + + ChatCanEdit = 0x00010000U, + + ChannelAmIn = 0x00010000U, + ChannelAmEditor = 0x00020000U, + ChannelCanEditPhoto = 0x00040000U, + ChannelCanAddMembers = 0x00080000U, + ChannelCanViewAdmins = 0x00100000U, + ChannelCanViewMembers = 0x00200000U, + }; + Q_DECLARE_FLAGS(Flags, Flag); + Flags flags = 0; + + // NameChanged data + PeerData::Names oldNames; + PeerData::NameFirstChars oldNameFirstChars; + + // SharedMediaChanged data + int32 mediaTypesMask = 0; + +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(PeerUpdate::Flags); + +void peerUpdatedDelayed(const PeerUpdate &update); +inline void peerUpdatedDelayed(PeerData *peer, PeerUpdate::Flags events) { + PeerUpdate update(peer); + update.flags = events; + peerUpdatedDelayed(update); +} +void peerUpdatedSendDelayed(); + +namespace internal { + +using PeerUpdateHandler = Function; +ConnectionId plainRegisterPeerObserver(PeerUpdate::Flags events, PeerUpdateHandler &&handler); + +} // namespace internal + +template +void registerPeerObserver(PeerUpdate::Flags events, ObserverType *observer, void (ObserverType::*handler)(const PeerUpdate &)) { + auto connection = internal::plainRegisterPeerObserver(events, func(observer, handler)); + observerRegistered(observer, connection); +} + +} // namespace Notify diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 58435d4b5..ad5397494 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -611,6 +611,9 @@ void OverviewInner::onDragExec() { mimeData->setData(qsl("application/x-td-forward-selected"), "1"); } drag->setMimeData(mimeData); + + // We don't receive mouseReleaseEvent when drag is finished. + ClickHandler::unpressed(); drag->exec(Qt::CopyAction); if (App::main()) App::main()->updateAfterDrag(); return; @@ -639,6 +642,9 @@ void OverviewInner::onDragExec() { } drag->setMimeData(mimeData); + + // We don't receive mouseReleaseEvent when drag is finished. + ClickHandler::unpressed(); drag->exec(Qt::CopyAction); if (App::main()) App::main()->updateAfterDrag(); return; @@ -1909,7 +1915,6 @@ OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewTyp , _scrollSetAfterShow(0) , _scrollDelta(0) , _selCount(0) -, _sideShadow(this, st::shadowColor) , _topShadow(this, st::shadowColor) , _inGrab(false) { _scroll.setFocusPolicy(Qt::NoFocus); @@ -1917,8 +1922,6 @@ OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewTyp _scroll.move(0, 0); _inner.move(0, 0); - _sideShadow.setVisible(!Adaptive::OneColumn()); - updateScrollColors(); _scroll.show(); @@ -1970,8 +1973,6 @@ void OverviewWidget::resizeEvent(QResizeEvent *e) { _topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth); _topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0); - _sideShadow.resize(st::lineWidth, height()); - _sideShadow.moveToLeft(0, 0); } void OverviewWidget::paintEvent(QPaintEvent *e) { @@ -1979,14 +1980,16 @@ void OverviewWidget::paintEvent(QPaintEvent *e) { Painter p(this); if (_a_show.animating()) { + int retina = cIntRetinaFactor(); + int inCacheTop = st::topBarHeight; if (a_coordOver.current() > 0) { - p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * cRetinaFactor(), 0, a_coordOver.current() * cRetinaFactor(), height() * cRetinaFactor())); - p.setOpacity(a_shadow.current() * st::slideFadeOut); - p.fillRect(0, 0, a_coordOver.current(), height(), st::black->b); + p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * retina, inCacheTop * retina, a_coordOver.current() * retina, height() * retina)); + p.setOpacity(a_progress.current() * st::slideFadeOut); + p.fillRect(0, 0, a_coordOver.current(), height(), st::black); p.setOpacity(1); } - p.drawPixmap(a_coordOver.current(), 0, _cacheOver); - p.setOpacity(a_shadow.current()); + p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, inCacheTop * retina, _cacheOver.width(), height() * retina)); + p.setOpacity(a_progress.current()); p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect()); return; } @@ -2012,9 +2015,15 @@ void OverviewWidget::scrollReset() { void OverviewWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { if (_a_show.animating()) { - p.drawPixmap(a_coordUnder.current(), 0, _cacheTopBarUnder); - p.drawPixmap(a_coordOver.current(), 0, _cacheTopBarOver); - p.setOpacity(a_shadow.current()); + int retina = cIntRetinaFactor(); + if (a_coordOver.current() > 0) { + p.drawPixmap(QRect(0, 0, a_coordOver.current(), st::topBarHeight), _cacheUnder, QRect(-a_coordUnder.current() * retina, 0, a_coordOver.current() * retina, st::topBarHeight * retina)); + p.setOpacity(a_progress.current() * st::slideFadeOut); + p.fillRect(0, 0, a_coordOver.current(), st::topBarHeight, st::black); + p.setOpacity(1); + } + p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, st::topBarHeight), _cacheOver, QRect(0, 0, _cacheOver.width(), st::topBarHeight * retina)); + p.setOpacity(a_progress.current()); p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), st::topBarHeight), App::sprite(), st::slideShadow.rect()); return; } @@ -2119,44 +2128,54 @@ void OverviewWidget::fastShow(bool back, int32 lastScrollTop) { if (App::app()) App::app()->mtpUnpause(); } -void OverviewWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back, int32 lastScrollTop) { - if (App::app()) App::app()->mtpPause(); - - (back ? _cacheOver : _cacheUnder) = bgAnimCache; - (back ? _cacheTopBarOver : _cacheTopBarUnder) = bgAnimTopBarCache; +void OverviewWidget::setLastScrollTop(int lastScrollTop) { resizeEvent(0); _scroll.scrollToY(lastScrollTop < 0 ? countBestScroll() : lastScrollTop); - (back ? _cacheUnder : _cacheOver) = myGrab(this); - App::main()->topBar()->stopAnim(); - (back ? _cacheTopBarUnder : _cacheTopBarOver) = myGrab(App::main()->topBar()); +} + +void OverviewWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) { + if (App::app()) App::app()->mtpPause(); + + resizeEvent(0); + + _cacheUnder = params.oldContentCache; + show(); + _topShadow.setVisible(params.withTopBarShadow ? false : true); + _cacheOver = App::main()->grabForShowAnimation(params); + _topShadow.setVisible(params.withTopBarShadow ? true : false); App::main()->topBar()->startAnim(); _scrollSetAfterShow = _scroll.scrollTop(); _scroll.hide(); - _topShadow.hide(); - a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); - a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); - a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); + int delta = st::slideShift; + if (direction == Window::SlideDirection::FromLeft) { + a_progress = anim::fvalue(1, 0); + std::swap(_cacheUnder, _cacheOver); + a_coordUnder = anim::ivalue(-delta, 0); + a_coordOver = anim::ivalue(0, width()); + } else { + a_progress = anim::fvalue(0, 1); + a_coordUnder = anim::ivalue(0, -delta); + a_coordOver = anim::ivalue(width(), 0); + } _a_show.start(); - show(); - App::main()->topBar()->update(); - _inner.activate(); + + activate(); } void OverviewWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; if (dt >= 1) { _a_show.stop(); - _sideShadow.setVisible(!Adaptive::OneColumn()); _topShadow.show(); a_coordUnder.finish(); a_coordOver.finish(); - a_shadow.finish(); - _cacheUnder = _cacheOver = _cacheTopBarUnder = _cacheTopBarOver = QPixmap(); + a_progress.finish(); + _cacheUnder = _cacheOver = QPixmap(); App::main()->topBar()->stopAnim(); doneShow(); @@ -2165,7 +2184,7 @@ void OverviewWidget::step_show(float64 ms, bool timer) { } else { a_coordUnder.update(dt, st::slideFunction); a_coordOver.update(dt, st::slideFunction); - a_shadow.update(dt, st::slideFunction); + a_progress.update(dt, st::slideFunction); } if (timer) { update(); @@ -2173,10 +2192,6 @@ void OverviewWidget::step_show(float64 ms, bool timer) { } } -void OverviewWidget::updateAdaptiveLayout() { - _sideShadow.setVisible(!Adaptive::OneColumn()); -} - void OverviewWidget::doneShow() { _scroll.show(); _scroll.scrollToY(_scrollSetAfterShow); @@ -2308,6 +2323,8 @@ void OverviewWidget::onDeleteSelected() { } void OverviewWidget::onDeleteSelectedSure() { + Ui::hideLayer(); + SelectedItemSet sel; _inner.fillSelectedItems(sel); if (sel.isEmpty()) return; @@ -2323,7 +2340,6 @@ void OverviewWidget::onDeleteSelectedSure() { for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) { i.value()->destroy(); } - Ui::hideLayer(); for (QMap >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) { App::main()->deleteMessages(i.key(), i.value()); @@ -2331,6 +2347,8 @@ void OverviewWidget::onDeleteSelectedSure() { } void OverviewWidget::onDeleteContextSure() { + Ui::hideLayer(); + HistoryItem *item = App::contextItem(); if (!item || item->type() != HistoryItemMsg) { return; @@ -2340,12 +2358,11 @@ void OverviewWidget::onDeleteContextSure() { History *h = item->history(); bool wasOnServer = (item->id > 0), wasLast = (h->lastMsg == item); item->destroy(); + if (!wasOnServer && wasLast && !h->lastMsg) { App::main()->checkPeerHistory(h->peer); } - Ui::hideLayer(); - if (wasOnServer) { App::main()->deleteMessages(h->peer, toDelete); } diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index 001f81df4..c053cb57f 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "window/section_widget.h" + namespace Overview { namespace Layout { @@ -276,10 +278,13 @@ public: int32 countBestScroll() const; void fastShow(bool back = false, int32 lastScrollTop = -1); - void animShow(const QPixmap &oldAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false, int32 lastScrollTop = -1); + bool hasTopBarShadow() const { + return true; + } + void setLastScrollTop(int lastScrollTop); + void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms); void step_show(float64 ms, bool timer); - void updateAdaptiveLayout(); void doneShow(); void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); @@ -300,14 +305,17 @@ public: void updateAfterDrag(); void grabStart() override { - _sideShadow.hide(); _inGrab = true; resizeEvent(0); } + void grapWithoutTopBarShadow() { + grabStart(); + _topShadow.hide(); + } void grabFinish() override { - _sideShadow.setVisible(!Adaptive::OneColumn()); _inGrab = false; resizeEvent(0); + _topShadow.show(); } void rpcClear() override { _inner.rpcClear(); @@ -343,9 +351,9 @@ private: QString _header; Animation _a_show; - QPixmap _cacheUnder, _cacheOver, _cacheTopBarUnder, _cacheTopBarOver; + QPixmap _cacheUnder, _cacheOver; anim::ivalue a_coordUnder, a_coordOver; - anim::fvalue a_shadow; + anim::fvalue a_progress; int32 _scrollSetAfterShow; @@ -354,7 +362,7 @@ private: int32 _selCount; - PlainShadow _sideShadow, _topShadow; + PlainShadow _topShadow; bool _inGrab; }; diff --git a/Telegram/SourceFiles/passcodewidget.cpp b/Telegram/SourceFiles/passcodewidget.cpp index 63e3e4810..ef4b8c9ed 100644 --- a/Telegram/SourceFiles/passcodewidget.cpp +++ b/Telegram/SourceFiles/passcodewidget.cpp @@ -119,7 +119,7 @@ void PasscodeWidget::animShow(const QPixmap &bgAnimCache, bool back) { (back ? _cacheUnder : _cacheOver) = myGrab(this); hideAll(); - a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); + a_coordUnder = back ? anim::ivalue(-st::slideShift, 0) : anim::ivalue(0, -st::slideShift); a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); _a_show.start(); diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index a8511b6d4..ca697b771 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -33,12 +33,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent) , _a_state(animation(this, &PlayerWidget::step_state)) -, _a_progress(animation(this, &PlayerWidget::step_progress)) -, _sideShadow(this, st::shadowColor) { +, _a_progress(animation(this, &PlayerWidget::step_progress)) { resize(st::wndMinWidth, st::playerHeight); setMouseTracking(true); memset(_stateHovers, 0, sizeof(_stateHovers)); - _sideShadow.setVisible(!Adaptive::OneColumn()); } void PlayerWidget::paintEvent(QPaintEvent *e) { @@ -343,10 +341,6 @@ void PlayerWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) } } -void PlayerWidget::updateAdaptiveLayout() { - _sideShadow.setVisible(!Adaptive::OneColumn()); -} - bool PlayerWidget::seekingSong(const SongMsgId &song) const { return (_down == OverPlayback) && (song == _song); } @@ -570,9 +564,6 @@ void PlayerWidget::resizeEvent(QResizeEvent *e) { int32 infoLeft = (_fullAvailable ? (_nextRect.x() + _nextRect.width()) : (_playRect.x() + _playRect.width())); _infoRect = QRect(infoLeft + st::playerSkip / 2, 0, (_fullAvailable ? _fullRect.x() : _repeatRect.x()) - infoLeft - st::playerSkip, availh); - _sideShadow.resize(st::lineWidth, height()); - _sideShadow.moveToLeft(0, 0); - update(); } diff --git a/Telegram/SourceFiles/playerwidget.h b/Telegram/SourceFiles/playerwidget.h index c5286fca5..4eda5b0d7 100644 --- a/Telegram/SourceFiles/playerwidget.h +++ b/Telegram/SourceFiles/playerwidget.h @@ -52,7 +52,6 @@ public: void clearSelection(); void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); - void updateAdaptiveLayout(); bool seekingSong(const SongMsgId &song) const; @@ -136,6 +135,4 @@ private: anim::fvalue a_loadProgress = { 0., 0. }; Animation _a_progress; - PlainShadow _sideShadow; - }; diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style new file mode 100644 index 000000000..3b8b93ab1 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile.style @@ -0,0 +1,169 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +using "basic.style"; +using "basic_types.style"; + +profileBg: windowBg; + +profileTopBarHeight: topBarHeight; +profileTopBarBackIconFg: #51b3e0; +profileTopBarBackIcon: icon { + { "topbar_back_arrow", profileTopBarBackIconFg }, +}; +profileTopBarBackIconPosition: point(15px, 19px); +profileTopBarBackFont: font(14px); +profileTopBarBackFg: #1485c2; +profileTopBarBackPosition: point(32px, 17px); +profileFixedBarButton: flatButton(topBarButton) { +} + +profileMarginTop: 13px; +profilePhotoSize: 112px; +profilePhotoLeftMin: 18px; +profilePhotoLeftMax: 45px; +profilePhotoDuration: 500; +profileNameLeft: 26px; +profileNameTop: 9px; +profileNameLabel: flatLabel(labelDefFlat) { + margin: margins(10px, 5px, 10px, 5px); + font: font(16px); + width: 160px; + maxHeight: 24px; +} +profileNameTextStyle: textStyle(defaultTextStyle) { +} +profileStatusLeft: 27px; +profileStatusTop: 35px; +profileStatusFont: normalFont; +profileStatusFg: windowSubTextFg; +profileStatusFgActive: windowActiveTextFg; +profileMarginBottom: 30px; + +profileActiveBg: #3fb0e4; +profileButtonLeft: 27px; +profileButtonTop: 88px; +profileButtonSkip: 10px; +profilePrimaryButton: BoxButton { + textFg: #ffffff; + textFgOver: #ffffff; + textBg: profileActiveBg; + textBgOver: profileActiveBg; + + width: -34px; + height: 34px; + + textTop: 8px; + + font: semiboldFont; + duration: 200; +} +profileSecondaryButton: BoxButton(profilePrimaryButton) { + textFg: #189dda; + textFgOver: #189dda; + textBg: #ffffff; + textBgOver: #f2f7fa; +} +profileAddMemberIcon: icon { + { "profile_add_member", profileActiveBg, point(20px, 10px) }, +}; +profileAddMemberButton: BoxButton(profileSecondaryButton) { + width: 62px; + icon: profileAddMemberIcon; +} + +profileDropAreaBg: profileBg; +profileDropAreaFg: profileActiveBg; +profileDropAreaPadding: margins(25px, 3px, 25px, 20px); +profileDropAreaTitleFont: font(24px); +profileDropAreaTitleTop: 30px; +profileDropAreaSubtitleFont: font(16px); +profileDropAreaSubtitleTop: 68px; +profileDropAreaBorderFg: profileDropAreaFg; +profileDropAreaBorderWidth: 3px; +profileDropAreaDuration: 200; + +profileDividerFg: windowShadowFg; +profileDividerLeft: icon { + { "profile_divider_left", profileDividerFg }, +}; +profileDividerFill: icon { + { "profile_divider_fill", profileDividerFg }, +}; + +profileBlocksTop: 7px; +profileBlocksBottom: 20px; +profileBlockLeftMin: 8px; +profileBlockLeftMax: 25px; +profileBlockNarrowWidthMin: 220px; +profileBlockWideWidthMin: 300px; +profileBlockWideWidthMax: 340px; +profileBlockMarginTop: 14px; +profileBlockMarginRight: 10px; +profileBlockMarginBottom: 4px; +profileBlockTitleHeight: 25px; +profileBlockTitleFont: font(14px semibold); +profileBlockTitleFg: black; +profileBlockTitlePosition: point(24px, 0px); +profileBlockLabel: flatLabel(labelDefFlat) { + textFg: windowSubTextFg; +} +profileBlockTextPart: flatLabel(labelDefFlat) { + width: 180px; + margin: margins(5px, 5px, 5px, 5px); +} +profileBlockOneLineTextPart: flatLabel(profileBlockTextPart) { + width: 0px; // No need to set minWidth in one-line text. + maxHeight: 20px; +} +profileBlockOneLineSkip: 9px; +profileBlockOneLineWidthMax: 240px; + +profileInviteLinkText: flatLabel(profileBlockTextPart) { + width: 1px; // Required for BreakEverywhere +} + +profileLimitReachedSkip: 6px; + +profileMemberHeight: 58px; +profileMemberPaddingLeft: 16px; +profileMemberPhotoSize: 46px; +profileMemberPhotoPosition: point(12px, 6px); +profileMemberNamePosition: point(68px, 11px); +profileMemberNameFg: windowTextFg; +profileMemberStatusPosition: point(68px, 31px); +profileMemberStatusFg: windowSubTextFg; +profileMemberStatusFgOver: windowSubTextFg; +profileMemberStatusFgActive: windowActiveTextFg; +profileMemberAdminIcon: icon { + { "profile_admin_star", profileActiveBg, point(4px, 2px) }, +}; +profileLimitReachedLabel: flatLabel(labelDefFlat) { + width: 180px; + margin: margins(profileMemberPaddingLeft, 9px, profileMemberPaddingLeft, 6px); +} +profileLimitReachedStyle: textStyle(defaultTextStyle) { + lineHeight: 19px; +} + +profileReportReasonOther: InputArea(defaultInputArea) { + textMargins: margins(1px, 6px, 1px, 4px); + heightMax: 115px; +} diff --git a/Telegram/SourceFiles/profile/profile_actions_widget.cpp b/Telegram/SourceFiles/profile/profile_actions_widget.cpp new file mode 100644 index 000000000..079969b48 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_actions_widget.cpp @@ -0,0 +1,363 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "profile/profile_actions_widget.h" + +#include "styles/style_profile.h" +#include "ui/buttons/left_outline_button.h" +#include "boxes/confirmbox.h" +#include "boxes/report_box.h" +#include "mainwidget.h" +#include "observer_peer.h" +#include "apiwrap.h" +#include "lang.h" + +namespace Profile { + +using UpdateFlag = Notify::PeerUpdate::Flag; + +ActionsWidget::ActionsWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_actions_section)) { + auto observeEvents = UpdateFlag::ChannelAmIn + | UpdateFlag::UserIsBlocked + | UpdateFlag::BotCommandsChanged + | UpdateFlag::MembersChanged; + Notify::registerPeerObserver(observeEvents, this, &ActionsWidget::notifyPeerUpdated); + + validateBlockStatus(); + refreshButtons(); +} + +void ActionsWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.peer != peer()) { + return; + } + + auto needFullRefresh = [&update, this]() { + if (update.flags & UpdateFlag::BotCommandsChanged) { + if (_hasBotHelp != hasBotCommand(qsl("help")) || _hasBotSettings != hasBotCommand(qsl("settings"))) { + return true; + } + } + return false; + }; + if (needFullRefresh()) { + refreshButtons(); + } else { + if (update.flags & UpdateFlag::MembersChanged) { + refreshDeleteChannel(); + } + if (update.flags & UpdateFlag::ChannelAmIn) { + refreshLeaveChannel(); + } + if (update.flags & UpdateFlag::UserIsBlocked) { + refreshBlockUser(); + } + refreshVisibility(); + } + + contentSizeUpdated(); +} + +void ActionsWidget::validateBlockStatus() const { + auto needFullPeer = [this]() { + if (auto user = peer()->asUser()) { + if (user->blockStatus() == UserData::BlockStatus::Unknown) { + return true; + } else if (user->botInfo && !user->botInfo->inited) { + return true; + } + } + return false; + }; + if (needFullPeer()) { + if (App::api()) App::api()->requestFullPeer(peer()); + } +} + +Ui::LeftOutlineButton *ActionsWidget::addButton(const QString &text, const char *slot, const style::OutlineButton &st, int skipHeight) { + auto result = new Ui::LeftOutlineButton(this, text, st); + connect(result, SIGNAL(clicked()), this, slot); + result->show(); + + int top = buttonsBottom() + skipHeight; + resizeButton(result, top); + + _buttons.push_back(result); + return result; +}; + +void ActionsWidget::resizeButton(Ui::LeftOutlineButton *button, int top) { + int left = defaultOutlineButtonLeft(); + int availableWidth = width() - left - st::profileBlockMarginRight; + accumulate_min(availableWidth, st::profileBlockOneLineWidthMax); + button->resizeToWidth(availableWidth); + button->moveToLeft(left, top); +} + +void ActionsWidget::refreshButtons() { + auto buttons = createAndSwap(_buttons); + for_const (auto &button, buttons) { + delete button; + } + _blockUser = _leaveChannel = nullptr; + + if (auto user = peer()->asUser()) { + if ((_hasBotHelp = hasBotCommand(qsl("help")))) { + addButton(lang(lng_profile_bot_help), SLOT(onBotHelp())); + } + if ((_hasBotSettings = hasBotCommand(qsl("settings")))) { + addButton(lang(lng_profile_bot_settings), SLOT(onBotSettings())); + } + addButton(lang(lng_profile_clear_history), SLOT(onClearHistory())); + addButton(lang(lng_profile_delete_conversation), SLOT(onDeleteConversation())); + refreshBlockUser(); + } else if (auto chat = peer()->asChat()) { + if (chat->amCreator()) { + addButton(lang(lng_profile_migrate_button), SLOT(onUpgradeToSupergroup())); + } + addButton(lang(lng_profile_clear_history), SLOT(onClearHistory())); + addButton(lang(lng_profile_clear_and_exit), SLOT(onDeleteConversation())); + } else if (auto channel = peer()->asChannel()) { + if (!channel->amCreator()) { + addButton(lang(lng_profile_report), SLOT(onReport())); + } + refreshDeleteChannel(); + refreshLeaveChannel(); + } + + refreshVisibility(); +} + +void ActionsWidget::refreshVisibility() { + setVisible(!_buttons.isEmpty()); +} + +QString ActionsWidget::getBlockButtonText() const { + auto user = peer()->asUser(); + if (!user || (user->id == peerFromUser(MTP::authedId()))) return QString(); + if (user->blockStatus() == UserData::BlockStatus::Unknown) return QString(); + + if (user->isBlocked()) { + if (user->botInfo) { + return lang(lng_profile_unblock_bot); + } + return lang(lng_profile_unblock_user); + } else if (user->botInfo) { + return lang(lng_profile_block_bot); + } + return lang(lng_profile_block_user); +} + +bool ActionsWidget::hasBotCommand(const QString &command) const { + auto user = peer()->asUser(); + if (!user || !user->botInfo || user->botInfo->commands.isEmpty()) { + return false; + } + + for_const (auto &cmd, user->botInfo->commands) { + if (!cmd.command.compare(command, Qt::CaseInsensitive)) { + return true; + } + } + return false; +} + +void ActionsWidget::sendBotCommand(const QString &command) { + auto user = peer()->asUser(); + if (user && user->botInfo && !user->botInfo->commands.isEmpty()) { + for_const (auto &cmd, user->botInfo->commands) { + if (!cmd.command.compare(command, Qt::CaseInsensitive)) { + Ui::showPeerHistory(user, ShowAtTheEndMsgId); + App::sendBotCommand(user, user, '/' + cmd.command); + return; + } + } + } + + // Command was not found. + refreshButtons(); + contentSizeUpdated(); +} + +void ActionsWidget::refreshBlockUser() { + if (auto user = peer()->asUser()) { + auto blockText = getBlockButtonText(); + if (_blockUser) { + if (blockText.isEmpty()) { + _buttons.removeOne(_blockUser); + delete _blockUser; + _blockUser = nullptr; + } else { + _blockUser->setText(blockText); + } + } else if (!blockText.isEmpty()) { + _blockUser = addButton(blockText, SLOT(onBlockUser()), st::attentionLeftOutlineButton, st::profileBlockOneLineSkip); + } + } +} + +void ActionsWidget::refreshDeleteChannel() { + if (auto channel = peer()->asChannel()) { + if (channel->canDelete() && !_deleteChannel) { + _deleteChannel = addButton(lang(channel->isMegagroup() ? lng_profile_delete_group : lng_profile_delete_channel), SLOT(onDeleteChannel()), st::attentionLeftOutlineButton); + } else if (!channel->canDelete() && _deleteChannel) { + _buttons.removeOne(_deleteChannel); + delete _deleteChannel; + _deleteChannel = nullptr; + } + } +} + +void ActionsWidget::refreshLeaveChannel() { + if (auto channel = peer()->asChannel()) { + if (!channel->amCreator()) { + if (channel->amIn() && !_leaveChannel) { + _leaveChannel = addButton(lang(channel->isMegagroup() ? lng_profile_leave_group : lng_profile_leave_channel), SLOT(onLeaveChannel())); + } else if (!channel->amIn() && _leaveChannel) { + _buttons.removeOne(_leaveChannel); + delete _leaveChannel; + _leaveChannel = nullptr; + } + } + } +} + +int ActionsWidget::resizeGetHeight(int newWidth) { + for_const (auto button, _buttons) { + resizeButton(button, button->y()); + } + return buttonsBottom(); +} + +int ActionsWidget::buttonsBottom() const { + if (_buttons.isEmpty()) { + return contentTop(); + } + auto lastButton = _buttons.back(); + return lastButton->y() + lastButton->height(); +} + +void ActionsWidget::onBotHelp() { + sendBotCommand(qsl("help")); +} + +void ActionsWidget::onBotSettings() { + sendBotCommand(qsl("settings")); +} + +void ActionsWidget::onClearHistory() { + QString confirmation; + if (auto user = peer()->asUser()) { + confirmation = lng_sure_delete_history(lt_contact, App::peerName(peer())); + } else if (auto chat = peer()->asChat()) { + confirmation = lng_sure_delete_group_history(lt_group, App::peerName(peer())); + } + if (!confirmation.isEmpty()) { + auto box = new ConfirmBox(confirmation, lang(lng_box_delete), st::attentionBoxButton); + connect(box, SIGNAL(confirmed()), this, SLOT(onClearHistorySure())); + Ui::showLayer(box); + } +} + +void ActionsWidget::onClearHistorySure() { + Ui::hideLayer(); + App::main()->clearHistory(peer()); +} + +void ActionsWidget::onDeleteConversation() { + QString confirmation, confirmButton; + if (auto user = peer()->asUser()) { + confirmation = lng_sure_delete_history(lt_contact, App::peerName(peer())); + confirmButton = lang(lng_box_delete); + } else if (auto chat = peer()->asChat()) { + confirmation = lng_sure_delete_and_exit(lt_group, App::peerName(peer())); + confirmButton = lang(lng_box_leave); + } + if (!confirmation.isEmpty()) { + auto box = new ConfirmBox(confirmation, confirmButton, st::attentionBoxButton); + connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteConversationSure())); + Ui::showLayer(box); + } +} + +void ActionsWidget::onDeleteConversationSure() { + Ui::hideLayer(); + Ui::showChatsList(); + if (auto user = peer()->asUser()) { + App::main()->deleteConversation(peer()); + } else if (auto chat = peer()->asChat()) { + App::main()->deleteAndExit(chat); + } +} + +void ActionsWidget::onBlockUser() { + if (auto user = peer()->asUser()) { + if (user->isBlocked()) { + App::api()->unblockUser(user); + } else { + App::api()->blockUser(user); + } + } +} + +void ActionsWidget::onUpgradeToSupergroup() { + if (auto chat = peer()->asChat()) { + Ui::showLayer(new ConvertToSupergroupBox(chat)); + } +} + +void ActionsWidget::onDeleteChannel() { + auto box = new ConfirmBox(lang(peer()->isMegagroup() ? lng_sure_delete_group : lng_sure_delete_channel), lang(lng_box_delete), st::attentionBoxButton); + connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteChannelSure())); + Ui::showLayer(box); +} + +void ActionsWidget::onDeleteChannelSure() { + Ui::hideLayer(); + Ui::showChatsList(); + if (auto chat = peer()->migrateFrom()) { + App::main()->deleteAndExit(chat); + } + if (auto channel = peer()->asChannel()) { + MTP::send(MTPchannels_DeleteChannel(channel->inputChannel), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::deleteChannelFailed)); + } +} + +void ActionsWidget::onLeaveChannel() { + auto channel = peer()->asChannel(); + if (!channel) return; + + auto box = new ConfirmBox(lang(channel->isMegagroup() ? lng_sure_leave_group : lng_sure_leave_channel), lang(lng_box_leave)); + connect(box, SIGNAL(confirmed()), this, SLOT(onLeaveChannelSure())); + Ui::showLayer(box); +} + +void ActionsWidget::onLeaveChannelSure() { + App::api()->leaveChannel(peer()->asChannel()); +} + +void ActionsWidget::onReport() { + if (auto channel = peer()->asChannel()) { + Ui::showLayer(new ReportBox(channel)); + } +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_actions_widget.h b/Telegram/SourceFiles/profile/profile_actions_widget.h new file mode 100644 index 000000000..08474c84e --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_actions_widget.h @@ -0,0 +1,102 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "profile/profile_block_widget.h" + +namespace Ui { +class LeftOutlineButton; +} // namespace Ui + +namespace Notify { +struct PeerUpdate; +} // namespace Notify + +namespace Profile { + +class ActionsWidget : public BlockWidget { + Q_OBJECT + +public: + ActionsWidget(QWidget *parent, PeerData *peer); + +protected: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +private slots: + void onBotHelp(); + void onBotSettings(); + void onClearHistory(); + void onClearHistorySure(); + void onDeleteConversation(); + void onDeleteConversationSure(); + void onBlockUser(); + void onUpgradeToSupergroup(); + void onDeleteChannel(); + void onDeleteChannelSure(); + void onLeaveChannel(); + void onLeaveChannelSure(); + void onReport(); + +private: + // Observed notifications. + void notifyPeerUpdated(const Notify::PeerUpdate &update); + + void validateBlockStatus() const; + + int buttonsBottom() const; + + void refreshButtons(); + void refreshBlockUser(); + void refreshDeleteChannel(); + void refreshLeaveChannel(); + void refreshVisibility(); + + Ui::LeftOutlineButton *addButton(const QString &text, const char *slot + , const style::OutlineButton &st = st::defaultLeftOutlineButton, int skipHeight = 0); + void resizeButton(Ui::LeftOutlineButton *button, int top); + + QString getBlockButtonText() const; + bool hasBotCommand(const QString &command) const; + void sendBotCommand(const QString &command); + + QList _buttons; + //ChildWidget _botHelp = { nullptr }; + //ChildWidget _botSettings = { nullptr }; + //ChildWidget _reportChannel = { nullptr }; + //ChildWidget _leaveChannel = { nullptr }; + //ChildWidget _deleteChannel = { nullptr }; + //ChildWidget _upgradeToSupergroup = { nullptr }; + //ChildWidget _clearHistory = { nullptr }; + //ChildWidget _deleteConversation = { nullptr }; + //ChildWidget _blockUser = { nullptr }; + + // Hold some button pointers to update / toggle them. + bool _hasBotHelp = false; + bool _hasBotSettings = false; + Ui::LeftOutlineButton *_blockUser = nullptr; + Ui::LeftOutlineButton *_deleteChannel = nullptr; + Ui::LeftOutlineButton *_leaveChannel = nullptr; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_block_widget.cpp b/Telegram/SourceFiles/profile/profile_block_widget.cpp new file mode 100644 index 000000000..ba2c8ac5f --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_block_widget.cpp @@ -0,0 +1,57 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "profile/profile_block_widget.h" + +#include "styles/style_profile.h" + +namespace Profile { + +BlockWidget::BlockWidget(QWidget *parent, PeerData *peer, const QString &title) : TWidget(parent) +, _peer(peer) +, _title(title) { +} + +void BlockWidget::resizeToWidth(int newWidth) { + resize(newWidth, resizeGetHeight(newWidth)); +} + +int BlockWidget::contentTop() const { + return st::profileBlockMarginTop + st::profileBlockTitleHeight; +} + +void BlockWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.setFont(st::profileBlockTitleFont); + p.setPen(st::profileBlockTitleFg); + int titleLeft = st::profileBlockTitlePosition.x(); + int titleTop = st::profileBlockMarginTop + st::profileBlockTitlePosition.y(); + p.drawTextLeft(titleLeft, titleTop, width(), _title); + + paintContents(p); +} + +int defaultOutlineButtonLeft() { + return st::profileBlockTitlePosition.x() - st::defaultLeftOutlineButton.padding.left(); +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_block_widget.h b/Telegram/SourceFiles/profile/profile_block_widget.h new file mode 100644 index 000000000..da93aad75 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_block_widget.h @@ -0,0 +1,74 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "core/observer.h" + +namespace Profile { + +class BlockWidget : public TWidget, public Notify::Observer { + Q_OBJECT + +public: + BlockWidget(QWidget *parent, PeerData *peer, const QString &title); + + // Count new height for width=newWidth and resize to it. + void resizeToWidth(int newWidth); + + // Updates the area that is visible inside the scroll container. + virtual void setVisibleTopBottom(int visibleTop, int visibleBottom) { + } + + virtual ~BlockWidget() { + } + +signals: + void heightUpdated(); + +protected: + void paintEvent(QPaintEvent *e) override; + virtual void paintContents(Painter &p) { + } + + // Where does the block content start (after the title). + int contentTop() const; + + // Resizes content and counts natural widget height for the desired width. + virtual int resizeGetHeight(int newWidth) = 0; + + void contentSizeUpdated() { + resizeToWidth(width()); + emit heightUpdated(); + } + + PeerData *peer() const { + return _peer; + } + +private: + PeerData *_peer; + QString _title; + +}; + +int defaultOutlineButtonLeft(); + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp new file mode 100644 index 000000000..974d2db72 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -0,0 +1,499 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "profile/profile_cover.h" + +#include "styles/style_profile.h" +#include "profile/profile_cover_drop_area.h" +#include "profile/profile_userpic_button.h" +#include "ui/buttons/round_button.h" +#include "ui/filedialog.h" +#include "observer_peer.h" +#include "boxes/confirmbox.h" +#include "boxes/contactsbox.h" +#include "boxes/photocropbox.h" +#include "lang.h" +#include "apiwrap.h" +#include "mainwidget.h" +#include "mainwindow.h" + +namespace Profile { +namespace { + +using UpdateFlag = Notify::PeerUpdate::Flag; +const auto ButtonsUpdateFlags = UpdateFlag::UserCanShareContact + | UpdateFlag::ChatCanEdit + | UpdateFlag::ChannelCanEditPhoto + | UpdateFlag::ChannelCanAddMembers + | UpdateFlag::ChannelAmIn; + +} // namespace + +CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) +, _peer(peer) +, _peerUser(peer->asUser()) +, _peerChat(peer->asChat()) +, _peerChannel(peer->asChannel()) +, _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr) +, _userpicButton(this, peer) +, _name(this, st::profileNameLabel) { + setAttribute(Qt::WA_OpaquePaintEvent); + setAcceptDrops(true); + + _name.setSelectable(true); + _name.setContextCopyText(lang(lng_profile_copy_fullname)); + + auto observeEvents = ButtonsUpdateFlags + | UpdateFlag::NameChanged + | UpdateFlag::UserOnlineChanged; + Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated); + FileDialog::registerObserver(this, &CoverWidget::notifyFileQueryUpdated); + + connect(_userpicButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); + validatePhoto(); + + refreshNameText(); + refreshStatusText(); + + refreshButtons(); +} + +PhotoData *CoverWidget::validatePhoto() const { + PhotoData *photo = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId) ? App::photo(_peer->photoId) : nullptr; + if ((_peer->photoId == UnknownPeerPhotoId) || (_peer->photoId && (!photo || !photo->date))) { + App::api()->requestFullPeer(_peer); + return nullptr; + } + return photo; +} + +void CoverWidget::onPhotoShow() { + if (auto photo = validatePhoto()) { + App::wnd()->showPhoto(photo, _peer); + } +} + +int CoverWidget::countPhotoLeft(int newWidth) const { + int result = st::profilePhotoLeftMin; + result += (newWidth - st::wndMinWidth) / 2; + return qMin(result, st::profilePhotoLeftMax); +} + +void CoverWidget::resizeToWidth(int newWidth) { + int newHeight = 0; + + newHeight += st::profileMarginTop; + + _photoLeft = countPhotoLeft(newWidth); + _userpicButton->moveToLeft(_photoLeft, newHeight); + + refreshNameGeometry(newWidth); + + int infoLeft = _userpicButton->x() + _userpicButton->width(); + _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _userpicButton->y() + st::profileStatusTop); + + moveAndToggleButtons(newWidth); + + newHeight += st::profilePhotoSize; + newHeight += st::profileMarginBottom; + + _dividerTop = newHeight; + newHeight += st::profileDividerFill.height(); + + newHeight += st::profileBlocksTop; + + resizeDropArea(); + resize(newWidth, newHeight); + update(); +} + +void CoverWidget::refreshNameGeometry(int newWidth) { + int infoLeft = _userpicButton->x() + _userpicButton->width(); + int nameLeft = infoLeft + st::profileNameLeft - st::profileNameLabel.margin.left(); + int nameTop = _userpicButton->y() + st::profileNameTop - st::profileNameLabel.margin.top(); + int nameWidth = newWidth - infoLeft - st::profileNameLeft - st::profileButtonSkip; + int marginsAdd = st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right(); + _name.resizeToWidth(qMin(nameWidth, _name.naturalWidth()) + marginsAdd); + _name.moveToLeft(nameLeft, nameTop); +} + +// A more generic solution would be allowing an optional icon button +// for each text button. But currently we use only one, so it is done easily: +// There can be primary + secondary + icon buttons. If primary + secondary fit, +// then icon is hidden, otherwise secondary is hidden and icon is shown. +void CoverWidget::moveAndToggleButtons(int newWiddth) { + int buttonLeft = _userpicButton->x() + _userpicButton->width() + st::profileButtonLeft; + int buttonsRight = newWiddth - st::profileButtonSkip; + for (int i = 0, count = _buttons.size(); i < count; ++i) { + auto &button = _buttons.at(i); + button.widget->moveToLeft(buttonLeft, st::profileButtonTop); + if (button.replacement) { + button.replacement->moveToLeft(buttonLeft, st::profileButtonTop); + if (buttonLeft + button.widget->width() > buttonsRight) { + button.widget->hide(); + button.replacement->show(); + buttonLeft += button.replacement->width() + st::profileButtonSkip; + } else { + button.widget->show(); + button.replacement->hide(); + buttonLeft += button.widget->width() + st::profileButtonSkip; + } + } else if (i == 1 && (buttonLeft + button.widget->width() > buttonsRight)) { + // If second button is not fitting. + button.widget->hide(); + } else { + button.widget->show(); + buttonLeft += button.widget->width() + st::profileButtonSkip; + } + } +} + +void CoverWidget::showFinished() { + _userpicButton->showFinished(); +} + +bool CoverWidget::shareContactButtonShown() const { + return _peerUser && (_buttons.size() > 1) && !(_buttons.at(1).widget->isHidden()); +} + +void CoverWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.fillRect(e->rect(), st::profileBg); + + p.setFont(st::profileStatusFont); + p.setPen(_statusTextIsOnline ? st::profileStatusFgActive : st::profileStatusFg); + p.drawTextLeft(_statusPosition.x(), _statusPosition.y(), width(), _statusText); + + paintDivider(p); +} + +void CoverWidget::resizeDropArea() { + if (_dropArea) { + _dropArea->setGeometry(0, 0, width(), _dividerTop); + } +} + +void CoverWidget::dropAreaHidden(CoverDropArea *dropArea) { + if (_dropArea == dropArea) { + _dropArea.destroyDelayed(); + } +} + +bool CoverWidget::canEditPhoto() const { + if (_peerChat && _peerChat->canEdit()) { + return true; + } else if (_peerMegagroup && _peerMegagroup->canEditPhoto()) { + return true; + } else if (_peerChannel && _peerChannel->canEditPhoto()) { + return true; + } + return false; +} + +bool CoverWidget::mimeDataHasImage(const QMimeData *mimeData) const { + if (!mimeData) return false; + + if (mimeData->hasImage()) return true; + + auto uriListFormat = qsl("text/uri-list"); + if (!mimeData->hasFormat(uriListFormat)) return false; + + const auto &urls = mimeData->urls(); + if (urls.size() != 1) return false; + + auto &url = urls.at(0); + if (!url.isLocalFile()) return false; + + auto file = psConvertFileUrl(url); + + QFileInfo info(file); + if (info.isDir()) return false; + + quint64 s = info.size(); + if (s >= MaxUploadDocumentSize) return false; + + for (auto &ext : cImgExtensions()) { + if (file.endsWith(ext, Qt::CaseInsensitive)) { + return true; + } + } + return false; +} + +void CoverWidget::dragEnterEvent(QDragEnterEvent *e) { + if (!canEditPhoto() || !mimeDataHasImage(e->mimeData())) { + e->ignore(); + return; + } + if (!_dropArea) { + auto title = lang(lng_profile_drop_area_title); + QString subtitle; + if (_peerChat || _peerMegagroup) { + subtitle = lang(lng_profile_drop_area_subtitle); + } else { + subtitle = lang(lng_profile_drop_area_subtitle_channel); + } + _dropArea = new CoverDropArea(this, title, subtitle); + resizeDropArea(); + } + _dropArea->showAnimated(); + e->setDropAction(Qt::CopyAction); + e->accept(); +} + +void CoverWidget::dragLeaveEvent(QDragLeaveEvent *e) { + if (_dropArea && !_dropArea->hiding()) { + _dropArea->hideAnimated(func(this, &CoverWidget::dropAreaHidden)); + } +} + +void CoverWidget::dropEvent(QDropEvent *e) { + auto mimeData = e->mimeData(); + + QImage img; + if (mimeData->hasImage()) { + img = qvariant_cast(mimeData->imageData()); + } else { + const auto &urls = mimeData->urls(); + if (urls.size() == 1) { + auto &url = urls.at(0); + if (url.isLocalFile()) { + img = App::readImage(psConvertFileUrl(url)); + } + } + } + + if (!_dropArea->hiding()) { + _dropArea->hideAnimated(func(this, &CoverWidget::dropAreaHidden)); + } + e->acceptProposedAction(); + + showSetPhotoBox(img); +} + +void CoverWidget::paintDivider(Painter &p) { + st::profileDividerLeft.paint(p, QPoint(st::lineWidth, _dividerTop), width()); + + int toFillLeft = st::lineWidth + st::profileDividerLeft.width(); + QRect toFill = rtlrect(toFillLeft, _dividerTop, width() - toFillLeft, st::profileDividerFill.height(), width()); + st::profileDividerFill.fill(p, toFill); +} + +void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.peer != _peer) { + return; + } + if ((update.flags & ButtonsUpdateFlags) != 0) { + refreshButtons(); + } + if (update.flags & UpdateFlag::NameChanged) { + refreshNameText(); + } + if (update.flags & UpdateFlag::UserOnlineChanged) { + refreshStatusText(); + } +} + +void CoverWidget::refreshNameText() { + _name.setText(App::peerName(_peer)); + refreshNameGeometry(width()); +} + +void CoverWidget::refreshStatusText() { + int currentTime = unixtime(); + if (_peerUser) { + _statusText = App::onlineText(_peerUser, currentTime, true); + _statusTextIsOnline = App::onlineColorUse(_peerUser, currentTime); + } else if (_peerChat && _peerChat->amIn()) { + int fullCount = qMax(_peerChat->count, _peerChat->participants.size()); + if (_onlineCount > 0 && _onlineCount <= fullCount) { + _statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount); + } else { + _statusText = lng_chat_status_members(lt_count, _peerChat->count); + } + } else if (_peerChannel) { + int fullCount = _peerChannel->membersCount(); + if (_onlineCount > 0 && _onlineCount <= fullCount) { + _statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount); + } else if (fullCount > 0 ) { + _statusText = lng_chat_status_members(lt_count, _peerChannel->membersCount()); + } else { + _statusText = lang(_peerChannel->isMegagroup() ? lng_group_status : lng_channel_status); + } + } else { + _statusText = lang(lng_chat_status_unaccessible); + } + update(); +} + +void CoverWidget::refreshButtons() { + clearButtons(); + if (_peerUser) { + setUserButtons(); + } else if (_peerChat) { + setChatButtons(); + } else if (_peerMegagroup) { + setMegagroupButtons(); + } else if (_peerChannel) { + setChannelButtons(); + } + resizeToWidth(width()); +} + +void CoverWidget::setUserButtons() { + addButton(lang(lng_profile_send_message), SLOT(onSendMessage())); + if (_peerUser->canShareThisContact()) { + addButton(lang(lng_profile_share_contact), SLOT(onShareContact())); + } +} + +void CoverWidget::setChatButtons() { + if (_peerChat->canEdit()) { + addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + addButton(lang(lng_profile_add_participant), SLOT(onAddMember()), &st::profileAddMemberButton); + } +} + +void CoverWidget::setMegagroupButtons() { + if (_peerMegagroup->amIn()) { + if (_peerMegagroup->canEditPhoto()) { + addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + } + } else { + addButton(lang(lng_profile_join_channel), SLOT(onJoin())); + } + if (_peerMegagroup->canAddMembers()) { + addButton(lang(lng_profile_add_participant), SLOT(onAddMember()), &st::profileAddMemberButton); + } +} + +void CoverWidget::setChannelButtons() { + if (_peerChannel->amCreator()) { + addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + } else if (_peerChannel->amIn()) { + addButton(lang(lng_profile_view_channel), SLOT(onViewChannel())); + } else { + addButton(lang(lng_profile_join_channel), SLOT(onJoin())); + } +} + +void CoverWidget::clearButtons() { + auto buttons = createAndSwap(_buttons); + for_const (auto button, buttons) { + delete button.widget; + delete button.replacement; + } +} + +void CoverWidget::addButton(const QString &text, const char *slot, const style::BoxButton *replacementStyle) { + auto &buttonStyle = _buttons.isEmpty() ? st::profilePrimaryButton : st::profileSecondaryButton; + auto button = new Ui::RoundButton(this, text, buttonStyle); + connect(button, SIGNAL(clicked()), this, slot); + button->show(); + + auto replacement = replacementStyle ? new Ui::RoundButton(this, QString(), *replacementStyle) : nullptr; + if (replacement) { + connect(replacement, SIGNAL(clicked()), this, slot); + replacement->hide(); + } + + _buttons.push_back({ button, replacement }); +} + +void CoverWidget::onOnlineCountUpdated(int onlineCount) { + _onlineCount = onlineCount; + refreshStatusText(); +} + +void CoverWidget::onSendMessage() { + Ui::showPeerHistory(_peer, ShowAtUnreadMsgId); +} + +void CoverWidget::onShareContact() { + App::main()->shareContactLayer(_peerUser); +} + +void CoverWidget::onSetPhoto() { + QStringList imgExtensions(cImgExtensions()); + QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;All files (*.*)")); + + _setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_images), filter); +} + +void CoverWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) { + if (_setPhotoFileQueryId != update.queryId) { + return; + } + _setPhotoFileQueryId = 0; + + if (update.filePaths.isEmpty() && update.remoteContent.isEmpty()) { + return; + } + + QImage img; + if (!update.remoteContent.isEmpty()) { + img = App::readImage(update.remoteContent); + } else { + img = App::readImage(update.filePaths.front()); + } + + showSetPhotoBox(img); +} + +void CoverWidget::showSetPhotoBox(const QImage &img) { + if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) { + Ui::showLayer(new InformBox(lang(lng_bad_photo))); + return; + } + + auto box = new PhotoCropBox(img, _peer); + connect(box, SIGNAL(closed()), this, SLOT(onPhotoUpdateStart())); + Ui::showLayer(box); +} + +void CoverWidget::onAddMember() { + if (_peerChat) { + if (_peerChat->count >= Global::ChatSizeMax() && _peerChat->amCreator()) { + Ui::showLayer(new ConvertToSupergroupBox(_peerChat)); + } else { + Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent)); + } + } else if (_peerChannel && _peerChannel->mgInfo) { + MembersAlreadyIn already; + for_const (auto user, _peerChannel->mgInfo->lastParticipants) { + already.insert(user); + } + Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already)); + } +} + +void CoverWidget::onJoin() { + if (!_peerChannel) return; + + App::api()->joinChannel(_peerChannel); +} + +void CoverWidget::onViewChannel() { + Ui::showPeerHistory(_peer, ShowAtUnreadMsgId); +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h new file mode 100644 index 000000000..9aa67f166 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -0,0 +1,137 @@ +/* +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-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "core/observer.h" +#include "ui/filedialog.h" +#include "ui/flatlabel.h" + +namespace Ui { +class RoundButton; +} // namespace Ui + +namespace Notify { +struct PeerUpdate; +} // namespace Notify + +namespace Profile { + +class BackButton; +class UserpicButton; +class CoverDropArea; + +class CoverWidget final : public TWidget, public Notify::Observer { + Q_OBJECT + +public: + CoverWidget(QWidget *parent, PeerData *peer); + + // Count new height for width=newWidth and resize to it. + void resizeToWidth(int newWidth); + + void showFinished(); + + // Profile fixed top bar should use this flag to decide + // if it shows "Share contact" button or not. + // It should show it only if it is hidden in the cover. + bool shareContactButtonShown() const; + +public slots: + void onOnlineCountUpdated(int onlineCount); + +private slots: + void onPhotoShow(); + + void onSendMessage(); + void onShareContact(); + void onSetPhoto(); + void onAddMember(); + void onJoin(); + void onViewChannel(); + +protected: + void paintEvent(QPaintEvent *e) override; + void dragEnterEvent(QDragEnterEvent *e) override; + void dragLeaveEvent(QDragLeaveEvent *e) override; + void dropEvent(QDropEvent *e) override; + +private: + // Observed notifications. + void notifyPeerUpdated(const Notify::PeerUpdate &update); + void notifyFileQueryUpdated(const FileDialog::QueryUpdate &update); + + // Counts userpic button left offset for a new widget width. + int countPhotoLeft(int newWidth) const; + PhotoData *validatePhoto() const; + + void refreshNameGeometry(int newWidth); + void moveAndToggleButtons(int newWiddth); + void refreshNameText(); + void refreshStatusText(); + + void refreshButtons(); + void setUserButtons(); + void setChatButtons(); + void setMegagroupButtons(); + void setChannelButtons(); + + void clearButtons(); + void addButton(const QString &text, const char *slot, const style::BoxButton *replacementStyle = nullptr); + + void paintDivider(Painter &p); + + bool canEditPhoto() const; + void showSetPhotoBox(const QImage &img); + void resizeDropArea(); + void dropAreaHidden(CoverDropArea *dropArea); + bool mimeDataHasImage(const QMimeData *mimeData) const; + + PeerData *_peer; + UserData *_peerUser; + ChatData *_peerChat; + ChannelData *_peerChannel; + ChannelData *_peerMegagroup; + + ChildWidget _userpicButton; + ChildWidget _dropArea = { nullptr }; + + FlatLabel _name; + + QPoint _statusPosition; + QString _statusText; + bool _statusTextIsOnline = false; + + struct Button { + Ui::RoundButton *widget; + Ui::RoundButton *replacement; + }; + QList