Merge commit 'e9a4a9347a1000386e148a490a28d601c3bd0db3' into bazel
@ -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):]
|
||||
|
39
Makefile
@ -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}" \
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
|
50
Telegram/Share/InfoBazel.plist
Normal 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 > 0</string>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.share-services</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>ShareRootController</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
@ -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 {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 370 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 665 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3.3 KiB |
@ -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
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 5.0 KiB |
@ -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>
|
||||
|
@ -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>
|
@ -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";
|
||||
|
@ -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>
|
||||
|
29
Telegram/Watch/App/InfoBazel.plist
Normal 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>
|
@ -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>
|
||||
|
20
Telegram/Watch/Extension/InfoBazel.plist
Normal 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>
|
@ -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
|
||||
|
||||
|
@ -7,6 +7,7 @@ public enum ContactMultiselectionControllerMode {
|
||||
case groupCreation
|
||||
case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool)
|
||||
case channelCreation
|
||||
case chatSelection
|
||||
}
|
||||
|
||||
public enum ContactListFilter {
|
||||
|
@ -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 = [
|
||||
],
|
||||
|
@ -9,7 +9,7 @@ static_library(
|
||||
"Sources/*.h",
|
||||
]),
|
||||
exported_headers = glob([
|
||||
"Sources/*.h",
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/PKCS:PKCS",
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -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, _):
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 []
|
||||
}
|
||||
|
@ -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 []
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
18
submodules/EncryptionKeyVisualization/Impl/BUCK
Normal 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",
|
||||
],
|
||||
)
|
20
submodules/FFMpegBinding/BUCK
Normal 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",
|
||||
],
|
||||
)
|
@ -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])) {
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
@ -16,6 +16,7 @@ objc_library(
|
||||
"Sources/*.mm",
|
||||
"Sources/*.c",
|
||||
"Sources/*.cpp",
|
||||
"Sources/*.h",
|
||||
]),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
|
@ -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",
|
||||
|
28
submodules/LegacyDataImport/Impl/BUCK
Normal 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",
|
||||
],
|
||||
)
|
@ -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 = [
|
||||
|
@ -9,7 +9,7 @@ static_library(
|
||||
"Sources/**/*.h",
|
||||
]),
|
||||
exported_headers = glob([
|
||||
"Sources/**/*.h",
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/EncryptionProvider:EncryptionProvider",
|
||||
|
2
submodules/Opus/BUCK
vendored
@ -33,7 +33,7 @@ static_library(
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
exported_headers = glob([
|
||||
"Sources/**/*.h",
|
||||
"Public/**/*.h",
|
||||
]),
|
||||
deps = [
|
||||
":opus_lib",
|
||||
|
14
submodules/OpusBinding/BUCK
vendored
@ -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",
|
||||
],
|
||||
|
@ -9,7 +9,7 @@ static_library(
|
||||
"Sources/**/*.h",
|
||||
]),
|
||||
exported_headers = glob([
|
||||
"Sources/**/*.h",
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/openssl:openssl",
|
||||
|
@ -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 = [
|
||||
|
@ -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 {
|
||||
|
@ -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()*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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] {
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
],
|
||||
|
@ -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",
|
||||
|
21
submodules/RaiseToListen/Impl/BUCK
Normal 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",
|
||||
],
|
||||
)
|
@ -1,7 +1,7 @@
|
||||
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||
|
||||
static_library(
|
||||
name = "Crc32",
|
||||
name = "RingBuffer",
|
||||
srcs = glob([
|
||||
"Sources/*.m",
|
||||
]),
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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",
|
||||
|
27
submodules/ShareItems/Impl/BUCK
Normal 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",
|
||||
],
|
||||
)
|
@ -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",
|
||||
|
31
submodules/Stripe/BUCK
vendored
@ -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",
|
||||
|
@ -11,7 +11,7 @@ static_library(
|
||||
headers = glob([
|
||||
"Sources/**/*.h",
|
||||
]),
|
||||
exported_headers = [
|
||||
"Sources/Svg.h",
|
||||
],
|
||||
exported_headers = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) }
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 : [] },
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ extension StickerPackReference {
|
||||
return .inputStickerSetShortName(shortName: name)
|
||||
case .animatedEmoji:
|
||||
return .inputStickerSetAnimatedEmoji
|
||||
case .dice:
|
||||
return .inputStickerSetDice
|
||||
}
|
||||
}
|
||||
}
|
||||
|