Merge commit 'e9a4a9347a1000386e148a490a28d601c3bd0db3' into bazel

This commit is contained in:
Ali 2020-03-01 19:10:10 +04:00
commit 5dc4d4d9c1
138 changed files with 6773 additions and 5008 deletions

View File

@ -309,9 +309,9 @@ def glob_map(glob_results):
result[file_name] = path
return result
def glob_sub_map(prefix, glob_specs):
def glob_sub_map(prefix, glob_specs, exclude = []):
result = dict()
for path in native.glob(glob_specs):
for path in native.glob(glob_specs, exclude = exclude):
if not path.startswith(prefix):
fail('\"%s\" does not start with \"%s\"' % (path, prefix))
file_key = path[len(prefix):]

View File

@ -341,16 +341,6 @@ build_buckdebug: check_env
//Telegram:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \
--verbose 7 ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS}
build_buckdebug_one: check_env
BUCK_DEBUG_MODE=1 $(BUCK) build \
//submodules/Postbox:Postbox#shared,iphoneos-arm64 \
--verbose 7 ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS}
build_verbose_one: check_env
$(BUCK) build \
//submodules/Postbox:Postbox#shared,iphoneos-arm64 \
--verbose 7 ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS}
build_verbose: check_env
$(BUCK) build \
//Telegram:AppPackage#iphoneos-arm64 \
@ -385,39 +375,12 @@ deps: check_env
$(BUCK) query "deps(//Telegram:AppPackage)" --dot \
${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS}
build_openssl: check_env
$(BUCK) build \
//submodules/openssl:openssl#iphoneos-arm64 \
--verbose 7 ${BUCK_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_DEBUG_OPTIONS}
build_libphonenumber: check_env
$(BUCK) build \
//submodules/libphonenumber:libphonenumber#iphoneos-arm64 \
${BUCK_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_DEBUG_OPTIONS}
build_ton: check_env
$(BUCK) build \
//submodules/ton:ton#iphoneos-arm64 \
--verbose 7 ${BUCK_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_DEBUG_OPTIONS}
clean: kill_xcode
sh clean.sh
project: check_env kill_xcode
$(BUCK) project //Telegram:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS}
open Telegram_Buck.xcworkspace
project_opt: check_env kill_xcode
$(BUCK) project //Telegram:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_RELEASE_OPTIONS}
open Telegram_Buck.xcworkspace
project_buckdebug: check_env kill_xcode
BUCK_DEBUG_MODE=1 $(BUCK) project //Telegram:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS}
open Telegram_Buck.xcworkspace
temp_project: check_env kill_xcode
$(BUCK) project //Temp:workspace --config custom.mode=project ${BUCK_OPTIONS} ${BUCK_DEBUG_OPTIONS}
open Temp/Telegram_Buck.xcworkspace
open Telegram/Telegram_Buck.xcworkspace
bazel_app_debug_arm64:
APP_VERSION="${APP_VERSION}" \

View File

