diff --git a/Telegram/Resources/icons/filters/filters_local_book.png b/Telegram/Resources/icons/filters/filters_local_book.png new file mode 100644 index 000000000..7a0fddc99 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_book.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_book@2x.png b/Telegram/Resources/icons/filters/filters_local_book@2x.png new file mode 100644 index 000000000..2a03b1365 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_book@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_book@3x.png b/Telegram/Resources/icons/filters/filters_local_book@3x.png new file mode 100644 index 000000000..08888a0c6 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_book@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_book_active.png b/Telegram/Resources/icons/filters/filters_local_book_active.png new file mode 100644 index 000000000..a29a4ec7c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_book_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_book_active@2x.png b/Telegram/Resources/icons/filters/filters_local_book_active@2x.png new file mode 100644 index 000000000..b5d858f62 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_book_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_book_active@3x.png b/Telegram/Resources/icons/filters/filters_local_book_active@3x.png new file mode 100644 index 000000000..b374cfbdd Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_book_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_brackets.png b/Telegram/Resources/icons/filters/filters_local_brackets.png new file mode 100644 index 000000000..43196adfa Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_brackets.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_brackets@2x.png b/Telegram/Resources/icons/filters/filters_local_brackets@2x.png new file mode 100644 index 000000000..2692489e8 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_brackets@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_brackets@3x.png b/Telegram/Resources/icons/filters/filters_local_brackets@3x.png new file mode 100644 index 000000000..aaba28e9d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_brackets@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_candle.png b/Telegram/Resources/icons/filters/filters_local_candle.png new file mode 100644 index 000000000..dd022cdc6 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_candle.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_candle@2x.png b/Telegram/Resources/icons/filters/filters_local_candle@2x.png new file mode 100644 index 000000000..bb4b4deb6 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_candle@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_candle@3x.png b/Telegram/Resources/icons/filters/filters_local_candle@3x.png new file mode 100644 index 000000000..a5e43ef13 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_candle@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_candle_active.png b/Telegram/Resources/icons/filters/filters_local_candle_active.png new file mode 100644 index 000000000..95f265f14 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_candle_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_candle_active@2x.png b/Telegram/Resources/icons/filters/filters_local_candle_active@2x.png new file mode 100644 index 000000000..b30a02514 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_candle_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_candle_active@3x.png b/Telegram/Resources/icons/filters/filters_local_candle_active@3x.png new file mode 100644 index 000000000..2b682272a Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_candle_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_city.png b/Telegram/Resources/icons/filters/filters_local_city.png new file mode 100644 index 000000000..2353e21c0 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_city.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_city@2x.png b/Telegram/Resources/icons/filters/filters_local_city@2x.png new file mode 100644 index 000000000..3c535a676 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_city@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_city@3x.png b/Telegram/Resources/icons/filters/filters_local_city@3x.png new file mode 100644 index 000000000..d04a586a0 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_city@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_city_active.png b/Telegram/Resources/icons/filters/filters_local_city_active.png new file mode 100644 index 000000000..10dd74501 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_city_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_city_active@2x.png b/Telegram/Resources/icons/filters/filters_local_city_active@2x.png new file mode 100644 index 000000000..ad7a92f2a Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_city_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_city_active@3x.png b/Telegram/Resources/icons/filters/filters_local_city_active@3x.png new file mode 100644 index 000000000..a1cf0cb52 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_city_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_desktop.png b/Telegram/Resources/icons/filters/filters_local_desktop.png new file mode 100644 index 000000000..1363663a6 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_desktop.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_desktop@2x.png b/Telegram/Resources/icons/filters/filters_local_desktop@2x.png new file mode 100644 index 000000000..d0bc8e817 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_desktop@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_desktop@3x.png b/Telegram/Resources/icons/filters/filters_local_desktop@3x.png new file mode 100644 index 000000000..fba4b26af Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_desktop@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_desktop_active.png b/Telegram/Resources/icons/filters/filters_local_desktop_active.png new file mode 100644 index 000000000..2d950f204 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_desktop_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_desktop_active@2x.png b/Telegram/Resources/icons/filters/filters_local_desktop_active@2x.png new file mode 100644 index 000000000..cef1aa33d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_desktop_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_desktop_active@3x.png b/Telegram/Resources/icons/filters/filters_local_desktop_active@3x.png new file mode 100644 index 000000000..5e557a80e Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_desktop_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_earth.png b/Telegram/Resources/icons/filters/filters_local_earth.png new file mode 100644 index 000000000..086a78f5c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_earth.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_earth@2x.png b/Telegram/Resources/icons/filters/filters_local_earth@2x.png new file mode 100644 index 000000000..5751b69b9 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_earth@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_earth@3x.png b/Telegram/Resources/icons/filters/filters_local_earth@3x.png new file mode 100644 index 000000000..e7850d6c8 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_earth@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_music.png b/Telegram/Resources/icons/filters/filters_local_music.png new file mode 100644 index 000000000..a83360a24 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_music.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_music@2x.png b/Telegram/Resources/icons/filters/filters_local_music@2x.png new file mode 100644 index 000000000..4abd97472 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_music@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_music@3x.png b/Telegram/Resources/icons/filters/filters_local_music@3x.png new file mode 100644 index 000000000..2cf80be45 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_music@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_music_active.png b/Telegram/Resources/icons/filters/filters_local_music_active.png new file mode 100644 index 000000000..341741cd8 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_music_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_music_active@2x.png b/Telegram/Resources/icons/filters/filters_local_music_active@2x.png new file mode 100644 index 000000000..da5334ee9 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_music_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_music_active@3x.png b/Telegram/Resources/icons/filters/filters_local_music_active@3x.png new file mode 100644 index 000000000..a5e39118e Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_music_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_news.png b/Telegram/Resources/icons/filters/filters_local_news.png new file mode 100644 index 000000000..94a07708c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_news.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_news@2x.png b/Telegram/Resources/icons/filters/filters_local_news@2x.png new file mode 100644 index 000000000..5582529ba Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_news@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_news@3x.png b/Telegram/Resources/icons/filters/filters_local_news@3x.png new file mode 100644 index 000000000..de901af6d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_news@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_news_active.png b/Telegram/Resources/icons/filters/filters_local_news_active.png new file mode 100644 index 000000000..1b102cee0 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_news_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_news_active@2x.png b/Telegram/Resources/icons/filters/filters_local_news_active@2x.png new file mode 100644 index 000000000..5da39880d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_news_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_news_active@3x.png b/Telegram/Resources/icons/filters/filters_local_news_active@3x.png new file mode 100644 index 000000000..24eb071ca Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_news_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_phone.png b/Telegram/Resources/icons/filters/filters_local_phone.png new file mode 100644 index 000000000..f620c7874 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_phone.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_phone@2x.png b/Telegram/Resources/icons/filters/filters_local_phone@2x.png new file mode 100644 index 000000000..1d324e103 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_phone@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_phone@3x.png b/Telegram/Resources/icons/filters/filters_local_phone@3x.png new file mode 100644 index 000000000..9cdde59e1 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_phone@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_phone_active.png b/Telegram/Resources/icons/filters/filters_local_phone_active.png new file mode 100644 index 000000000..5163642a3 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_phone_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_phone_active@2x.png b/Telegram/Resources/icons/filters/filters_local_phone_active@2x.png new file mode 100644 index 000000000..cd1dc6460 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_phone_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_phone_active@3x.png b/Telegram/Resources/icons/filters/filters_local_phone_active@3x.png new file mode 100644 index 000000000..e0cc8e541 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_phone_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_smile.png b/Telegram/Resources/icons/filters/filters_local_smile.png new file mode 100644 index 000000000..88de7ca3b Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_smile.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_smile@2x.png b/Telegram/Resources/icons/filters/filters_local_smile@2x.png new file mode 100644 index 000000000..9685e77f6 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_smile@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_smile@3x.png b/Telegram/Resources/icons/filters/filters_local_smile@3x.png new file mode 100644 index 000000000..21107845c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_smile@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_smile_active.png b/Telegram/Resources/icons/filters/filters_local_smile_active.png new file mode 100644 index 000000000..b50df0b0c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_smile_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_smile_active@2x.png b/Telegram/Resources/icons/filters/filters_local_smile_active@2x.png new file mode 100644 index 000000000..473bd6a2d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_smile_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_smile_active@3x.png b/Telegram/Resources/icons/filters/filters_local_smile_active@3x.png new file mode 100644 index 000000000..c2fc71a5d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_smile_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_sun.png b/Telegram/Resources/icons/filters/filters_local_sun.png new file mode 100644 index 000000000..a0383a249 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_sun.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_sun@2x.png b/Telegram/Resources/icons/filters/filters_local_sun@2x.png new file mode 100644 index 000000000..5b74e3cf7 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_sun@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_sun@3x.png b/Telegram/Resources/icons/filters/filters_local_sun@3x.png new file mode 100644 index 000000000..2eee38f2d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_sun@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_sun_active.png b/Telegram/Resources/icons/filters/filters_local_sun_active.png new file mode 100644 index 000000000..664c01d55 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_sun_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_sun_active@2x.png b/Telegram/Resources/icons/filters/filters_local_sun_active@2x.png new file mode 100644 index 000000000..909114250 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_sun_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_sun_active@3x.png b/Telegram/Resources/icons/filters/filters_local_sun_active@3x.png new file mode 100644 index 000000000..adc2f3057 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_sun_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_video.png b/Telegram/Resources/icons/filters/filters_local_video.png new file mode 100644 index 000000000..abe54e5be Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_video.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_video@2x.png b/Telegram/Resources/icons/filters/filters_local_video@2x.png new file mode 100644 index 000000000..1d0abbdfe Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_video@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_video@3x.png b/Telegram/Resources/icons/filters/filters_local_video@3x.png new file mode 100644 index 000000000..b4591851f Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_video@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_video_active.png b/Telegram/Resources/icons/filters/filters_local_video_active.png new file mode 100644 index 000000000..c3cf65deb Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_video_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_video_active@2x.png b/Telegram/Resources/icons/filters/filters_local_video_active@2x.png new file mode 100644 index 000000000..f79cde899 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_video_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_local_video_active@3x.png b/Telegram/Resources/icons/filters/filters_local_video_active@3x.png new file mode 100644 index 000000000..caaa2b49c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_local_video_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_admin.png b/Telegram/Resources/icons/filters/filters_type_admin.png new file mode 100644 index 000000000..e09c3ff5f Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_admin.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_admin@2x.png b/Telegram/Resources/icons/filters/filters_type_admin@2x.png new file mode 100644 index 000000000..f8e11bf9a Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_admin@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_admin@3x.png b/Telegram/Resources/icons/filters/filters_type_admin@3x.png new file mode 100644 index 000000000..e7582d031 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_admin@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_filtered.png b/Telegram/Resources/icons/filters/filters_type_filtered.png new file mode 100644 index 000000000..83adbe76b Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_filtered.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_filtered@2x.png b/Telegram/Resources/icons/filters/filters_type_filtered@2x.png new file mode 100644 index 000000000..805a19dc7 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_filtered@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_filtered@3x.png b/Telegram/Resources/icons/filters/filters_type_filtered@3x.png new file mode 100644 index 000000000..374222fbd Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_filtered@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_not_admin.png b/Telegram/Resources/icons/filters/filters_type_not_admin.png new file mode 100644 index 000000000..3716685be Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_not_admin.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_not_admin@2x.png b/Telegram/Resources/icons/filters/filters_type_not_admin@2x.png new file mode 100644 index 000000000..3ae1cab7e Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_not_admin@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_not_admin@3x.png b/Telegram/Resources/icons/filters/filters_type_not_admin@3x.png new file mode 100644 index 000000000..67846f330 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_not_admin@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_not_owner.png b/Telegram/Resources/icons/filters/filters_type_not_owner.png new file mode 100644 index 000000000..b79afc726 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_not_owner.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_not_owner@2x.png b/Telegram/Resources/icons/filters/filters_type_not_owner@2x.png new file mode 100644 index 000000000..7589433b8 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_not_owner@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_not_owner@3x.png b/Telegram/Resources/icons/filters/filters_type_not_owner@3x.png new file mode 100644 index 000000000..af528028c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_not_owner@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_owner.png b/Telegram/Resources/icons/filters/filters_type_owner.png new file mode 100644 index 000000000..8450e7d76 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_owner.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_owner@2x.png b/Telegram/Resources/icons/filters/filters_type_owner@2x.png new file mode 100644 index 000000000..03335ba2f Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_owner@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_owner@3x.png b/Telegram/Resources/icons/filters/filters_type_owner@3x.png new file mode 100644 index 000000000..461122328 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_owner@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_recent.png b/Telegram/Resources/icons/filters/filters_type_recent.png new file mode 100644 index 000000000..d54155751 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_recent.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_recent@2x.png b/Telegram/Resources/icons/filters/filters_type_recent@2x.png new file mode 100644 index 000000000..e30e54f45 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_recent@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_recent@3x.png b/Telegram/Resources/icons/filters/filters_type_recent@3x.png new file mode 100644 index 000000000..88d8ec74f Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_recent@3x.png differ diff --git a/Telegram/Resources/icons/settings_cloud.png b/Telegram/Resources/icons/settings_cloud.png new file mode 100644 index 000000000..c28239251 Binary files /dev/null and b/Telegram/Resources/icons/settings_cloud.png differ diff --git a/Telegram/Resources/icons/settings_cloud@2x.png b/Telegram/Resources/icons/settings_cloud@2x.png new file mode 100644 index 000000000..6e004a9ba Binary files /dev/null and b/Telegram/Resources/icons/settings_cloud@2x.png differ diff --git a/Telegram/Resources/icons/settings_cloud@3x.png b/Telegram/Resources/icons/settings_cloud@3x.png new file mode 100644 index 000000000..3cb99dfab Binary files /dev/null and b/Telegram/Resources/icons/settings_cloud@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3ec06e94b..2778a541b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2636,4 +2636,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "ktg_profile_mention_user" = "Mention user"; +"ktg_filters_exclude_not_owned" = "Not owned"; +"ktg_filters_exclude_not_admin" = "Not administrated"; +"ktg_filters_exclude_owned" = "Owned"; +"ktg_filters_exclude_admin" = "Administrated"; +"ktg_filters_exclude_not_recent" = "Not opened in this session"; +"ktg_filters_exclude_filtered" = "From other folders"; + +"ktg_filters_create_cloud" = "Create cloud folder"; +"ktg_filters_create_local" = "Create local folder"; + +"ktg_filters_description" = "Cloud folders are synced between all your Telegram apps, but local folders have more features to offer."; + +"ktg_filters_new_cloud" = "New cloud folder"; +"ktg_filters_new_local" = "New local folder"; + +"ktg_filters_edit_cloud" = "Edit cloud folder"; +"ktg_filters_edit_local" = "Edit local folder"; + +"ktg_filters_local" = "local folder"; +"ktg_filters_cloud" = "cloud folder"; + +"ktg_filters_cloud_limit" = "Sorry, you can't create more cloud folders. You can create local folder instead."; + // Keys finished diff --git a/Telegram/Resources/langs/rewrites/en.json b/Telegram/Resources/langs/rewrites/en.json index 4dad11358..5506f3d1c 100644 --- a/Telegram/Resources/langs/rewrites/en.json +++ b/Telegram/Resources/langs/rewrites/en.json @@ -184,6 +184,22 @@ "ktg_forward_subtitle_group_all_media": "as albums", "ktg_forward_subtitle_separate_messages": "one by one", "ktg_profile_mention_user": "Mention user", + "ktg_filters_exclude_not_owned": "Not owned", + "ktg_filters_exclude_not_admin": "Not administrated", + "ktg_filters_exclude_owned": "Owned", + "ktg_filters_exclude_admin": "Administrated", + "ktg_filters_exclude_not_recent": "Not opened in this session", + "ktg_filters_exclude_filtered": "From other folders", + "ktg_filters_create_cloud": "Create cloud folder", + "ktg_filters_create_local": "Create local folder", + "ktg_filters_description": "Cloud folders are synced between all your Telegram apps, but local folders have more features to offer.", + "ktg_filters_new_cloud": "New cloud folder", + "ktg_filters_new_local": "New local folder", + "ktg_filters_edit_cloud": "Edit cloud folder", + "ktg_filters_edit_local": "Edit local folder", + "ktg_filters_local": "local folder", + "ktg_filters_cloud": "cloud folder", + "ktg_filters_cloud_limit": "Sorry, you can't create more cloud folders. You can create local folder instead.", // This string should always be last for better work with Git. "dummy_last_string": "" diff --git a/Telegram/Resources/langs/rewrites/it.json b/Telegram/Resources/langs/rewrites/it.json index 7c2169bc2..cdbc6c640 100644 --- a/Telegram/Resources/langs/rewrites/it.json +++ b/Telegram/Resources/langs/rewrites/it.json @@ -184,6 +184,22 @@ "ktg_forward_subtitle_group_all_media": "as albums", "ktg_forward_subtitle_separate_messages": "one by one", "ktg_profile_mention_user": "Mention user", + "ktg_filters_exclude_not_owned": "Not owned", + "ktg_filters_exclude_not_admin": "Not administrated", + "ktg_filters_exclude_owned": "Owned", + "ktg_filters_exclude_admin": "Administrated", + "ktg_filters_exclude_not_recent": "Not opened in this session", + "ktg_filters_exclude_filtered": "From other folders", + "ktg_filters_create_cloud": "Create cloud folder", + "ktg_filters_create_local": "Create local folder", + "ktg_filters_description": "Cloud folders are synced between all your Telegram apps, but local folders have more features to offer.", + "ktg_filters_new_cloud": "New cloud folder", + "ktg_filters_new_local": "New local folder", + "ktg_filters_edit_cloud": "Edit cloud folder", + "ktg_filters_edit_local": "Edit local folder", + "ktg_filters_local": "local folder", + "ktg_filters_cloud": "cloud folder", + "ktg_filters_cloud_limit": "Sorry, you can't create more cloud folders. You can create local folder instead.", // This string should always be last for better work with Git. "dummy_last_string": "" diff --git a/Telegram/Resources/langs/rewrites/pl.json b/Telegram/Resources/langs/rewrites/pl.json index d5081c560..ffb89c9e9 100644 --- a/Telegram/Resources/langs/rewrites/pl.json +++ b/Telegram/Resources/langs/rewrites/pl.json @@ -192,6 +192,22 @@ "ktg_forward_subtitle_group_all_media": "as albums", "ktg_forward_subtitle_separate_messages": "one by one", "ktg_profile_mention_user": "Mention user", + "ktg_filters_exclude_not_owned": "Not owned", + "ktg_filters_exclude_not_admin": "Not administrated", + "ktg_filters_exclude_owned": "Owned", + "ktg_filters_exclude_admin": "Administrated", + "ktg_filters_exclude_not_recent": "Not opened in this session", + "ktg_filters_exclude_filtered": "From other folders", + "ktg_filters_create_cloud": "Create cloud folder", + "ktg_filters_create_local": "Create local folder", + "ktg_filters_description": "Cloud folders are synced between all your Telegram apps, but local folders have more features to offer.", + "ktg_filters_new_cloud": "New cloud folder", + "ktg_filters_new_local": "New local folder", + "ktg_filters_edit_cloud": "Edit cloud folder", + "ktg_filters_edit_local": "Edit local folder", + "ktg_filters_local": "local folder", + "ktg_filters_cloud": "cloud folder", + "ktg_filters_cloud_limit": "Sorry, you can't create more cloud folders. You can create local folder instead.", // This string should always be last for better work with Git. "dummy_last_string": "" diff --git a/Telegram/Resources/langs/rewrites/pt-br.json b/Telegram/Resources/langs/rewrites/pt-br.json index 83f9af4da..0826630ac 100644 --- a/Telegram/Resources/langs/rewrites/pt-br.json +++ b/Telegram/Resources/langs/rewrites/pt-br.json @@ -184,6 +184,22 @@ "ktg_forward_subtitle_group_all_media": "as albums", "ktg_forward_subtitle_separate_messages": "one by one", "ktg_profile_mention_user": "Mention user", + "ktg_filters_exclude_not_owned": "Not owned", + "ktg_filters_exclude_not_admin": "Not administrated", + "ktg_filters_exclude_owned": "Owned", + "ktg_filters_exclude_admin": "Administrated", + "ktg_filters_exclude_not_recent": "Not opened in this session", + "ktg_filters_exclude_filtered": "From other folders", + "ktg_filters_create_cloud": "Create cloud folder", + "ktg_filters_create_local": "Create local folder", + "ktg_filters_description": "Cloud folders are synced between all your Telegram apps, but local folders have more features to offer.", + "ktg_filters_new_cloud": "New cloud folder", + "ktg_filters_new_local": "New local folder", + "ktg_filters_edit_cloud": "Edit cloud folder", + "ktg_filters_edit_local": "Edit local folder", + "ktg_filters_local": "local folder", + "ktg_filters_cloud": "cloud folder", + "ktg_filters_cloud_limit": "Sorry, you can't create more cloud folders. You can create local folder instead.", // This string should always be last for better work with Git. "dummy_last_string": "" diff --git a/Telegram/Resources/langs/rewrites/ru.json b/Telegram/Resources/langs/rewrites/ru.json index f9811192c..cf29a9288 100644 --- a/Telegram/Resources/langs/rewrites/ru.json +++ b/Telegram/Resources/langs/rewrites/ru.json @@ -191,6 +191,22 @@ "ktg_forward_subtitle_group_all_media": "альбомами", "ktg_forward_subtitle_separate_messages": "по одному", "ktg_profile_mention_user": "Упомянуть пользователя", + "ktg_filters_exclude_not_owned": "Без прав владельца", + "ktg_filters_exclude_not_admin": "Без прав админа", + "ktg_filters_exclude_owned": "С правами владельца", + "ktg_filters_exclude_admin": "С правами админа", + "ktg_filters_exclude_not_recent": "Не открытые за сессию", + "ktg_filters_exclude_filtered": "Из других папок", + "ktg_filters_create_cloud": "Создать облачную папку", + "ktg_filters_create_local": "Создать локальную папку", + "ktg_filters_description": "Облачные папки синхронизируются со остальными вашими приложениями Telegram, но у локальных папок больше функций.", + "ktg_filters_new_cloud": "Новая облачная папка", + "ktg_filters_new_local": "Новая локальная папка", + "ktg_filters_edit_cloud": "Изменить облачную папку", + "ktg_filters_edit_local": "Изменить локальную папку", + "ktg_filters_local": "локальная папка", + "ktg_filters_cloud": "облачная папка", + "ktg_filters_cloud_limit": "Вы создали максимальное число облачных папок. Вместо этого можно создать локальную папку.", // This string should always be last for better work with Git. "dummy_last_string": "" diff --git a/Telegram/Resources/langs/rewrites/tr.json b/Telegram/Resources/langs/rewrites/tr.json index bab37f01f..da0c2dd1d 100644 --- a/Telegram/Resources/langs/rewrites/tr.json +++ b/Telegram/Resources/langs/rewrites/tr.json @@ -184,6 +184,22 @@ "ktg_forward_subtitle_group_all_media": "as albums", "ktg_forward_subtitle_separate_messages": "one by one", "ktg_profile_mention_user": "Mention user", + "ktg_filters_exclude_not_owned": "Not owned", + "ktg_filters_exclude_not_admin": "Not administrated", + "ktg_filters_exclude_owned": "Owned", + "ktg_filters_exclude_admin": "Administrated", + "ktg_filters_exclude_not_recent": "Not opened in this session", + "ktg_filters_exclude_filtered": "From other folders", + "ktg_filters_create_cloud": "Create cloud folder", + "ktg_filters_create_local": "Create local folder", + "ktg_filters_description": "Cloud folders are synced between all your Telegram apps, but local folders have more features to offer.", + "ktg_filters_new_cloud": "New cloud folder", + "ktg_filters_new_local": "New local folder", + "ktg_filters_edit_cloud": "Edit cloud folder", + "ktg_filters_edit_local": "Edit local folder", + "ktg_filters_local": "local folder", + "ktg_filters_cloud": "cloud folder", + "ktg_filters_cloud_limit": "Sorry, you can't create more cloud folders. You can create local folder instead.", // This string should always be last for better work with Git. "dummy_last_string": "" diff --git a/Telegram/Resources/langs/rewrites/uk.json b/Telegram/Resources/langs/rewrites/uk.json index 9b67eceea..5b261ff6c 100644 --- a/Telegram/Resources/langs/rewrites/uk.json +++ b/Telegram/Resources/langs/rewrites/uk.json @@ -191,6 +191,22 @@ "ktg_forward_subtitle_group_all_media": "as albums", "ktg_forward_subtitle_separate_messages": "one by one", "ktg_profile_mention_user": "Mention user", + "ktg_filters_exclude_not_owned": "Not owned", + "ktg_filters_exclude_not_admin": "Not administrated", + "ktg_filters_exclude_owned": "Owned", + "ktg_filters_exclude_admin": "Administrated", + "ktg_filters_exclude_not_recent": "Not opened in this session", + "ktg_filters_exclude_filtered": "From other folders", + "ktg_filters_create_cloud": "Create cloud folder", + "ktg_filters_create_local": "Create local folder", + "ktg_filters_description": "Cloud folders are synced between all your Telegram apps, but local folders have more features to offer.", + "ktg_filters_new_cloud": "New cloud folder", + "ktg_filters_new_local": "New local folder", + "ktg_filters_edit_cloud": "Edit cloud folder", + "ktg_filters_edit_local": "Edit local folder", + "ktg_filters_local": "local folder", + "ktg_filters_cloud": "cloud folder", + "ktg_filters_cloud_limit": "Sorry, you can't create more cloud folders. You can create local folder instead.", // This string should always be last for better work with Git. "dummy_last_string": "" diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index 68b0dbebe..55c2902b8 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_chat_filters.h" #include "main/main_session.h" +#include "kotato/json_settings.h" #include "apiwrap.h" namespace Api { @@ -24,11 +25,16 @@ void SaveNewFilterPinned( const auto &filter = filters.applyUpdatedPinned( filterId, order); - session->api().request(MTPmessages_UpdateDialogFilter( - MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter), - MTP_int(filterId), - filter.tl() - )).send(); + if (filter.isLocal()) { + filters.saveLocal(filterId); + Kotato::JsonSettings::Write(); + } else { + session->api().request(MTPmessages_UpdateDialogFilter( + MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter), + MTP_int(filterId), + filter.tl() + )).send(); + } } diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index c3b39bdf5..61fe58f1b 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -61,6 +61,12 @@ constexpr auto kAllTypes = { Flag::NoMuted, Flag::NoRead, Flag::NoArchived, + Flag::Owned, + Flag::Admin, + Flag::NotOwned, + Flag::NotAdmin, + Flag::Recent, + Flag::NoFilter, }; class FilterChatsPreview final : public Ui::RpWidget { @@ -133,7 +139,9 @@ not_null SetupChatsPreview( (rules.flags() & ~flag), rules.always(), rules.pinned(), - rules.never()); + rules.never(), + rules.isDefault(), + rules.isLocal()); updateDefaultTitle(computed); *data = std::move(computed); }, preview->lifetime()); @@ -154,7 +162,9 @@ not_null SetupChatsPreview( rules.flags(), std::move(always), std::move(pinned), - std::move(never)); + std::move(never), + rules.isDefault(), + rules.isLocal()); updateDefaultTitle(computed); *data = std::move(computed); }, preview->lifetime()); @@ -403,7 +413,8 @@ void EditExceptions( : tr::lng_filters_exclude_title()), options, rules.flags() & options, - include ? rules.always() : rules.never()); + include ? rules.always() : rules.never(), + rules.isLocal()); const auto rawController = controller.get(); auto initBox = [=](not_null box) { box->setCloseByOutsideClick(false); @@ -437,7 +448,9 @@ void EditExceptions( | rawController->chosenOptions()), include ? std::move(changed) : std::move(removeFrom), std::move(pinned), - include ? std::move(removeFrom) : std::move(changed)); + include ? std::move(removeFrom) : std::move(changed), + rules.isDefault(), + rules.isLocal()); updateDefaultTitle(computed); *data = computed; refresh(); @@ -488,7 +501,8 @@ void CreateIconSelector( }, toggle->lifetime()); const auto panel = toggle->lifetime().make_state( - outer); + outer, + rules.isLocal()); toggle->installEventFilter(panel); toggle->addClickHandler([=] { panel->toggleAnimated(); @@ -506,7 +520,9 @@ void CreateIconSelector( rules.flags(), rules.always(), rules.pinned(), - rules.never()); + rules.never(), + rules.isDefault(), + rules.isLocal()); }, panel->lifetime()); const auto updatePanelGeometry = [=] { @@ -574,7 +590,14 @@ void EditFilterBox( const Data::ChatFilter &filter, Fn doneCallback) { const auto creating = filter.title().isEmpty(); - box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit()); + const auto isLocal = filter.isLocal(); + box->setTitle(creating + ? (isLocal + ? tr::ktg_filters_new_local() + : tr::ktg_filters_new_cloud()) + : (isLocal + ? tr::ktg_filters_edit_local() + : tr::ktg_filters_edit_cloud())); box->setCloseByOutsideClick(false); using State = rpl::variable; @@ -588,7 +611,9 @@ void EditFilterBox( tr::lng_filters_new_name(), filter.title()), st::markdownLinkFieldPadding); - name->setMaxLength(kMaxFilterTitleLength); + if (!isLocal) { + name->setMaxLength(kMaxFilterTitleLength); + } name->setInstantReplaces(Ui::InstantReplaces::Default()); name->setInstantReplacesEnabled( Core::App().settings().replaceEmojiValue()); @@ -609,7 +634,9 @@ void EditFilterBox( if (nameEditing->custom) { return; } - const auto title = TrimDefaultTitle(DefaultTitle(filter)); + const auto title = isLocal + ? DefaultTitle(filter) + : TrimDefaultTitle(DefaultTitle(filter)); if (nameEditing->field->getLastText() != title) { nameEditing->settingDefault = true; nameEditing->field->setText(title); @@ -633,6 +660,13 @@ void EditFilterBox( constexpr auto kExcludeTypes = Flag::NoMuted | Flag::NoArchived | Flag::NoRead; + constexpr auto kExcludeTypesLocal = kExcludeTypes + | Flag::Owned + | Flag::Admin + | Flag::NotOwned + | Flag::NotAdmin + | Flag::Recent + | Flag::NoFilter; box->setFocusCallback([=] { name->setFocusFast(); @@ -690,7 +724,7 @@ void EditFilterBox( content, data, updateDefaultTitle, - kExcludeTypes, + (isLocal ? kExcludeTypesLocal : kExcludeTypes), &Data::ChatFilter::never); AddSkip(content); @@ -706,7 +740,7 @@ void EditFilterBox( data->current().flags() & kTypes, data->current().always()); exclude->updateData( - data->current().flags() & kExcludeTypes, + data->current().flags() & (isLocal ? kExcludeTypesLocal : kExcludeTypes), data->current().never()); }; includeAdd->setClickedCallback([=] { @@ -722,7 +756,7 @@ void EditFilterBox( EditExceptions( window, box, - kExcludeTypes, + (isLocal ? kExcludeTypesLocal : kExcludeTypes), data, updateDefaultTitle, refreshPreviews); @@ -740,7 +774,8 @@ void EditFilterBox( rules.always(), rules.pinned(), rules.never(), - checked); + checked, + isLocal); if (title.isEmpty()) { name->showError(); return; @@ -767,29 +802,40 @@ void EditExistingFilter( not_null window, FilterId id) { const auto session = &window->session(); - const auto &list = session->data().chatsFilters().list(); + const auto filters = &session->data().chatsFilters(); + const auto &list = filters->list(); const auto i = ranges::find(list, id, &Data::ChatFilter::id); if (i == end(list)) { return; } const auto doneCallback = [=](const Data::ChatFilter &result) { Expects(id == result.id()); + auto needSave = false; - const auto tl = result.tl(); - session->data().chatsFilters().apply(MTP_updateDialogFilter( - MTP_flags(MTPDupdateDialogFilter::Flag::f_filter), - MTP_int(id), - tl)); - session->api().request(MTPmessages_UpdateDialogFilter( - MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter), - MTP_int(id), - tl - )).send(); + if (result.isLocal()) { + filters->set(result); + filters->saveLocal(id); + needSave = true; + } else { + const auto tl = result.tl(); + session->data().chatsFilters().apply(MTP_updateDialogFilter( + MTP_flags(MTPDupdateDialogFilter::Flag::f_filter), + MTP_int(id), + tl)); + session->api().request(MTPmessages_UpdateDialogFilter( + MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter), + MTP_int(id), + tl + )).send(); + } const auto defaultFilterId = session->account().defaultFilterId(); const auto isCurrentDefault = result.id() == defaultFilterId; if ((isCurrentDefault && !result.isDefault()) || (!isCurrentDefault && result.isDefault())) { session->account().setDefaultFilterId(result.isDefault() ? result.id() : 0); + needSave = true; + } + if (needSave) { Kotato::JsonSettings::Write(); } }; diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index 73e71b0f0..a97054da5 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -32,6 +32,12 @@ constexpr auto kAllTypes = { Flag::NoMuted, Flag::NoRead, Flag::NoArchived, + Flag::Owned, + Flag::Admin, + Flag::NotOwned, + Flag::NotAdmin, + Flag::Recent, + Flag::NoFilter, }; struct RowSelectionChange { @@ -158,7 +164,7 @@ PaintRoundImageCallback TypeRow::generatePaintUserpicCallback() { } Flag TypeRow::flag() const { - return static_cast(id() & 0xFF); + return static_cast(id() & 0xFFFF); } ExceptionRow::ExceptionRow(not_null history) : Row(history) { @@ -296,6 +302,12 @@ auto TypeController::rowSelectionChanges() const case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now); case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now); case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now); + case Flag::Owned: return tr::ktg_filters_exclude_not_owned(tr::now); + case Flag::Admin: return tr::ktg_filters_exclude_not_admin(tr::now); + case Flag::NotOwned: return tr::ktg_filters_exclude_owned(tr::now); + case Flag::NotAdmin: return tr::ktg_filters_exclude_admin(tr::now); + case Flag::Recent: return tr::ktg_filters_exclude_not_recent(tr::now); + case Flag::NoFilter: return tr::ktg_filters_exclude_filtered(tr::now); } Unexpected("Flag in TypeName."); } @@ -317,6 +329,12 @@ void PaintFilterChatsTypeIcon( case Flag::NoMuted: return st::historyPeer6UserpicBg; case Flag::NoArchived: return st::historyPeer4UserpicBg; case Flag::NoRead: return st::historyPeer7UserpicBg; + case Flag::Owned: return st::historyPeer2UserpicBg; + case Flag::Admin: return st::historyPeer3UserpicBg; + case Flag::NotOwned: return st::historyPeer2UserpicBg; + case Flag::NotAdmin: return st::historyPeer3UserpicBg; + case Flag::Recent: return st::historyPeer6UserpicBg; + case Flag::NoFilter: return st::historyPeer7UserpicBg; } Unexpected("Flag in color paintFlagIcon."); }(); @@ -330,6 +348,12 @@ void PaintFilterChatsTypeIcon( case Flag::NoMuted: return st::windowFilterTypeNoMuted; case Flag::NoArchived: return st::windowFilterTypeNoArchived; case Flag::NoRead: return st::windowFilterTypeNoRead; + case Flag::Owned: return st::windowFilterTypeOwned; + case Flag::Admin: return st::windowFilterTypeAdmin; + case Flag::NotOwned: return st::windowFilterTypeNotOwned; + case Flag::NotAdmin: return st::windowFilterTypeNotAdmin; + case Flag::Recent: return st::windowFilterTypeRecent; + case Flag::NoFilter: return st::windowFilterTypeNoFilter; } Unexpected("Flag in icon paintFlagIcon."); }(); @@ -367,13 +391,15 @@ EditFilterChatsListController::EditFilterChatsListController( rpl::producer title, Flags options, Flags selected, - const base::flat_set> &peers) + const base::flat_set> &peers, + bool isLocal) : ChatsListBoxController(navigation) , _navigation(navigation) , _title(std::move(title)) , _peers(peers) , _options(options) -, _selected(selected) { +, _selected(selected) +, _isLocal(isLocal) { } Main::Session &EditFilterChatsListController::session() const { @@ -382,7 +408,7 @@ Main::Session &EditFilterChatsListController::session() const { void EditFilterChatsListController::rowClicked(not_null row) { const auto count = delegate()->peerListSelectedRowsCount(); - if (count < kMaxExceptions || row->checked()) { + if (count < kMaxExceptions || row->checked() || _isLocal) { delegate()->peerListSetRowChecked(row, !row->checked()); updateTitle(); } @@ -500,6 +526,8 @@ void EditFilterChatsListController::updateTitle() { } } const auto count = delegate()->peerListSelectedRowsCount() - types; - const auto additional = qsl("%1 / %2").arg(count).arg(kMaxExceptions); + const auto additional = _isLocal + ? tr::lng_filters_chats_count(tr::now, lt_count_short, count) + : qsl("%1 / %2").arg(count).arg(kMaxExceptions); delegate()->peerListSetAdditionalTitle(rpl::single(additional)); } diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h index e993cafb7..4dc934419 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h @@ -45,7 +45,8 @@ public: rpl::producer title, Flags options, Flags selected, - const base::flat_set> &peers); + const base::flat_set> &peers, + bool isLocal); [[nodiscard]] Main::Session &session() const override; [[nodiscard]] Flags chosenOptions() const { @@ -69,6 +70,7 @@ private: base::flat_set> _peers; Flags _options; Flags _selected; + bool _isLocal; Fn _deselectOption; diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 01a78c644..c67f105c4 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "main/main_session.h" #include "main/main_account.h" +#include "mainwidget.h" #include "apiwrap.h" namespace Data { @@ -27,10 +28,13 @@ namespace { constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000); constexpr auto kLoadExceptionsAfter = 100; constexpr auto kLoadExceptionsPerRequest = 100; +constexpr auto kFiltersLimit = 10; } // namespace -ChatFilter::ChatFilter(FilterId id) : _id(id) { +ChatFilter::ChatFilter(FilterId id, bool isLocal) +: _id(id) +, _isLocal(isLocal) { } ChatFilter::ChatFilter( @@ -41,7 +45,8 @@ ChatFilter::ChatFilter( base::flat_set> always, std::vector> pinned, base::flat_set> never, - bool isDefault) + bool isDefault, + bool isLocal) : _id(id) , _title(title) , _iconEmoji(iconEmoji) @@ -49,12 +54,67 @@ ChatFilter::ChatFilter( , _pinned(std::move(pinned)) , _never(std::move(never)) , _flags(flags) -, _isDefault(isDefault) { +, _isDefault(isDefault) +, _isLocal(isLocal) { +} + +ChatFilter ChatFilter::local( + const LocalFolder &data, + not_null owner) { + const auto flags = Flag(data.flags); + auto &&to_histories = ranges::view::transform([&]( + const LocalFolder::Peer &filterPeer) { + PeerData *peer = nullptr; + + if (filterPeer.type == LocalFolder::Peer::Type::User) { + const auto user = owner->user(filterPeer.id); + user->setAccessHash(filterPeer.accessHash); + peer = (PeerData *)user; + } else if (filterPeer.type == LocalFolder::Peer::Type::Chat) { + const auto chat = owner->chat(filterPeer.id); + peer = (PeerData *)chat; + } else if (filterPeer.type == LocalFolder::Peer::Type::Channel) { + const auto channel = owner->channel(filterPeer.id); + channel->setAccessHash(filterPeer.accessHash); + peer = (PeerData *)channel; + } + return peer ? owner->history(peer).get() : nullptr; + }) | ranges::view::filter([](History *history) { + return history != nullptr; + }) | ranges::view::transform([](History *history) { + return not_null(history); + }); + auto &&always = ranges::view::concat( + data.always + ) | to_histories; + auto pinned = ranges::view::all( + data.pinned + ) | to_histories | ranges::to_vector; + auto &&never = ranges::view::all( + data.never + ) | to_histories; + auto &&all = ranges::view::concat(always, pinned); + auto list = base::flat_set>{ + all.begin(), + all.end() + }; + const auto defaultFilterId = owner->session().account().defaultFilterId(); + return ChatFilter( + data.id, + data.name, + data.emoticon, + flags, + std::move(list), + std::move(pinned), + { never.begin(), never.end() }, + (data.id == defaultFilterId), + true); } ChatFilter ChatFilter::FromTL( const MTPDialogFilter &data, - not_null owner) { + not_null owner, + bool isLocal) { return data.match([&](const MTPDdialogFilter &data) { const auto flags = (data.is_contacts() ? Flag::Contacts : Flag(0)) | (data.is_non_contacts() ? Flag::NonContacts : Flag(0)) @@ -110,7 +170,8 @@ ChatFilter ChatFilter::FromTL( std::move(list), std::move(pinned), { never.begin(), never.end() }, - (data.vid().v == defaultFilterId)); + (data.vid().v == defaultFilterId), + isLocal); }); } @@ -154,6 +215,85 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { MTP_vector(never)); } +LocalFolder ChatFilter::toLocal(int cloudOrder, FilterId replaceId) const { + auto always = _always; + auto pinned = std::vector(); + pinned.reserve(_pinned.size()); + for (const auto history : _pinned) { + const auto &peer = history->peer; + const auto hash = peer->isChannel() + ? peer->asChannel()->access + : peer->isUser() + ? peer->asUser()->accessHash() + : 0; + + pinned.push_back({ + .type = history->peer->isChannel() + ? LocalFolder::Peer::Type::Channel + : history->peer->isChat() + ? LocalFolder::Peer::Type::Chat + : LocalFolder::Peer::Type::User, + .id = peerToBareInt(peer->id), + .accessHash = hash + }); + always.remove(history); + } + auto include = std::vector(); + include.reserve(always.size()); + for (const auto history : always) { + const auto &peer = history->peer; + const auto hash = peer->isChannel() + ? peer->asChannel()->access + : peer->isUser() + ? peer->asUser()->accessHash() + : 0; + + include.push_back({ + .type = history->peer->isChannel() + ? LocalFolder::Peer::Type::Channel + : history->peer->isChat() + ? LocalFolder::Peer::Type::Chat + : LocalFolder::Peer::Type::User, + .id = peerToBareInt(peer->id), + .accessHash = hash + }); + } + auto never = std::vector(); + never.reserve(_never.size()); + for (const auto history : _never) { + const auto &peer = history->peer; + const auto hash = peer->isChannel() + ? peer->asChannel()->access + : peer->isUser() + ? peer->asUser()->accessHash() + : 0; + + never.push_back({ + .type = history->peer->isChannel() + ? LocalFolder::Peer::Type::Channel + : history->peer->isChat() + ? LocalFolder::Peer::Type::Chat + : LocalFolder::Peer::Type::User, + .id = peerToBareInt(peer->id), + .accessHash = hash + }); + } + const auto &session = App::main()->session(); + return { + .id = replaceId ? replaceId : _id, + .ownerId = session.mtp().isTestMode() + ? -session.userId() + : session.userId(), + .cloudOrder = cloudOrder, + .name = _title, + .emoticon = _iconEmoji, + .always = include, + .never = never, + .pinned = pinned, + .flags = _flags.value() + }; +} + FilterId ChatFilter::id() const { return _id; } @@ -187,6 +327,9 @@ const base::flat_set> &ChatFilter::never() const { } bool ChatFilter::contains(not_null history) const { + if (_never.contains(history)) { + return false; + } const auto flag = [&] { const auto peer = history->peer; if (const auto user = peer->asUser()) { @@ -207,11 +350,56 @@ bool ChatFilter::contains(not_null history) const { Unexpected("Peer type in ChatFilter::contains."); } }(); - if (_never.contains(history)) { + const auto filterAdmin = [&] { + if (!(_flags & Flag::Owned) + && !(_flags & Flag::NotOwned) + && !(_flags & Flag::Admin) + && !(_flags & Flag::NotAdmin)) { + return true; + } + + const auto peer = history->peer; + if (const auto chat = peer->asChat()) { + if ((chat->amCreator() && (_flags & Flag::Owned) && !(_flags & Flag::NotOwned)) + || (chat->hasAdminRights() && (_flags & Flag::Admin) && !(_flags & Flag::NotAdmin)) + || (!chat->amCreator() && !(_flags & Flag::Owned) && (_flags & Flag::NotOwned)) + || (!chat->hasAdminRights() && !(_flags & Flag::Admin) && (_flags & Flag::NotAdmin))) { + return true; + } + } else if (const auto channel = peer->asChannel()) { + if ((channel->amCreator() && (_flags & Flag::Owned) && !(_flags & Flag::NotOwned)) + || (channel->hasAdminRights() && (_flags & Flag::Admin) && !(_flags & Flag::NotAdmin)) + || (!channel->amCreator() && !(_flags & Flag::Owned) && (_flags & Flag::NotOwned)) + || (!channel->hasAdminRights() && !(_flags & Flag::Admin) && (_flags & Flag::NotAdmin))) { + return true; + } + } + return false; - } + }; + const auto filterUnfiltered = [&] { + if (!(_flags & Flag::NoFilter)) { + return true; + } + + const auto &list = history->owner().chatsFilters().list(); + for (auto filter : list) { + if (filter.id() == _id) { + continue; + } + + if (filter.contains(history)) { + return false; + } + } + + return true; + }; return false || ((_flags & flag) + && filterAdmin() + && (!(_flags & Flag::Recent) + || history->owner().session().account().isRecent(history->peer->id)) && (!(_flags & Flag::NoMuted) || !history->mute() || (history->hasUnreadMentions() @@ -223,10 +411,15 @@ bool ChatFilter::contains(not_null history) const { || history->hasUnreadMentions() || history->fakeUnreadWhileOpened()) && (!(_flags & Flag::NoArchived) - || (history->folderKnown() && !history->folder()))) + || (history->folderKnown() && !history->folder())) + && filterUnfiltered()) || _always.contains(history); } +bool ChatFilter::isLocal() const { + return _isLocal; +} + ChatFilters::ChatFilters(not_null owner) : _owner(owner) { crl::on_main(&owner->session(), [=] { load(); }); } @@ -274,10 +467,14 @@ void ChatFilters::load(bool force) { } void ChatFilters::received(const QVector &list) { + const auto account = &_owner->session().account(); + const auto defaultFilterId = account->defaultFilterId(); + const auto localFilters = cRefLocalFolders(); auto position = 0; + auto originalPosition = 0; auto changed = false; - for (const auto &filter : list) { - auto parsed = ChatFilter::FromTL(filter, _owner); + + auto addToList = [&] (ChatFilter parsed) { const auto b = begin(_list) + position, e = end(_list); const auto i = ranges::find(b, e, parsed.id(), &ChatFilter::id); if (i == e) { @@ -293,7 +490,42 @@ void ChatFilters::received(const QVector &list) { changed = true; } ++position; + }; + + // First we're adding cloud filters and corresponding local filters. + for (const auto &filter : list) { + for (const auto &localFilter : localFilters) { + if (!account->isCurrent(localFilter.ownerId) + || localFilter.cloudOrder != originalPosition) { + continue; + } + addToList(ChatFilter::local(localFilter, _owner)); + } + addToList(ChatFilter::FromTL(filter, _owner)); + ++originalPosition; } + + // Then we adding local filters, retaining cloud order + while (originalPosition < kFiltersLimit) { + for (const auto &localFilter : localFilters) { + if (!account->isCurrent(localFilter.ownerId) + || localFilter.cloudOrder != originalPosition) { + continue; + } + addToList(ChatFilter::local(localFilter, _owner)); + } + ++originalPosition; + } + + // And finally we adding other filters + for (const auto &localFilter : localFilters) { + if (!account->isCurrent(localFilter.ownerId) + || localFilter.cloudOrder < kFiltersLimit) { + continue; + } + addToList(ChatFilter::local(localFilter, _owner)); + } + while (position < _list.size()) { applyRemove(position); changed = true; @@ -342,7 +574,7 @@ void ChatFilters::applyInsert(ChatFilter filter, int position) { _list.insert( begin(_list) + position, - ChatFilter(filter.id(), {}, {}, {}, {}, {}, {})); + ChatFilter(filter.id(), {}, {}, {}, {}, {}, {}, false, filter.isLocal())); applyChange(*(begin(_list) + position), std::move(filter)); } @@ -468,7 +700,7 @@ const ChatFilter &ChatFilters::applyUpdatedPinned( if (const auto history = row.history()) { if (always.contains(history)) { pinned.push_back(history); - } else if (always.size() < ChatFilter::kPinnedLimit) { + } else if (always.size() < ChatFilter::kPinnedLimit || i->isLocal()) { always.insert(history); pinned.push_back(history); } @@ -483,7 +715,8 @@ const ChatFilter &ChatFilters::applyUpdatedPinned( std::move(always), std::move(pinned), i->never(), - (id == defaultFilterId))); + (id == defaultFilterId), + i->isLocal())); return *i; } @@ -498,15 +731,37 @@ void ChatFilters::saveOrder( auto ids = QVector(); ids.reserve(order.size()); + auto cloudIds = QVector(); + cloudIds.reserve(kFiltersLimit); + auto &localFolders = cRefLocalFolders(); + const auto account = &_owner->session().account(); + for (const auto id : order) { ids.push_back(MTP_int(id)); + + const auto i = ranges::find(_list, id, &ChatFilter::id); + Assert(i != end(_list)); + + if ((*i).isLocal()) { + auto j = ranges::find_if(localFolders, [id, account](LocalFolder localFolder) { + return (id == localFolder.id + && account->isCurrent(localFolder.ownerId)); + }); + (*j).cloudOrder = cloudIds.size(); + std::rotate(j, j+1, localFolders.end()); + } else { + cloudIds.push_back(MTP_int(id)); + } } const auto wrapped = MTP_vector(ids); - apply(MTP_updateDialogFilterOrder(wrapped)); - _saveOrderRequestId = api->request(MTPmessages_UpdateDialogFiltersOrder( - wrapped - )).afterRequest(_saveOrderAfterId).send(); + + if (!cloudIds.isEmpty()) { + const auto cloudWrapped = MTP_vector(cloudIds); + _saveOrderRequestId = api->request(MTPmessages_UpdateDialogFiltersOrder( + cloudWrapped + )).afterRequest(_saveOrderAfterId).send(); + } } bool ChatFilters::archiveNeeded() const { @@ -628,4 +883,20 @@ rpl::producer<> ChatFilters::suggestedUpdated() const { return _suggestedUpdated.events(); } +void ChatFilters::saveLocal(FilterId filterId) { + const auto i = ranges::find(_list, filterId, &ChatFilter::id); + auto &localFolders = cRefLocalFolders(); + const auto account = &_owner->session().account(); + const auto j = ranges::find_if(localFolders, [filterId, account](LocalFolder localFolder) { + return (filterId == localFolder.id + && account->isCurrent(localFolder.ownerId)); + }); + Assert(i != end(_list)); + Assert(j != end(localFolders)); + + const auto cloudOrder = (*j).cloudOrder; + + *j = (*i).toLocal(cloudOrder); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index a6ecf18b0..9d84a42bf 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -22,7 +22,7 @@ class Session; class ChatFilter final { public: - enum class Flag : uchar { + enum class Flag : ushort { Contacts = 0x01, NonContacts = 0x02, Groups = 0x04, @@ -31,6 +31,14 @@ public: NoMuted = 0x20, NoRead = 0x40, NoArchived = 0x80, + + // Local flags + Owned = 0x0100, + Admin = 0x0200, + NotOwned = 0x0400, + NotAdmin = 0x0800, + Recent = 0x1000, + NoFilter = 0x2000, }; friend constexpr inline bool is_flag_type(Flag) { return true; }; using Flags = base::flags; @@ -38,7 +46,7 @@ public: static constexpr int kPinnedLimit = 100; ChatFilter() = default; - ChatFilter(FilterId id); + ChatFilter(FilterId id, bool isLocal = false); ChatFilter( FilterId id, const QString &title, @@ -47,12 +55,19 @@ public: base::flat_set> always, std::vector> pinned, base::flat_set> never, - bool isDefault = false); + bool isDefault = false, + bool isLocal = false); + + [[nodiscard]] static ChatFilter local( + const LocalFolder &data, + not_null owner); [[nodiscard]] static ChatFilter FromTL( const MTPDialogFilter &data, - not_null owner); + not_null owner, + bool isLocal = false); [[nodiscard]] MTPDialogFilter tl(FilterId replaceId = 0) const; + [[nodiscard]] LocalFolder toLocal(int cloudOrder, FilterId replaceId = 0) const; [[nodiscard]] FilterId id() const; [[nodiscard]] QString title() const; @@ -65,6 +80,8 @@ public: [[nodiscard]] bool contains(not_null history) const; + [[nodiscard]] bool isLocal() const; + private: FilterId _id = 0; QString _title; @@ -74,6 +91,7 @@ private: base::flat_set> _never; Flags _flags; bool _isDefault = false; + bool _isLocal = false; }; @@ -129,6 +147,8 @@ public: -> const std::vector &; [[nodiscard]] rpl::producer<> suggestedUpdated() const; + void saveLocal(FilterId filterId); + private: void load(bool force); void received(const QVector &list); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 2b3b9d653..8636086b6 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1619,7 +1619,10 @@ int Session::pinnedChatsLimit( const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); const auto pinned = (i != end(list)) ? i->pinned().size() : 0; const auto already = (i != end(list)) ? i->always().size() : 0; - return Data::ChatFilter::kPinnedLimit + pinned - already; + const auto limit = (i != end(list) && i->isLocal()) + ? std::numeric_limits::max() + : Data::ChatFilter::kPinnedLimit; + return limit + pinned - already; } const std::vector &Session::pinnedChatsOrder( diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index d5187d349..5c95e29ca 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -94,6 +94,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/unread_badge.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "main/main_account.h" #include "window/themes/window_theme.h" #include "window/notifications_manager.h" #include "window/window_session_controller.h" @@ -2062,6 +2063,8 @@ void HistoryWidget::showHistory( } unreadCountUpdated(); // set _historyDown badge. showAboutTopPromotion(); + _history->owner().session().account().addToRecent(_peer->id); + _history->owner().chatsFilters().refreshHistory(_history); } else { _topBar->setActiveChat( Dialogs::Key(), diff --git a/Telegram/SourceFiles/kotato/json_settings.cpp b/Telegram/SourceFiles/kotato/json_settings.cpp index 0ea35e449..acc93c696 100644 --- a/Telegram/SourceFiles/kotato/json_settings.cpp +++ b/Telegram/SourceFiles/kotato/json_settings.cpp @@ -14,6 +14,7 @@ https://github.com/kotatogram/kotatogram-desktop/blob/dev/LEGAL #include "base/parse_helper.h" #include "facades.h" #include "ui/widgets/input_fields.h" +#include "data/data_chat_filters.h" #include #include @@ -25,6 +26,7 @@ namespace Kotato { namespace JsonSettings { namespace { +constexpr auto kFiltersLimit = 10; constexpr auto kWriteJsonTimeout = crl::time(5000); QString DefaultFilePath() { @@ -185,6 +187,7 @@ QByteArray GenerateSettingsJson(bool areDefault = false) { auto settingsFonts = QJsonObject(); auto settingsFolders = QJsonObject(); auto settingsFoldersDefault = QJsonObject(); + auto settingsFoldersLocal = QJsonObject(); auto settingsScales = QJsonArray(); auto settingsReplaces = QJsonArray(); @@ -237,6 +240,128 @@ QByteArray GenerateSettingsJson(bool areDefault = false) { settingsFoldersDefault.insert(key, value); } + + using PeerType = LocalFolder::Peer::Type; + + auto peerTypeToStr = [](PeerType type) { + return (type == PeerType::Channel) + ? qsl("channel") + : (type == PeerType::Chat) + ? qsl("chat") + : qsl("user"); + }; + + auto &localFolders = cRefLocalFolders(); + for (auto folder : localFolders) { + + // We can't allow to use cloud IDs for local folders. + if (folder.id <= kFiltersLimit) { + continue; + } + + auto accountId = QString::number(std::abs(folder.ownerId)); + auto flags = base::flags::from_raw(folder.flags); + + if (folder.ownerId < 0) { + accountId.prepend("test_"); + } + + if (!settingsFoldersLocal.contains(accountId)) { + settingsFoldersLocal.insert(accountId, QJsonArray()); + } + + auto accountRef = settingsFoldersLocal.find(accountId).value(); + auto accountArray = accountRef.toArray(); + + auto folderObject = QJsonObject(); + + folderObject.insert(qsl("id"), folder.id); + folderObject.insert(qsl("order"), folder.cloudOrder); + folderObject.insert(qsl("name"), folder.name); + folderObject.insert(qsl("emoticon"), folder.emoticon); + + if (flags & Data::ChatFilter::Flag::Contacts) { + folderObject.insert(qsl("include_contacts"), true); + } + if (flags & Data::ChatFilter::Flag::NonContacts) { + folderObject.insert(qsl("include_non_contacts"), true); + } + if (flags & Data::ChatFilter::Flag::Groups) { + folderObject.insert(qsl("include_groups"), true); + } + if (flags & Data::ChatFilter::Flag::Channels) { + folderObject.insert(qsl("include_channels"), true); + } + if (flags & Data::ChatFilter::Flag::Bots) { + folderObject.insert(qsl("include_bots"), true); + } + if (flags & Data::ChatFilter::Flag::NoMuted) { + folderObject.insert(qsl("exclude_muted"), true); + } + if (flags & Data::ChatFilter::Flag::NoRead) { + folderObject.insert(qsl("exclude_read"), true); + } + if (flags & Data::ChatFilter::Flag::NoArchived) { + folderObject.insert(qsl("exclude_archived"), true); + } + if (flags & Data::ChatFilter::Flag::Owned) { + folderObject.insert(qsl("exclude_not_owned"), true); + } + if (flags & Data::ChatFilter::Flag::Admin) { + folderObject.insert(qsl("exclude_not_admin"), true); + } + if (flags & Data::ChatFilter::Flag::NotOwned) { + folderObject.insert(qsl("exclude_owned"), true); + } + if (flags & Data::ChatFilter::Flag::NotAdmin) { + folderObject.insert(qsl("exclude_admin"), true); + } + if (flags & Data::ChatFilter::Flag::Recent) { + folderObject.insert(qsl("exclude_non_recent"), true); + } + if (flags & Data::ChatFilter::Flag::NoFilter) { + folderObject.insert(qsl("exclude_filtered"), true); + } + + auto folderNever = QJsonArray(); + for (auto peer : folder.never) { + auto peerObj = QJsonObject(); + peerObj.insert(qsl("type"), peerTypeToStr(peer.type)); + peerObj.insert(qsl("id"), peer.id); + if (peer.accessHash != 0) { + peerObj.insert(qsl("hash"), QString::number(peer.accessHash)); + } + folderNever << peerObj; + } + folderObject.insert(qsl("never"), folderNever); + + auto folderPinned = QJsonArray(); + for (auto peer : folder.pinned) { + auto peerObj = QJsonObject(); + peerObj.insert(qsl("type"), peerTypeToStr(peer.type)); + peerObj.insert(qsl("id"), peer.id); + if (peer.accessHash != 0) { + peerObj.insert(qsl("hash"), QString::number(peer.accessHash)); + } + folderPinned << peerObj; + } + folderObject.insert(qsl("pinned"), folderPinned); + + auto folderAlways = QJsonArray(); + for (auto peer : folder.always) { + auto peerObj = QJsonObject(); + peerObj.insert(qsl("type"), peerTypeToStr(peer.type)); + peerObj.insert(qsl("id"), peer.id); + if (peer.accessHash != 0) { + peerObj.insert(qsl("hash"), QString::number(peer.accessHash)); + } + folderAlways << peerObj; + } + folderObject.insert(qsl("always"), folderAlways); + + accountArray << folderObject; + accountRef = accountArray; + } } settings.insert(qsl("sticker_height"), StickerHeight()); @@ -270,6 +395,7 @@ QByteArray GenerateSettingsJson(bool areDefault = false) { settingsFolders.insert(qsl("hide_edit_button"), cHideFilterEditButton()); settingsFolders.insert(qsl("hide_names"), cHideFilterNames()); settingsFolders.insert(qsl("hide_all_chats"), cHideFilterAllChats()); + settingsFolders.insert(qsl("local"), settingsFoldersLocal); settings.insert(qsl("fonts"), settingsFonts); settings.insert(qsl("folders"), settingsFolders); @@ -536,6 +662,252 @@ bool Manager::readCustomFile() { ReadBoolOption(o, "hide_all_chats", [&](auto v) { cSetHideFilterAllChats(v); }); + + ReadAccountObjectOption(o, "local", [&](auto account_id, auto value) { + auto v = value.toArray(); + auto &folderOptionRef = cRefLocalFolders(); + for (auto i = v.constBegin(), e = v.constEnd(); i != e; ++i) { + if (!(*i).isObject()) { + continue; + } + + const auto folderObject = (*i).toObject(); + LocalFolder folderStruct; + folderStruct.ownerId = account_id; + auto flags = base::flags(0); + + ReadIntOption(folderObject, "id", [&](auto id) { + folderStruct.id = id; + }); + + // We can't allow to use cloud IDs for local folders. + if (folderStruct.id <= kFiltersLimit) { + continue; + } + + ReadIntOption(folderObject, "order", [&](auto order) { + folderStruct.cloudOrder = order; + }); + + ReadStringOption(folderObject, "name", [&](auto name) { + folderStruct.name = name; + }); + + ReadStringOption(folderObject, "emoticon", [&](auto emoticon) { + folderStruct.emoticon = emoticon; + }); + + ReadBoolOption(folderObject, "include_contacts", [&](auto include_contacts) { + if (include_contacts) { + flags |= Data::ChatFilter::Flag::Contacts; + } + }); + + ReadBoolOption(folderObject, "include_non_contacts", [&](auto include_non_contacts) { + if (include_non_contacts) { + flags |= Data::ChatFilter::Flag::NonContacts; + } + }); + + ReadBoolOption(folderObject, "include_groups", [&](auto include_groups) { + if (include_groups) { + flags |= Data::ChatFilter::Flag::Groups; + } + }); + + ReadBoolOption(folderObject, "include_channels", [&](auto include_channels) { + if (include_channels) { + flags |= Data::ChatFilter::Flag::Channels; + } + }); + + ReadBoolOption(folderObject, "include_bots", [&](auto include_bots) { + if (include_bots) { + flags |= Data::ChatFilter::Flag::Bots; + } + }); + + ReadBoolOption(folderObject, "exclude_muted", [&](auto exclude_muted) { + if (exclude_muted) { + flags |= Data::ChatFilter::Flag::NoMuted; + } + }); + + ReadBoolOption(folderObject, "exclude_read", [&](auto exclude_read) { + if (exclude_read) { + flags |= Data::ChatFilter::Flag::NoRead; + } + }); + + ReadBoolOption(folderObject, "exclude_archived", [&](auto exclude_archived) { + if (exclude_archived) { + flags |= Data::ChatFilter::Flag::NoArchived; + } + }); + + ReadBoolOption(folderObject, "exclude_not_owned", [&](auto exclude_not_owned) { + if (exclude_not_owned) { + flags |= Data::ChatFilter::Flag::Owned; + } + }); + + ReadBoolOption(folderObject, "exclude_not_admin", [&](auto exclude_not_admin) { + if (exclude_not_admin) { + flags |= Data::ChatFilter::Flag::Admin; + } + }); + + ReadBoolOption(folderObject, "exclude_owned", [&](auto exclude_owned) { + if (exclude_owned) { + flags |= Data::ChatFilter::Flag::NotOwned; + } + }); + + ReadBoolOption(folderObject, "exclude_admin", [&](auto exclude_admin) { + if (exclude_admin) { + flags |= Data::ChatFilter::Flag::NotAdmin; + } + }); + + ReadBoolOption(folderObject, "exclude_non_recent", [&](auto exclude_non_recent) { + if (exclude_non_recent) { + flags |= Data::ChatFilter::Flag::Recent; + } + }); + + ReadBoolOption(folderObject, "exclude_filtered", [&](auto exclude_filtered) { + if (exclude_filtered) { + flags |= Data::ChatFilter::Flag::NoFilter; + } + }); + + folderStruct.flags = flags.value(); + + ReadArrayOption(folderObject, "never", [&](auto never) { + for (auto j = never.constBegin(), z = never.constEnd(); j != z; ++j) { + if (!(*j).isObject()) { + continue; + } + + auto peer = (*j).toObject(); + LocalFolder::Peer peerStruct; + + auto isPeerIdRead = ReadIntOption(peer, "id", [&](auto id) { + peerStruct.id = id; + }); + + if (peerStruct.id == 0 || !isPeerIdRead) { + continue; + } + + auto isPeerTypeRead = ReadStringOption(peer, "type", [&](auto type) { + peerStruct.type = type.toLower() == qsl("channel") + ? LocalFolder::Peer::Type::Channel + : type.toLower() == qsl("chat") + ? LocalFolder::Peer::Type::Chat + : LocalFolder::Peer::Type::User; + }); + + if (!isPeerTypeRead) { + peerStruct.type = LocalFolder::Peer::Type::User; + } + + ReadStringOption(peer, "hash", [&](auto hashString) { + const auto hash = hashString.toULongLong(); + if (hash) { + peerStruct.accessHash = hash; + } + }); + + folderStruct.never.push_back(peerStruct); + } + }); + + ReadArrayOption(folderObject, "pinned", [&](auto pinned) { + for (auto j = pinned.constBegin(), z = pinned.constEnd(); j != z; ++j) { + if (!(*j).isObject()) { + continue; + } + + auto peer = (*j).toObject(); + LocalFolder::Peer peerStruct; + + auto isPeerIdRead = ReadIntOption(peer, "id", [&](auto id) { + peerStruct.id = id; + }); + + if (peerStruct.id == 0 || !isPeerIdRead) { + continue; + } + + auto isPeerTypeRead = ReadStringOption(peer, "type", [&](auto type) { + peerStruct.type = type.toLower() == qsl("channel") + ? LocalFolder::Peer::Type::Channel + : type.toLower() == qsl("chat") + ? LocalFolder::Peer::Type::Chat + : LocalFolder::Peer::Type::User; + }); + + if (!isPeerTypeRead) { + peerStruct.type = LocalFolder::Peer::Type::User; + } + + ReadStringOption(peer, "hash", [&](auto hashString) { + const auto hash = hashString.toULongLong(); + if (hash) { + peerStruct.accessHash = hash; + } + }); + + folderStruct.pinned.push_back(peerStruct); + } + }); + + ReadArrayOption(folderObject, "always", [&](auto always) { + for (auto j = always.constBegin(), z = always.constEnd(); j != z; ++j) { + if (!(*j).isObject()) { + continue; + } + + auto peer = (*j).toObject(); + LocalFolder::Peer peerStruct; + + auto isPeerIdRead = ReadIntOption(peer, "id", [&](auto id) { + peerStruct.id = id; + }); + + if (peerStruct.id == 0 || !isPeerIdRead) { + continue; + } + + auto isPeerTypeRead = ReadStringOption(peer, "type", [&](auto type) { + peerStruct.type = type.toLower() == qsl("channel") + ? LocalFolder::Peer::Type::Channel + : type.toLower() == qsl("chat") + ? LocalFolder::Peer::Type::Chat + : LocalFolder::Peer::Type::User; + }); + + if (!isPeerTypeRead) { + peerStruct.type = LocalFolder::Peer::Type::User; + } + + ReadStringOption(peer, "hash", [&](auto hashString) { + const auto hash = hashString.toULongLong(); + if (hash) { + peerStruct.accessHash = hash; + } + }); + + folderStruct.always.push_back(peerStruct); + } + }); + + folderOptionRef.push_back(folderStruct); + } + }, [](auto v) { + return v.isArray(); + }); }); ReadBoolOption(settings, "profile_top_mute", [&](auto v) { diff --git a/Telegram/SourceFiles/kotato/settings.cpp b/Telegram/SourceFiles/kotato/settings.cpp index b378aae8b..c4ddf4130 100644 --- a/Telegram/SourceFiles/kotato/settings.cpp +++ b/Telegram/SourceFiles/kotato/settings.cpp @@ -224,3 +224,5 @@ bool gForwardQuoted = true; bool gForwardCaptioned = true; bool gForwardAlbumsAsIs = true; bool gForwardGrouped = false; + +LocalFolderVector gLocalFolders; diff --git a/Telegram/SourceFiles/kotato/settings.h b/Telegram/SourceFiles/kotato/settings.h index 51f74fb2f..cac33a2c7 100644 --- a/Telegram/SourceFiles/kotato/settings.h +++ b/Telegram/SourceFiles/kotato/settings.h @@ -134,3 +134,35 @@ DeclareSetting(bool, ForwardQuoted); DeclareSetting(bool, ForwardCaptioned); DeclareSetting(bool, ForwardAlbumsAsIs); DeclareSetting(bool, ForwardGrouped); + +struct LocalFolder { + struct Peer { + enum Type { + User = 0, + Chat = 1, + Channel = 2, + }; + + Type type; + int32 id; + uint64 accessHash = 0; + + inline bool operator==(const Peer& other) { + return type == other.type + && id == other.id; + } + }; + + int id; + int ownerId; + int cloudOrder; + QString name; + QString emoticon; + std::vector always; + std::vector never; + std::vector pinned; + ushort flags; +}; + +using LocalFolderVector = std::vector; +DeclareRefSetting(LocalFolderVector, LocalFolders); diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp index 1492a7809..66084de2d 100644 --- a/Telegram/SourceFiles/main/main_account.cpp +++ b/Telegram/SourceFiles/main/main_account.cpp @@ -607,6 +607,25 @@ void Account::setDefaultFilterId(int id) { } } +bool Account::isCurrent(int id) { + Expects(_mtp != nullptr); + Expects(_session != nullptr); + + return id == (_mtp->isTestMode() + ? -session().userId() + : session().userId()); +} + +void Account::addToRecent(PeerId id) { + if (!_recent.contains(id)) { + _recent << id; + } +} + +bool Account::isRecent(PeerId id) { + return _recent.contains(id); +} + void Account::resetAuthorizationKeys() { Expects(_mtp != nullptr); diff --git a/Telegram/SourceFiles/main/main_account.h b/Telegram/SourceFiles/main/main_account.h index 2ac6252e1..79f8bab02 100644 --- a/Telegram/SourceFiles/main/main_account.h +++ b/Telegram/SourceFiles/main/main_account.h @@ -112,6 +112,11 @@ public: } void setDefaultFilterId(int id); + [[nodiscard]] bool isCurrent(int id); + + void addToRecent(PeerId id); + [[nodiscard]] bool isRecent(PeerId id); + private: static constexpr auto kDefaultSaveDelay = crl::time(1000); enum class DestroyReason { @@ -159,6 +164,8 @@ private: bool _loggingOut = false; int _defaultFilterId = 0; + QSet _recent; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 8fb97e97b..e660d495c 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -63,6 +63,7 @@ settingsIconEmoji: icon {{ "settings_emoji", menuIconFg }}; settingsIconThemes: icon {{ "settings_themes", menuIconFg }}; settingsIconKotato: icon {{ "settings_kotato", menuIconFg }}; settingsIconKotatoOld: icon {{ "settings_kotato_old", menuIconFg }}; +settingsIconCloud: icon {{ "settings_cloud", menuIconFg }}; settingsSetPhotoSkip: 7px; diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index b75fb3d48..f783d9209 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_account.h" #include "window/window_session_controller.h" #include "window/window_controller.h" +#include "ui/toast/toast.h" #include "ui/layers/generic_box.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" @@ -151,9 +152,13 @@ struct FilterRow { const Data::ChatFilter &filter, bool check = false) { const auto count = ComputeCount(session, filter, check); - return count - ? tr::lng_filters_chats_count(tr::now, lt_count_short, count) - : tr::lng_filters_no_chats(tr::now); + return (count + ? tr::lng_filters_chats_count(tr::now, lt_count_short, count) + : tr::lng_filters_no_chats(tr::now)) + + ", " + + (filter.isLocal() + ? tr::ktg_filters_local(tr::now) + : tr::ktg_filters_cloud(tr::now)); } FilterRowButton::FilterRowButton( @@ -317,6 +322,16 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { const auto account = &session->account(); const auto currentDefaultId = account->defaultFilterId(); localNewFilterId = kFiltersLimit; + const auto generateNewId = [=] { + const auto filters = &controller->session().data().chatsFilters(); + + do { + localNewFilterId++; + } while (ranges::contains(filters->list(), localNewFilterId, &Data::ChatFilter::id)); + + return localNewFilterId; + }; + currentDefaultRemoved = false; AddSkip(container, st::settingsSectionSkip); @@ -329,17 +344,43 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { Assert(i != end(*rows)); return &*i; }; + const auto toast = Ui::Toast::Config{ + .text = { tr::ktg_filters_cloud_limit(tr::now) }, + .st = &st::windowArchiveToast, + .multiline = true, + }; const auto showLimitReached = [=] { - const auto removed = ranges::count_if(*rows, &FilterRow::removed); + const auto removed = ranges::count_if(*rows, [](FilterRow row) { + return row.removed || row.filter.isLocal(); + }); if (rows->size() < kFiltersLimit + removed) { return false; } - controller->window().showToast(tr::lng_filters_limit(tr::now)); + Ui::Toast::Show(toast); return true; }; + const auto newCloudButton = AddButton( + container, + tr::ktg_filters_create_cloud(), + st::settingsChatButton, + &st::settingsIconCloud, + st::settingsChatIconLeft + ); + const auto newLocalButton = AddButton( + container, + tr::ktg_filters_create_local(), + st::settingsChatButton, + &st::settingsIconFolders, + st::settingsChatIconLeft + ); const auto wrap = container->add(object_ptr( container)); const auto addFilter = [=](const Data::ChatFilter &filter) { + if (rows->size() == 0) { + AddSkip(wrap); + AddDivider(wrap); + AddSkip(wrap); + } const auto button = wrap->add( object_ptr(wrap, session, filter)); button->removeRequests( @@ -424,11 +465,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { addFilter(filter); } - AddButton( - container, - tr::lng_filters_create() | Ui::Text::ToUpper(), - st::settingsUpdate - )->setClickedCallback([=] { + newCloudButton->setClickedCallback([=] { if (showLimitReached()) { return; } @@ -441,7 +478,20 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { controller->window().show(Box( EditFilterBox, controller, - Data::ChatFilter(++localNewFilterId), + Data::ChatFilter(generateNewId()), + crl::guard(container, doneCallback))); + }); + newLocalButton->setClickedCallback([=] { + const auto doneCallback = [=](const Data::ChatFilter &result) { + if (result.isDefault()) { + account->setDefaultFilterId(result.id()); + } + addFilter(result); + }; + controller->window().show(Box( + EditFilterBox, + controller, + Data::ChatFilter(generateNewId(), true), crl::guard(container, doneCallback))); }); AddSkip(container); @@ -450,7 +500,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { container, object_ptr( container, - tr::lng_filters_about(), + tr::ktg_filters_description(), st::boxDividerLabel), st::settingsDividerLabelPadding) )->setDuration(0); @@ -460,7 +510,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { object_ptr(container)) )->setDuration(0); const auto aboutRows = nonEmptyAbout->entity(); - AddDividerText(aboutRows, tr::lng_filters_about()); + AddDividerText(aboutRows, tr::ktg_filters_description()); AddSkip(aboutRows); AddSubsectionTitle(aboutRows, tr::lng_filters_recommended()); @@ -526,7 +576,21 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { auto result = base::flat_map, FilterId>(); for (auto &row : *rows) { const auto id = row.filter.id(); - if (row.removed) { + if (row.removed || row.filter.isLocal()) { + continue; + } else if (!ranges::contains(list, id, &Data::ChatFilter::id)) { + result.emplace(row.button, chooseNextId()); + if (account->defaultFilterId() == id) { + account->setDefaultFilterId(localId); + } + } + } + + // We're prioritizing cloud IDs before local. + localId = kFiltersLimit; + for (auto &row : *rows) { + const auto id = row.filter.id(); + if (row.removed || !row.filter.isLocal()) { continue; } else if (!ranges::contains(list, id, &Data::ChatFilter::id)) { result.emplace(row.button, chooseNextId()); @@ -540,13 +604,16 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { return [=] { auto ids = prepareGoodIdsForNewFilters(); + bool needSave = false; using Requests = std::vector; auto addRequests = Requests(), removeRequests = Requests(); auto &realFilters = session->data().chatsFilters(); const auto &list = realFilters.list(); + auto &localFolders = cRefLocalFolders(); auto order = std::vector(); order.reserve(rows->size()); + auto localFoldersAdded = false; for (const auto &row : *rows) { const auto id = row.filter.id(); const auto removed = row.removed; @@ -557,28 +624,60 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { order.push_back(id); continue; } - const auto newId = ids.take(row.button).value_or(id); - const auto tl = removed - ? MTPDialogFilter() - : row.filter.tl(newId); - const auto request = MTPmessages_UpdateDialogFilter( - MTP_flags(removed - ? MTPmessages_UpdateDialogFilter::Flag(0) - : MTPmessages_UpdateDialogFilter::Flag::f_filter), - MTP_int(newId), - tl); - if (removed) { - removeRequests.push_back(request); + if (row.filter.isLocal()) { + const auto j = ranges::find_if(localFolders, [id, account](LocalFolder localFolder) { + return (id == localFolder.id + && account->isCurrent(localFolder.ownerId)); + }); + + if (j == end(localFolders)) { + if (removed) { + continue; + } else { + localFolders.push_back(row.filter.toLocal(kFiltersLimit)); + realFilters.set(row.filter); + order.push_back(id); + needSave = true; + localFoldersAdded = true; + } + } else { + if (removed) { + localFolders.erase(j); + realFilters.remove(id); + needSave = true; + } else { + const auto cloudOrder = (*j).cloudOrder; + *j = row.filter.toLocal(cloudOrder); + realFilters.set(row.filter); + order.push_back(id); + needSave = true; + localFoldersAdded = true; + } + } } else { - addRequests.push_back(request); - order.push_back(newId); + const auto newId = ids.take(row.button).value_or(id); + const auto tl = removed + ? MTPDialogFilter() + : row.filter.tl(newId); + const auto request = MTPmessages_UpdateDialogFilter( + MTP_flags(removed + ? MTPmessages_UpdateDialogFilter::Flag(0) + : MTPmessages_UpdateDialogFilter::Flag::f_filter), + MTP_int(newId), + tl); + if (removed) { + removeRequests.push_back(request); + } else { + addRequests.push_back(request); + order.push_back(newId); + } + realFilters.apply(MTP_updateDialogFilter( + MTP_flags(removed + ? MTPDupdateDialogFilter::Flag(0) + : MTPDupdateDialogFilter::Flag::f_filter), + MTP_int(newId), + tl)); } - realFilters.apply(MTP_updateDialogFilter( - MTP_flags(removed - ? MTPDupdateDialogFilter::Flag(0) - : MTPDupdateDialogFilter::Flag::f_filter), - MTP_int(newId), - tl)); } auto previousId = mtpRequestId(0); auto &&requests = ranges::view::concat(removeRequests, addRequests); @@ -587,7 +686,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { std::move(request) ).afterRequest(previousId).send(); } - if (!order.empty() && !addRequests.empty()) { + if (!order.empty() && (!addRequests.empty() || localFoldersAdded)) { realFilters.saveOrder(order, previousId); } if (currentDefaultRemoved) { @@ -595,6 +694,9 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { controller->setActiveChatsFilter(0); } if (currentDefaultId != account->defaultFilterId()) { + needSave = true; + } + if (needSave) { Kotato::JsonSettings::Write(); } }; diff --git a/Telegram/SourceFiles/ui/filter_icon_panel.cpp b/Telegram/SourceFiles/ui/filter_icon_panel.cpp index c4df60cf6..06e64fa3f 100644 --- a/Telegram/SourceFiles/ui/filter_icon_panel.cpp +++ b/Telegram/SourceFiles/ui/filter_icon_panel.cpp @@ -51,11 +51,27 @@ constexpr auto kIcons = std::array{ FilterIcon::Setup, }; +constexpr auto kLocalIcons = std::array{ + FilterIcon::LocalBook, + FilterIcon::LocalBrackets, + FilterIcon::LocalCandle, + FilterIcon::LocalCity, + FilterIcon::LocalDesktop, + FilterIcon::LocalEarth, + FilterIcon::LocalMusic, + FilterIcon::LocalNews, + FilterIcon::LocalPhone, + FilterIcon::LocalSmile, + FilterIcon::LocalSun, + FilterIcon::LocalVideo, +}; + } // namespace -FilterIconPanel::FilterIconPanel(QWidget *parent) +FilterIconPanel::FilterIconPanel(QWidget *parent, bool isLocal) : RpWidget(parent) -, _inner(Ui::CreateChild(this)) { +, _inner(Ui::CreateChild(this)) +, _isLocal(isLocal) { setup(); } @@ -88,7 +104,9 @@ void FilterIconPanel::setup() { } void FilterIconPanel::setupInner() { - const auto count = kIcons.size(); + const auto count = kIcons.size() + (_isLocal + ? kLocalIcons.size() + : 0); const auto rows = (count / kIconsPerRow) + ((count % kIconsPerRow) ? 1 : 0); const auto single = st::windowFilterIconSingle; @@ -131,6 +149,25 @@ void FilterIconPanel::setupInner() { const auto icon = LookupFilterIcon(kIcons[i]).normal; icon->paintInCenter(p, rect, st::emojiIconFg->c); } + if (_isLocal) { + const auto cloudSize = kIcons.size(); + const auto fullSize = kIcons.size() + kLocalIcons.size(); + for (auto i = cloudSize; i != fullSize; ++i) { + const auto rect = countRect(i); + if (!rect.intersects(clip)) { + continue; + } + if (i == selected) { + App::roundRect( + p, + rect, + st::emojiPanHover, + StickerHoverCorners); + } + const auto icon = LookupFilterIcon(kLocalIcons[i-cloudSize]).normal; + icon->paintInCenter(p, rect, st::emojiIconFg->c); + } + } }, _inner->lifetime()); _inner->setMouseTracking(true); @@ -203,7 +240,11 @@ void FilterIconPanel::mouseMove(QPoint position) { const auto column = point.x() / st::windowFilterIconSingle.width(); const auto row = point.y() / st::windowFilterIconSingle.height(); const auto index = row * kIconsPerRow + column; - setSelected(index < kIcons.size() ? index : -1); + const auto size = kIcons.size() + + (_isLocal + ? kLocalIcons.size() + : 0); + setSelected(index < size ? index : -1); } } @@ -221,8 +262,12 @@ void FilterIconPanel::mouseRelease(Qt::MouseButton button) { const auto pressed = _pressed; setPressed(-1); if (pressed == _selected && pressed >= 0) { - Assert(pressed < kIcons.size()); - _chosen.fire_copy(kIcons[pressed]); + Assert(pressed < kIcons.size() + (_isLocal + ? kLocalIcons.size() + : 0)); + _chosen.fire_copy(pressed < kIcons.size() + ? kIcons[pressed] + : kLocalIcons[pressed-kIcons.size()]); } } diff --git a/Telegram/SourceFiles/ui/filter_icon_panel.h b/Telegram/SourceFiles/ui/filter_icon_panel.h index 44b4f6c1a..71ff13691 100644 --- a/Telegram/SourceFiles/ui/filter_icon_panel.h +++ b/Telegram/SourceFiles/ui/filter_icon_panel.h @@ -18,7 +18,7 @@ class PanelAnimation; class FilterIconPanel final : public Ui::RpWidget { public: - FilterIconPanel(QWidget *parent); + FilterIconPanel(QWidget *parent, bool isLocal); ~FilterIconPanel(); void hideFast(); @@ -80,6 +80,7 @@ private: QPixmap _cache; Ui::Animations::Simple _a_opacity; base::Timer _hideTimer; + bool _isLocal = false; }; diff --git a/Telegram/SourceFiles/ui/filter_icons.cpp b/Telegram/SourceFiles/ui/filter_icons.cpp index 83d84f943..3306a5507 100644 --- a/Telegram/SourceFiles/ui/filter_icons.cpp +++ b/Telegram/SourceFiles/ui/filter_icons.cpp @@ -130,6 +130,66 @@ const auto kIcons = std::vector{ &st::foldersWorkActive, "\xF0\x9F\x92\xBC"_cs.utf16() }, + { + &st::foldersLocalBook, + &st::foldersLocalBookActive, + "\xF0\x9F\x94\x96"_cs.utf16() + }, + { + &st::foldersLocalBrackets, + &st::foldersLocalBracketsActive, + "\xE2\x8C\xA8"_cs.utf16() + }, + { + &st::foldersLocalCandle, + &st::foldersLocalCandleActive, + "\xF0\x9F\x95\xAF"_cs.utf16() + }, + { + &st::foldersLocalCity, + &st::foldersLocalCityActive, + "\xF0\x9F\x8F\x99"_cs.utf16() + }, + { + &st::foldersLocalDesktop, + &st::foldersLocalDesktopActive, + "\xF0\x9F\x96\xA5"_cs.utf16() + }, + { + &st::foldersLocalEarth, + &st::foldersLocalEarthActive, + "\xF0\x9F\x8C\x8E"_cs.utf16() + }, + { + &st::foldersLocalMusic, + &st::foldersLocalMusicActive, + "\xF0\x9F\x8E\xA7"_cs.utf16() + }, + { + &st::foldersLocalNews, + &st::foldersLocalNewsActive, + "\xF0\x9F\x93\xB0"_cs.utf16() + }, + { + &st::foldersLocalPhone, + &st::foldersLocalPhoneActive, + "\xF0\x9F\x93\xB1"_cs.utf16() + }, + { + &st::foldersLocalSmile, + &st::foldersLocalSmileActive, + "\xF0\x9F\x99\x82"_cs.utf16() + }, + { + &st::foldersLocalSun, + &st::foldersLocalSunActive, + "\xE2\x98\x80"_cs.utf16() + }, + { + &st::foldersLocalVideo, + &st::foldersLocalVideoActive, + "\xF0\x9F\x93\xB9"_cs.utf16() + }, { &st::filtersEdit, &st::filtersEdit, diff --git a/Telegram/SourceFiles/ui/filter_icons.h b/Telegram/SourceFiles/ui/filter_icons.h index 8c3aa09eb..a71b52084 100644 --- a/Telegram/SourceFiles/ui/filter_icons.h +++ b/Telegram/SourceFiles/ui/filter_icons.h @@ -45,6 +45,19 @@ enum class FilterIcon : uchar { Travel, Work, + LocalBook, + LocalBrackets, + LocalCandle, + LocalCity, + LocalDesktop, + LocalEarth, + LocalMusic, + LocalNews, + LocalPhone, + LocalSmile, + LocalSun, + LocalVideo, + Edit, }; diff --git a/Telegram/SourceFiles/ui/filter_icons.style b/Telegram/SourceFiles/ui/filter_icons.style index 92227a0df..268e90f75 100644 --- a/Telegram/SourceFiles/ui/filter_icons.style +++ b/Telegram/SourceFiles/ui/filter_icons.style @@ -56,3 +56,28 @@ foldersTravel: icon {{ "filters/folders_travel", sideBarIconFg }}; foldersTravelActive: icon {{ "filters/folders_travel_active", sideBarIconFgActive }}; foldersWork: icon {{ "filters/folders_work", sideBarIconFg }}; foldersWorkActive: icon {{ "filters/folders_work_active", sideBarIconFgActive }}; + +foldersLocalBook: icon {{ "filters/filters_local_book", sideBarIconFg }}; +foldersLocalBookActive: icon {{ "filters/filters_local_book_active", sideBarIconFgActive }}; +foldersLocalBrackets: icon {{ "filters/filters_local_brackets", sideBarIconFg }}; +foldersLocalBracketsActive: icon {{ "filters/filters_local_brackets", sideBarIconFgActive }}; +foldersLocalCandle: icon {{ "filters/filters_local_candle", sideBarIconFg }}; +foldersLocalCandleActive: icon {{ "filters/filters_local_candle_active", sideBarIconFgActive }}; +foldersLocalCity: icon {{ "filters/filters_local_city", sideBarIconFg }}; +foldersLocalCityActive: icon {{ "filters/filters_local_city_active", sideBarIconFgActive }}; +foldersLocalDesktop: icon {{ "filters/filters_local_desktop", sideBarIconFg }}; +foldersLocalDesktopActive: icon {{ "filters/filters_local_desktop_active", sideBarIconFgActive }}; +foldersLocalEarth: icon {{ "filters/filters_local_earth", sideBarIconFg }}; +foldersLocalEarthActive: icon {{ "filters/filters_local_earth", sideBarIconFgActive }}; +foldersLocalMusic: icon {{ "filters/filters_local_music", sideBarIconFg }}; +foldersLocalMusicActive: icon {{ "filters/filters_local_music_active", sideBarIconFgActive }}; +foldersLocalNews: icon {{ "filters/filters_local_news", sideBarIconFg }}; +foldersLocalNewsActive: icon {{ "filters/filters_local_news_active", sideBarIconFgActive }}; +foldersLocalPhone: icon {{ "filters/filters_local_phone", sideBarIconFg }}; +foldersLocalPhoneActive: icon {{ "filters/filters_local_phone_active", sideBarIconFgActive }}; +foldersLocalSmile: icon {{ "filters/filters_local_smile", sideBarIconFg }}; +foldersLocalSmileActive: icon {{ "filters/filters_local_smile_active", sideBarIconFgActive }}; +foldersLocalSun: icon {{ "filters/filters_local_sun", sideBarIconFg }}; +foldersLocalSunActive: icon {{ "filters/filters_local_sun_active", sideBarIconFgActive }}; +foldersLocalVideo: icon {{ "filters/filters_local_video", sideBarIconFg }}; +foldersLocalVideoActive: icon {{ "filters/filters_local_video_active", sideBarIconFgActive }}; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 70fb6a1a6..ec954cd5d 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -334,6 +334,12 @@ windowFilterTypeBots: icon {{ "filters/filters_type_bots", historyPeerUserpicFg windowFilterTypeNoMuted: icon {{ "filters/filters_type_muted", historyPeerUserpicFg }}; windowFilterTypeNoArchived: icon {{ "filters/filters_type_archived", historyPeerUserpicFg }}; windowFilterTypeNoRead: icon {{ "filters/filters_type_read", historyPeerUserpicFg }}; +windowFilterTypeOwned: icon {{ "filters/filters_type_not_owner", historyPeerUserpicFg }}; +windowFilterTypeAdmin: icon {{ "filters/filters_type_not_admin", historyPeerUserpicFg }}; +windowFilterTypeNotOwned: icon {{ "filters/filters_type_owner", historyPeerUserpicFg }}; +windowFilterTypeNotAdmin: icon {{ "filters/filters_type_admin", historyPeerUserpicFg }}; +windowFilterTypeRecent: icon {{ "filters/filters_type_recent", historyPeerUserpicFg }}; +windowFilterTypeNoFilter: icon {{ "filters/filters_type_filtered", historyPeerUserpicFg }}; windowFilterChatsSectionSubtitle: FlatLabel(defaultFlatLabel) { style: TextStyle(defaultTextStyle) { font: searchedBarFont; diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 957637dcf..cda377017 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -402,22 +402,42 @@ void FiltersMenu::showRemoveBox(FilterId id) { void FiltersMenu::remove(FilterId id) { const auto defaultFilterId = _session->session().account().defaultFilterId(); - _session->session().data().chatsFilters().apply(MTP_updateDialogFilter( - MTP_flags(MTPDupdateDialogFilter::Flag(0)), - MTP_int(id), - MTPDialogFilter())); - _session->session().api().request(MTPmessages_UpdateDialogFilter( - MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)), - MTP_int(id), - MTPDialogFilter() - )).send(); + const auto filters = &_session->session().data().chatsFilters(); + const auto &list = filters->list(); + const auto i = ranges::find(list, id, &Data::ChatFilter::id); + Assert(i != end(list)); + bool needSave = false; + if (i->isLocal()) { + const auto account = &_session->session().account(); + auto &localFolders = cRefLocalFolders(); + const auto j = ranges::find_if(localFolders, [id, account](LocalFolder localFolder) { + return (id == localFolder.id + && account->isCurrent(localFolder.ownerId)); + }); + filters->remove(id); + localFolders.erase(j); + needSave = true; + } else { + _session->session().data().chatsFilters().apply(MTP_updateDialogFilter( + MTP_flags(MTPDupdateDialogFilter::Flag(0)), + MTP_int(id), + MTPDialogFilter())); + _session->session().api().request(MTPmessages_UpdateDialogFilter( + MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)), + MTP_int(id), + MTPDialogFilter() + )).send(); + } if (id == defaultFilterId) { + needSave = true; _session->session().account().setDefaultFilterId(0); - Kotato::JsonSettings::Write(); if (id == _session->activeChatsFilterCurrent()) { _session->setActiveChatsFilter(0); } } + if (needSave) { + Kotato::JsonSettings::Write(); + } } void FiltersMenu::applyReorder( @@ -447,6 +467,7 @@ void FiltersMenu::applyReorder( _ignoreRefresh = true; filters->saveOrder(order); _ignoreRefresh = false; + Kotato::JsonSettings::Write(); } } // namespace Window