@ -400,6 +400,7 @@ apple_binary(
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/BuildConfig:BuildConfig",
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
"//submodules/AppLockState:AppLockState",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -4,6 +4,8 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${APP_NAME}</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -14,6 +16,10 @@
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
<key>CFBundleVersion</key>
<string>${BUILD_NUMBER}</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>IntentsSupported</key>
<array>
<string>INSendMessageIntent</string>
</array>
<key>NSExtensionActivationRule</key>
<string>SUBQUERY (
extensionItems,
$extensionItem,
SUBQUERY (
$extensionItem.attachments,
$attachment,
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.audio" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.data" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.vcard" ||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.apple.pkpass"
).@count == $extensionItem.attachments.@count
).@count &gt; 0</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionPrincipalClass</key>
<string>ShareRootController</string>
</dict>
</dict>
</plist>

View File

@ -106,7 +106,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|> mapToSignal { account -> Signal<Account?, NoError> in
if let account = account {
switch account {

View File

@ -0,0 +1,104 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "icon@120px.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "icon@180px.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "icon@76px.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "icon@152px.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "icon@167px.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "icon@1024px.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,119 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "BlueNotificationIcon@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "BlueNotificationIcon@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Simple@58x58.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Simple@87x87.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Simple@80x80.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "BlueIcon@2x-1.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "BlueIcon@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "BlueIcon@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "BlueNotificationIcon.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "BlueNotificationIcon@2x-1.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Simple@29x29.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Simple@58x58-1.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Simple@40x40-1.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Simple@80x80-1.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "BlueIconIpad.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "BlueIconIpad@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "BlueIconLargeIpad@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Simple-iTunesArtwork.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"pre-rendered" : true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -14,6 +14,172 @@
<string>${APP_NAME}</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundleAlternateIcons</key>
<dict>
<key>Black</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlackIcon</string>
<string>BlackNotificationIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>BlackClassic</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlackClassicIcon</string>
<string>BlackClassicNotificationIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>BlackFilled</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlackFilledIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>Blue</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlueIcon</string>
<string>BlueNotificationIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>BlueClassic</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlueClassicIcon</string>
<string>BlueClassicNotificationIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>BlueFilled</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlueFilledIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>WhiteFilled</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>WhiteFilledIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
</dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconName</key>
<string>AppIconLLC</string>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
</dict>
<key>CFBundleIcons~ipad</key>
<dict>
<key>CFBundleAlternateIcons</key>
<dict>
<key>Black</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlackIconIpad</string>
<string>BlackIconLargeIpad</string>
<string>BlackNotificationIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>BlackClassic</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlackClassicIconIpad</string>
<string>BlackClassicIconLargeIpad</string>
<string>BlackClassicNotificationIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>BlackFilled</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlackFilledIconIpad</string>
<string>BlackFilledIconLargeIpad</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>Blue</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlueIconIpad</string>
<string>BlueIconLargeIpad</string>
<string>BlueNotificationIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>BlueClassic</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlueClassicIconIpad</string>
<string>BlueClassicIconLargeIpad</string>
<string>BlueClassicNotificationIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>BlueFilled</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>BlueFilledIconIpad</string>
<string>BlueFilledIconLargeIpad</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
<key>WhiteFilled</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>WhiteFilledIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
</dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconName</key>
<string>AppIconLLC</string>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
</dict>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>

View File

@ -11,7 +11,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<string>${APP_NAME}</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -22,8 +22,46 @@
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleURLSchemes</key>
<array>
<string>telegram</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).compatibility</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tg</string>
<string>$(APP_SPECIFIC_URL_SCHEME)</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).ton</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ton</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>${BUILD_NUMBER}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>

View File

@ -5366,3 +5366,5 @@ Any member of this group will be able to see messages in the channel.";
"Stats.ViewsBySourceTitle" = "VIEWS BY SOURCE";
"Stats.FollowersBySourceTitle" = "FOLLOWERS BY SOURCE";
"Stats.LanguagesTitle" = "LANGUAGES";
"ChatListFilter.AddChatsTitle" = "Add Chats";

View File

@ -4,6 +4,8 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>$(APP_NAME)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -14,6 +16,10 @@
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
<key>CFBundleVersion</key>
<string>${BUILD_NUMBER}</string>
<key>UIDeviceFamily</key>
<array>
<integer>4</integer>
@ -23,6 +29,8 @@
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>WKCompanionAppBundleIdentifier</key>
<string>$(APP_BUNDLE_ID)</string>
<key>WKWatchKitApp</key>
<true/>
</dict>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>UIDeviceFamily</key>
<array>
<integer>4</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>WKWatchKitApp</key>
<true/>
</dict>
</plist>

View File

@ -4,6 +4,8 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>$(APP_NAME)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -14,6 +16,20 @@
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
<key>CFBundleVersion</key>
<string>${BUILD_NUMBER}</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>WKAppBundleIdentifier</key>
<string>$(APP_BUNDLE_ID).watchkitapp</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.watchkit</string>
</dict>
<key>WKExtensionDelegateClassName</key>
<string>TGExtensionDelegate</string>
</dict>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>WKExtensionDelegateClassName</key>
<string>TGExtensionDelegate</string>
</dict>
</plist>

View File

@ -1,6 +1,5 @@
#!/bin/sh
#set -x
set -e
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
@ -55,7 +54,7 @@ mkdir -p "$TEMP_ENTITLEMENTS_PATH"
if [ "$APP_TYPE" == "wallet" ]; then
cp "buck-out/gen/Wallet/AppPackage#$PLATFORM_FLAVORS.ipa" "$IPA_PATH.original"
else
cp "buck-out/gen/AppPackage#$PLATFORM_FLAVORS.ipa" "$IPA_PATH.original"
cp "buck-out/gen/Telegram/AppPackage#$PLATFORM_FLAVORS.ipa" "$IPA_PATH.original"
fi
rm -rf "$IPA_PATH.original.unpacked"
rm -f "$BUILD_PATH/${APP_NAME}_signed.ipa"
@ -312,7 +311,7 @@ done
if [ "$APP_TYPE" == "wallet" ]; then
APP_BINARY_DSYM_PATH="buck-out/gen/Wallet/Wallet#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/Wallet.app.dSYM"
else
APP_BINARY_DSYM_PATH="buck-out/gen/Telegram#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/Telegram.app.dSYM"
APP_BINARY_DSYM_PATH="buck-out/gen/Telegram/Telegram#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/Telegram.app.dSYM"
fi
cp -r "$APP_BINARY_DSYM_PATH" "$DSYMS_DIR/"
@ -323,12 +322,12 @@ else
fi
for EXTENSION in $EXTENSIONS; do
EXTENSION_DSYM_PATH="buck-out/gen/${EXTENSION}Extension#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/${EXTENSION}Extension.appex.dSYM"
EXTENSION_DSYM_PATH="buck-out/gen/Telegram/${EXTENSION}Extension#dwarf-and-dsym,$PLATFORM_FLAVORS,no-include-frameworks/${EXTENSION}Extension.appex.dSYM"
cp -r "$EXTENSION_DSYM_PATH" "$DSYMS_DIR/"
done
if [ "$APP_TYPE" != "wallet" ]; then
WATCH_EXTENSION_DSYM_PATH="buck-out/gen/WatchAppExtension#dwarf-and-dsym,no-include-frameworks,watchos-arm64_32,watchos-armv7k/WatchAppExtension.appex.dSYM"
WATCH_EXTENSION_DSYM_PATH="buck-out/gen/Telegram/WatchAppExtension#dwarf-and-dsym,no-include-frameworks,watchos-arm64_32,watchos-armv7k/WatchAppExtension.appex.dSYM"
cp -r "$WATCH_EXTENSION_DSYM_PATH" "$DSYMS_DIR/"
fi

View File

@ -7,6 +7,7 @@ public enum ContactMultiselectionControllerMode {
case groupCreation
case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool)
case channelCreation
case chatSelection
}
public enum ContactListFilter {

View File

@ -3,10 +3,13 @@ load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "AppBundle",
srcs = glob([
"Source/AppBundle/*.m",
"Sources/**/*.m",
]),
headers = glob([
"Sources/**/*.h",
]),
exported_headers = glob([
"Source/AppBundle/*.h",
"PublicHeaders/**/*.h",
]),
deps = [
],

View File

@ -9,7 +9,7 @@ static_library(
"Sources/*.h",
]),
exported_headers = glob([
"Sources/*.h",
"PublicHeaders/**/*.h",
]),
deps = [
"//submodules/PKCS:PKCS",

View File

@ -24,19 +24,26 @@ import LocalizedPeerData
import TelegramIntents
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
if listNode.scroller.isDragging {
return false
}
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
let scrollToItem: ListViewScrollToItem
let targetProgress: CGFloat
let offset: CGFloat
if searchNode.expansionProgress < 0.6 {
scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: true, curve: .Default(duration: nil), directionHint: .Up)
targetProgress = 0.0
offset = navigationBarSearchContentHeight
} else {
scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up)
targetProgress = 1.0
offset = 0.0
}
searchNode.updateExpansionProgress(targetProgress, animated: true)
//searchNode.updateExpansionProgress(targetProgress, animated: true)
listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
//listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
listNode.scrollToOffsetFromTop(offset)
return true
} else if searchNode.expansionProgress == 1.0 {
var sortItemNode: ListViewItemNode?
@ -139,13 +146,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
private var searchContentNode: NavigationBarSearchContentNode?
private let tabContainerNode: ChatListFilterTabContainerNode
private var tabContainerData: ([ChatListFilterTabEntry], ChatListFilterTabEntryId)?
private let chatListFilterValue = Promise<ChatListFilter?>()
private var tabContainerData: [ChatListFilterTabEntry]?
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
if self.isNodeLoaded {
self.chatListDisplayNode.chatListNode.updateSelectedChatLocation(data as? ChatLocation, progress: progress, transition: transition)
self.chatListDisplayNode.containerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition)
}
}
@ -233,12 +238,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
if strongSelf.chatListDisplayNode.searchDisplayController != nil {
strongSelf.deactivateSearch(animated: true)
} else {
switch strongSelf.chatListDisplayNode.chatListNode.visibleContentOffset() {
switch strongSelf.chatListDisplayNode.containerNode.currentItemNode.visibleContentOffset() {
case .none, .unknown:
if let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateExpansionProgress(1.0, animated: true)
}
strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.top)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top)
case let .known(offset):
if offset <= navigationBarSearchContentHeight + 1.0 {
strongSelf.tabContainerNode.tabSelected?(.all)
@ -246,24 +251,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
if let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateExpansionProgress(1.0, animated: true)
}
strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.top)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top)
}
}
}
}
self.longTapWithTabBar = { [weak self] in
guard let strongSelf = self else {
return
}
if strongSelf.chatListDisplayNode.searchDisplayController != nil {
strongSelf.deactivateSearch(animated: true)
} else {
if let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateExpansionProgress(1.0, animated: true)
}
strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.auto)
}
}
let hasProxy = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.proxySettings])
|> map { sharedData -> (Bool, Bool) in
@ -288,22 +280,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
context.account.networkState,
hasProxy,
passcode,
self.chatListDisplayNode.chatListNode.state
self.chatListDisplayNode.containerNode.currentItemState
).start(next: { [weak self] networkState, proxy, passcode, state in
if let strongSelf = self {
let defaultTitle: String
if strongSelf.groupId == .root {
if let chatListFilter = strongSelf.filter {
let title: String = chatListFilter.title ?? strongSelf.presentationData.strings.DialogList_Title
defaultTitle = title
} else {
defaultTitle = strongSelf.presentationData.strings.DialogList_Title
}
defaultTitle = strongSelf.presentationData.strings.DialogList_Title
} else {
defaultTitle = strongSelf.presentationData.strings.ChatList_ArchivedChatsTitle
}
if state.editing {
if strongSelf.groupId == .root && strongSelf.filter == nil {
if strongSelf.groupId == .root {
strongSelf.navigationItem.rightBarButtonItem = nil
}
@ -313,11 +300,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
var isRoot = false
if case .root = strongSelf.groupId {
isRoot = true
if strongSelf.filter == nil {
let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed))
rightBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.VoiceOver_Navigation_Compose
strongSelf.navigationItem.rightBarButtonItem = rightBarButtonItem
}
let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed))
rightBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.VoiceOver_Navigation_Compose
strongSelf.navigationItem.rightBarButtonItem = rightBarButtonItem
}
let (hasProxy, connectsViaProxy) = proxy
@ -421,30 +406,39 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
if self.filter == nil {
self.chatListDisplayNode.containerNode.currentItemFilterUpdated = { [weak self] filter, fraction, transition in
guard let strongSelf = self else {
return
}
guard let layout = strongSelf.validLayout else {
return
}
guard let tabContainerData = strongSelf.tabContainerData else {
return
}
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData, selectedFilter: filter, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
}
let preferencesKey: PostboxViewKey = .preferences(keys: Set([
ApplicationSpecificPreferencesKeys.chatListFilterSettings
]))
let filterItems = chatListFilterItems(context: context)
|> map { totalCount, items -> [ChatListFilterTabEntry] in
var result: [ChatListFilterTabEntry] = []
result.append(.all(unreadCount: totalCount))
for (filter, unreadCount) in items {
result.append(.filter(id: filter.id, text: filter.title ?? "", unreadCount: unreadCount))
}
return result
}
|> distinctUntilChanged
self.filterDisposable = (combineLatest(queue: .mainQueue(),
context.account.postbox.combinedView(keys: [
preferencesKey
]),
filterItems,
self.chatListFilterValue.get() |> map { $0?.id } |> distinctUntilChanged
filterItems
)
|> deliverOnMainQueue).start(next: { [weak self] combinedView, filterItems, selectedFilter in
|> deliverOnMainQueue).start(next: { [weak self] combinedView, countAndFilterItems in
guard let strongSelf = self else {
return
}
let (totalCount, items) = countAndFilterItems
var filterItems: [ChatListFilterTabEntry] = []
filterItems.append(.all(unreadCount: 0))
for (filter, unreadCount) in items {
filterItems.append(.filter(id: filter.id, text: filter.title, unreadCount: unreadCount))
}
var filterSettings: ChatListFilterSettings = .default
if let preferencesView = combinedView.views[preferencesKey] as? PreferencesView {
if let value = preferencesView.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings {
@ -459,12 +453,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
var wasEmpty = false
if let tabContainerData = strongSelf.tabContainerData {
wasEmpty = tabContainerData.0.count <= 1
wasEmpty = tabContainerData.count <= 1
} else {
wasEmpty = true
}
let selectedEntryId: ChatListFilterTabEntryId = selectedFilter.flatMap { .filter($0) } ?? .all
strongSelf.tabContainerData = (resolvedItems, selectedEntryId)
let selectedEntryId = strongSelf.chatListDisplayNode.containerNode.currentItemFilter
strongSelf.tabContainerData = resolvedItems
var availableFilters: [ChatListContainerNodeFilter] = []
availableFilters.append(.all)
for item in items {
availableFilters.append(.filter(item.0))
}
strongSelf.chatListDisplayNode.containerNode.updateAvailableFilters(availableFilters)
let isEmpty = resolvedItems.count <= 1
@ -480,7 +480,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
(strongSelf.parent as? TabBarController)?.updateLayout()
} else {
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
}
}
})
@ -498,7 +498,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
let previousFilter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter
let previousFilter = strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter
let updatedFilter: ChatListFilter?
switch id {
case .all:
@ -519,26 +519,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
updatedFilter = nil
}
}
if previousFilter?.id != updatedFilter?.id {
var paneSwitchAnimationDirection: ChatListNodePaneSwitchAnimationDirection?
if let previousId = previousFilter?.id, let updatedId = updatedFilter?.id, let previousIndex = filters.index(where: { $0.id == previousId }), let updatedIndex = filters.index(where: { $0.id == updatedId }) {
if previousIndex > updatedIndex {
paneSwitchAnimationDirection = .right
} else {
paneSwitchAnimationDirection = .left
}
} else if (previousFilter != nil) != (updatedFilter != nil) {
if previousFilter != nil {
paneSwitchAnimationDirection = .right
} else {
paneSwitchAnimationDirection = .left
}
}
if let direction = paneSwitchAnimationDirection {
strongSelf.chatListDisplayNode.chatListNode.paneSwitchAnimation = (direction, .animated(duration: 0.4, curve: .spring))
}
}
strongSelf.chatListDisplayNode.chatListNode.updateFilter(updatedFilter)
strongSelf.chatListDisplayNode.containerNode.switchToFilter(id: updatedFilter.flatMap { .filter($0.id) } ?? .all)
})
}
@ -550,38 +531,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Edit, icon: { _ in
return nil
}, action: { c, f in
c.dismiss(completion: {
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
|> deliverOnMainQueue).start(next: { presetList in
guard let strongSelf = self else {
return
}
var found = false
for filter in presetList {
if filter.id == id {
strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in }))
f(.dismissWithoutContent)
found = true
break
}
}
})
})
})))
if let chatListFilter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter, chatListFilter.includePeers.count < 100 {
//TODO:localization
items.append(.action(ContextMenuActionItem(text: "Add Chats", icon: { _ in
return nil
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
|> deliverOnMainQueue).start(next: { [weak self] filters in
guard let strongSelf = self else {
return
}
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Edit, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { c, f in
c.dismiss(completion: {
guard let strongSelf = self else {
@ -598,7 +558,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
var found = false
for filter in presetList {
if filter.id == id {
strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter))
strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in }))
f(.dismissWithoutContent)
found = true
break
@ -607,10 +567,74 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
})
})
})))
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
if let filter = filters.first(where: { $0.id == id }), filter.data.includePeers.count < 100 {
//TODO:localization
items.append(.action(ContextMenuActionItem(text: "Add Chats", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
}, action: { c, f in
c.dismiss(completion: {
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
|> deliverOnMainQueue).start(next: { presetList in
guard let strongSelf = self else {
return
}
var found = false
for filter in presetList {
if filter.id == id {
strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter))
f(.dismissWithoutContent)
found = true
break
}
}
})
})
})))
items.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { c, f in
c.dismiss(completion: {
guard let strongSelf = self else {
return
}
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Delete, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
guard let strongSelf = self else {
return
}
let _ = updateChatListFilterSettingsInteractively(postbox: strongSelf.context.account.postbox, { settings in
var settings = settings
settings.filters = settings.filters.filter({ $0.id != id })
return settings
}).start()
let _ = replaceRemoteChatListFilters(account: strongSelf.context.account).start()
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
strongSelf.present(actionSheet, in: .window(.root))
})
})))
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
})
}
}
@ -643,11 +667,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel)
var editing = false
self.chatListDisplayNode.chatListNode.updateState { state in
editing = state.editing
return state
}
let editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing
let editItem: UIBarButtonItem
if editing {
editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
@ -677,7 +697,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
override public func loadDisplayNode() {
self.displayNode = ChatListControllerNode(context: self.context, groupId: self.groupId, filter: self.filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, controller: self)
self.chatListFilterValue.set(self.chatListDisplayNode.chatListNode.appliedChatListFilterSignal)
self.chatListDisplayNode.navigationBar = self.navigationBar
@ -685,37 +704,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
self?.deactivateSearch(animated: true)
}
self.chatListDisplayNode.chatListNode.activateSearch = { [weak self] in
self.chatListDisplayNode.containerNode.activateSearch = { [weak self] in
self?.activateSearch()
}
self.chatListDisplayNode.chatListNode.presentAlert = { [weak self] text in
self.chatListDisplayNode.containerNode.presentAlert = { [weak self] text in
if let strongSelf = self {
self?.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
self.chatListDisplayNode.chatListNode.present = { [weak self] c in
self.chatListDisplayNode.containerNode.present = { [weak self] c in
if let strongSelf = self {
self?.present(c, in: .window(.root))
}
}
self.chatListDisplayNode.chatListNode.toggleArchivedFolderHiddenByDefault = { [weak self] in
self.chatListDisplayNode.containerNode.toggleArchivedFolderHiddenByDefault = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.toggleArchivedFolderHiddenByDefault()
}
self.chatListDisplayNode.chatListNode.deletePeerChat = { [weak self] peerId in
self.chatListDisplayNode.containerNode.deletePeerChat = { [weak self] peerId in
guard let strongSelf = self else {
return
}
strongSelf.deletePeerChat(peerId: peerId)
}
self.chatListDisplayNode.chatListNode.peerSelected = { [weak self] peerId, animated, isAd in
self.chatListDisplayNode.containerNode.peerSelected = { [weak self] peer, animated, isAd in
if let strongSelf = self {
if let navigationController = strongSelf.navigationController as? NavigationController {
if isAd {
@ -739,37 +758,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
scrollToEndIfExists = true
}
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] in
self?.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] in
self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
}))
}
}
}
self.chatListDisplayNode.chatListNode.groupSelected = { [weak self] groupId in
self.chatListDisplayNode.containerNode.groupSelected = { [weak self] groupId in
if let strongSelf = self {
if let navigationController = strongSelf.navigationController as? NavigationController {
let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupId, controlsHistoryPreload: false, enableDebugActions: false)
chatListController.navigationPresentation = .master
navigationController.pushViewController(chatListController)
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
}
}
}
self.chatListDisplayNode.chatListNode.updatePeerGrouping = { [weak self] peerId, group in
self.chatListDisplayNode.containerNode.updatePeerGrouping = { [weak self] peerId, group in
guard let strongSelf = self else {
return
}
if group {
strongSelf.archiveChats(peerIds: [peerId])
} else {
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId)
let _ = updatePeerGroupIdInteractively(postbox: strongSelf.context.account.postbox, peerId: peerId, groupId: group ? Namespaces.PeerGroup.archive : .root).start(completed: {
guard let strongSelf = self else {
return
}
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
})
}
}
@ -787,7 +806,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: .message(messageId), purposefulAction: {
self?.deactivateSearch(animated: false)
}, scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : []))
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
}
}
}))
@ -816,7 +835,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in
self?.deactivateSearch(animated: false)
}, scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : []))
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
}
}
}))
@ -868,7 +887,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
navigationController.filterController(strongSelf, animated: true)
}
self.chatListDisplayNode.chatListNode.contentOffsetChanged = { [weak self] offset in
self.chatListDisplayNode.containerNode.contentOffsetChanged = { [weak self] offset in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout {
var offset = offset
if validLayout.inVoiceOver {
@ -878,7 +897,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
}
self.chatListDisplayNode.chatListNode.contentScrollingEnded = { [weak self] listView in
self.chatListDisplayNode.containerNode.contentScrollingEnded = { [weak self] listView in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
return fixListNodeScrolling(listView, searchNode: searchContentNode)
} else {
@ -898,7 +917,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
if let filter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter {
if let filter = strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter {
strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in }))
} else {
strongSelf.composePressed()
@ -909,7 +928,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
self?.toolbarActionSelected(action: action)
}
self.chatListDisplayNode.chatListNode.activateChatPreview = { [weak self] item, node, gesture in
self.chatListDisplayNode.containerNode.activateChatPreview = { [weak self] item, node, gesture in
guard let strongSelf = self else {
gesture?.cancel()
return
@ -941,7 +960,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
let context = self.context
let peerIdsAndOptions: Signal<(ChatListSelectionOptions, Set<PeerId>)?, NoError> = self.chatListDisplayNode.chatListNode.state
let peerIdsAndOptions: Signal<(ChatListSelectionOptions, Set<PeerId>)?, NoError> = self.chatListDisplayNode.containerNode.currentItemState
|> map { state -> Set<PeerId>? in
if !state.editing {
return nil
@ -989,7 +1008,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
}
}
toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: strongSelf.chatListDisplayNode.chatListNode.chatListFilter != nil ? nil : ToolbarAction(title: presentationData.strings.ChatList_ArchiveAction, isEnabled: archiveEnabled))
toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil ? nil : ToolbarAction(title: presentationData.strings.ChatList_ArchiveAction, isEnabled: archiveEnabled))
}
} else {
if let (options, peerIds) = peerIdsAndOptions {
@ -1007,7 +1026,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
strongSelf.setToolbar(toolbar, transition: .animated(duration: 0.3, curve: .easeInOut))
}))
self.ready.set(self.chatListDisplayNode.chatListNode.ready)
self.ready.set(self.chatListDisplayNode.containerNode.ready)
self.displayNodeDidLoad()
}
@ -1106,7 +1125,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}))
}
self.chatListDisplayNode.chatListNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in
self.chatListDisplayNode.containerNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in
guard let strongSelf = self else {
return
}
@ -1146,7 +1165,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
self.deactivateSearch(animated: false)
}
self.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@ -1162,12 +1181,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
tabContainerOffset += 44.0 + 44.0 + 44.0
}
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - self.additionalHeight - NavigationBar.defaultSecondaryContentHeight + tabContainerOffset), size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight)))
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.tabContainerData?.1, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - self.additionalHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0)))
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
if let searchContentNode = self.searchContentNode, layout.inVoiceOver != wasInVoiceOver {
searchContentNode.updateListVisibleContentOffset(.known(0.0))
self.chatListDisplayNode.chatListNode.scrollToPosition(.top)
self.chatListDisplayNode.scrollToTop()
}
self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, visualNavigationHeight: self.visualNavigationInsetHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition)
@ -1189,7 +1208,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
self.searchContentNode?.setIsEnabled(false, animated: true)
self.chatListDisplayNode.chatListNode.updateState { state in
self.chatListDisplayNode.containerNode.updateState { state in
var state = state
state.editing = true
state.peerIdWithRevealedOptions = nil
@ -1207,7 +1226,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
(self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(nil, transition: .animated(duration: 0.4, curve: .spring))
self.searchContentNode?.setIsEnabled(true, animated: true)
self.chatListDisplayNode.chatListNode.updateState { state in
self.chatListDisplayNode.containerNode.updateState { state in
var state = state
state.editing = false
state.peerIdWithRevealedOptions = nil
@ -1218,7 +1237,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
public func activateSearch() {
if self.displayNavigationBar {
let _ = (self.chatListDisplayNode.chatListNode.contentsReady
let _ = (self.chatListDisplayNode.containerNode.currentItemNode.contentsReady
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
guard let strongSelf = self else {
@ -1304,10 +1323,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return nil
}
let listLocation = self.view.convert(location, to: self.chatListDisplayNode.chatListNode.view)
let listLocation = self.view.convert(location, to: self.chatListDisplayNode.containerNode.currentItemNode.view)
var selectedNode: ChatListItemNode?
self.chatListDisplayNode.chatListNode.forEachItemNode { itemNode in
self.chatListDisplayNode.containerNode.currentItemNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatListItemNode, itemNode.frame.contains(listLocation), !itemNode.isDisplayingRevealedOptions {
selectedNode = itemNode
}
@ -1347,12 +1366,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
chatController.updatePresentationMode(.standard(previewing: false))
if let navigationController = self.navigationController as? NavigationController {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: chatController, context: self.context, chatLocation: chatController.chatLocation, animated: false))
self.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
}
} else if let chatListController = viewControllerToCommit as? ChatListController {
if let navigationController = self.navigationController as? NavigationController {
navigationController.pushViewController(chatListController, animated: false, completion: {})
self.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
}
}
}
@ -1374,22 +1393,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
let inputShortcuts: [KeyShortcut] = [
KeyShortcut(title: strings.KeyCommand_JumpToPreviousChat, input: UIKeyCommand.inputUpArrow, modifiers: [.alternate], action: { [weak self] in
if let strongSelf = self {
strongSelf.chatListDisplayNode.chatListNode.selectChat(.previous(unread: false))
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.previous(unread: false))
}
}),
KeyShortcut(title: strings.KeyCommand_JumpToNextChat, input: UIKeyCommand.inputDownArrow, modifiers: [.alternate], action: { [weak self] in
if let strongSelf = self {
strongSelf.chatListDisplayNode.chatListNode.selectChat(.next(unread: false))
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.next(unread: false))
}
}),
KeyShortcut(title: strings.KeyCommand_JumpToPreviousUnreadChat, input: UIKeyCommand.inputUpArrow, modifiers: [.alternate, .shift], action: { [weak self] in
if let strongSelf = self {
strongSelf.chatListDisplayNode.chatListNode.selectChat(.previous(unread: true))
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.previous(unread: true))
}
}),
KeyShortcut(title: strings.KeyCommand_JumpToNextUnreadChat, input: UIKeyCommand.inputDownArrow, modifiers: [.alternate, .shift], action: { [weak self] in
if let strongSelf = self {
strongSelf.chatListDisplayNode.chatListNode.selectChat(.next(unread: true))
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.next(unread: true))
}
}),
KeyShortcut(title: strings.KeyCommand_NewMessage, input: "N", modifiers: [.command], action: { [weak self] in
@ -1404,9 +1423,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
let openChat: (Int) -> Void = { [weak self] index in
if let strongSelf = self {
if index == 0 {
strongSelf.chatListDisplayNode.chatListNode.selectChat(.peerId(strongSelf.context.account.peerId))
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.peerId(strongSelf.context.account.peerId))
} else {
strongSelf.chatListDisplayNode.chatListNode.selectChat(.index(index - 1))
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.index(index - 1))
}
}
}
@ -1421,7 +1440,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
override public func toolbarActionSelected(action: ToolbarActionOption) {
let peerIds = self.chatListDisplayNode.chatListNode.currentState.selectedPeerIds
let peerIds = self.chatListDisplayNode.containerNode.currentItemNode.currentState.selectedPeerIds
if case .left = action {
let signal: Signal<Void, NoError>
let context = self.context
@ -1434,7 +1453,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
} else {
let groupId = self.groupId
signal = self.context.account.postbox.transaction { transaction -> Void in
markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: self.chatListDisplayNode.chatListNode.chatListFilter.flatMap(chatListFilterPredicate))
markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: (self.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.data).flatMap(chatListFilterPredicate))
}
}
let _ = (signal
@ -1451,7 +1470,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return
}
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
var state = state
for peerId in peerIds {
state.pendingRemovalPeerIds.insert(peerId)
@ -1495,15 +1514,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|> deliverOnMainQueue).start()
return true
} else if value == .undo {
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!)
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!)
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
var state = state
for peerId in peerIds {
state.pendingRemovalPeerIds.remove(peerId)
}
return state
})
self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!)
self?.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!)
return true
}
return false
@ -1527,7 +1546,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
self.archiveChats(peerIds: Array(peerIds))
} else {
if !peerIds.isEmpty {
self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!)
self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!)
let _ = (self.context.account.postbox.transaction { transaction -> Void in
for peerId in peerIds {
updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root)
@ -1537,7 +1556,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
strongSelf.donePressed()
})
}
@ -1560,7 +1579,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
strongSelf.chatListDisplayNode.chatListNode.updateState { state in
strongSelf.chatListDisplayNode.containerNode.updateState { state in
var state = state
if value {
state.archiveShouldBeTemporaryRevealed = false
@ -1678,7 +1697,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
var state = state
state.pendingClearHistoryPeerIds.insert(peer.peerId)
return state
@ -1699,7 +1718,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
var state = state
state.pendingClearHistoryPeerIds.remove(peer.peerId)
return state
@ -1707,7 +1726,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
})
return true
} else if value == .undo {
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
var state = state
state.pendingClearHistoryPeerIds.remove(peer.peerId)
return state
@ -1871,7 +1890,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return
}
let postbox = self.context.account.postbox
self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds[0])
self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds[0])
let _ = (ApplicationSpecificNotice.incrementArchiveChatTips(accountManager: self.context.sharedContext.accountManager, count: 1)
|> deliverOnMainQueue).start(next: { [weak self] previousHintCount in
let _ = (postbox.transaction { transaction -> Void in
@ -1883,7 +1902,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
for peerId in peerIds {
deleteSendMessageIntents(peerId: peerId)
@ -1894,7 +1913,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return false
}
if value == .undo {
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds[0])
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds[0])
let _ = (postbox.transaction { transaction -> Void in
for peerId in peerIds {
updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root)
@ -1904,7 +1923,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
})
return true
} else {
@ -1950,13 +1969,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
let peerId = peer.peerId
self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
self.chatListDisplayNode.chatListNode.updateState({ state in
self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId)
self.chatListDisplayNode.containerNode.updateState({ state in
var state = state
state.pendingRemovalPeerIds.insert(peer.peerId)
return state
})
self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
let statusText: String
if let channel = chatPeer as? TelegramChannel {
if deleteGloballyIfPossible {
@ -2000,7 +2019,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return false
}
if value == .commit {
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId)
if let channel = chatPeer as? TelegramChannel {
strongSelf.context.peerChannelMemberCategoriesContextsManager.externallyRemoved(peerId: channel.id, memberId: strongSelf.context.account.peerId)
}
@ -2008,25 +2027,25 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else {
return
}
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
var state = state
state.pendingRemovalPeerIds.remove(peer.peerId)
return state
})
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
deleteSendMessageIntents(peerId: peerId)
})
completion()
return true
} else if value == .undo {
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId)
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
var state = state
state.pendingRemovalPeerIds.remove(peer.peerId)
return state
})
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
return true
}
return false
@ -2055,35 +2074,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}))
}
public func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) {
if self.isNodeLoaded {
let _ = (self.context.account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
|> deliverOnMainQueue).start(next: { [weak self] presetList in
guard let strongSelf = self else {
return
}
let controller = TabBarChatListFilterController(context: strongSelf.context, sourceNodes: sourceNodes, presetList: presetList, currentPreset: strongSelf.chatListDisplayNode.chatListNode.chatListFilter, setup: {
guard let strongSelf = self else {
return
}
strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in
}))
}, updatePreset: { value in
guard let strongSelf = self else {
return
}
if let value = value {
strongSelf.tabContainerNode.tabSelected?(.filter(value.id))
}
})
strongSelf.context.sharedContext.mainWindow?.present(controller, on: .root)
})
}
}
override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
let _ = (combineLatest(queue: .mainQueue(),
self.context.account.postbox.transaction { transaction -> [ChatListFilter] in
@ -2121,6 +2111,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
})
})))
if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil {
items.append(.action(ContextMenuActionItem(text: "All Chats", icon: { theme in
return nil
}, action: { c, f in
f(.dismissWithoutContent)
guard let strongSelf = self else {
return
}
strongSelf.tabContainerNode.tabSelected?(.all)
})))
}
if !presetList.isEmpty {
items.append(.separator)
@ -2136,25 +2138,25 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
case privateChats
}
let filterType: ChatListFilterType
if preset.includePeers.isEmpty {
if preset.categories == .all {
if preset.excludeRead {
if preset.data.includePeers.isEmpty {
if preset.data.categories == .all {
if preset.data.excludeRead {
filterType = .unread
} else if preset.excludeMuted {
} else if preset.data.excludeMuted {
filterType = .unmuted
} else {
filterType = .generic
}
} else {
if preset.categories == .channels {
if preset.data.categories == .channels {
filterType = .channels
} else if preset.categories.isSubset(of: [.publicGroups, .privateGroups]) {
} else if preset.data.categories.isSubset(of: [.publicGroups, .privateGroups]) {
filterType = .groups
} else if preset.categories == .bots {
} else if preset.data.categories == .bots {
filterType = .bots
} else if preset.categories == .secretChats {
} else if preset.data.categories == .secretChats {
filterType = .secretChats
} else if preset.categories == .privateChats {
} else if preset.data.categories == .privateChats {
filterType = .privateChats
} else {
filterType = .generic
@ -2204,6 +2206,27 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
})
}
override public func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) {
guard let entries = self.tabContainerData, var index = entries.index(where: { $0.id == self.chatListDisplayNode.containerNode.currentItemFilter }) else {
return
}
switch direction {
case .right:
if index == 0 {
index = entries.count - 1
} else {
index -= 1
}
case .left:
if index == entries.count - 1 {
index = 0
} else {
index += 1
}
}
self.tabContainerNode.tabSelected?(entries[index].id)
}
}
private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource {

View File

@ -26,19 +26,585 @@ private final class ChatListControllerNodeView: UITracingLayerView, PreviewingHo
weak var controller: ChatListControllerImpl?
}
private struct TestItem: Comparable, Identifiable {
var value: Int
var version: Int
enum ChatListContainerNodeFilter: Equatable {
case all
case filter(ChatListFilter)
var stableId: Int {
return self.value
var id: ChatListFilterTabEntryId {
switch self {
case .all:
return .all
case let .filter(filter):
return .filter(filter.id)
}
}
static func <(lhs: TestItem, rhs: TestItem) -> Bool {
if lhs.version != rhs.version {
return lhs.version < rhs.version
var filter: ChatListFilter? {
switch self {
case .all:
return nil
case let .filter(filter):
return filter
}
}
}
private final class ChatListContainerItemNode: ASDisplayNode {
private var presentationData: PresentationData
private let becameEmpty: (ChatListFilter?) -> Void
private let emptyAction: (ChatListFilter?) -> Void
private var emptyNode: ChatListEmptyNode?
let listNode: ChatListNode
private var validLayout: (CGSize, UIEdgeInsets, CGFloat)?
init(context: AccountContext, groupId: PeerGroupId, filter: ChatListFilter?, previewing: Bool, presentationData: PresentationData, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void) {
self.presentationData = presentationData
self.becameEmpty = becameEmpty
self.emptyAction = emptyAction
self.listNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, controlsHistoryPreload: false, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
super.init()
self.addSubnode(self.listNode)
self.listNode.isEmptyUpdated = { [weak self] isEmptyState, _, _, transition in
guard let strongSelf = self else {
return
}
switch isEmptyState {
case let .empty(isLoading):
if let currentNode = strongSelf.emptyNode {
currentNode.updateIsLoading(isLoading)
} else {
let emptyNode = ChatListEmptyNode(isFilter: filter != nil, isLoading: isLoading, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, action: {
self?.emptyAction(filter)
})
strongSelf.emptyNode = emptyNode
strongSelf.addSubnode(emptyNode)
if let (size, insets, _) = strongSelf.validLayout {
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
emptyNode.frame = emptyNodeFrame
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate)
}
}
strongSelf.becameEmpty(filter)
case .notEmpty:
if let emptyNode = strongSelf.emptyNode {
strongSelf.emptyNode = nil
transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in
emptyNode?.removeFromSupernode()
})
}
}
}
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.listNode.updateThemeAndStrings(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, insets, visualNavigationHeight)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: 0.0, curve: .Default(duration: 0.0))
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
self.listNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
self.listNode.updateLayout(transition: .immediate, updateSizeAndInsets: updateSizeAndInsets)
if let emptyNode = self.emptyNode {
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
transition.updateFrame(node: emptyNode, frame: emptyNodeFrame)
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: transition)
}
}
}
final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private let context: AccountContext
private let groupId: PeerGroupId
private let previewing: Bool
private let filterBecameEmpty: (ChatListFilter?) -> Void
private let filterEmptyAction: (ChatListFilter?) -> Void
private var presentationData: PresentationData
private var itemNodes: [ChatListFilterTabEntryId: ChatListContainerItemNode] = [:]
private var pendingItemNode: (ChatListFilterTabEntryId, ChatListContainerItemNode, Disposable)?
private var availableFilters: [ChatListContainerNodeFilter] = [.all]
private var selectedId: ChatListFilterTabEntryId
private(set) var transitionFraction: CGFloat = 0.0
private var disableItemNodeOperationsWhileAnimating: Bool = false
private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat)?
private let _ready = Promise<Bool>()
var ready: Signal<Bool, NoError> {
return _ready.get()
}
private var currentItemNodeValue: ChatListContainerItemNode?
var currentItemNode: ChatListNode {
return self.currentItemNodeValue!.listNode
}
private let currentItemStateValue = Promise<ChatListNodeState>()
var currentItemState: Signal<ChatListNodeState, NoError> {
return self.currentItemStateValue.get()
}
var currentItemFilterUpdated: ((ChatListFilterTabEntryId, CGFloat, ContainedViewLayoutTransition) -> Void)?
var currentItemFilter: ChatListFilterTabEntryId {
return self.currentItemNode.chatListFilter.flatMap { .filter($0.id) } ?? .all
}
private func applyItemNodeAsCurrent(id: ChatListFilterTabEntryId, itemNode: ChatListContainerItemNode) {
if let previousItemNode = self.currentItemNodeValue {
previousItemNode.listNode.activateSearch = nil
previousItemNode.listNode.presentAlert = nil
previousItemNode.listNode.present = nil
previousItemNode.listNode.toggleArchivedFolderHiddenByDefault = nil
previousItemNode.listNode.deletePeerChat = nil
previousItemNode.listNode.peerSelected = nil
previousItemNode.listNode.groupSelected = nil
previousItemNode.listNode.updatePeerGrouping = nil
previousItemNode.listNode.contentOffsetChanged = nil
previousItemNode.listNode.contentScrollingEnded = nil
previousItemNode.listNode.activateChatPreview = nil
previousItemNode.listNode.addedVisibleChatsWithPeerIds = nil
previousItemNode.accessibilityElementsHidden = true
}
self.currentItemNodeValue = itemNode
itemNode.accessibilityElementsHidden = false
itemNode.listNode.activateSearch = { [weak self] in
self?.activateSearch?()
}
itemNode.listNode.presentAlert = { [weak self] text in
self?.presentAlert?(text)
}
itemNode.listNode.present = { [weak self] c in
self?.present?(c)
}
itemNode.listNode.toggleArchivedFolderHiddenByDefault = { [weak self] in
self?.toggleArchivedFolderHiddenByDefault?()
}
itemNode.listNode.deletePeerChat = { [weak self] peerId in
self?.deletePeerChat?(peerId)
}
itemNode.listNode.peerSelected = { [weak self] peerId, a, b in
self?.peerSelected?(peerId, a, b)
}
itemNode.listNode.groupSelected = { [weak self] groupId in
self?.groupSelected?(groupId)
}
itemNode.listNode.updatePeerGrouping = { [weak self] peerId, group in
self?.updatePeerGrouping?(peerId, group)
}
itemNode.listNode.contentOffsetChanged = { [weak self] offset in
self?.contentOffsetChanged?(offset)
}
itemNode.listNode.contentScrollingEnded = { [weak self] listView in
return self?.contentScrollingEnded?(listView) ?? false
}
itemNode.listNode.activateChatPreview = { [weak self] item, sourceNode, gesture in
self?.activateChatPreview?(item, sourceNode, gesture)
}
itemNode.listNode.addedVisibleChatsWithPeerIds = { [weak self] ids in
self?.addedVisibleChatsWithPeerIds?(ids)
}
self.currentItemStateValue.set(itemNode.listNode.state)
}
var activateSearch: (() -> Void)?
var presentAlert: ((String) -> Void)?
var present: ((ViewController) -> Void)?
var toggleArchivedFolderHiddenByDefault: (() -> Void)?
var deletePeerChat: ((PeerId) -> Void)?
var peerSelected: ((Peer, Bool, Bool) -> Void)?
var groupSelected: ((PeerGroupId) -> Void)?
var updatePeerGrouping: ((PeerId, Bool) -> Void)?
var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
var contentScrollingEnded: ((ListView) -> Bool)?
var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?) -> Void)?
var addedVisibleChatsWithPeerIds: (([PeerId]) -> Void)?
init(context: AccountContext, groupId: PeerGroupId, previewing: Bool, presentationData: PresentationData, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void) {
self.context = context
self.groupId = groupId
self.previewing = previewing
self.filterBecameEmpty = filterBecameEmpty
self.filterEmptyAction = filterEmptyAction
self.presentationData = presentationData
self.selectedId = .all
super.init()
let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: nil, previewing: self.previewing, presentationData: presentationData, becameEmpty: { [weak self] filter in
self?.filterBecameEmpty(filter)
}, emptyAction: { [weak self] filter in
self?.filterEmptyAction(filter)
})
self.itemNodes[.all] = itemNode
self.addSubnode(itemNode)
self._ready.set(itemNode.listNode.ready)
self.applyItemNodeAsCurrent(id: .all, itemNode: itemNode)
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
guard let strongSelf = self, let index = strongSelf.availableFilters.index(where: { $0.id == strongSelf.selectedId }) else {
return []
}
var directions: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter]
if strongSelf.availableFilters.count > 1 {
if index == 0 {
directions.remove(.rightCenter)
}
if index == strongSelf.availableFilters.count - 1 {
directions.remove(.leftCenter)
}
} else {
directions = []
}
return directions
})
panRecognizer.delegate = self
panRecognizer.delaysTouchesBegan = false
panRecognizer.cancelsTouchesInView = true
self.view.addGestureRecognizer(panRecognizer)
}
deinit {
self.pendingItemNode?.2.dispose()
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
return false
}
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
return true
}
return false
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .changed:
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = self.validLayout, let selectedIndex = self.availableFilters.index(where: { $0.id == self.selectedId }) {
let translation = recognizer.translation(in: self.view)
var transitionFraction = translation.x / layout.size.width
if selectedIndex <= 0 {
transitionFraction = min(0.0, transitionFraction)
}
if selectedIndex >= self.availableFilters.count - 1 {
transitionFraction = max(0.0, transitionFraction)
}
self.transitionFraction = transitionFraction
if let currentItemNode = self.currentItemNodeValue {
let isNavigationHidden = currentItemNode.listNode.isNavigationHidden
for (_, itemNode) in self.itemNodes {
if itemNode !== currentItemNode {
itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: isNavigationHidden)
}
}
}
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate)
}
case .cancelled, .ended:
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = self.validLayout, let selectedIndex = self.availableFilters.index(where: { $0.id == self.selectedId }) {
let translation = recognizer.translation(in: self.view)
let velocity = recognizer.velocity(in: self.view)
var directionIsToRight: Bool?
if abs(velocity.x) > 10.0 {
directionIsToRight = velocity.x < 0.0
} else {
if abs(translation.x) > layout.size.width / 2.0 {
directionIsToRight = translation.x > layout.size.width / 2.0
}
}
if let directionIsToRight = directionIsToRight {
var updatedIndex = selectedIndex
if directionIsToRight {
updatedIndex = min(updatedIndex + 1, self.availableFilters.count - 1)
} else {
updatedIndex = max(updatedIndex - 1, 0)
}
let switchToId = self.availableFilters[updatedIndex].id
if switchToId != self.selectedId, let itemNode = self.itemNodes[switchToId] {
self.selectedId = switchToId
self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode)
}
}
self.transitionFraction = 0.0
let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring)
self.disableItemNodeOperationsWhileAnimating = true
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: transition)
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition)
/*transition.updateBounds(node: self, bounds: self.bounds, force: true, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.disableItemNodeOperationsWhileAnimating = false
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = strongSelf.validLayout {
strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
}
})*/
DispatchQueue.main.async {
self.disableItemNodeOperationsWhileAnimating = false
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = self.validLayout {
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
}
}
}
default:
break
}
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
for (_, itemNode) in self.itemNodes {
itemNode.updatePresentationData(presentationData)
}
}
func playArchiveAnimation() {
if let itemNode = self.itemNodes[self.selectedId] {
itemNode.listNode.forEachVisibleItemNode { node in
if let node = node as? ChatListItemNode {
node.playArchiveAnimation()
}
}
}
}
func scrollToTop() {
if let itemNode = self.itemNodes[self.selectedId] {
itemNode.listNode.scrollToPosition(.top)
}
}
func updateSelectedChatLocation(data: ChatLocation?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
for (_, itemNode) in self.itemNodes {
itemNode.listNode.updateSelectedChatLocation(data, progress: progress, transition: transition)
}
}
func updateState(_ f: (ChatListNodeState) -> ChatListNodeState) {
self.currentItemNode.updateState(f)
let updatedState = self.currentItemNode.currentState
for (id, itemNode) in self.itemNodes {
if id != self.selectedId {
itemNode.listNode.updateState { state in
var state = state
state.editing = updatedState.editing
state.selectedPeerIds = updatedState.selectedPeerIds
return state
}
}
}
}
func updateAvailableFilters(_ availableFilters: [ChatListContainerNodeFilter]) {
if self.availableFilters != availableFilters {
self.availableFilters = availableFilters
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = self.validLayout {
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
}
}
}
func switchToFilter(id: ChatListFilterTabEntryId) {
guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = self.validLayout else {
return
}
if id != self.selectedId, let index = self.availableFilters.index(where: { $0.id == id }) {
if let itemNode = self.itemNodes[id] {
self.selectedId = id
if let currentItemNode = self.currentItemNodeValue {
itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: currentItemNode.listNode.isNavigationHidden)
}
self.applyItemNodeAsCurrent(id: id, itemNode: itemNode)
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: transition)
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition)
} else if self.pendingItemNode == nil {
let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[index].filter, previewing: self.previewing, presentationData: self.presentationData, becameEmpty: { [weak self] filter in
self?.filterBecameEmpty(filter)
}, emptyAction: { [weak self] filter in
self?.filterEmptyAction(filter)
})
let disposable = MetaDisposable()
self.pendingItemNode = (id, itemNode, disposable)
disposable.set((itemNode.listNode.ready
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self, weak itemNode] _ in
guard let strongSelf = self, let itemNode = itemNode, itemNode === strongSelf.pendingItemNode?.1 else {
return
}
guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = strongSelf.validLayout else {
return
}
strongSelf.pendingItemNode = nil
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
if let previousIndex = strongSelf.availableFilters.index(where: { $0.id == strongSelf.selectedId }), let index = strongSelf.availableFilters.index(where: { $0.id == id }) {
let previousId = strongSelf.selectedId
let offsetDirection: CGFloat = index < previousIndex ? 1.0 : -1.0
let offset = offsetDirection * layout.size.width
var validNodeIds: [ChatListFilterTabEntryId] = []
for i in max(0, index - 1) ... min(strongSelf.availableFilters.count - 1, index + 1) {
validNodeIds.append(strongSelf.availableFilters[i].id)
}
var removeIds: [ChatListFilterTabEntryId] = []
for (id, _) in strongSelf.itemNodes {
if !validNodeIds.contains(id) {
removeIds.append(id)
}
}
for id in removeIds {
if let itemNode = strongSelf.itemNodes.removeValue(forKey: id) {
if id == previousId {
transition.updateFrame(node: itemNode, frame: itemNode.frame.offsetBy(dx: offset, dy: 0.0), completion: { [weak itemNode] _ in
itemNode?.removeFromSupernode()
})
} else {
itemNode.removeFromSupernode()
}
}
}
strongSelf.itemNodes[id] = itemNode
strongSelf.addSubnode(itemNode)
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)
itemNode.frame = itemFrame
transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: -offset, y: 0.0))
var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight
insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right
itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: .immediate)
strongSelf.selectedId = id
if let currentItemNode = strongSelf.currentItemNodeValue {
itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: currentItemNode.listNode.isNavigationHidden)
}
strongSelf.applyItemNodeAsCurrent(id: id, itemNode: itemNode)
strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition)
}
}))
}
}
}
func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight)
var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight
insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right
if let selectedIndex = self.availableFilters.index(where: { $0.id == self.selectedId }) {
var validNodeIds: [ChatListFilterTabEntryId] = []
for i in max(0, selectedIndex - 1) ... min(self.availableFilters.count - 1, selectedIndex + 1) {
let id = self.availableFilters[i].id
validNodeIds.append(id)
if self.itemNodes[id] == nil && !self.disableItemNodeOperationsWhileAnimating {
let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[i].filter, previewing: self.previewing, presentationData: self.presentationData, becameEmpty: { [weak self] filter in
self?.filterBecameEmpty(filter)
}, emptyAction: { [weak self] filter in
self?.filterEmptyAction(filter)
})
self.itemNodes[id] = itemNode
}
}
var removeIds: [ChatListFilterTabEntryId] = []
var animateSlidingIds: [ChatListFilterTabEntryId] = []
var slidingOffset: CGFloat?
for (id, itemNode) in self.itemNodes {
if !validNodeIds.contains(id) {
removeIds.append(id)
}
guard let index = self.availableFilters.index(where: { $0.id == id }) else {
continue
}
let indexDistance = CGFloat(index - selectedIndex) + self.transitionFraction
let wasAdded = itemNode.supernode == nil
var nodeTransition = transition
if wasAdded {
self.addSubnode(itemNode)
nodeTransition = .immediate
}
let itemFrame = CGRect(origin: CGPoint(x: indexDistance * layout.size.width, y: 0.0), size: layout.size)
if !wasAdded && slidingOffset == nil {
slidingOffset = itemNode.frame.minX - itemFrame.minX
}
nodeTransition.updateFrame(node: itemNode, frame: itemFrame, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}
})
itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: nodeTransition)
if wasAdded, case .animated = transition {
animateSlidingIds.append(id)
}
}
if let slidingOffset = slidingOffset {
for id in animateSlidingIds {
if let itemNode = self.itemNodes[id] {
transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: slidingOffset, y: 0.0), completion: {
})
}
}
}
if !self.disableItemNodeOperationsWhileAnimating {
for id in removeIds {
if let itemNode = self.itemNodes.removeValue(forKey: id) {
itemNode.removeFromSupernode()
}
}
}
}
return lhs.value < rhs.value
}
}
@ -47,9 +613,7 @@ final class ChatListControllerNode: ASDisplayNode {
private let groupId: PeerGroupId
private var presentationData: PresentationData
private var chatListEmptyNodeContainer: ChatListEmptyNodeContainer
private var chatListEmptyIndicator: ActivityIndicator?
let chatListNode: ChatListNode
let containerNode: ChatListContainerNode
var navigationBar: NavigationBar?
weak var controller: ChatListControllerImpl?
@ -78,8 +642,13 @@ final class ChatListControllerNode: ASDisplayNode {
self.groupId = groupId
self.presentationData = presentationData
self.chatListEmptyNodeContainer = ChatListEmptyNodeContainer(theme: presentationData.theme, strings: presentationData.strings)
self.chatListNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
var filterBecameEmpty: ((ChatListFilter?) -> Void)?
var filterEmptyAction: ((ChatListFilter?) -> Void)?
self.containerNode = ChatListContainerNode(context: context, groupId: groupId, previewing: previewing, presentationData: presentationData, filterBecameEmpty: { filter in
filterBecameEmpty?(filter)
}, filterEmptyAction: { filter in
filterEmptyAction?(filter)
})
self.controller = controller
@ -91,35 +660,24 @@ final class ChatListControllerNode: ASDisplayNode {
self.backgroundColor = presentationData.theme.chatList.backgroundColor
self.addSubnode(self.chatListNode)
self.addSubnode(self.chatListEmptyNodeContainer)
self.chatListNode.isEmptyUpdated = { [weak self] isEmptyState, isFilter, transitionDirection, transition in
self.addSubnode(self.containerNode)
self.addSubnode(self.debugListView)
filterBecameEmpty = { [weak self] _ in
guard let strongSelf = self else {
return
}
switch isEmptyState {
case .empty(false):
if case .group = strongSelf.groupId {
strongSelf.dismissSelf?()
} else {
strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition)
}
case .notEmpty(false):
if case .group = strongSelf.groupId {
strongSelf.dismissSelf?()
} else {
strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition)
}
default:
strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition)
if case .group = strongSelf.groupId {
strongSelf.dismissSelf?()
}
}
self.chatListEmptyNodeContainer.action = { [weak self] in
self?.emptyListAction?()
filterEmptyAction = { [weak self] filter in
guard let strongSelf = self else {
return
}
strongSelf.emptyListAction?()
}
self.addSubnode(self.debugListView)
}
override func didLoad() {
@ -133,9 +691,8 @@ final class ChatListControllerNode: ASDisplayNode {
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.chatListNode.updateThemeAndStrings(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)
self.containerNode.updatePresentationData(presentationData)
self.searchDisplayController?.updatePresentationData(presentationData)
self.chatListEmptyNodeContainer.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
if let toolbarNode = self.toolbarNode {
toolbarNode.updateTheme(TabBarControllerTheme(rootControllerTheme: self.presentationData.theme))
@ -194,23 +751,8 @@ final class ChatListControllerNode: ASDisplayNode {
})
}
self.chatListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
self.chatListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve)
self.chatListNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
let emptySize = CGSize(width: updateSizeAndInsets.size.width, height: updateSizeAndInsets.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom)
transition.updateFrame(node: self.chatListEmptyNodeContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: updateSizeAndInsets.insets.top), size: emptySize))
self.chatListEmptyNodeContainer.updateLayout(size: emptySize, transition: transition)
if let chatListEmptyIndicator = self.chatListEmptyIndicator {
let indicatorSize = chatListEmptyIndicator.measure(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: chatListEmptyIndicator, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - indicatorSize.width) / 2.0), y: updateSizeAndInsets.insets.top + floor((layout.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom - indicatorSize.height) / 2.0)), size: indicatorSize))
}
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
self.containerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: transition)
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition)
@ -242,7 +784,7 @@ final class ChatListControllerNode: ASDisplayNode {
requestDeactivateSearch()
}
})
self.chatListNode.accessibilityElementsHidden = true
self.containerNode.accessibilityElementsHidden = true
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
@ -260,23 +802,19 @@ final class ChatListControllerNode: ASDisplayNode {
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
self.searchDisplayController = nil
self.chatListNode.accessibilityElementsHidden = false
self.containerNode.accessibilityElementsHidden = false
}
}
func playArchiveAnimation() {
self.chatListNode.forEachVisibleItemNode { node in
if let node = node as? ChatListItemNode {
node.playArchiveAnimation()
}
}
self.containerNode.playArchiveAnimation()
}
func scrollToTop() {
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.contentNode.scrollToTop()
} else {
self.chatListNode.scrollToPosition(.top)
self.containerNode.scrollToTop()
}
}
}

View File

@ -18,13 +18,15 @@ private final class ChatListFilterPresetControllerArguments {
let openAddPeer: () -> Void
let deleteAdditionalPeer: (PeerId) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let focusOnName: () -> Void
init(context: AccountContext, updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void, openAddPeer: @escaping () -> Void, deleteAdditionalPeer: @escaping (PeerId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
init(context: AccountContext, updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void, openAddPeer: @escaping () -> Void, deleteAdditionalPeer: @escaping (PeerId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, focusOnName: @escaping () -> Void) {
self.context = context
self.updateState = updateState
self.openAddPeer = openAddPeer
self.deleteAdditionalPeer = deleteAdditionalPeer
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.focusOnName = focusOnName
}
}
@ -168,13 +170,15 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case let .nameHeader(title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .name(placeholder, value):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), sectionId: self.section, textUpdated: { value in
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), clearType: .always, sectionId: self.section, textUpdated: { value in
arguments.updateState { current in
var state = current
state.name = value
return state
}
}, action: {})
}, action: {}, cleared: {
arguments.focusOnName()
})
case let .typesHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .filterPrivateChats(title, value):
@ -276,7 +280,7 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
}
func chatListFilterAddChatsController(context: AccountContext, filter: ChatListFilter) -> ViewController {
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: true), options: []))
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection, options: []))
controller.navigationPresentation = .modal
let _ = (controller.result
|> take(1)
@ -285,7 +289,7 @@ func chatListFilterAddChatsController(context: AccountContext, filter: ChatListF
var settings = settings
for i in 0 ..< settings.filters.count {
if settings.filters[i].id == filter.id {
let previousIncludePeers = settings.filters[i].includePeers
let previousIncludePeers = settings.filters[i].data.includePeers
var chatPeerIds: [PeerId] = []
for peerId in peerIds {
@ -296,7 +300,7 @@ func chatListFilterAddChatsController(context: AccountContext, filter: ChatListF
break
}
}
settings.filters[i].includePeers = chatPeerIds + previousIncludePeers.filter { peerId in
settings.filters[i].data.includePeers = chatPeerIds + previousIncludePeers.filter { peerId in
return !chatPeerIds.contains(peerId)
}
}
@ -305,6 +309,8 @@ func chatListFilterAddChatsController(context: AccountContext, filter: ChatListF
})
|> deliverOnMainQueue).start(next: { settings in
controller?.dismiss()
let _ = replaceRemoteChatListFilters(account: context.account).start()
})
})
return controller
@ -317,7 +323,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
} else {
initialName = "New Filter"
}
let initialState = ChatListFilterPresetControllerState(name: initialName, includeCategories: currentPreset?.categories ?? .all, excludeMuted: currentPreset?.excludeMuted ?? false, excludeRead: currentPreset?.excludeRead ?? false, additionallyIncludePeers: currentPreset?.includePeers ?? [])
let initialState = ChatListFilterPresetControllerState(name: initialName, includeCategories: currentPreset?.data.categories ?? .all, excludeMuted: currentPreset?.data.excludeMuted ?? false, excludeRead: currentPreset?.data.excludeRead ?? false, additionallyIncludePeers: currentPreset?.data.includePeers ?? [])
let stateValue = Atomic(value: initialState)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void = { f in
@ -331,6 +337,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var dismissImpl: (() -> Void)?
var focusOnNameImpl: (() -> Void)?
let arguments = ChatListFilterPresetControllerArguments(
context: context,
@ -338,7 +345,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
updateState(f)
},
openAddPeer: {
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: true), options: []))
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection, options: []))
addPeerDisposable.set((controller.result
|> take(1)
|> deliverOnMainQueue).start(next: { [weak controller] peerIds in
@ -377,6 +384,9 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
}
return state
}
},
focusOnName: {
focusOnNameImpl?()
}
)
@ -409,7 +419,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
})
let rightNavigationButton = ItemListNavigationButton(content: .text(currentPreset == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: {
let state = stateValue.with { $0 }
let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, includePeers: state.additionallyIncludePeers)
let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, includePeers: state.additionallyIncludePeers))
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
var preset = preset
if currentPreset == nil {
@ -435,6 +445,8 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|> deliverOnMainQueue).start(next: { settings in
updated(settings.filters)
dismissImpl?()
let _ = replaceRemoteChatListFilters(account: context.account).start()
})
})
@ -455,6 +467,16 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
dismissImpl = { [weak controller] in
let _ = controller?.dismiss()
}
focusOnNameImpl = { [weak controller] in
guard let controller = controller else {
return
}
controller.forEachItemNode { itemNode in
if let itemNode = itemNode as? ItemListSingleLineInputItemNode {
itemNode.focus()
}
}
}
return controller
}

View File

@ -53,7 +53,7 @@ private enum ChatListFilterPresetListEntryStableId: Hashable {
private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
case listHeader(String)
case preset(index: Int, title: String?, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool)
case preset(index: Int, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool)
case addItem(text: String, isEditing: Bool)
case listFooter(String)
@ -99,8 +99,8 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
switch self {
case let .listHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
case let .preset(index, title, preset, canBeReordered, canBeDeleted, isEditing):
return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title ?? "", editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, sectionId: self.section, action: {
case let .preset(index, title, label, preset, canBeReordered, canBeDeleted, isEditing):
return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title ?? "", label: label, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, sectionId: self.section, action: {
arguments.openPreset(preset)
}, setItemWithRevealedOptions: { lhs, rhs in
arguments.setItemWithRevealedOptions(lhs, rhs)
@ -122,14 +122,14 @@ private struct ChatListFilterPresetListControllerState: Equatable {
var revealedPreset: Int32? = nil
}
private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filtersState: ChatListFiltersState, settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] {
private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filters: [(ChatListFilter, Int)], settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] {
var entries: [ChatListFilterPresetListEntry] = []
entries.append(.listHeader("FILTERS"))
for preset in filtersState.filters {
entries.append(.preset(index: entries.count, title: preset.title, preset: preset, canBeReordered: filtersState.filters.count > 1, canBeDeleted: true, isEditing: state.isEditing))
for (filter, chatCount) in filters {
entries.append(.preset(index: entries.count, title: filter.title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing))
}
if filtersState.filters.count < 10 {
if filters.count < 10 {
entries.append(.addItem(text: "Create New Filter", isEditing: state.isEditing))
}
entries.append(.listFooter("Tap \"Edit\" to change the order or delete filters."))
@ -137,7 +137,7 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
return entries
}
func chatListFilterPresetListController(context: AccountContext, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController {
public func chatListFilterPresetListController(context: AccountContext, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController {
let initialState = ChatListFilterPresetListControllerState()
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
@ -171,21 +171,51 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
})
|> deliverOnMainQueue).start(next: { settings in
updated(settings.filters)
let _ = replaceRemoteChatListFilters(account: context.account).start()
})
})
let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters, ApplicationSpecificPreferencesKeys.chatListFilterSettings])
let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:])
let filtersWithCounts = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|> map { preferences -> [ChatListFilter] in
let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
return filtersState.filters
}
|> distinctUntilChanged
|> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in
return context.account.postbox.transaction { transaction -> [(ChatListFilter, Int)] in
return filters.map { filter -> (ChatListFilter, Int) in
let count: Int
if let cachedValue = chatCountCache.with({ dict -> Int? in
return dict[filter.data]
}) {
count = cachedValue
} else {
count = transaction.getChatCountMatchingPredicate(chatListFilterPredicate(filter: filter.data))
let _ = chatCountCache.modify { dict in
var dict = dict
dict[filter.data] = count
return dict
}
}
return (filter, count)
}
}
}
let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings])
let signal = combineLatest(queue: .mainQueue(),
context.sharedContext.presentationData,
statePromise.get(),
filtersWithCounts,
preferences
)
|> map { presentationData, state, preferences -> (ItemListControllerState, (ItemListNodeState, Any)) in
let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
|> map { presentationData, state, filtersWithCounts, preferences -> (ItemListControllerState, (ItemListNodeState, Any)) in
let filterSettings = preferences.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings ?? ChatListFilterSettings.default
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: {
let _ = replaceRemoteChatListFilters(account: context.account).start()
dismissImpl?()
})
let rightNavigationButton: ItemListNavigationButton
@ -208,7 +238,7 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Filters"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filtersState: filtersState, settings: filterSettings), style: .blocks, animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCounts, settings: filterSettings), style: .blocks, animateChanges: true)
return (controllerState, (listState, arguments))
}
@ -217,6 +247,9 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
let controller = ItemListController(context: context, state: signal)
controller.navigationPresentation = .modal
controller.willDisappear = { _ in
let _ = replaceRemoteChatListFilters(account: context.account).start()
}
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}

View File

@ -20,6 +20,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let preset: ChatListFilter
let title: String
let label: String
let editing: ChatListFilterPresetListItemEditing
let canBeReordered: Bool
let canBeDeleted: Bool
@ -32,6 +33,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
presentationData: ItemListPresentationData,
preset: ChatListFilter,
title: String,
label: String,
editing: ChatListFilterPresetListItemEditing,
canBeReordered: Bool,
canBeDeleted: Bool,
@ -43,6 +45,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
self.presentationData = presentationData
self.preset = preset
self.title = title
self.label = label
self.editing = editing
self.canBeReordered = canBeReordered
self.canBeDeleted = canBeDeleted
@ -108,6 +111,8 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
private let maskNode: ASImageNode
private let titleNode: TextNode
private let labelNode: TextNode
private let arrowNode: ASImageNode
private let activateArea: AccessibilityAreaNode
@ -141,6 +146,14 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
self.titleNode.contentMode = .left
self.titleNode.contentsScale = UIScreen.main.scale
self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false
self.arrowNode = ASImageNode()
self.arrowNode.displayWithoutProcessing = true
self.arrowNode.displaysAsynchronously = false
self.arrowNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
self.highlightedBackgroundNode = ASDisplayNode()
@ -149,6 +162,8 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.arrowNode)
self.addSubnode(self.activateArea)
self.activateArea.activate = { [weak self] in
@ -159,6 +174,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
func asyncLayout() -> (_ item: ChatListFilterPresetListItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
@ -166,9 +182,11 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
return { item, params, neighbors in
var updatedTheme: PresentationTheme?
var updateArrowImage: UIImage?
if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
}
let peerRevealOptions: [ItemListRevealOption]
@ -187,21 +205,27 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
var editingOffset: CGFloat = 0.0
var reorderInset: CGFloat = 0.0
if item.editing.editing && item.canBeReordered {
if item.editing.editing {
let sizeAndApply = editableControlLayout(item.presentationData.theme, false)
editableControlSizeAndApply = sizeAndApply
editingOffset = sizeAndApply.0
let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
reorderControlSizeAndApply = reorderSizeAndApply
reorderInset = reorderSizeAndApply.0
if item.canBeReordered {
let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
reorderControlSizeAndApply = reorderSizeAndApply
reorderInset = reorderSizeAndApply.0
}
}
let leftInset: CGFloat = 16.0 + params.leftInset
let rightInset: CGFloat = params.rightInset + max(reorderInset, 55.0)
let rightArrowInset: CGFloat = 34.0 + params.rightInset
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: titleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let insets = itemListNeighborsGroupedInsets(neighbors)
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + 11.0 * 2.0)
let separatorHeight = UIScreenPixel
@ -280,6 +304,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
}
let _ = titleApply()
let _ = labelApply()
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
@ -326,6 +351,20 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 11.0), size: titleLayout.size))
let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width, y: 11.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.arrowNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
if let updateArrowImage = updateArrowImage {
strongSelf.arrowNode.image = updateArrowImage
}
if let arrowImage = strongSelf.arrowNode.image {
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floorToScreenPixels((layout.contentSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
}
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 0.0), size: CGSize(width: params.width - params.rightInset - 56.0 - (leftInset + revealOffset + editingOffset), height: layout.contentSize.height))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))

View File

@ -257,13 +257,12 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
}
}
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
let focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter
var previousSelectedAbsFrame: CGRect?
private var previousSelectedAbsFrame: CGRect?
private var previousSelectedFrame: CGRect?
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, transitionFraction: CGFloat, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter
let previousScrollBounds = self.scrollNode.bounds
if let currentSelectedFilter = self.currentParams?.selectedFilter, let itemNode = self.itemNodes[currentSelectedFilter] {
previousSelectedAbsFrame = itemNode.frame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0)
}
if self.currentParams?.presentationData.theme !== presentationData.theme {
self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in
@ -362,7 +361,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
}
}
let minSpacing: CGFloat = 30.0
let minSpacing: CGFloat = 26.0
let sideInset: CGFloat = 16.0
var leftOffset: CGFloat = sideInset
@ -391,7 +390,8 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
self.scrollNode.view.contentSize = CGSize(width: leftOffset - minSpacing + sideInset - 5.0, height: size.height)
let transitionFraction: CGFloat = 0.0
var previousFrame: CGRect?
var nextFrame: CGRect?
var selectedFrame: CGRect?
if let selectedFilter = selectedFilter, let currentIndex = filters.index(where: { $0.id == selectedFilter }) {
func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect {
@ -422,28 +422,41 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
} else {
transition.updateFrame(node: self.selectedLineNode, frame: lineFrame)
}
if !transitionFraction.isZero {
if previousScrollBounds.minX.isZero {
focusOnSelectedFilter = true
} else if previousScrollBounds.maxX == previousScrollBounds.width {
focusOnSelectedFilter = true
} else if let previousSelectedFrame = self.previousSelectedFrame, abs(previousSelectedFrame.offsetBy(dx: -previousScrollBounds.minX, dy: 0.0).midX - previousScrollBounds.width / 2.0) < 1.0 {
focusOnSelectedFilter = true
}
}
if focusOnSelectedFilter {
if selectedFilter == filters.first?.id {
if transitionFraction.isZero && selectedFilter == filters.first?.id {
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size))
} else if selectedFilter == filters.last?.id {
} else if transitionFraction.isZero && selectedFilter == filters.last?.id {
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: max(0.0, self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width), y: 0.0), size: self.scrollNode.bounds.size))
} else {
let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0)))
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size))
}
} else if !wasAdded, let previousSelectedAbsFrame = previousSelectedAbsFrame {
} else if !wasAdded, transitionFraction.isZero, let previousSelectedAbsFrame = self.previousSelectedAbsFrame {
let contentOffsetX: CGFloat
if previousScrollBounds.minX.isZero {
contentOffsetX = 0.0
} else if previousScrollBounds.maxX == previousScrollBounds.width {
contentOffsetX = self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width
} else {
contentOffsetX = selectedFrame.midX - previousSelectedAbsFrame.midX
contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, selectedFrame.midX - previousSelectedAbsFrame.midX))
}
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size))
}
self.previousSelectedAbsFrame = selectedFrame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0)
self.previousSelectedFrame = selectedFrame
} else {
self.selectedLineNode.isHidden = true
self.previousSelectedAbsFrame = nil
self.previousSelectedFrame = nil
}
}
}

View File

@ -17,7 +17,7 @@ import SearchUI
public enum ChatListNodeMode {
case chatList
case peers(filter: ChatListNodePeersFilter)
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool)
}
struct ChatListNodeListViewTransition {
@ -153,7 +153,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
switch mode {
case .chatList:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .peers(filter):
case let .peers(filter, _):
let itemPeer = peer.chatMainPeer
var chatPeer: Peer?
if let peer = peer.peers[peer.peerId] {
@ -218,7 +218,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
}
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: editing ? .selectable(selected: selected) : .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer)
}
@ -245,7 +245,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
switch mode {
case .chatList:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .peers(filter):
case let .peers(filter, _):
let itemPeer = peer.chatMainPeer
var chatPeer: Peer?
if let peer = peer.peers[peer.peerId] {
@ -266,7 +266,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
enabled = false
}
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: editing ? .selectable(selected: selected) : .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
if let chatPeer = chatPeer {
nodeInteraction.peerSelected(chatPeer)
}
@ -352,7 +352,7 @@ public final class ChatListNode: ListView {
return _contentsReady.get()
}
public var peerSelected: ((PeerId, Bool, Bool) -> Void)?
public var peerSelected: ((Peer, Bool, Bool) -> Void)?
public var disabledPeerSelected: ((Peer) -> Void)?
public var groupSelected: ((PeerGroupId) -> Void)?
public var addContact: ((String) -> Void)?
@ -373,7 +373,7 @@ public final class ChatListNode: ListView {
private var dequeuedInitialTransitionOnLayout = false
private var enqueuedTransition: (ChatListNodeListViewTransition, () -> Void)?
private(set) var currentState: ChatListNodeState
public private(set) var currentState: ChatListNodeState
private let statePromise: ValuePromise<ChatListNodeState>
public var state: Signal<ChatListNodeState, NoError> {
return self.statePromise.get()
@ -453,7 +453,12 @@ public final class ChatListNode: ListView {
self.controlsHistoryPreload = controlsHistoryPreload
self.mode = mode
self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: false, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), peerInputActivities: nil, pendingRemovalPeerIds: Set(), pendingClearHistoryPeerIds: Set(), archiveShouldBeTemporaryRevealed: false)
var isSelecting = false
if case .peers(_, true) = mode {
isSelecting = true
}
self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), peerInputActivities: nil, pendingRemovalPeerIds: Set(), pendingClearHistoryPeerIds: Set(), archiveShouldBeTemporaryRevealed: false)
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
self.theme = theme
@ -471,7 +476,7 @@ public final class ChatListNode: ListView {
}
}, peerSelected: { [weak self] peer in
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
peerSelected(peer.id, true, false)
peerSelected(peer, true, false)
}
}, disabledPeerSelected: { [weak self] peer in
if let strongSelf = self, let disabledPeerSelected = strongSelf.disabledPeerSelected {
@ -491,7 +496,7 @@ public final class ChatListNode: ListView {
}
}, messageSelected: { [weak self] peer, message, isAd in
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
peerSelected(peer.id, true, isAd)
peerSelected(peer, true, isAd)
}
}, groupSelected: { [weak self] groupId in
if let strongSelf = self, let groupSelected = strongSelf.groupSelected {
@ -586,7 +591,7 @@ public final class ChatListNode: ListView {
let currentRemovingPeerId = self.currentRemovingPeerId
let savedMessagesPeer: Signal<Peer?, NoError>
if case let .peers(filter) = mode, filter.contains(.onlyWriteable) {
if case let .peers(filter, _) = mode, filter.contains(.onlyWriteable) {
savedMessagesPeer = context.account.postbox.loadedPeerWithId(context.account.peerId)
|> map(Optional.init)
} else {
@ -639,7 +644,7 @@ public final class ChatListNode: ListView {
switch mode {
case .chatList:
return true
case let .peers(filter):
case let .peers(filter, _):
guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false }
guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false }
guard !filter.contains(.onlyPrivateChats) || peer.peerId.namespace == Namespaces.Peer.CloudUser else { return false }
@ -1422,6 +1427,35 @@ public final class ChatListNode: ListView {
}
}
var isNavigationHidden: Bool {
switch self.visibleContentOffset() {
case let .known(value) where abs(value) < navigationBarSearchContentHeight:
return false
default:
return true
}
}
func adjustScrollOffsetForNavigation(isNavigationHidden: Bool) {
if self.isNavigationHidden == isNavigationHidden {
return
}
var scrollToItem: ListViewScrollToItem?
switch self.visibleContentOffset() {
case let .known(value) where abs(value) < navigationBarSearchContentHeight:
if isNavigationHidden {
scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
}
default:
if !isNavigationHidden {
scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
}
}
if let scrollToItem = scrollToItem {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
}
}
public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
@ -1524,9 +1558,9 @@ public final class ChatListNode: ListView {
}
let entryCount = chatListView.filteredEntries.count
var current: (ChatListIndex, PeerId, Int)? = nil
var previous: (ChatListIndex, PeerId)? = nil
var next: (ChatListIndex, PeerId)? = nil
var current: (ChatListIndex, Peer, Int)? = nil
var previous: (ChatListIndex, Peer)? = nil
var next: (ChatListIndex, Peer)? = nil
outer: for i in range.firstIndex ..< range.lastIndex {
if i < 0 || i >= entryCount {
@ -1536,7 +1570,7 @@ public final class ChatListNode: ListView {
switch chatListView.filteredEntries[entryCount - i - 1] {
case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _):
if interaction.highlightedChatLocation?.location == ChatLocation.peer(peer.peerId) {
current = (index, peer.peerId, entryCount - i - 1)
current = (index, peer.peer!, entryCount - i - 1)
break outer
}
default:
@ -1556,22 +1590,35 @@ public final class ChatListNode: ListView {
} else {
position = .later(than: nil)
}
let _ = (relativeUnreadChatListIndex(position: position) |> deliverOnMainQueue).start(next: { [weak self] index in
guard let strongSelf = self, let index = index else {
let postbox = self.context.account.postbox
let _ = (relativeUnreadChatListIndex(position: position)
|> mapToSignal { index -> Signal<(ChatListIndex, Peer)?, NoError> in
if let index = index {
return postbox.transaction { transaction -> (ChatListIndex, Peer)? in
return transaction.getPeer(index.messageIndex.id.peerId).flatMap { peer -> (ChatListIndex, Peer)? in
(index, peer)
}
}
} else {
return .single(nil)
}
}
|> deliverOnMainQueue).start(next: { [weak self] indexAndPeer in
guard let strongSelf = self, let (index, peer) = indexAndPeer else {
return
}
let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: strongSelf.currentlyVisibleLatestChatListIndex() ?? .absoluteUpperBound, scrollPosition: .center(.top), animated: true, filter: strongSelf.chatListFilter)
strongSelf.setChatListLocation(location)
strongSelf.peerSelected?(index.messageIndex.id.peerId, false, false)
strongSelf.peerSelected?(peer, false, false)
})
case .previous(unread: false), .next(unread: false):
var target: (ChatListIndex, PeerId)? = nil
var target: (ChatListIndex, Peer)? = nil
if let current = current, entryCount > 1 {
if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] {
next = (index, peer.peerId)
next = (index, peer.peer!)
}
if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] {
previous = (index, peer.peerId)
previous = (index, peer.peer!)
}
if case .previous = option {
target = previous
@ -1580,7 +1627,7 @@ public final class ChatListNode: ListView {
}
} else if entryCount > 0 {
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] {
target = (index, peer.peerId)
target = (index, peer.peer!)
}
}
if let target = target {
@ -1589,7 +1636,15 @@ public final class ChatListNode: ListView {
self.peerSelected?(target.1, false, false)
}
case let .peerId(peerId):
self.peerSelected?(peerId, false, false)
let _ = (self.context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self, let peer = peer else {
return
}
strongSelf.peerSelected?(peer, false, false)
})
case let .index(index):
guard index < 10 else {
return
@ -1607,7 +1662,7 @@ public final class ChatListNode: ListView {
if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _) = entries[10 - index - 1] {
let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true, filter: filter)
self.setChatListLocation(location)
self.peerSelected?(renderedPeer.peerId, false, false)
self.peerSelected?(renderedPeer.peer!, false, false)
}
})
})

View File

@ -29,7 +29,7 @@ struct ChatListNodeViewUpdate {
let scrollPosition: ChatListNodeViewScrollPosition?
}
func chatListFilterPredicate(filter: ChatListFilter) -> ChatListFilterPredicate {
func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilterPredicate {
let includePeers = Set(filter.includePeers)
return ChatListFilterPredicate(includePeerIds: includePeers, include: { peer, notificationSettings, isUnread in
if filter.excludeRead {
@ -97,7 +97,7 @@ func chatListFilterPredicate(filter: ChatListFilter) -> ChatListFilterPredicate
}
func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal<ChatListNodeViewUpdate, NoError> {
let filterPredicate: ChatListFilterPredicate? = location.filter.flatMap(chatListFilterPredicate)
let filterPredicate: ChatListFilterPredicate? = (location.filter?.data).flatMap(chatListFilterPredicate)
switch location {
case let .initial(count, _):

View File

@ -320,7 +320,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
unreadCountItems.append(.total(nil))
var additionalPeerIds = Set<PeerId>()
for filter in filters {
additionalPeerIds.formUnion(filter.includePeers)
additionalPeerIds.formUnion(filter.data.includePeers)
}
if !additionalPeerIds.isEmpty {
for peerId in additionalPeerIds {
@ -390,22 +390,22 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
var shouldUpdateLayout = false
for filter in filters {
var tags: [PeerSummaryCounterTags] = []
if filter.categories.contains(.privateChats) {
if filter.data.categories.contains(.privateChats) {
tags.append(.privateChat)
}
if filter.categories.contains(.secretChats) {
if filter.data.categories.contains(.secretChats) {
tags.append(.secretChat)
}
if filter.categories.contains(.privateGroups) {
if filter.data.categories.contains(.privateGroups) {
tags.append(.privateGroup)
}
if filter.categories.contains(.bots) {
if filter.data.categories.contains(.bots) {
tags.append(.bot)
}
if filter.categories.contains(.publicGroups) {
if filter.data.categories.contains(.publicGroups) {
tags.append(.publicGroup)
}
if filter.categories.contains(.channels) {
if filter.data.categories.contains(.channels) {
tags.append(.channel)
}
@ -417,7 +417,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
}
}
}
for peerId in filter.includePeers {
for peerId in filter.data.includePeers {
if let (tag, peerCount) = peerTagAndCount[peerId] {
if !tags.contains(tag) {
if peerCount != 0 {
@ -513,7 +513,7 @@ private final class TabBarChatListFilterControllerNode: ViewControllerTracingNod
unreadCountItems.append(.total(nil))
var additionalPeerIds = Set<PeerId>()
for preset in presetList {
additionalPeerIds.formUnion(preset.includePeers)
additionalPeerIds.formUnion(preset.data.includePeers)
}
if !additionalPeerIds.isEmpty {
for peerId in additionalPeerIds {
@ -567,22 +567,22 @@ private final class TabBarChatListFilterControllerNode: ViewControllerTracingNod
let badgeString: String
if let preset = contentNode.preset {
var tags: [PeerSummaryCounterTags] = []
if preset.categories.contains(.privateChats) {
if preset.data.categories.contains(.privateChats) {
tags.append(.privateChat)
}
if preset.categories.contains(.secretChats) {
if preset.data.categories.contains(.secretChats) {
tags.append(.secretChat)
}
if preset.categories.contains(.privateGroups) {
if preset.data.categories.contains(.privateGroups) {
tags.append(.privateGroup)
}
if preset.categories.contains(.bots) {
if preset.data.categories.contains(.bots) {
tags.append(.bot)
}
if preset.categories.contains(.publicGroups) {
if preset.data.categories.contains(.publicGroups) {
tags.append(.publicGroup)
}
if preset.categories.contains(.channels) {
if preset.data.categories.contains(.channels) {
tags.append(.channel)
}
@ -594,7 +594,7 @@ private final class TabBarChatListFilterControllerNode: ViewControllerTracingNod
}
}
}
for peerId in preset.includePeers {
for peerId in preset.data.includePeers {
if let (tag, peerCount) = peerTagAndCount[peerId] {
if !tags.contains(tag) {
count += peerCount

View File

@ -747,7 +747,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.withoutBlurDimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
}
self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in
self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15 * animationDurationFactor, removeOnCompletion: false, completion: { _ in
completedActionsNode = true
intermediateCompletion()
})

View File

@ -1,16 +1,23 @@
import Foundation
import UIKit
private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> Bool {
if view.disablesInteractiveTransitionGestureRecognizer {
return true
}
private enum HorizontalGestures {
case none
case some
case strict
}
private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> HorizontalGestures {
if let disablesInteractiveTransitionGestureRecognizerNow = view.disablesInteractiveTransitionGestureRecognizerNow, disablesInteractiveTransitionGestureRecognizerNow() {
return true
return .strict
}
if view.disablesInteractiveTransitionGestureRecognizer {
return .some
}
if let point = point, let test = view.interactiveTransitionGestureRecognizerTest, test(point) {
return true
return .some
}
if let view = view as? ListViewBackingView {
@ -20,14 +27,14 @@ private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> Bool {
let term2: Double = abs(angle + Double.pi / 2.0)
let term3: Double = abs(angle - Double.pi * 3.0 / 2.0)
if term1 < 0.001 || term2 < 0.001 || term3 < 0.001 {
return true
return .some
}
}
if let superview = view.superview {
return hasHorizontalGestures(superview, point: point != nil ? view.convert(point!, to: superview) : nil)
} else {
return false
return .none
}
}
@ -38,18 +45,23 @@ public struct InteractiveTransitionGestureRecognizerDirections: OptionSet {
self.rawValue = rawValue
}
public static let left = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 0)
public static let right = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 1)
public static let leftEdge = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 2)
public static let rightEdge = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 3)
public static let leftCenter = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 0)
public static let rightCenter = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 1)
public static let left: InteractiveTransitionGestureRecognizerDirections = [.leftEdge, .leftCenter]
public static let right: InteractiveTransitionGestureRecognizerDirections = [.rightEdge, .rightCenter]
}
public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
private let allowedDirections: () -> InteractiveTransitionGestureRecognizerDirections
private let allowedDirections: (CGPoint) -> InteractiveTransitionGestureRecognizerDirections
private var validatedGesture = false
private var firstLocation: CGPoint = CGPoint()
private var currentAllowedDirections: InteractiveTransitionGestureRecognizerDirections = []
public init(target: Any?, action: Selector?, allowedDirections: @escaping () -> InteractiveTransitionGestureRecognizerDirections) {
public init(target: Any?, action: Selector?, allowedDirections: @escaping (CGPoint) -> InteractiveTransitionGestureRecognizerDirections) {
self.allowedDirections = allowedDirections
super.init(target: target, action: action)
@ -65,37 +77,68 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
}
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
self.currentAllowedDirections = self.allowedDirections()
if self.currentAllowedDirections.isEmpty {
let touch = touches.first!
let point = touch.location(in: self.view)
var allowedDirections = self.allowedDirections(point)
if allowedDirections.isEmpty {
self.state = .failed
return
}
super.touchesBegan(touches, with: event)
let touch = touches.first!
self.firstLocation = touch.location(in: self.view)
self.firstLocation = point
if let target = self.view?.hitTest(self.firstLocation, with: event) {
if hasHorizontalGestures(target, point: self.view?.convert(self.firstLocation, to: target)) {
self.state = .cancelled
let horizontalGestures = hasHorizontalGestures(target, point: self.view?.convert(self.firstLocation, to: target))
switch horizontalGestures {
case .some, .strict:
if case .strict = horizontalGestures {
allowedDirections = []
} else if allowedDirections.contains(.leftEdge) || allowedDirections.contains(.rightEdge) {
allowedDirections.remove(.leftCenter)
allowedDirections.remove(.rightCenter)
}
case .none:
break
}
}
if allowedDirections.isEmpty {
self.state = .failed
} else {
self.currentAllowedDirections = allowedDirections
}
}
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
let location = touches.first!.location(in: self.view)
let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y)
let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y)
let absTranslationX: CGFloat = abs(translation.x)
let absTranslationY: CGFloat = abs(translation.y)
let size = self.view?.bounds.size ?? CGSize()
let edgeWidth: CGFloat = 20.0
if !self.validatedGesture {
if self.currentAllowedDirections.contains(.right) && self.firstLocation.x < 16.0 {
self.validatedGesture = true
} else if !self.currentAllowedDirections.contains(.left) && translation.x < 0.0 {
if self.firstLocation.x < edgeWidth && !self.currentAllowedDirections.contains(.rightEdge) {
self.state = .failed
} else if !self.currentAllowedDirections.contains(.right) && translation.x > 0.0 {
return
}
if self.firstLocation.x > size.width - edgeWidth && !self.currentAllowedDirections.contains(.leftEdge) {
self.state = .failed
return
}
if self.currentAllowedDirections.contains(.rightEdge) && self.firstLocation.x < edgeWidth {
self.validatedGesture = true
} else if self.currentAllowedDirections.contains(.leftEdge) && self.firstLocation.x > size.width - edgeWidth {
self.validatedGesture = true
} else if !self.currentAllowedDirections.contains(.leftCenter) && translation.x < 0.0 {
self.state = .failed
} else if !self.currentAllowedDirections.contains(.rightCenter) && translation.x > 0.0 {
self.state = .failed
} else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 {
self.state = .failed
@ -104,7 +147,7 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
}
}
if validatedGesture {
if self.validatedGesture {
super.touchesMoved(touches, with: event)
}
}

View File

@ -111,7 +111,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
override func didLoad() {
super.didLoad()
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] in
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
guard let strongSelf = self, strongSelf.controllers.count > 1 else {
return []
}

View File

@ -91,7 +91,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
self.scrollNode.view.clipsToBounds = false
self.scrollNode.view.delegate = self
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] in
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
guard let strongSelf = self, !strongSelf.isDismissed else {
return []
}

View File

@ -25,10 +25,10 @@ final class TabBarControllerNode: ASDisplayNode {
}
}
init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) {
init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) {
self.theme = theme
self.navigationBar = navigationBar
self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction)
self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction, swipeAction: swipeAction)
self.toolbarActionSelected = toolbarActionSelected
super.init()

View File

@ -240,6 +240,13 @@ open class TabBarController: ViewController {
if index >= 0 && index < strongSelf.controllers.count {
strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture)
}
}, swipeAction: { [weak self] index, direction in
guard let strongSelf = self else {
return
}
if index >= 0 && index < strongSelf.controllers.count {
strongSelf.controllers[index].tabBarItemSwipeAction(direction: direction)
}
}, toolbarActionSelected: { [weak self] action in
self?.currentController?.toolbarActionSelected(action: action)
})

View File

@ -89,6 +89,11 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
private let badgeFont = Font.regular(13.0)
public enum TabBarItemSwipeDirection {
case left
case right
}
private final class TabBarItemNode: ASDisplayNode {
let extractedContainerNode: ContextExtractedContentContainingNode
let containerNode: ContextControllerSourceNode
@ -98,6 +103,8 @@ private final class TabBarItemNode: ASDisplayNode {
let contextTextImageNode: ASImageNode
var contentWidth: CGFloat?
var swiped: ((TabBarItemSwipeDirection) -> Void)?
override init() {
self.extractedContainerNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
@ -147,6 +154,26 @@ private final class TabBarItemNode: ASDisplayNode {
transition.updateAlpha(node: strongSelf.contextImageNode, alpha: isExtracted ? 1.0 : 0.0)
transition.updateAlpha(node: strongSelf.contextTextImageNode, alpha: isExtracted ? 1.0 : 0.0)
}
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
leftSwipe.direction = .left
self.containerNode.view.addGestureRecognizer(leftSwipe)
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
rightSwipe.direction = .right
self.containerNode.view.addGestureRecognizer(rightSwipe)
}
@objc private func swipeGesture(_ gesture: UISwipeGestureRecognizer) {
if case .ended = gesture.state {
self.containerNode.cancelGesture()
switch gesture.direction {
case .left:
self.swiped?(.left)
default:
self.swiped?(.right)
}
}
}
}
@ -174,7 +201,7 @@ private final class TabBarNodeContainer {
var selectedImageValue: UIImage?
var appliedSelectedImageValue: UIImage?
init(item: TabBarNodeItem, imageNode: TabBarItemNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void, contextAction: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void) {
init(item: TabBarNodeItem, imageNode: TabBarItemNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void, contextAction: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (TabBarItemSwipeDirection) -> Void) {
self.item = item.item
self.imageNode = imageNode
@ -226,6 +253,9 @@ private final class TabBarNodeContainer {
}
contextAction(strongSelf.imageNode.extractedContainerNode, gesture)
}
imageNode.swiped = { [weak self] direction in
swipeAction(direction)
}
imageNode.containerNode.isGestureEnabled = item.hasContext
}
@ -270,6 +300,7 @@ class TabBarNode: ASDisplayNode {
private let itemSelected: (Int, Bool, [ASDisplayNode]) -> Void
private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void
private let swipeAction: (Int, TabBarItemSwipeDirection) -> Void
private var theme: TabBarControllerTheme
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
@ -283,9 +314,10 @@ class TabBarNode: ASDisplayNode {
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void) {
init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void) {
self.itemSelected = itemSelected
self.contextAction = contextAction
self.swipeAction = swipeAction
self.theme = theme
self.separatorNode = ASDisplayNode()
@ -382,6 +414,8 @@ class TabBarNode: ASDisplayNode {
}, contextAction: { [weak self] node, gesture in
self?.tapRecognizer?.cancel()
self?.contextAction(i, node, gesture)
}, swipeAction: { [weak self] direction in
self?.swipeAction(i, direction)
})
if let selectedIndex = self.selectedIndex, selectedIndex == i {
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)

View File

@ -629,6 +629,9 @@ public enum ViewControllerNavigationPresentation {
open func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
}
open func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) {
}
}
func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool {

View File

@ -4,17 +4,11 @@ static_library(
name = "EncryptionKeyVisualization",
srcs = glob([
"Sources/*.swift",
"Sources/*.m",
]),
headers = glob([
"Sources/*.h",
], exclude = ["Sources/EncryptionKeyVisualization.h"]),
exported_headers = glob([
"Sources/*.h",
], exclude = ["Sources/EncryptionKeyVisualization.h"]),
deps = [
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/EncryptionKeyVisualization/Impl:EncryptionKeyVisualizationImpl",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -0,0 +1,18 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "EncryptionKeyVisualizationImpl",
srcs = glob([
"Sources/*.m",
]),
headers = glob([
"Sources/*.h",
]),
exported_headers = glob([
"PublicHeaders/**/*.h",
]),
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
)

View File

@ -0,0 +1,20 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "FFMpegBinding",
srcs = glob([
"Sources/*.m",
]),
headers = glob([
"Sources/*.h",
]),
exported_headers = glob([
"Public/**/*.h",
]),
deps = [
"//submodules/ffmpeg:ffmpeg",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
],
)

View File

@ -91,6 +91,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
let recognizer = ItemListRevealOptionsGestureRecognizer(target: self, action: #selector(self.revealGesture(_:)))
self.recognizer = recognizer
recognizer.delegate = self
recognizer.allowAnyDirection = self.allowAnyDirection
self.view.addGestureRecognizer(recognizer)
@ -100,6 +101,16 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
self.view.addGestureRecognizer(tapRecognizer)
self.view.disablesInteractiveTransitionGestureRecognizer = self.allowAnyDirection
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
guard let strongSelf = self else {
return false
}
if !strongSelf.revealOffset.isZero {
return true
}
return false
}
}
open func setRevealOptions(_ options: (left: [ItemListRevealOption], right: [ItemListRevealOption])) {

View File

@ -45,9 +45,10 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let shouldUpdateText: (String) -> Bool
let processPaste: ((String) -> String)?
let updatedFocus: ((Bool) -> Void)?
let cleared: (() -> Void)?
public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, enabled: Bool = true, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void) {
public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, enabled: Bool = true, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
self.presentationData = presentationData
self.title = title
self.text = text
@ -64,6 +65,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
self.processPaste = processPaste
self.updatedFocus = updatedFocus
self.action = action
self.cleared = cleared
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
@ -420,6 +422,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
@objc private func clearButtonPressed() {
self.textNode.textField.text = ""
self.textUpdated("")
self.item?.cleared?()
}
private func textUpdated(_ text: String) {

View File

@ -3,7 +3,7 @@ load("//Config:buck_rule_macros.bzl", "static_library")
apple_resource(
name = "LegacyComponentsResources",
dirs = [
"LegacyComponents/Resources/LegacyComponentsResources.bundle",
"Resources/LegacyComponentsResources.bundle",
],
visibility = ["PUBLIC"],
)
@ -11,351 +11,17 @@ apple_resource(
static_library(
name = "LegacyComponents",
srcs = glob([
"LegacyComponents/*.m",
"LegacyComponents/*.mm",
"LegacyComponents/*.c",
"LegacyComponents/*.cpp",
"Sources/*.m",
"Sources/*.mm",
"Sources/*.c",
"Sources/*.cpp",
]),
headers = glob([
"LegacyComponents/*.h",
"Sources/*.h",
]),
exported_headers = glob([
"PublicHeaders/**/*.h",
]),
exported_headers = [
"LegacyComponents/LegacyComponentsGlobals.h",
"LegacyComponents/LegacyComponentsContext.h",
"LegacyComponents/TGLocalization.h",
"LegacyComponents/TGPluralization.h",
"LegacyComponents/TGStringUtils.h",
"LegacyComponents/TGPhoneUtils.h",
"LegacyComponents/NSObject+TGLock.h",
"LegacyComponents/RMPhoneFormat.h",
"LegacyComponents/NSInputStream+TL.h",
"LegacyComponents/TGFont.h",
"LegacyComponents/TGImageUtils.h",
"LegacyComponents/TGDateUtils.h",
"LegacyComponents/Freedom.h",
"LegacyComponents/FreedomUIKit.h",
"LegacyComponents/TGHacks.h",
"LegacyComponents/TGImageBlur.h",
"LegacyComponents/UIDevice+PlatformInfo.h",
"LegacyComponents/TGObserverProxy.h",
"LegacyComponents/TGModernCache.h",
"LegacyComponents/TGMemoryImageCache.h",
"LegacyComponents/LegacyComponentsAccessChecker.h",
"LegacyComponents/TGTimerTarget.h",
"LegacyComponents/TGKeyCommand.h",
"LegacyComponents/TGKeyCommandController.h",
"LegacyComponents/TGWeakDelegate.h",
"LegacyComponents/TGCache.h",
"LegacyComponents/TGLiveUploadInterface.h",
"LegacyComponents/JNWSpringAnimation.h",
"LegacyComponents/POPAnimationEvent.h",
"LegacyComponents/POPAnimationTracer.h",
"LegacyComponents/POPAnimation.h",
"LegacyComponents/POPBasicAnimation.h",
"LegacyComponents/POPCustomAnimation.h",
"LegacyComponents/POPDecayAnimation.h",
"LegacyComponents/POPPropertyAnimation.h",
"LegacyComponents/POPSpringAnimation.h",
"LegacyComponents/POPGeometry.h",
"LegacyComponents/POPAnimatableProperty.h",
"LegacyComponents/lmdb.h",
"LegacyComponents/PSLMDBTable.h",
"LegacyComponents/PSLMDBKeyValueStore.h",
"LegacyComponents/PSLMDBKeyValueReaderWriter.h",
"LegacyComponents/PSLMDBKeyValueCursor.h",
"LegacyComponents/PSCoding.h",
"LegacyComponents/PSData.h",
"LegacyComponents/PSKeyValueCoder.h",
"LegacyComponents/PSKeyValueDecoder.h",
"LegacyComponents/PSKeyValueEncoder.h",
"LegacyComponents/PSKeyValueReader.h",
"LegacyComponents/PSKeyValueStore.h",
"LegacyComponents/PSKeyValueWriter.h",
"LegacyComponents/TGPeerIdAdapter.h",
"LegacyComponents/TGUser.h",
"LegacyComponents/TGBotInfo.h",
"LegacyComponents/TGBotComandInfo.h",
"LegacyComponents/TGConversation.h",
"LegacyComponents/TGModernConversationAssociatedInputPanel.h",
"LegacyComponents/TGModernConversationHashtagsAssociatedPanel.h",
"LegacyComponents/TGModernConversationMentionsAssociatedPanel.h",
"LegacyComponents/TGModernConversationAlphacodeAssociatedPanel.h",
"LegacyComponents/TGSuggestionContext.h",
"LegacyComponents/TGAlphacode.h",
"LegacyComponents/TGTextCheckingResult.h",
"LegacyComponents/TGChannelBannedRights.h",
"LegacyComponents/TGChannelAdminRights.h",
"LegacyComponents/TGDatabaseMessageDraft.h",
"LegacyComponents/TGMessageGroup.h",
"LegacyComponents/TGMessageHole.h",
"LegacyComponents/TGMessageViewCountContentProperty.h",
"LegacyComponents/TGAuthorSignatureMediaAttachment.h",
"LegacyComponents/TGWebDocument.h",
"LegacyComponents/TGInvoiceMediaAttachment.h",
"LegacyComponents/TGGameMediaAttachment.h",
"LegacyComponents/TGViaUserAttachment.h",
"LegacyComponents/TGBotContextResultAttachment.h",
"LegacyComponents/TGMessageEntity.h",
"LegacyComponents/TGMessageEntityBold.h",
"LegacyComponents/TGMessageEntityBotCommand.h",
"LegacyComponents/TGMessageEntityCode.h",
"LegacyComponents/TGMessageEntityEmail.h",
"LegacyComponents/TGMessageEntityHashtag.h",
"LegacyComponents/TGMessageEntityItalic.h",
"LegacyComponents/TGMessageEntityMention.h",
"LegacyComponents/TGMessageEntityMentionName.h",
"LegacyComponents/TGMessageEntityPre.h",
"LegacyComponents/TGMessageEntityTextUrl.h",
"LegacyComponents/TGMessageEntityUrl.h",
"LegacyComponents/TGMessageEntitiesAttachment.h",
"LegacyComponents/TGBotReplyMarkup.h",
"LegacyComponents/TGBotReplyMarkupButton.h",
"LegacyComponents/TGBotReplyMarkupRow.h",
"LegacyComponents/TGReplyMarkupAttachment.h",
"LegacyComponents/TGInstantPage.h",
"LegacyComponents/TGWebPageMediaAttachment.h",
"LegacyComponents/TGAudioMediaAttachment.h",
"LegacyComponents/TGAudioWaveform.h",
"LegacyComponents/TGStickerPackReference.h",
"LegacyComponents/TGDocumentAttributeFilename.h",
"LegacyComponents/TGDocumentAttributeImageSize.h",
"LegacyComponents/TGDocumentAttributeSticker.h",
"LegacyComponents/TGDocumentAttributeVideo.h",
"LegacyComponents/TGDocumentAttributeAnimated.h",
"LegacyComponents/TGDocumentAttributeAudio.h",
"LegacyComponents/TGDocumentMediaAttachment.h",
"LegacyComponents/TGUnsupportedMediaAttachment.h",
"LegacyComponents/TGForwardedMessageMediaAttachment.h",
"LegacyComponents/TGContactMediaAttachment.h",
"LegacyComponents/TGVideoInfo.h",
"LegacyComponents/TGVideoMediaAttachment.h",
"LegacyComponents/TGLocalMessageMetaMediaAttachment.h",
"LegacyComponents/TGLocationMediaAttachment.h",
"LegacyComponents/TGImageMediaAttachment.h",
"LegacyComponents/TGMediaAttachment.h",
"LegacyComponents/TGImageInfo.h",
"LegacyComponents/TGMediaOriginInfo.h",
"LegacyComponents/TGMessage.h",
"LegacyComponents/TGStickerPack.h",
"LegacyComponents/TGStickerAssociation.h",
"LegacyComponents/TGPhotoMaskPosition.h",
"LegacyComponents/ActionStage.h",
"LegacyComponents/ASActor.h",
"LegacyComponents/ASHandle.h",
"LegacyComponents/ASQueue.h",
"LegacyComponents/ASWatcher.h",
"LegacyComponents/SGraphListNode.h",
"LegacyComponents/SGraphNode.h",
"LegacyComponents/SGraphObjectNode.h",
"LegacyComponents/TGLabel.h",
"LegacyComponents/TGToolbarButton.h",
"LegacyComponents/UIScrollView+TGHacks.h",
"LegacyComponents/TGAnimationBlockDelegate.h",
"LegacyComponents/TGBackdropView.h",
"LegacyComponents/UIImage+TG.h",
"LegacyComponents/TGStaticBackdropAreaData.h",
"LegacyComponents/TGStaticBackdropImageData.h",
"LegacyComponents/TGImageLuminanceMap.h",
"LegacyComponents/TGFullscreenContainerView.h",
"LegacyComponents/TGDoubleTapGestureRecognizer.h",
"LegacyComponents/TGModernButton.h",
"LegacyComponents/TGModernToolbarButton.h",
"LegacyComponents/TGModernBackToolbarButton.h",
"LegacyComponents/UIControl+HitTestEdgeInsets.h",
"LegacyComponents/TGMenuView.h",
"LegacyComponents/TGImageView.h",
"LegacyComponents/UICollectionView+Utils.h",
"LegacyComponents/TGMessageImageViewOverlayView.h",
"LegacyComponents/TGLetteredAvatarView.h",
"LegacyComponents/TGGradientLabel.h",
"LegacyComponents/TGRemoteImageView.h",
"LegacyComponents/TGProgressSpinnerView.h",
"LegacyComponents/TGProgressWindow.h",
"LegacyComponents/TGMenuSheetController.h",
"LegacyComponents/TGMenuSheetButtonItemView.h",
"LegacyComponents/TGMenuSheetCollectionView.h",
"LegacyComponents/TGMenuSheetItemView.h",
"LegacyComponents/TGMenuSheetTitleItemView.h",
"LegacyComponents/TGMenuSheetView.h",
"LegacyComponents/HPGrowingTextView.h",
"LegacyComponents/HPTextViewInternal.h",
"LegacyComponents/TGInputTextTag.h",
"LegacyComponents/TGStickerKeyboardTabPanel.h",
"LegacyComponents/TGItemPreviewController.h",
"LegacyComponents/TGItemPreviewView.h",
"LegacyComponents/TGItemMenuSheetPreviewView.h",
"LegacyComponents/TGImageManager.h",
"LegacyComponents/TGDataResource.h",
"LegacyComponents/TGImageDataSource.h",
"LegacyComponents/TGImageManagerTask.h",
"LegacyComponents/TGRTLScreenEdgePanGestureRecognizer.h",
"LegacyComponents/TGNavigationController.h",
"LegacyComponents/TGNavigationBar.h",
"LegacyComponents/TGViewController.h",
"LegacyComponents/TGViewController+TGRecursiveEnumeration.h",
"LegacyComponents/TGOverlayController.h",
"LegacyComponents/TGOverlayControllerWindow.h",
"LegacyComponents/TGMediaAssetsLibrary.h",
"LegacyComponents/TGMediaAssetsModernLibrary.h",
"LegacyComponents/TGMediaAsset.h",
"LegacyComponents/TGMediaAssetFetchResult.h",
"LegacyComponents/TGMediaAssetFetchResultChange.h",
"LegacyComponents/TGMediaAssetGroup.h",
"LegacyComponents/TGMediaAssetMoment.h",
"LegacyComponents/TGMediaAssetMomentList.h",
"LegacyComponents/TGMediaAssetImageSignals.h",
"LegacyComponents/TGMediaSelectionContext.h",
"LegacyComponents/TGMediaEditingContext.h",
"LegacyComponents/TGModernGalleryZoomableItemViewContent.h",
"LegacyComponents/TGModernGalleryZoomableScrollView.h",
"LegacyComponents/TGModernGalleryZoomableScrollViewSwipeGestureRecognizer.h",
"LegacyComponents/TGModernGalleryVideoView.h",
"LegacyComponents/TGModernGalleryScrollView.h",
"LegacyComponents/TGModernGalleryItem.h",
"LegacyComponents/TGModernGalleryItemView.h",
"LegacyComponents/TGModernGalleryImageItem.h",
"LegacyComponents/TGModernGalleryImageItemView.h",
"LegacyComponents/TGModernGalleryImageItemImageView.h",
"LegacyComponents/TGModernGalleryEditableItemView.h",
"LegacyComponents/TGModernGallerySelectableItem.h",
"LegacyComponents/TGModernGalleryDefaultFooterAccessoryView.h",
"LegacyComponents/TGModernGalleryDefaultFooterView.h",
"LegacyComponents/TGModernGalleryDefaultHeaderView.h",
"LegacyComponents/TGModernGalleryDefaultInterfaceView.h",
"LegacyComponents/TGModernGalleryInterfaceView.h",
"LegacyComponents/TGModernGalleryImageItemContainerView.h",
"LegacyComponents/TGModernGalleryZoomableItemView.h",
"LegacyComponents/TGModernGalleryModel.h",
"LegacyComponents/TGModernGalleryTransitionView.h",
"LegacyComponents/TGModernGalleryView.h",
"LegacyComponents/TGModernGalleryContainerView.h",
"LegacyComponents/TGModernGalleryEmbeddedStickersHeaderView.h",
"LegacyComponents/TGModernGalleryController.h",
"LegacyComponents/TGPhotoToolbarView.h",
"LegacyComponents/TGMediaPickerGalleryModel.h",
"LegacyComponents/TGMediaPickerGalleryInterfaceView.h",
"LegacyComponents/TGPhotoEditorController.h",
"LegacyComponents/TGMediaAvatarEditorTransition.h",
"LegacyComponents/TGPhotoEditorUtils.h",
"LegacyComponents/PGPhotoEditorValues.h",
"LegacyComponents/TGVideoEditAdjustments.h",
"LegacyComponents/AVURLAsset+TGMediaItem.h",
"LegacyComponents/UIImage+TGMediaEditableItem.h",
"LegacyComponents/TGMediaVideoConverter.h",
"LegacyComponents/TGGifConverter.h",
"LegacyComponents/TGPhotoEditorAnimation.h",
"LegacyComponents/TGPaintingData.h",
"LegacyComponents/TGPaintUtils.h",
"LegacyComponents/TGPhotoPaintEntity.h",
"LegacyComponents/TGPhotoPaintStickerEntity.h",
"LegacyComponents/TGPaintUndoManager.h",
"LegacyComponents/PGCamera.h",
"LegacyComponents/PGCameraCaptureSession.h",
"LegacyComponents/PGCameraDeviceAngleSampler.h",
"LegacyComponents/PGCameraMomentSegment.h",
"LegacyComponents/PGCameraMomentSession.h",
"LegacyComponents/PGCameraMovieWriter.h",
"LegacyComponents/PGCameraShotMetadata.h",
"LegacyComponents/PGCameraVolumeButtonHandler.h",
"LegacyComponents/TGCameraPreviewView.h",
"LegacyComponents/TGCameraMainPhoneView.h",
"LegacyComponents/TGCameraMainTabletView.h",
"LegacyComponents/TGCameraMainView.h",
"LegacyComponents/TGCameraFlashActiveView.h",
"LegacyComponents/TGCameraFlashControl.h",
"LegacyComponents/TGCameraFlipButton.h",
"LegacyComponents/TGCameraInterfaceAssets.h",
"LegacyComponents/TGCameraModeControl.h",
"LegacyComponents/TGCameraSegmentsView.h",
"LegacyComponents/TGCameraShutterButton.h",
"LegacyComponents/TGCameraTimeCodeView.h",
"LegacyComponents/TGCameraZoomView.h",
"LegacyComponents/TGCameraPhotoPreviewController.h",
"LegacyComponents/TGCameraController.h",
"LegacyComponents/TGCameraCapturedPhoto.h",
"LegacyComponents/TGCameraCapturedVideo.h",
"LegacyComponents/TGPhotoVideoEditor.h",
"LegacyComponents/TGModernConversationTitleActivityIndicator.h",
"LegacyComponents/TGEmbedPIPButton.h",
"LegacyComponents/TGEmbedPIPPullArrowView.h",
"LegacyComponents/TGEmbedPlayerState.h",
"LegacyComponents/TGAttachmentCameraView.h",
"LegacyComponents/TGMediaAvatarMenuMixin.h",
"LegacyComponents/TGPassportAttachMenu.h",
"LegacyComponents/TGPassportScanController.h",
"LegacyComponents/TGPassportOCR.h",
"LegacyComponents/TGPassportMRZ.h",
"LegacyComponents/TGPassportICloud.h",
"LegacyComponents/TGEmbedPlayerView.h",
"LegacyComponents/LegacyHTTPRequestOperation.h",
"LegacyComponents/TGAttachmentCarouselItemView.h",
"LegacyComponents/TGMediaAssetsController.h",
"LegacyComponents/TGLocationVenue.h",
"LegacyComponents/TGLocationMapViewController.h",
"LegacyComponents/TGLocationPickerController.h",
"LegacyComponents/TGLocationViewController.h",
"LegacyComponents/TGListsTableView.h",
"LegacyComponents/TGSearchBar.h",
"LegacyComponents/TGSearchDisplayMixin.h",
"LegacyComponents/TGPhotoEditorSliderView.h",
"LegacyComponents/TGClipboardGalleryMixin.h",
"LegacyComponents/TGClipboardGalleryPhotoItem.h",
"LegacyComponents/TGVideoMessageCaptureController.h",
"LegacyComponents/TGModernConversationInputMicButton.h",
"LegacyComponents/TGLocationPulseView.h",
"LegacyComponents/TGLocationWavesView.h",
"LegacyComponents/TGLocationLiveElapsedView.h",
"LegacyComponents/TGLocationLiveSessionItemView.h",
"LegacyComponents/TGTooltipView.h",
"LegacyComponents/TGCheckButtonView.h",
"LegacyComponents/TGClipboardMenu.h",
"LegacyComponents/TGImagePickerController.h",
"LegacyComponents/TGLegacyCameraController.h",
"LegacyComponents/TGProxyWindow.h",
"LegacyComponents/TGIconSwitchView.h",
"LegacyComponents/TGModernGalleryEditableItem.h",
"LegacyComponents/TGPhotoEditorButton.h",
"LegacyComponents/TGActionMediaAttachment.h",
"LegacyComponents/TGReplyMessageMediaAttachment.h",
"LegacyComponents/TGMessageEntityPhone.h",
"LegacyComponents/TGMessageEntityCashtag.h",
"LegacyComponents/TGPIPAblePlayerView.h",
"LegacyComponents/TGEmbedPlayerControls.h",
"LegacyComponents/TGMediaAssetsUtils.h",
],
deps = [
"//submodules/SSignalKit/SSignalKit:SSignalKit",
"//submodules/AppBundle:AppBundle",

View File

@ -16,6 +16,7 @@ objc_library(
"Sources/*.mm",
"Sources/*.c",
"Sources/*.cpp",
"Sources/*.h",
]),
hdrs = glob([
"PublicHeaders/**/*.h",

View File

@ -4,14 +4,7 @@ static_library(
name = "LegacyDataImport",
srcs = glob([
"Sources/**/*.swift",
"Sources/*.m",
]),
headers = glob([
"Sources/*.h",
], exclude = ["Sources/LegacyDataImport.h"]),
exported_headers = glob([
"Sources/*.h",
], exclude = ["Sources/LegacyDataImport.h"]),
deps = [
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
@ -21,6 +14,7 @@ static_library(
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/LegacyComponents:LegacyComponents",
"//submodules/LegacyDataImport/Impl:LegacyDataImportImpl",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -0,0 +1,28 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "LegacyDataImportImpl",
srcs = glob([
"Sources/*.m",
]),
headers = glob([
"Sources/*.h",
]),
exported_headers = glob([
"PublicHeaders/**/*.h",
]),
deps = [
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/TelegramNotices:TelegramNotices",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/LegacyComponents:LegacyComponents",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
)

View File

@ -13,7 +13,7 @@ static_library(
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/TelegramAudio:TelegramAudio",
"//submodules/FFMpeg:FFMpeg",
"//submodules/FFMpegBinding:FFMpegBinding",
"//submodules/RingBuffer:RingBuffer",
],
frameworks = [

View File

@ -9,7 +9,7 @@ static_library(
"Sources/**/*.h",
]),
exported_headers = glob([
"Sources/**/*.h",
"PublicHeaders/**/*.h",
]),
deps = [
"//submodules/EncryptionProvider:EncryptionProvider",

View File

@ -33,7 +33,7 @@ static_library(
"Sources/**/*.swift",
]),
exported_headers = glob([
"Sources/**/*.h",
"Public/**/*.h",
]),
deps = [
":opus_lib",

View File

@ -9,9 +9,9 @@ static_library(
headers = {
"ogg/ogg.h": "Sources/ogg/ogg.h",
"ogg/os_types.h": "Sources/ogg/os_types.h",
"OggOpusReader.h": "Sources/OggOpusReader.h",
"TGDataItem.h": "Sources/TGDataItem.h",
"TGOggOpusWriter.h": "Sources/TGOggOpusWriter.h",
"OggOpusReader.h": "PublicHeaders/OpusBinding/OggOpusReader.h",
"TGDataItem.h": "PublicHeaders/OpusBinding/TGDataItem.h",
"TGOggOpusWriter.h": "PublicHeaders/OpusBinding/TGOggOpusWriter.h",
"opusenc/diag_range.h": "Sources/opusenc/diag_range.h",
"opusenc/opus_header.h": "Sources/opusenc/opus_header.h",
"opusenc/picture.h": "Sources/opusenc/picture.h",
@ -19,11 +19,9 @@ static_library(
"opusfile/internal.h": "Sources/opusfile/internal.h",
"OpusBinding/opusfile.h": "Sources/opusfile/opusfile.h",
},
exported_headers = [
"Sources/TGDataItem.h",
"Sources/TGOggOpusWriter.h",
"Sources/OggOpusReader.h",
],
exported_headers = glob([
"PublicHeaders/**/*.h",
]),
deps = [
"//submodules/Opus:opus",
],

View File

@ -9,7 +9,7 @@ static_library(
"Sources/**/*.h",
]),
exported_headers = glob([
"Sources/**/*.h",
"PublicHeaders/**/*.h",
]),
deps = [
"//submodules/openssl:openssl",

View File

@ -18,7 +18,7 @@ static_library(
"//submodules/AccountContext:AccountContext",
"//submodules/MediaResources:MediaResources",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/WebP:WebPImage",
"//submodules/WebPBinding:WebPBinding",
"//submodules/AppBundle:AppBundle",
],
frameworks = [

View File

@ -421,8 +421,8 @@ final class ChatListTable: Table {
lowerEntries.append(entry)
return .accept
}
}, limit: count / 2 + 1)
if lowerEntries.count >= count / 2 + 1 {
}, limit: count + 1)
if lowerEntries.count >= count + 1 {
lower = lowerEntries.last
lowerEntries.removeLast()
}
@ -440,42 +440,12 @@ final class ChatListTable: Table {
upperEntries.append(entry)
return .accept
}
}, limit: count - lowerEntries.count + 1)
if upperEntries.count >= count - lowerEntries.count + 1 {
}, limit: count + 1)
if upperEntries.count >= count + 1 {
upper = upperEntries.last
upperEntries.removeLast()
}
if lowerEntries.count != 0 && lowerEntries.count + upperEntries.count < count {
var additionalLowerEntries: [ChatListIntermediateEntry] = []
let startEntryType: ChatListEntryType
switch lowerEntries.last! {
case .message:
startEntryType = .message
case .hole:
startEntryType = .hole
}
self.valueBox.filteredRange(self.table, start: self.key(groupId: groupId, index: lowerEntries.last!.index, type: startEntryType), end: self.lowerBound(groupId: groupId), values: { key, value in
let entry = readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value)
if let predicate = predicate {
if predicate(entry) {
additionalLowerEntries.append(entry)
return .accept
} else {
return .skip
}
} else {
additionalLowerEntries.append(entry)
return .accept
}
}, limit: count - lowerEntries.count - upperEntries.count + 1)
if additionalLowerEntries.count >= count - lowerEntries.count + upperEntries.count + 1 {
lower = additionalLowerEntries.last
additionalLowerEntries.removeLast()
}
lowerEntries.append(contentsOf: additionalLowerEntries)
}
var entries: [ChatListIntermediateEntry] = []
entries.append(contentsOf: lowerEntries.reversed())
entries.append(contentsOf: upperEntries)
@ -641,6 +611,25 @@ final class ChatListTable: Table {
return entries
}
func countWithPredicate(groupId: PeerGroupId, predicate: (PeerId) -> Bool) -> Int {
var result = 0
self.valueBox.filteredRange(self.table, start: self.lowerBound(groupId: groupId), end: self.upperBound(groupId: groupId), keys: { key in
let (_, _, messageIndex, type) = extractKey(key)
if type == ChatListEntryType.message.rawValue {
if predicate(messageIndex.id.peerId) {
result += 1
return .accept
} else {
return .skip
}
} else {
return .skip
}
}, limit: 10000)
return result
}
func getStandalone(peerId: PeerId, messageHistoryTable: MessageHistoryTable) -> ChatListIntermediateEntry? {
let index = self.indexTable.get(peerId: peerId)
switch index.inclusion {

View File

@ -320,6 +320,7 @@ final class MutableChatListView {
fileprivate var additionalItemIds: Set<PeerId>
fileprivate var additionalItemEntries: [MutableChatListEntry]
fileprivate var additionalMixedItemIds: Set<PeerId>
fileprivate var additionalMixedPinnedItemIds: Set<PeerId>
fileprivate var additionalMixedItemEntries: [MutableChatListEntry]
fileprivate var earlier: MutableChatListEntry?
fileprivate var later: MutableChatListEntry?
@ -340,10 +341,17 @@ final class MutableChatListView {
self.additionalItemEntries = []
self.additionalMixedItemEntries = []
self.additionalMixedItemIds = Set()
self.additionalMixedPinnedItemIds = Set()
if let filterPredicate = self.filterPredicate {
self.additionalMixedItemIds.formUnion(filterPredicate.includePeerIds)
for (itemId, _) in postbox.chatListTable.getPinnedItemIds(groupId: self.groupId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) {
switch itemId {
case let .peer(peerId):
self.additionalMixedPinnedItemIds.insert(peerId)
}
}
}
for peerId in self.additionalMixedItemIds {
for peerId in self.additionalMixedItemIds.union(self.additionalMixedPinnedItemIds) {
if let entry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) {
self.additionalMixedItemEntries.append(MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable))
}
@ -554,7 +562,7 @@ final class MutableChatListView {
if !updatedPeerNotificationSettings.isEmpty {
if let filterPredicate = self.filterPredicate {
for (peerId, settingsChange) in updatedPeerNotificationSettings {
if let peer = postbox.peerTable.get(peerId) {
if let peer = postbox.peerTable.get(peerId), !self.additionalMixedItemIds.contains(peerId), !self.additionalMixedPinnedItemIds.contains(peerId) {
let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false
let wasIncluded = filterPredicate.includes(peer: peer, notificationSettings: settingsChange.0, isUnread: isUnread)
let isIncluded = filterPredicate.includes(peer: peer, notificationSettings: settingsChange.1, isUnread: isUnread)
@ -611,6 +619,22 @@ final class MutableChatListView {
continue
}
}
for i in 0 ..< self.additionalMixedItemEntries.count {
switch self.additionalMixedItemEntries[i] {
case let .MessageEntry(index, message, readState, _, embeddedState, peer, peerPresence, summaryInfo, hasFailed):
var notificationSettingsPeerId = peer.peerId
if let peer = peer.peers[peer.peerId], let associatedPeerId = peer.associatedPeerId {
notificationSettingsPeerId = associatedPeerId
}
if let (_, settings) = updatedPeerNotificationSettings[notificationSettingsPeerId] {
self.additionalMixedItemEntries[i] = .MessageEntry(index, message, readState, settings, embeddedState, peer, peerPresence, summaryInfo, hasFailed)
hasChanges = true
}
default:
continue
}
}
}
if !transaction.updatedFailedMessagePeerIds.isEmpty {
@ -723,7 +747,7 @@ final class MutableChatListView {
hasChanges = true
}
var updateAdditionalMixedItems = false
for peerId in self.additionalMixedItemIds {
for peerId in self.additionalMixedItemIds.union(self.additionalMixedPinnedItemIds) {
if transaction.currentOperationsByPeerId[peerId] != nil {
updateAdditionalMixedItems = true
}
@ -736,7 +760,7 @@ final class MutableChatListView {
}
if updateAdditionalMixedItems {
self.additionalMixedItemEntries.removeAll()
for peerId in self.additionalMixedItemIds {
for peerId in self.additionalMixedItemIds.union(self.additionalMixedPinnedItemIds) {
if let entry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) {
self.additionalMixedItemEntries.append(MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable))
}
@ -755,6 +779,12 @@ final class MutableChatListView {
if !filterPredicate.includes(peer: peer, notificationSettings: postbox.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId), isUnread: isUnread) {
return false
}
if self.additionalMixedItemIds.contains(peer.id) {
return false
}
if self.additionalMixedPinnedItemIds.contains(peer.id) {
return false
}
} else {
return false
}
@ -1076,12 +1106,17 @@ public final class ChatListView {
existingIds.insert(messageEntry.0.messageIndex.id.peerId)
}
}
for entry in mutableView.additionalMixedItemEntries {
loop: for entry in mutableView.additionalMixedItemEntries {
if case let .MessageEntry(messageEntry) = entry {
if !existingIds.contains(messageEntry.0.messageIndex.id.peerId) {
switch entry {
case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed):
entries.append(.MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed))
if let filterPredicate = mutableView.filterPredicate, let peerValue = peer.peer {
if filterPredicate.includes(peer: peerValue, notificationSettings: notificationSettings, isUnread: combinedReadState?.isUnread ?? false) {
existingIds.insert(messageEntry.0.messageIndex.id.peerId)
entries.append(.MessageEntry(ChatListIndex(pinningIndex: nil, messageIndex: index.messageIndex), message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed))
}
}
case let .HoleEntry(hole):
entries.append(.HoleEntry(hole))
case .IntermediateMessageEntry:
@ -1104,12 +1139,8 @@ public final class ChatListView {
additionalItemEntries.append(.MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed))
case .HoleEntry:
assertionFailure()
/*case .GroupReferenceEntry:
assertionFailure()*/
case .IntermediateMessageEntry:
assertionFailure()
/*case .IntermediateGroupReferenceEntry:
assertionFailure()*/
}
}

View File

@ -763,6 +763,39 @@ public final class Transaction {
}
}
public func getChatCountMatchingPredicate(_ predicate: ChatListFilterPredicate) -> Int {
assert(!self.disposed)
guard let postbox = self.postbox else {
return 0
}
var includedPeerIds: [PeerId: Bool] = [:]
for peerId in predicate.includePeerIds {
includedPeerIds[peerId] = false
}
var count = postbox.chatListTable.countWithPredicate(groupId: .root, predicate: { peerId in
if let peer = postbox.peerTable.get(peerId) {
let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false
let notificationsPeerId = peer.notificationSettingsPeerId ?? peerId
if predicate.includes(peer: peer, notificationSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId), isUnread: isUnread) {
includedPeerIds[peer.id] = true
return true
} else {
return false
}
} else {
return false
}
})
for (peerId, included) in includedPeerIds {
if !included {
if postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil {
count += 1
}
}
}
return count
}
public func legacyGetAccessChallengeData() -> PostboxAccessChallengeData {
assert(!self.disposed)
if let postbox = self.postbox {
@ -1657,9 +1690,13 @@ public final class Postbox {
return { entry in
switch entry {
case let .message(index, _, _):
if index.pinningIndex != nil {
return false
}
if let peer = self.peerTable.get(index.messageIndex.id.peerId) {
let isUnread = self.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false
if predicate.includes(peer: peer, notificationSettings: self.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId), isUnread: isUnread) {
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
if predicate.includes(peer: peer, notificationSettings: self.peerNotificationSettingsTable.getEffective(notificationsPeerId), isUnread: isUnread) {
return true
} else {
return false

View File

@ -1505,7 +1505,10 @@ public final class SqliteValueBox: ValueBox {
public func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> ValueBoxFilterResult, limit: Int) {
var currentStart = start
var acceptedCount = 0
while acceptedCount < limit {
while true {
if limit > 0 && acceptedCount >= limit {
break
}
var hadStop = false
var lastKey: ValueBoxKey?
self.range(table, start: currentStart, end: end, values: { key, value in
@ -1534,6 +1537,41 @@ public final class SqliteValueBox: ValueBox {
}
}
public func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> ValueBoxFilterResult, limit: Int) {
var currentStart = start
var acceptedCount = 0
while true {
if limit > 0 && acceptedCount >= limit {
break
}
var hadStop = false
var lastKey: ValueBoxKey?
self.range(table, start: currentStart, end: end, keys: { key in
lastKey = key
let result = keys(key)
switch result {
case .accept:
acceptedCount += 1
return true
case .skip:
return true
case .stop:
hadStop = true
return false
}
return true
}, limit: limit)
if let lastKey = lastKey {
currentStart = lastKey
} else {
break
}
if hadStop {
break
}
}
}
public func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> Bool, limit: Int) {
precondition(self.queue.isCurrent())
if let _ = self.tables[table.id] {

View File

@ -73,6 +73,7 @@ public protocol ValueBox {
func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> Bool, limit: Int)
func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> ValueBoxFilterResult, limit: Int)
func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> ValueBoxFilterResult, limit: Int)
func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> Bool, limit: Int)
func scan(_ table: ValueBoxTable, values: (ValueBoxKey, ReadBuffer) -> Bool)
func scan(_ table: ValueBoxTable, keys: (ValueBoxKey) -> Bool)

View File

@ -8,10 +8,10 @@ static_library(
]),
headers = glob([
"Sources/**/*.h",
], exclude = ["Sources/RMIntro.h"]),
]),
exported_headers = glob([
"Sources/**/*.h",
], exclude = ["Sources/RMIntro.h"]),
"PublicHeaders/**/*.h",
]),
deps = [
"//submodules/LegacyComponents:LegacyComponents",
],

View File

@ -3,17 +3,10 @@ load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "RaiseToListen",
srcs = glob([
"Sources/*.m",
"Sources/*.swift",
]),
headers = glob([
"Sources/*.h",
], exclude = ["Sources/RaiseToListen.h"]),
exported_headers = glob([
"Sources/*.h",
], exclude = ["Sources/RaiseToListen.h"]),
deps = [
"//submodules/DeviceProximity:DeviceProximity",
"//submodules/RaiseToListen/Impl:RaiseToListenImpl",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -0,0 +1,21 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "RaiseToListenImpl",
srcs = glob([
"Sources/**/*.m",
]),
headers = glob([
"Sources/**/*.h",
]),
exported_headers = glob([
"PublicHeaders/**/*.h",
]),
deps = [
"//submodules/DeviceProximity:DeviceProximity",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
)

View File

@ -1,7 +1,7 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "Crc32",
name = "RingBuffer",
srcs = glob([
"Sources/*.m",
]),

View File

@ -114,6 +114,7 @@ private final class SettingsItemArguments {
let openPhoneNumberChange: () -> Void
let accountContextAction: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
let openDevices: () -> Void
let openFilters: () -> Void
init(
sharedContext: SharedAccountContext,
@ -147,7 +148,8 @@ private final class SettingsItemArguments {
keepPhone: @escaping () -> Void,
openPhoneNumberChange: @escaping () -> Void,
accountContextAction: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void,
openDevices: @escaping () -> Void
openDevices: @escaping () -> Void,
openFilters: @escaping () -> Void
) {
self.sharedContext = sharedContext
self.avatarAndNameInfoContext = avatarAndNameInfoContext
@ -181,6 +183,7 @@ private final class SettingsItemArguments {
self.openPhoneNumberChange = openPhoneNumberChange
self.accountContextAction = accountContextAction
self.openDevices = openDevices
self.openFilters = openFilters
}
}
@ -211,6 +214,8 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
case devices(PresentationTheme, UIImage?, String, String)
case filters(PresentationTheme, UIImage?, String, String)
case savedMessages(PresentationTheme, UIImage?, String)
case recentCalls(PresentationTheme, UIImage?, String)
case stickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?)
@ -240,7 +245,7 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return SettingsSection.accounts.rawValue
case .proxy:
return SettingsSection.proxy.rawValue
case .devices:
case .devices, .filters:
return SettingsSection.media.rawValue
case .savedMessages, .recentCalls, .stickers:
return SettingsSection.media.rawValue
@ -285,30 +290,32 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return 1006
case .devices:
return 1007
case .notificationsAndSounds:
case .filters:
return 1008
case .privacyAndSecurity:
case .notificationsAndSounds:
return 1009
case .dataAndStorage:
case .privacyAndSecurity:
return 1010
case .themes:
case .dataAndStorage:
return 1011
case .language:
case .themes:
return 1012
case .contentStickers:
case .language:
return 1013
case .contentStickers:
return 1014
#if ENABLE_WALLET
case .wallet:
return 1014
return 1015
#endif
case .passport:
return 1015
case .watch:
return 1016
case .askAQuestion:
case .watch:
return 1017
case .faq:
case .askAQuestion:
return 1018
case .faq:
return 1019
}
}
@ -406,6 +413,12 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
} else {
return false
}
case let .filters(lhsTheme, lhsImage, lhsText, lhsValue):
if case let .filters(rhsTheme, rhsImage, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .savedMessages(lhsTheme, lhsImage, lhsText):
if case let .savedMessages(rhsTheme, rhsImage, rhsText) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText {
return true
@ -567,6 +580,10 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openDevices()
})
case let .filters(theme, image, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openFilters()
})
case let .savedMessages(theme, image, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openSavedMessages()
@ -635,7 +652,7 @@ private struct SettingsState: Equatable {
var isSearching: Bool
}
private func settingsEntries(account: Account, presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, notificationsAuthorizationStatus: AccessType, notificationsWarningSuppressed: Bool, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, privacySettings: AccountPrivacySettings?, hasWallet: Bool, hasPassport: Bool, hasWatchApp: Bool, accountsAndPeers: [(Account, Peer, Int32)], inAppNotificationSettings: InAppNotificationSettings, experimentalUISettings: ExperimentalUISettings, displayPhoneNumberConfirmation: Bool, otherSessionCount: Int, enableQRLogin: Bool) -> [SettingsEntry] {
private func settingsEntries(account: Account, presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, notificationsAuthorizationStatus: AccessType, notificationsWarningSuppressed: Bool, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, privacySettings: AccountPrivacySettings?, hasWallet: Bool, hasPassport: Bool, hasWatchApp: Bool, accountsAndPeers: [(Account, Peer, Int32)], inAppNotificationSettings: InAppNotificationSettings, experimentalUISettings: ExperimentalUISettings, displayPhoneNumberConfirmation: Bool, otherSessionCount: Int, enableQRLogin: Bool, enableFilters: Bool) -> [SettingsEntry] {
var entries: [SettingsEntry] = []
if let peer = peerViewMainPeer(view) as? TelegramUser {
@ -688,6 +705,10 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
} else {
entries.append(.devices(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/Sessions")?.precomposed(), presentationData.strings.Settings_Devices, otherSessionCount == 0 ? "" : "\(otherSessionCount + 1)"))
}
if enableFilters {
//TODO:localize
entries.append(.filters(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/SavedMessages")?.precomposed(), "Chat Filters", ""))
}
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
entries.append(.notificationsAndSounds(presentationData.theme, PresentationResourcesSettings.notifications, presentationData.strings.Settings_NotificationsAndSounds, notifyExceptions, notificationsWarning))
@ -925,6 +946,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let privacySettings = Promise<AccountPrivacySettings?>(nil)
let enableQRLogin = Promise<Bool>()
let enableFilters = Promise<Bool>()
let openFaq: (Promise<ResolvedUrl>, String?) -> Void = { resolvedUrl, customAnchor in
let _ = (contextValue.get()
@ -1221,17 +1243,27 @@ public func settingsController(context: AccountContext, accountManager: AccountM
gesture?.cancel()
}
}, openDevices: {
let _ = (combineLatest(queue: .mainQueue(),
activeSessionsContextAndCount.get(),
enableQRLogin.get()
)
|> take(1)).start(next: { activeSessionsContextAndCount, enableQRLogin in
let (activeSessionsContext, count, webSessionsContext) = activeSessionsContextAndCount
if count == 0 && enableQRLogin {
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
} else {
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false))
}
let _ = (contextValue.get()
|> deliverOnMainQueue
|> take(1)).start(next: { context in
let _ = (combineLatest(queue: .mainQueue(),
activeSessionsContextAndCount.get(),
enableQRLogin.get()
)
|> take(1)).start(next: { activeSessionsContextAndCount, enableQRLogin in
let (activeSessionsContext, count, webSessionsContext) = activeSessionsContextAndCount
if count == 0 && enableQRLogin {
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
} else {
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false))
}
})
})
}, openFilters: {
let _ = (contextValue.get()
|> deliverOnMainQueue
|> take(1)).start(next: { context in
pushControllerImpl?(chatListFilterPresetListController(context: context, updated: { _ in }))
})
})
@ -1496,7 +1528,21 @@ public func settingsController(context: AccountContext, accountManager: AccountM
}
enableQRLogin.set(enableQRLoginSignal)
let signal = combineLatest(queue: Queue.mainQueue(), contextValue.get(), updatedPresentationData, statePromise.get(), peerView, combineLatest(queue: Queue.mainQueue(), preferences, notifyExceptions.get(), notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), privacySettings.get(), displayPhoneNumberConfirmation.get()), combineLatest(featuredStickerPacks, archivedPacks.get()), combineLatest(hasWallet, hasPassport.get(), hasWatchApp, enableQRLogin.get()), accountsAndPeers.get(), activeSessionsContextAndCount.get())
let enableFiltersSignal = contextValue.get()
|> mapToSignal { context -> Signal<Bool, NoError> in
return context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { view -> Bool in
guard let appConfiguration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration else {
return false
}
let configuration = ChatListFilteringConfiguration(appConfiguration: appConfiguration)
return configuration.isEnabled
}
|> distinctUntilChanged
}
enableFilters.set(enableFiltersSignal)
let signal = combineLatest(queue: Queue.mainQueue(), contextValue.get(), updatedPresentationData, statePromise.get(), peerView, combineLatest(queue: Queue.mainQueue(), preferences, notifyExceptions.get(), notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), privacySettings.get(), displayPhoneNumberConfirmation.get()), combineLatest(featuredStickerPacks, archivedPacks.get()), combineLatest(hasWallet, hasPassport.get(), hasWatchApp, enableQRLogin.get(), enableFilters.get()), accountsAndPeers.get(), activeSessionsContextAndCount.get())
|> map { context, presentationData, state, view, preferencesAndExceptions, featuredAndArchived, hasWalletPassportAndWatch, accountsAndPeers, activeSessionsContextAndCount -> (ItemListControllerState, (ItemListNodeState, Any)) in
let otherSessionCount = activeSessionsContextAndCount.1
@ -1536,8 +1582,8 @@ public func settingsController(context: AccountContext, accountManager: AccountM
pushControllerImpl?(c)
}, getNavigationController: getNavigationControllerImpl, exceptionsList: notifyExceptions.get(), archivedStickerPacks: archivedPacks.get(), privacySettings: privacySettings.get(), hasWallet: hasWallet, activeSessionsContext: activeSessionsContextAndCountSignal |> map { $0.0 } |> distinctUntilChanged(isEqual: { $0 === $1 }), webSessionsContext: activeSessionsContextAndCountSignal |> map { $0.2 } |> distinctUntilChanged(isEqual: { $0 === $1 }))
let (hasWallet, hasPassport, hasWatchApp, enableQRLogin) = hasWalletPassportAndWatch
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, privacySettings: preferencesAndExceptions.4, hasWallet: hasWallet, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings, experimentalUISettings: experimentalUISettings, displayPhoneNumberConfirmation: preferencesAndExceptions.5, otherSessionCount: otherSessionCount, enableQRLogin: enableQRLogin), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up))
let (hasWallet, hasPassport, hasWatchApp, enableQRLogin, enableFilters) = hasWalletPassportAndWatch
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, privacySettings: preferencesAndExceptions.4, hasWallet: hasWallet, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings, experimentalUISettings: experimentalUISettings, displayPhoneNumberConfirmation: preferencesAndExceptions.5, otherSessionCount: otherSessionCount, enableQRLogin: enableQRLogin, enableFilters: enableFilters), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up))
return (controllerState, (listState, arguments))
}

View File

@ -4,14 +4,7 @@ static_library(
name = "ShareItems",
srcs = glob([
"Sources/**/*.swift",
"Sources/*.m",
]),
headers = glob([
"Sources/*.h",
], exclude = ["Sources/ShareItems.h"]),
exported_headers = glob([
"Sources/*.h",
], exclude = ["Sources/ShareItems.h"]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/Postbox:Postbox#shared",
@ -25,6 +18,7 @@ static_library(
"//submodules/AccountContext:AccountContext",
"//submodules/MimeTypes:MimeTypes",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/ShareItems/Impl:ShareItemsImpl",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -0,0 +1,27 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "ShareItemsImpl",
srcs = glob([
"Sources/*.m",
]),
headers = glob([
"Sources/*.h",
]),
exported_headers = glob([
"PublicHeaders/**/*.h",
]),
deps = [
"//submodules/MtProtoKit:MtProtoKit#shared",
"//submodules/MimeTypes:MimeTypes",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
"$SDKROOT/System/Library/Frameworks/MobileCoreServices.framework",
"$SDKROOT/System/Library/Frameworks/AddressBook.framework",
"$SDKROOT/System/Library/Frameworks/AVFoundation.framework",
"$SDKROOT/System/Library/Frameworks/PassKit.framework",
],
)

View File

@ -15,7 +15,7 @@ static_library(
"//submodules/Tuples:Tuples",
"//submodules/MediaResources:MediaResources",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/WebP:WebPImage",
"//submodules/WebPBinding:WebPBinding",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -7,33 +7,10 @@ static_library(
]),
headers = glob([
"Sources/*.h",
], exclude = ["Sources/Stripe.h"]),
exported_headers = [
"Sources/STPAddress.h",
"Sources/STPPaymentCardTextField.h",
"Sources/STPAPIClient.h",
"Sources/STPAPIClient+ApplePay.h",
"Sources/STPAPIResponseDecodable.h",
"Sources/STPPaymentConfiguration.h",
"Sources/STPCard.h",
"Sources/STPCardBrand.h",
"Sources/STPCardParams.h",
"Sources/STPToken.h",
"Sources/STPBankAccount.h",
"Sources/STPBankAccountParams.h",
"Sources/STPBINRange.h",
"Sources/STPCardValidator.h",
"Sources/STPCardValidationState.h",
"Sources/STPCustomer.h",
"Sources/STPFormEncodable.h",
"Sources/STPPaymentMethod.h",
"Sources/STPPhoneNumberValidator.h",
"Sources/STPPostalCodeValidator.h",
"Sources/STPSource.h",
"Sources/STPBlocks.h",
"Sources/StripeError.h",
"Sources/STPBackendAPIAdapter.h",
],
]),
exported_headers = glob([
"PublicHeaders/**/*.h",
]),
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",

View File

@ -11,7 +11,7 @@ static_library(
headers = glob([
"Sources/**/*.h",
]),
exported_headers = [
"Sources/Svg.h",
],
exported_headers = glob([
"PublicHeaders/**/*.h",
]),
)

View File

@ -43,6 +43,7 @@ public struct Namespaces {
public static let CloudMaskPacks: Int32 = 1
public static let EmojiKeywords: Int32 = 2
public static let CloudAnimatedEmoji: Int32 = 3
public static let CloudDice: Int32 = 4
}
public struct OrderedItemList {

View File

@ -13,6 +13,7 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable {
case id(id: Int64, accessHash: Int64)
case name(String)
case animatedEmoji
case dice
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("r", orElse: 0) {
@ -22,6 +23,8 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable {
self = .name(decoder.decodeStringForKey("n", orElse: ""))
case 2:
self = .animatedEmoji
case 3:
self = .dice
default:
self = .name("")
assertionFailure()
@ -39,6 +42,8 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable {
encoder.encodeString(name, forKey: "n")
case .animatedEmoji:
encoder.encodeInt32(2, forKey: "r")
case .dice:
encoder.encodeInt32(3, forKey: "r")
}
}
@ -62,6 +67,12 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable {
} else {
return false
}
case .dice:
if case .dice = rhs {
return true
} else {
return false
}
}
}
}

View File

@ -513,7 +513,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1502174430] = { return Api.InputMessage.parse_inputMessageID($0) }
dict[-1160215659] = { return Api.InputMessage.parse_inputMessageReplyTo($0) }
dict[-2037963464] = { return Api.InputMessage.parse_inputMessagePinned($0) }
dict[-1564789301] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) }
dict[-58224696] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) }
dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) }
dict[-1567175714] = { return Api.MessageFwdAuthor.parse_messageFwdAuthor($0) }
dict[-1539849235] = { return Api.WallPaper.parse_wallPaper($0) }
@ -579,6 +579,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1645763991] = { return Api.InputStickerSet.parse_inputStickerSetID($0) }
dict[-2044933984] = { return Api.InputStickerSet.parse_inputStickerSetShortName($0) }
dict[42402760] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmoji($0) }
dict[2044861011] = { return Api.InputStickerSet.parse_inputStickerSetDice($0) }
dict[-1729618630] = { return Api.BotInfo.parse_botInfo($0) }
dict[-1519637954] = { return Api.updates.State.parse_state($0) }
dict[372165663] = { return Api.FoundGif.parse_foundGif($0) }

View File

@ -14600,25 +14600,30 @@ public extension Api {
}
public enum PhoneCallProtocol: TypeConstructorDescription {
case phoneCallProtocol(flags: Int32, minLayer: Int32, maxLayer: Int32)
case phoneCallProtocol(flags: Int32, minLayer: Int32, maxLayer: Int32, libraryVersions: [String])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .phoneCallProtocol(let flags, let minLayer, let maxLayer):
case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions):
if boxed {
buffer.appendInt32(-1564789301)
buffer.appendInt32(-58224696)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(minLayer, buffer: buffer, boxed: false)
serializeInt32(maxLayer, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(libraryVersions.count))
for item in libraryVersions {
serializeString(item, buffer: buffer, boxed: false)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .phoneCallProtocol(let flags, let minLayer, let maxLayer):
return ("phoneCallProtocol", [("flags", flags), ("minLayer", minLayer), ("maxLayer", maxLayer)])
case .phoneCallProtocol(let flags, let minLayer, let maxLayer, let libraryVersions):
return ("phoneCallProtocol", [("flags", flags), ("minLayer", minLayer), ("maxLayer", maxLayer), ("libraryVersions", libraryVersions)])
}
}
@ -14629,11 +14634,16 @@ public extension Api {
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: [String]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.PhoneCallProtocol.phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!)
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.PhoneCallProtocol.phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!, libraryVersions: _4!)
}
else {
return nil
@ -16258,6 +16268,7 @@ public extension Api {
case inputStickerSetID(id: Int64, accessHash: Int64)
case inputStickerSetShortName(shortName: String)
case inputStickerSetAnimatedEmoji
case inputStickerSetDice
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -16285,6 +16296,12 @@ public extension Api {
buffer.appendInt32(42402760)
}
break
case .inputStickerSetDice:
if boxed {
buffer.appendInt32(2044861011)
}
break
}
}
@ -16299,6 +16316,8 @@ public extension Api {
return ("inputStickerSetShortName", [("shortName", shortName)])
case .inputStickerSetAnimatedEmoji:
return ("inputStickerSetAnimatedEmoji", [])
case .inputStickerSetDice:
return ("inputStickerSetDice", [])
}
}
@ -16333,6 +16352,9 @@ public extension Api {
public static func parse_inputStickerSetAnimatedEmoji(_ reader: BufferReader) -> InputStickerSet? {
return Api.InputStickerSet.inputStickerSetAnimatedEmoji
}
public static func parse_inputStickerSetDice(_ reader: BufferReader) -> InputStickerSet? {
return Api.InputStickerSet.inputStickerSetDice
}
}
public enum BotInfo: TypeConstructorDescription {

View File

@ -75,6 +75,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
return OngoingCallContext.maxLayer
}
public static var voipVersions: [String] {
return [OngoingCallContext.version]
}
public init(accountManager: AccountManager, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), isMediaPlaying: @escaping () -> Bool, resumeMediaPlayback: @escaping () -> Void, audioSession: ManagedAudioSession, activeAccounts: Signal<[Account], NoError>) {
self.getDeviceAccessData = getDeviceAccessData
self.accountManager = accountManager

View File

@ -901,7 +901,7 @@ public class Account {
self.supplementary = supplementary
self.peerInputActivityManager = PeerInputActivityManager()
self.callSessionManager = CallSessionManager(postbox: postbox, network: network, maxLayer: networkArguments.voipMaxLayer, addUpdates: { [weak self] updates in
self.callSessionManager = CallSessionManager(postbox: postbox, network: network, maxLayer: networkArguments.voipMaxLayer, versions: networkArguments.voipVersions, addUpdates: { [weak self] updates in
self?.stateManager?.addUpdates(updates)
})
self.stateManager = AccountStateManager(accountPeerId: self.peerId, accountManager: accountManager, postbox: self.postbox, network: self.network, callSessionManager: self.callSessionManager, addIsContactUpdates: { [weak self] updates in
@ -1044,7 +1044,7 @@ public class Account {
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
//self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network).start())
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },

View File

@ -76,6 +76,20 @@ public func cachedStickerPack(postbox: Postbox, network: Network, reference: Sti
} else {
return (.fetching, true, nil)
}
case .dice:
let namespace = Namespaces.ItemCollection.CloudDice
let id: ItemCollectionId.Id = 0
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id)))) as? CachedStickerPack, let info = cached.info {
previousHash = cached.hash
let current: CachedStickerPackResult = .result(info, cached.items, false)
if cached.hash != info.hash {
return (current, true, previousHash)
} else {
return (current, true, previousHash)
}
} else {
return (.fetching, true, nil)
}
}
}
|> mapToSignal { result, loadRemote, previousHash in
@ -148,6 +162,18 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id)))) as? CachedStickerPack, let info = cached.info {
return (info, cached.items, false)
}
case .dice:
let namespace = Namespaces.ItemCollection.CloudDice
let id: ItemCollectionId.Id = 0
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id)))) as? CachedStickerPack, let info = cached.info {
return (info, cached.items, false)
}
}
return nil
}

View File

@ -4,7 +4,6 @@ import MtProtoKit
import SwiftSignalKit
import TelegramApi
import SyncCore
private let minLayer: Int32 = 65
@ -241,6 +240,7 @@ private final class CallSessionManagerContext {
private let postbox: Postbox
private let network: Network
private let maxLayer: Int32
private let versions: [String]
private let addUpdates: (Api.Updates) -> Void
private let ringingSubscribers = Bag<([CallSessionRingingState]) -> Void>()
@ -249,11 +249,12 @@ private final class CallSessionManagerContext {
private let disposables = DisposableSet()
init(queue: Queue, postbox: Postbox, network: Network, maxLayer: Int32, addUpdates: @escaping (Api.Updates) -> Void) {
init(queue: Queue, postbox: Postbox, network: Network, maxLayer: Int32, versions: [String], addUpdates: @escaping (Api.Updates) -> Void) {
self.queue = queue
self.postbox = postbox
self.network = network
self.maxLayer = maxLayer
self.versions = versions
self.addUpdates = addUpdates
}
@ -469,7 +470,7 @@ private final class CallSessionManagerContext {
if let context = self.contexts[internalId] {
switch context.state {
case let .ringing(id, accessHash, gAHash, b):
context.state = .accepting(id: id, accessHash: accessHash, gAHash: gAHash, b: b, disposable: (acceptCallSession(postbox: self.postbox, network: self.network, stableId: id, accessHash: accessHash, b: b, maxLayer: self.maxLayer) |> deliverOn(self.queue)).start(next: { [weak self] result in
context.state = .accepting(id: id, accessHash: accessHash, gAHash: gAHash, b: b, disposable: (acceptCallSession(postbox: self.postbox, network: self.network, stableId: id, accessHash: accessHash, b: b, maxLayer: self.maxLayer, versions: self.versions) |> deliverOn(self.queue)).start(next: { [weak self] result in
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
if case .accepting = context.state {
switch result {
@ -534,7 +535,7 @@ private final class CallSessionManagerContext {
let keyVisualHash = MTSha256(key + gA)!
context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId, maxLayer: self.maxLayer) |> deliverOnMainQueue).start(next: { [weak self] updatedCall in
context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId, maxLayer: self.maxLayer, versions: self.versions) |> deliverOnMainQueue).start(next: { [weak self] updatedCall in
if let strongSelf = self, let context = strongSelf.contexts[internalId], case .confirming = context.state {
if let updatedCall = updatedCall {
strongSelf.updateSession(updatedCall, completion: { _ in })
@ -616,7 +617,7 @@ private final class CallSessionManagerContext {
if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) {
if keyFingerprint == calculatedKeyId {
switch callProtocol {
case let .phoneCallProtocol(_, _, maxLayer):
case let .phoneCallProtocol(_, _, maxLayer, _):
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: calculatedKeyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId)
}
@ -628,7 +629,7 @@ private final class CallSessionManagerContext {
}
case let .confirming(id, accessHash, key, keyId, keyVisualHash, _):
switch callProtocol {
case let .phoneCallProtocol(_, _, maxLayer):
case let .phoneCallProtocol(_, _, maxLayer, _):
context.state = .active(id: id, accessHash: accessHash, beginTimestamp: startDate, key: key, keyId: keyId, keyVisualHash: keyVisualHash, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, allowsP2P: allowsP2P)
self.contextUpdated(internalId: internalId)
}
@ -709,7 +710,7 @@ private final class CallSessionManagerContext {
let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free)
if randomStatus == 0 {
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, state: .requesting(a: a, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer) |> deliverOn(queue)).start(next: { [weak self] result in
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, state: .requesting(a: a, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.versions) |> deliverOn(queue)).start(next: { [weak self] result in
if let strongSelf = self, let context = strongSelf.contexts[internalId] {
if case .requesting = context.state {
switch result {
@ -743,9 +744,9 @@ public final class CallSessionManager {
private let queue = Queue()
private var contextRef: Unmanaged<CallSessionManagerContext>?
init(postbox: Postbox, network: Network, maxLayer: Int32, addUpdates: @escaping (Api.Updates) -> Void) {
init(postbox: Postbox, network: Network, maxLayer: Int32, versions: [String], addUpdates: @escaping (Api.Updates) -> Void) {
self.queue.async {
let context = CallSessionManagerContext(queue: self.queue, postbox: postbox, network: network, maxLayer: maxLayer, addUpdates: addUpdates)
let context = CallSessionManagerContext(queue: self.queue, postbox: postbox, network: network, maxLayer: maxLayer, versions: versions, addUpdates: addUpdates)
self.contextRef = Unmanaged.passRetained(context)
}
}
@ -846,7 +847,7 @@ private enum AcceptCallResult {
case success(AcceptedCall)
}
private func acceptCallSession(postbox: Postbox, network: Network, stableId: CallSessionStableId, accessHash: Int64, b: Data, maxLayer: Int32) -> Signal<AcceptCallResult, NoError> {
private func acceptCallSession(postbox: Postbox, network: Network, stableId: CallSessionStableId, accessHash: Int64, b: Data, maxLayer: Int32, versions: [String]) -> Signal<AcceptCallResult, NoError> {
return validatedEncryptionConfig(postbox: postbox, network: network)
|> mapToSignal { config -> Signal<AcceptCallResult, NoError> in
var gValue: Int32 = config.g.byteSwapped
@ -860,8 +861,8 @@ private func acceptCallSession(postbox: Postbox, network: Network, stableId: Cal
if !MTCheckIsSafeGAOrB(network.encryptionProvider, gb, p) {
return .single(.failed)
}
return network.request(Api.functions.phone.acceptCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash), gB: Buffer(data: gb), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer)))
return network.request(Api.functions.phone.acceptCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash), gB: Buffer(data: gb), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer, libraryVersions: versions)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.phone.PhoneCall?, NoError> in
return .single(nil)
@ -887,7 +888,7 @@ private func acceptCallSession(postbox: Postbox, network: Network, stableId: Cal
case let .phoneCall(flags, id, _, _, _, _, gAOrB, _, callProtocol, connections, startDate):
if id == stableId {
switch callProtocol{
case let .phoneCallProtocol(_, _, maxLayer):
case let .phoneCallProtocol(_, _, maxLayer, _):
return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connections.first!, alternative: Array(connections[1...])), maxLayer: maxLayer, allowsP2P: (flags & (1 << 5)) != 0))
}
} else {
@ -908,7 +909,7 @@ private enum RequestCallSessionResult {
case failed(CallSessionError)
}
private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32) -> Signal<RequestCallSessionResult, NoError> {
private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32, versions: [String]) -> Signal<RequestCallSessionResult, NoError> {
return validatedEncryptionConfig(postbox: postbox, network: network)
|> mapToSignal { config -> Signal<RequestCallSessionResult, NoError> in
return postbox.transaction { transaction -> Signal<RequestCallSessionResult, NoError> in
@ -924,7 +925,7 @@ private func requestCallSession(postbox: Postbox, network: Network, peerId: Peer
let gAHash = MTSha256(ga)!
return network.request(Api.functions.phone.requestCall(flags: 0, userId: inputUser, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer)))
return network.request(Api.functions.phone.requestCall(flags: 0, userId: inputUser, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer, libraryVersions: versions)))
|> map { result -> RequestCallSessionResult in
switch result {
case let .phoneCall(phoneCall, _):
@ -960,8 +961,8 @@ private func requestCallSession(postbox: Postbox, network: Network, peerId: Peer
}
}
private func confirmCallSession(network: Network, stableId: CallSessionStableId, accessHash: Int64, gA: Data, keyFingerprint: Int64, maxLayer: Int32) -> Signal<Api.PhoneCall?, NoError> {
return network.request(Api.functions.phone.confirmCall(peer: Api.InputPhoneCall.inputPhoneCall(id: stableId, accessHash: accessHash), gA: Buffer(data: gA), keyFingerprint: keyFingerprint, protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer)))
private func confirmCallSession(network: Network, stableId: CallSessionStableId, accessHash: Int64, gA: Data, keyFingerprint: Int64, maxLayer: Int32, versions: [String]) -> Signal<Api.PhoneCall?, NoError> {
return network.request(Api.functions.phone.confirmCall(peer: Api.InputPhoneCall.inputPhoneCall(id: stableId, accessHash: accessHash), gA: Buffer(data: gA), keyFingerprint: keyFingerprint, protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer, libraryVersions: versions)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.phone.PhoneCall?, NoError> in
return .single(nil)

View File

@ -5,7 +5,19 @@ import TelegramApi
import SyncCore
public struct ChatListFilterPeerCategories: OptionSet {
public struct ChatListFilteringConfiguration: Equatable {
public let isEnabled: Bool
public init(appConfiguration: AppConfiguration) {
var isEnabled = false
if let data = appConfiguration.data, let value = data["dialog_filters_enabled"] as? Bool, value {
isEnabled = true
}
self.isEnabled = isEnabled
}
}
public struct ChatListFilterPeerCategories: OptionSet, Hashable {
public var rawValue: Int32
public init(rawValue: Int32) {
@ -93,50 +105,58 @@ extension ChatListFilterPeerCategories {
}
}
public struct ChatListFilter: PostboxCoding, Equatable {
public var id: Int32
public var title: String?
public struct ChatListFilterData: Equatable, Hashable {
public var categories: ChatListFilterPeerCategories
public var excludeMuted: Bool
public var excludeRead: Bool
public var includePeers: [PeerId]
public init(
id: Int32,
title: String?,
categories: ChatListFilterPeerCategories,
excludeMuted: Bool,
excludeRead: Bool,
includePeers: [PeerId]
) {
self.id = id
self.title = title
self.categories = categories
self.excludeMuted = excludeMuted
self.excludeRead = excludeRead
self.includePeers = includePeers
}
}
public struct ChatListFilter: PostboxCoding, Equatable {
public var id: Int32
public var title: String
public var data: ChatListFilterData
public init(
id: Int32,
title: String,
data: ChatListFilterData
) {
self.id = id
self.title = title
self.data = data
}
public init(decoder: PostboxDecoder) {
self.id = decoder.decodeInt32ForKey("id", orElse: 0)
self.title = decoder.decodeOptionalStringForKey("title")
self.categories = ChatListFilterPeerCategories(rawValue: decoder.decodeInt32ForKey("categories", orElse: 0))
self.excludeMuted = decoder.decodeInt32ForKey("excludeMuted", orElse: 0) != 0
self.excludeRead = decoder.decodeInt32ForKey("excludeRead", orElse: 0) != 0
self.includePeers = decoder.decodeInt64ArrayForKey("includePeers").map(PeerId.init)
self.title = decoder.decodeStringForKey("title", orElse: "")
self.data = ChatListFilterData(
categories: ChatListFilterPeerCategories(rawValue: decoder.decodeInt32ForKey("categories", orElse: 0)),
excludeMuted: decoder.decodeInt32ForKey("excludeMuted", orElse: 0) != 0,
excludeRead: decoder.decodeInt32ForKey("excludeRead", orElse: 0) != 0,
includePeers: decoder.decodeInt64ArrayForKey("includePeers").map(PeerId.init)
)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.id, forKey: "id")
if let title = self.title {
encoder.encodeString(title, forKey: "title")
} else {
encoder.encodeNil(forKey: "title")
}
encoder.encodeInt32(self.categories.rawValue, forKey: "categories")
encoder.encodeInt32(self.excludeMuted ? 1 : 0, forKey: "excludeMuted")
encoder.encodeInt32(self.excludeRead ? 1 : 0, forKey: "excludeRead")
encoder.encodeInt64Array(self.includePeers.map { $0.toInt64() }, forKey: "includePeers")
encoder.encodeString(self.title, forKey: "title")
encoder.encodeInt32(self.data.categories.rawValue, forKey: "categories")
encoder.encodeInt32(self.data.excludeMuted ? 1 : 0, forKey: "excludeMuted")
encoder.encodeInt32(self.data.excludeRead ? 1 : 0, forKey: "excludeRead")
encoder.encodeInt64Array(self.data.includePeers.map { $0.toInt64() }, forKey: "includePeers")
}
}
@ -146,36 +166,38 @@ extension ChatListFilter {
case let .dialogFilter(flags, id, title, includePeers):
self.init(
id: id,
title: title.isEmpty ? nil : title,
categories: ChatListFilterPeerCategories(apiFlags: flags),
excludeMuted: (flags & (1 << 11)) != 0,
excludeRead: (flags & (1 << 12)) != 0,
includePeers: includePeers.compactMap { peer -> PeerId? in
switch peer {
case let .inputPeerUser(userId, _):
return PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
case let .inputPeerChat(chatId):
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)
case let .inputPeerChannel(channelId, _):
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
default:
return nil
title: title,
data: ChatListFilterData(
categories: ChatListFilterPeerCategories(apiFlags: flags),
excludeMuted: (flags & (1 << 11)) != 0,
excludeRead: (flags & (1 << 12)) != 0,
includePeers: includePeers.compactMap { peer -> PeerId? in
switch peer {
case let .inputPeerUser(userId, _):
return PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
case let .inputPeerChat(chatId):
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)
case let .inputPeerChannel(channelId, _):
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
default:
return nil
}
}
}
)
)
}
}
func apiFilter(transaction: Transaction) -> Api.DialogFilter {
var flags: Int32 = 0
if self.excludeMuted {
if self.data.excludeMuted {
flags |= 1 << 11
}
if self.excludeRead {
if self.data.excludeRead {
flags |= 1 << 12
}
flags |= self.categories.apiFlags
return .dialogFilter(flags: flags, id: self.id, title: self.title ?? "", includePeers: self.includePeers.compactMap { peerId -> Api.InputPeer? in
flags |= self.data.categories.apiFlags
return .dialogFilter(flags: flags, id: self.id, title: self.title, includePeers: self.data.includePeers.compactMap { peerId -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
})
}
@ -287,46 +309,70 @@ func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Never,
}
public func replaceRemoteChatListFilters(account: Account) -> Signal<Never, NoError> {
return requestChatListFilters(postbox: account.postbox, network: account.network)
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
return .complete()
return account.postbox.transaction { transaction -> [ChatListFilter] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters
}
|> mapToSignal { remoteFilters -> Signal<Never, NoError> in
var deleteSignals: [Signal<Never, NoError>] = []
for filter in remoteFilters {
deleteSignals.append(requestUpdateChatListFilter(account: account, id: filter.id, filter: nil)
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
|> ignoreValues)
|> mapToSignal { filters -> Signal<Never, NoError> in
return requestChatListFilters(postbox: account.postbox, network: account.network)
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
return .complete()
}
let addFilters = account.postbox.transaction { transaction -> [(Int32, ChatListFilter)] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters.map { filter -> (Int32, ChatListFilter) in
return (filter.id, filter)
|> mapToSignal { remoteFilters -> Signal<Never, NoError> in
var deleteSignals: [Signal<Never, NoError>] = []
for filter in remoteFilters {
if !filters.contains(where: { $0.id == filter.id }) {
deleteSignals.append(requestUpdateChatListFilter(account: account, id: filter.id, filter: nil)
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
|> ignoreValues)
}
}
}
|> mapToSignal { filters -> Signal<Never, NoError> in
var signals: [Signal<Never, NoError>] = []
for (id, filter) in filters {
signals.append(requestUpdateChatListFilter(account: account, id: id, filter: filter)
let addFilters = account.postbox.transaction { transaction -> [(Int32, ChatListFilter)] in
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
return settings.filters.map { filter -> (Int32, ChatListFilter) in
return (filter.id, filter)
}
}
|> mapToSignal { filters -> Signal<Never, NoError> in
var signals: [Signal<Never, NoError>] = []
for (id, filter) in filters {
if !remoteFilters.contains(filter) {
signals.append(requestUpdateChatListFilter(account: account, id: id, filter: filter)
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
|> ignoreValues)
}
}
return combineLatest(signals)
|> ignoreValues
}
let reorderFilters: Signal<Never, NoError>
if remoteFilters.map({ $0.id }) != filters.map({ $0.id }) {
reorderFilters = account.network.request(Api.functions.messages.updateDialogFiltersOrder(order: filters.map { $0.id }))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
|> ignoreValues)
} else {
reorderFilters = .complete()
}
return combineLatest(signals)
return combineLatest(
deleteSignals
)
|> ignoreValues
|> then(
addFilters
)
|> then(
reorderFilters
)
}
return combineLatest(
deleteSignals
)
|> ignoreValues
|> then(
addFilters
)
}
}

View File

@ -18,6 +18,8 @@ extension StickerPackReference {
return .inputStickerSetShortName(shortName: name)
case .animatedEmoji:
return .inputStickerSetAnimatedEmoji
case .dice:
return .inputStickerSetDice
}
}
}

Some files were not shown because too many files have changed in this diff Show More