Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
@ -315,13 +315,8 @@ apple_bundle(
|
||||
apple_binary(
|
||||
name = "NotificationServiceBinary",
|
||||
srcs = glob([
|
||||
"NotificationService/**/*.m",
|
||||
"NotificationService/**/*.swift",
|
||||
"NotificationService/Sources/*.swift",
|
||||
]),
|
||||
headers = glob([
|
||||
"NotificationService/**/*.h",
|
||||
]),
|
||||
bridging_header = "NotificationService/NotificationService-Bridging-Header.h",
|
||||
configs = notification_service_extension_configs(),
|
||||
swift_compiler_flags = [
|
||||
"-application-extension",
|
||||
@ -339,6 +334,7 @@ apple_binary(
|
||||
"@executable_path/../../Frameworks",
|
||||
],
|
||||
deps = [
|
||||
"//Telegram/NotificationService/NotificationServiceObjC:NotificationServiceObjC",
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/MtProtoKit:MtProtoKit#shared",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
||||
|
||||
440
Telegram/BUILD
@ -130,6 +130,13 @@ filegroup(
|
||||
], exclude = ["Telegram-iOS/DefaultAppIcon.xcassets/**/.*"]),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "DefaultIcon",
|
||||
srcs = glob([
|
||||
"Telegram-iOS/AppIcons.xcassets/BlueIcon.appiconset/*.png",
|
||||
]),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "AdditionalIcons",
|
||||
srcs = glob([
|
||||
@ -501,17 +508,6 @@ watchos_application(
|
||||
],
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "ShareExtensionLib",
|
||||
module_name = "ShareExtensionLib",
|
||||
srcs = glob([
|
||||
"Share/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/TelegramUI:TelegramUI"
|
||||
],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "MtProtoKitInfoPlist",
|
||||
extension = "plist",
|
||||
@ -975,6 +971,17 @@ plist_fragment(
|
||||
)
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "ShareExtensionLib",
|
||||
module_name = "ShareExtensionLib",
|
||||
srcs = glob([
|
||||
"Share/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/TelegramUI:TelegramUI"
|
||||
],
|
||||
)
|
||||
|
||||
ios_extension(
|
||||
name = "ShareExtension",
|
||||
bundle_id = "{telegram_bundle_id}.Share".format(
|
||||
@ -997,13 +1004,211 @@ ios_extension(
|
||||
],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "NotificationContentInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>{telegram_bundle_id}.NotificationContent</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Telegram</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>UNNotificationExtensionCategory</key>
|
||||
<array>
|
||||
<string>withReplyMedia</string>
|
||||
<string>withMuteMedia</string>
|
||||
</array>
|
||||
<key>UNNotificationExtensionInitialContentSizeRatio</key>
|
||||
<real>0.0001</real>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.usernotifications.content-extension</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>NotificationViewController</string>
|
||||
</dict>
|
||||
""".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
)
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "NotificationServiceExtensionLib",
|
||||
module_name = "NotificationServiceExtensionLib",
|
||||
name = "NotificationContentExtensionLib",
|
||||
module_name = "NotificationContentExtensionLib",
|
||||
srcs = glob([
|
||||
"NotificationServiceNext/**/*.swift",
|
||||
"NotificationContent/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/TelegramUI:TelegramUI"
|
||||
],
|
||||
)
|
||||
|
||||
ios_extension(
|
||||
name = "NotificationContentExtension",
|
||||
bundle_id = "{telegram_bundle_id}.NotificationContent".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = [
|
||||
"iphone",
|
||||
"ipad",
|
||||
],
|
||||
infoplists = [
|
||||
":NotificationContentInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":AppNameInfoPlist",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
provisioning_profile = "//build-input/data/provisioning-profiles:NotificationContent.mobileprovision",
|
||||
deps = [":NotificationContentExtensionLib"],
|
||||
frameworks = [
|
||||
":TelegramUIFramework"
|
||||
],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "WidgetInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>{telegram_bundle_id}.Widget</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Telegram</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
""".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
)
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "WidgetExtensionLib",
|
||||
module_name = "WidgetExtensionLib",
|
||||
srcs = glob([
|
||||
"WidgetKitWidget/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/WidgetItems:WidgetItems",
|
||||
"//submodules/AppLockState:AppLockState",
|
||||
],
|
||||
)
|
||||
|
||||
ios_extension(
|
||||
name = "WidgetExtension",
|
||||
bundle_id = "{telegram_bundle_id}.Widget".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = [
|
||||
"iphone",
|
||||
"ipad",
|
||||
],
|
||||
infoplists = [
|
||||
":WidgetInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":AppNameInfoPlist",
|
||||
],
|
||||
minimum_os_version = "14.0",
|
||||
provides_main = True,
|
||||
provisioning_profile = "//build-input/data/provisioning-profiles:Widget.mobileprovision",
|
||||
deps = [":WidgetExtensionLib"],
|
||||
frameworks = [],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "IntentsInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>{telegram_bundle_id}.SiriIntents</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Telegram</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>IntentsRestrictedWhileLocked</key>
|
||||
<array/>
|
||||
<key>IntentsRestrictedWhileProtectedDataUnavailable</key>
|
||||
<array/>
|
||||
<key>IntentsSupported</key>
|
||||
<array>
|
||||
<string>INSendMessageIntent</string>
|
||||
<string>INStartAudioCallIntent</string>
|
||||
<string>INSearchForMessagesIntent</string>
|
||||
<string>INSetMessageAttributeIntent</string>
|
||||
<string>INSearchCallHistoryIntent</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.intents-service</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>IntentHandler</string>
|
||||
</dict>
|
||||
""".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
)
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "IntentsExtensionLib",
|
||||
module_name = "IntentsExtensionLib",
|
||||
srcs = glob([
|
||||
"SiriIntents/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramApi:TelegramApi",
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
|
||||
"//submodules/AppLockState:AppLockState",
|
||||
],
|
||||
)
|
||||
|
||||
ios_extension(
|
||||
name = "IntentsExtension",
|
||||
bundle_id = "{telegram_bundle_id}.SiriIntents".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = [
|
||||
"iphone",
|
||||
"ipad",
|
||||
],
|
||||
infoplists = [
|
||||
":IntentsInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":AppNameInfoPlist",
|
||||
],
|
||||
minimum_os_version = "9.0",
|
||||
provisioning_profile = "//build-input/data/provisioning-profiles:Intents.mobileprovision",
|
||||
deps = [":IntentsExtensionLib"],
|
||||
frameworks = [
|
||||
":SwiftSignalKitFramework",
|
||||
":PostboxFramework",
|
||||
":TelegramApiFramework",
|
||||
":SyncCoreFramework",
|
||||
],
|
||||
)
|
||||
|
||||
@ -1048,8 +1253,13 @@ ios_extension(
|
||||
],
|
||||
minimum_os_version = "10.0",
|
||||
provisioning_profile = "//build-input/data/provisioning-profiles:NotificationService.mobileprovision",
|
||||
deps = [":NotificationServiceExtensionLib"],
|
||||
deps = ["//Telegram/NotificationService:NotificationServiceExtensionLib"],
|
||||
frameworks = [
|
||||
":MtProtoKitFramework",
|
||||
":SwiftSignalKitFramework",
|
||||
":PostboxFramework",
|
||||
":TelegramApiFramework",
|
||||
":SyncCoreFramework",
|
||||
],
|
||||
)
|
||||
|
||||
@ -1219,6 +1429,191 @@ plist_fragment(
|
||||
)
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "TelegramInfoIconsPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<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>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>IconDefault-60</string>
|
||||
<string>IconDefault-76</string>
|
||||
<string>IconDefault-83.5</string>
|
||||
<string>IconDefault-Small-40</string>
|
||||
<string>IconDefault-Small</string>
|
||||
</array>
|
||||
<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>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>IconDefault-60</string>
|
||||
<string>IconDefault-76</string>
|
||||
<string>IconDefault-83.5</string>
|
||||
<string>IconDefault-Small-40</string>
|
||||
<string>IconDefault-Small</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
""")
|
||||
|
||||
ios_application(
|
||||
name = "Telegram",
|
||||
bundle_id = "{telegram_bundle_id}".format(
|
||||
@ -1231,9 +1626,15 @@ ios_application(
|
||||
infoplists = [
|
||||
":TelegramInfoPlist",
|
||||
":AdditionalInfoPlist",
|
||||
":TelegramInfoIconsPlist",
|
||||
],
|
||||
app_icons = [
|
||||
":DefaultAppIcon",
|
||||
#app_icons = [
|
||||
# ":DefaultAppIcon",
|
||||
#],
|
||||
resources = [
|
||||
":AdditionalIcons",
|
||||
#":DefaultAppIcon",
|
||||
#":AppIcons",
|
||||
],
|
||||
frameworks = [
|
||||
":MtProtoKitFramework",
|
||||
@ -1251,7 +1652,10 @@ ios_application(
|
||||
],
|
||||
extensions = [
|
||||
":ShareExtension",
|
||||
#":NotificationServiceExtension",
|
||||
":WidgetExtension",
|
||||
":NotificationContentExtension",
|
||||
":NotificationServiceExtension",
|
||||
":IntentsExtension",
|
||||
],
|
||||
watch_application = ":TelegramWatchApp",
|
||||
deps = [
|
||||
|
||||
@ -5,7 +5,7 @@ import TelegramUI
|
||||
import BuildConfig
|
||||
|
||||
@objc(NotificationViewController)
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
class NotificationViewController: UIViewController, UNNotificationContentExtension {
|
||||
private var impl: NotificationViewControllerImpl?
|
||||
|
||||
|
||||
28
Telegram/NotificationService/BUILD
Normal file
@ -0,0 +1,28 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "NotificationServiceExtensionLib",
|
||||
module_name = "NotificationServiceExtensionLib",
|
||||
srcs = glob([
|
||||
"Sources/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/MtProtoKit:MtProtoKit",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/EncryptionProvider:EncryptionProvider",
|
||||
"//submodules/Database/ValueBox:ValueBox",
|
||||
"//submodules/Database/PostboxDataTypes:PostboxDataTypes",
|
||||
"//submodules/Database/MessageHistoryReadStateTable:MessageHistoryReadStateTable",
|
||||
"//submodules/Database/MessageHistoryMetadataTable:MessageHistoryMetadataTable",
|
||||
"//submodules/Database/PreferencesTable:PreferencesTable",
|
||||
"//submodules/Database/PeerTable:PeerTable",
|
||||
"//submodules/sqlcipher:sqlcipher",
|
||||
"//submodules/AppLockState:AppLockState",
|
||||
"//submodules/NotificationsPresentationData:NotificationsPresentationData",
|
||||
"//Telegram/NotificationService/NotificationServiceObjC:NotificationServiceObjC",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -1,6 +0,0 @@
|
||||
#ifndef NotificationService_BridgingHeader_h
|
||||
#define NotificationService_BridgingHeader_h
|
||||
|
||||
#import "NotificationService.h"
|
||||
|
||||
#endif
|
||||
23
Telegram/NotificationService/NotificationServiceObjC/BUCK
Normal file
@ -0,0 +1,23 @@
|
||||
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||
|
||||
static_library(
|
||||
name = "NotificationServiceObjC",
|
||||
srcs = glob([
|
||||
"Sources/*.m",
|
||||
]),
|
||||
headers = glob([
|
||||
"Sources/*.h",
|
||||
]),
|
||||
exported_headers = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/MtProtoKit:MtProtoKit#shared",
|
||||
"//submodules/NotificationsPresentationData:NotificationsPresentationData",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||
],
|
||||
)
|
||||
30
Telegram/NotificationService/NotificationServiceObjC/BUILD
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
objc_library(
|
||||
name = "NotificationServiceObjC",
|
||||
enable_modules = True,
|
||||
module_name = "NotificationServiceObjC",
|
||||
srcs = glob([
|
||||
"Sources/**/*.m",
|
||||
"Sources/**/*.h",
|
||||
]),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/MtProtoKit:MtProtoKit",
|
||||
"//submodules/NotificationsPresentationData:NotificationsPresentationData",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
weak_sdk_frameworks = [
|
||||
"BackgroundTasks",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -1,4 +1,4 @@
|
||||
#import "NotificationService.h"
|
||||
#import <NotificationServiceObjC/NotificationServiceObjC.h>
|
||||
|
||||
#import <mach/mach.h>
|
||||
|
||||
@ -14,8 +14,6 @@
|
||||
#import "Api.h"
|
||||
#import "FetchImage.h"
|
||||
|
||||
#import "NotificationService-Bridging-Header.h"
|
||||
|
||||
static NSData * _Nullable parseBase64(NSString *string) {
|
||||
string = [string stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
|
||||
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
import SwiftSignalKit
|
||||
import NotificationServiceObjC
|
||||
|
||||
private let queue = Queue()
|
||||
|
||||
@ -9,6 +9,7 @@ import PeerTable
|
||||
import PostboxCoding
|
||||
import AppLockState
|
||||
import NotificationsPresentationData
|
||||
import BuildConfig
|
||||
|
||||
private let registeredTypes: Void = {
|
||||
declareEncodable(InAppNotificationSettings.self, f: InAppNotificationSettings.init(decoder:))
|
||||
@ -1,8 +0,0 @@
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@objc(NotificationService)
|
||||
public final class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
}
|
||||
@ -54,7 +54,7 @@ private func cleanPhoneNumber(_ text: String) -> String {
|
||||
return result
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
func matchingDeviceContacts(stableIds: [String]) -> Signal<[MatchingDeviceContact], IntentContactsError> {
|
||||
guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
|
||||
return .fail(.generic)
|
||||
@ -128,7 +128,7 @@ func matchingCloudContact(postbox: Postbox, peerId: PeerId) -> Signal<TelegramUs
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
func personWithUser(stableId: String, user: TelegramUser) -> INPerson {
|
||||
var nameComponents = PersonNameComponents()
|
||||
nameComponents.givenName = user.firstName
|
||||
|
||||
@ -50,7 +50,7 @@ enum IntentHandlingError {
|
||||
case generic
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
@objc(IntentHandler)
|
||||
public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling, INSearchCallHistoryIntentHandling {
|
||||
private let accountPromise = Promise<Account?>()
|
||||
@ -68,7 +68,6 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
}
|
||||
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||
|
||||
let apiId: Int32 = buildConfig.apiId
|
||||
@ -151,7 +150,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
case noResult
|
||||
case skip
|
||||
|
||||
@available(iOSApplicationExtension 11.0, *)
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
var sendMessageRecipientResulutionResult: INSendMessageRecipientResolutionResult {
|
||||
switch self {
|
||||
case let .success(person):
|
||||
@ -204,7 +203,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
filteredPersons.append(person)
|
||||
}
|
||||
|
||||
if #available(iOSApplicationExtension 10.3, *) {
|
||||
if #available(iOSApplicationExtension 10.3, iOS 10.3, *) {
|
||||
if let siriMatches = person.siriMatches {
|
||||
for match in siriMatches {
|
||||
if let contactIdentifier = match.contactIdentifier, !contactIdentifier.isEmpty {
|
||||
@ -237,7 +236,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
if let contactIdentifier = person.contactIdentifier {
|
||||
return contactIdentifier
|
||||
}
|
||||
if #available(iOSApplicationExtension 10.3, *) {
|
||||
if #available(iOSApplicationExtension 10.3, iOS 10.3, *) {
|
||||
if let siriMatches = person.siriMatches {
|
||||
for match in siriMatches {
|
||||
if let contactIdentifier = match.contactIdentifier, !contactIdentifier.isEmpty {
|
||||
@ -289,7 +288,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
})
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, *)
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
public func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
|
||||
if let appGroupUrl = self.appGroupUrl {
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
@ -598,7 +597,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
})
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 11.0, *)
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
public func resolveDestinationType(for intent: INStartAudioCallIntent, with completion: @escaping (INCallDestinationTypeResolutionResult) -> Void) {
|
||||
completion(.success(with: .normal))
|
||||
}
|
||||
@ -647,7 +646,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
|
||||
// MARK: - INSearchCallHistoryIntentHandling
|
||||
|
||||
@available(iOSApplicationExtension 11.0, *)
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
public func resolveCallTypes(for intent: INSearchCallHistoryIntent, with completion: @escaping (INCallRecordTypeOptionsResolutionResult) -> Void) {
|
||||
completion(.success(with: .missed))
|
||||
}
|
||||
@ -685,7 +684,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
|> deliverOnMainQueue).start(next: { calls in
|
||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self))
|
||||
let response: INSearchCallHistoryIntentResponse
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
response = INSearchCallHistoryIntentResponse(code: .success, userActivity: userActivity)
|
||||
response.callRecords = calls.map { $0.intentCall }
|
||||
} else {
|
||||
|
||||
@ -17,7 +17,7 @@ extension MessageId {
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
func getMessages(account: Account, ids: [MessageId]) -> Signal<[INMessage], NoError> {
|
||||
return account.postbox.transaction { transaction -> [INMessage] in
|
||||
var messages: [INMessage] = []
|
||||
@ -30,7 +30,7 @@ func getMessages(account: Account, ids: [MessageId]) -> Signal<[INMessage], NoEr
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
||||
return account.postbox.tailChatListView(groupId: .root, count: 20, summaryComponents: ChatListEntrySummaryComponents())
|
||||
|> take(1)
|
||||
@ -89,7 +89,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
struct CallRecord {
|
||||
let identifier: String
|
||||
let date: Date
|
||||
@ -97,13 +97,13 @@ struct CallRecord {
|
||||
let duration: Int32?
|
||||
let unseen: Bool
|
||||
|
||||
@available(iOSApplicationExtension 11.0, *)
|
||||
@available(iOSApplicationExtension 11.0, iOS 11.0, *)
|
||||
var intentCall: INCallRecord {
|
||||
return INCallRecord(identifier: self.identifier, dateCreated: self.date, caller: self.caller, callRecordType: .missed, callCapability: .audioCall, callDuration: self.duration.flatMap(Double.init), unseen: self.unseen)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
func missedCalls(account: Account) -> Signal<[CallRecord], NoError> {
|
||||
return account.viewTracker.callListView(type: .missed, index: MessageIndex.absoluteUpperBound(), count: 30)
|
||||
|> take(1)
|
||||
@ -125,7 +125,7 @@ func missedCalls(account: Account) -> Signal<[CallRecord], NoError> {
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
private func callWithTelegramMessage(_ telegramMessage: Message, account: Account) -> CallRecord? {
|
||||
guard let author = telegramMessage.author, let user = telegramMessage.peers[author.id] as? TelegramUser else {
|
||||
return nil
|
||||
@ -133,7 +133,7 @@ private func callWithTelegramMessage(_ telegramMessage: Message, account: Accoun
|
||||
|
||||
let identifier = "\(telegramMessage.id.peerId.toInt64())_\(telegramMessage.id.namespace)_\(telegramMessage.id.id)"
|
||||
let personHandle: INPersonHandle
|
||||
if #available(iOSApplicationExtension 10.2, *) {
|
||||
if #available(iOSApplicationExtension 10.2, iOS 10.2, *) {
|
||||
var type: INPersonHandleType
|
||||
var label: INPersonHandleLabel?
|
||||
if let username = user.username {
|
||||
@ -164,7 +164,7 @@ private func callWithTelegramMessage(_ telegramMessage: Message, account: Accoun
|
||||
return CallRecord(identifier: identifier, date: date, caller: caller, duration: duration, unseen: true)
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
private func messageWithTelegramMessage(_ telegramMessage: Message) -> INMessage? {
|
||||
guard let author = telegramMessage.author, let user = telegramMessage.peers[author.id] as? TelegramUser, user.id.id != 777000 else {
|
||||
return nil
|
||||
@ -172,7 +172,7 @@ private func messageWithTelegramMessage(_ telegramMessage: Message) -> INMessage
|
||||
|
||||
let identifier = "\(telegramMessage.id.peerId.toInt64())_\(telegramMessage.id.namespace)_\(telegramMessage.id.id)"
|
||||
let personHandle: INPersonHandle
|
||||
if #available(iOSApplicationExtension 10.2, *) {
|
||||
if #available(iOSApplicationExtension 10.2, iOS 10.2, *) {
|
||||
var type: INPersonHandleType
|
||||
var label: INPersonHandleLabel?
|
||||
if let username = user.username {
|
||||
@ -195,7 +195,7 @@ private func messageWithTelegramMessage(_ telegramMessage: Message) -> INMessage
|
||||
let date = Date(timeIntervalSince1970: TimeInterval(telegramMessage.timestamp))
|
||||
|
||||
let message: INMessage
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
var messageType: INMessageType = .text
|
||||
loop: for media in telegramMessage.media {
|
||||
if media is TelegramMediaImage {
|
||||
@ -218,7 +218,7 @@ private func messageWithTelegramMessage(_ telegramMessage: Message) -> INMessage
|
||||
} else if file.isAnimated {
|
||||
messageType = .mediaVideo
|
||||
break loop
|
||||
} else if #available(iOSApplicationExtension 12.0, *) {
|
||||
} else if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
|
||||
messageType = .file
|
||||
break loop
|
||||
}
|
||||
|
||||
BIN
Telegram/Telegram-iOS/IconDefault-60@2x.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
Telegram/Telegram-iOS/IconDefault-60@3x.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
Telegram/Telegram-iOS/IconDefault-76.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Telegram/Telegram-iOS/IconDefault-76@2x.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
Telegram/Telegram-iOS/IconDefault-83.5@2x.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
Telegram/Telegram-iOS/IconDefault-Small-40.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Telegram-iOS/IconDefault-Small-40@2x.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Telegram/Telegram-iOS/IconDefault-Small-40@3x.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
Telegram/Telegram-iOS/IconDefault-Small.png
Normal file
|
After Width: | Height: | Size: 925 B |
BIN
Telegram/Telegram-iOS/IconDefault-Small@2x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Telegram-iOS/IconDefault-Small@3x.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
@ -2195,6 +2195,8 @@ Unused sets are archived when you add more.";
|
||||
|
||||
"Widget.AuthRequired" = "Log in to Telegram";
|
||||
"Widget.NoUsers" = "Start messaging to see your friends here";
|
||||
"Widget.GalleryTitle" = "Telegram";
|
||||
"Widget.GalleryDescription" = "See your friends here";
|
||||
|
||||
"ShareMenu.CopyShareLinkGame" = "Copy link to game";
|
||||
|
||||
@ -5866,3 +5868,18 @@ Any member of this group will be able to see messages in the channel.";
|
||||
|
||||
"ChatList.MessageFiles_1" = "%@ File";
|
||||
"ChatList.MessageFiles_any" = "%@ Files";
|
||||
|
||||
"Chat.TitlePinnedMessages_1" = "Pinned Message";
|
||||
"Chat.TitlePinnedMessages_any" = "%@ Pinned Messages";
|
||||
|
||||
"Chat.PanelHidePinnedMessages" = "Don't Show Pinned Messages";
|
||||
"Chat.PanelUnpinAllMessages_1" = "Unpin Message";
|
||||
"Chat.PanelUnpinAllMessages_any" = "Unpin All %@ Messages";
|
||||
"Chat.UnpinAllMessagesConfirmation_1" = "Do you want to unpin 1 message in this chat?";
|
||||
"Chat.UnpinAllMessagesConfirmation_any" = "Do you want to unpin all %@ messages in this chat?";
|
||||
|
||||
"Chat.MessagesUnpinned_1" = "Message Unpinned";
|
||||
"Chat.MessagesUnpinned_any" = "%@ Messages Unpinned";
|
||||
|
||||
"Chat.PinnedMessagesHiddenTitle" = "Pinned Messages Hidden";
|
||||
"Chat.PinnedMessagesHiddenText" = "You will see the bar with pinned messages only if a new message is pinned.";
|
||||
|
||||
@ -42,7 +42,7 @@ class TodayViewController: UIViewController, NCWidgetProviding {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: widgetPresentationDataPath(rootPath: rootPath))), let value = try? JSONDecoder().decode(WidgetPresentationData.self, from: data) {
|
||||
presentationData = value
|
||||
} else {
|
||||
presentationData = WidgetPresentationData(applicationLockedString: "Unlock the app to use the widget", applicationStartRequiredString: "Open the app to use the widget")
|
||||
presentationData = WidgetPresentationData(applicationLockedString: "Unlock the app to use the widget", applicationStartRequiredString: "Open the app to use the widget", widgetGalleryTitle: "", widgetGalleryDescription: "")
|
||||
}
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) {
|
||||
|
||||
31
Telegram/WidgetKitWidget/Info.plist
Normal file
@ -0,0 +1,31 @@
|
||||
<?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>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME}</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>CFBundleShortVersionString</key>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widget-extension</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>TodayViewController</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
180
Telegram/WidgetKitWidget/PeerNode.swift
Normal file
@ -0,0 +1,180 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import WidgetItems
|
||||
|
||||
private extension UIColor {
|
||||
convenience init(rgb: UInt32) {
|
||||
self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
private let UIScreenScale = UIScreen.main.scale
|
||||
private func floorToScreenPixels(_ value: CGFloat) -> CGFloat {
|
||||
return floor(value * UIScreenScale) / UIScreenScale
|
||||
}
|
||||
|
||||
private let gradientColors: [NSArray] = [
|
||||
[UIColor(rgb: 0xff516a).cgColor, UIColor(rgb: 0xff885e).cgColor],
|
||||
[UIColor(rgb: 0xffa85c).cgColor, UIColor(rgb: 0xffcd6a).cgColor],
|
||||
[UIColor(rgb: 0x665fff).cgColor, UIColor(rgb: 0x82b1ff).cgColor],
|
||||
[UIColor(rgb: 0x54cb68).cgColor, UIColor(rgb: 0xa0de7e).cgColor],
|
||||
[UIColor(rgb: 0x4acccd).cgColor, UIColor(rgb: 0x00fcfd).cgColor],
|
||||
[UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor],
|
||||
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
|
||||
]
|
||||
|
||||
private func avatarRoundImage(size: CGSize, source: UIImage) -> UIImage? {
|
||||
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
|
||||
context?.beginPath()
|
||||
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||
context?.clip()
|
||||
|
||||
source.draw(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return image
|
||||
}
|
||||
|
||||
private let deviceColorSpace: CGColorSpace = {
|
||||
if #available(iOSApplicationExtension 9.3, iOS 9.3, *) {
|
||||
if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) {
|
||||
return colorSpace
|
||||
} else {
|
||||
return CGColorSpaceCreateDeviceRGB()
|
||||
}
|
||||
} else {
|
||||
return CGColorSpaceCreateDeviceRGB()
|
||||
}
|
||||
}()
|
||||
|
||||
private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId: Int64, letters: [String]) -> UIImage? {
|
||||
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
|
||||
context?.beginPath()
|
||||
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||
context?.clip()
|
||||
|
||||
let colorIndex = abs(Int(accountPeerId + peerId))
|
||||
|
||||
let colorsArray = gradientColors[colorIndex % gradientColors.count]
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray, locations: &locations)!
|
||||
|
||||
context?.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
||||
context?.setBlendMode(.normal)
|
||||
|
||||
let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1]))
|
||||
let attributedString = NSAttributedString(string: string, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20.0), NSAttributedString.Key.foregroundColor: UIColor.white])
|
||||
|
||||
let line = CTLineCreateWithAttributedString(attributedString)
|
||||
let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
|
||||
|
||||
let lineOffset = CGPoint(x: string == "B" ? 1.0 : 0.0, y: 0.0)
|
||||
let lineOrigin = CGPoint(x: floorToScreenPixels(-lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) + lineOffset.x, y: floorToScreenPixels(-lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0))
|
||||
|
||||
context?.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context?.scaleBy(x: 1.0, y: -1.0)
|
||||
context?.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
context?.translateBy(x: lineOrigin.x, y: lineOrigin.y)
|
||||
if let context = context {
|
||||
CTLineDraw(line, context)
|
||||
}
|
||||
context?.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
|
||||
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return image
|
||||
}
|
||||
|
||||
private let avatarSize = CGSize(width: 50.0, height: 50.0)
|
||||
|
||||
func avatarImage(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) -> UIImage {
|
||||
if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||
return roundImage
|
||||
} else {
|
||||
return avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)!
|
||||
}
|
||||
}
|
||||
|
||||
private final class AvatarView: UIImageView {
|
||||
init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) {
|
||||
super.init(frame: CGRect())
|
||||
|
||||
if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||
self.image = roundImage
|
||||
} else {
|
||||
self.image = avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerView: UIView {
|
||||
let peer: WidgetDataPeer
|
||||
private let avatarView: AvatarView
|
||||
private let titleLabel: UILabel
|
||||
|
||||
private let tapped: () -> Void
|
||||
|
||||
init(primaryColor: UIColor, accountPeerId: Int64, peer: WidgetDataPeer, tapped: @escaping () -> Void) {
|
||||
self.peer = peer
|
||||
self.tapped = tapped
|
||||
self.avatarView = AvatarView(accountPeerId: accountPeerId, peer: peer, size: avatarSize)
|
||||
|
||||
self.titleLabel = UILabel()
|
||||
var title = peer.name
|
||||
if let lastName = peer.lastName, !lastName.isEmpty {
|
||||
title.append("\n")
|
||||
title.append(lastName)
|
||||
}
|
||||
|
||||
let systemFontSize = UIFont.preferredFont(forTextStyle: .body).pointSize
|
||||
let fontSize = floor(systemFontSize * 11.0 / 17.0)
|
||||
|
||||
self.titleLabel.text = title
|
||||
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
|
||||
self.titleLabel.textColor = UIColor.label
|
||||
} else {
|
||||
self.titleLabel.textColor = primaryColor
|
||||
}
|
||||
self.titleLabel.font = UIFont.systemFont(ofSize: fontSize)
|
||||
self.titleLabel.lineBreakMode = .byTruncatingTail
|
||||
self.titleLabel.numberOfLines = 2
|
||||
self.titleLabel.textAlignment = .center
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.avatarView)
|
||||
self.addSubview(self.titleLabel)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
self.avatarView.frame = CGRect(origin: CGPoint(x: floor((size.width - avatarSize.width) / 2.0), y: 0.0), size: avatarSize)
|
||||
|
||||
var titleSize = self.titleLabel.sizeThatFits(size)
|
||||
titleSize.width = min(size.width - 6.0, ceil(titleSize.width))
|
||||
titleSize.height = ceil(titleSize.height)
|
||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: avatarSize.height + 5.0), size: titleSize)
|
||||
}
|
||||
|
||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.tapped()
|
||||
}
|
||||
}
|
||||
}
|
||||
221
Telegram/WidgetKitWidget/TodayViewController.swift
Normal file
@ -0,0 +1,221 @@
|
||||
import UIKit
|
||||
import NotificationCenter
|
||||
import BuildConfig
|
||||
import WidgetItems
|
||||
import AppLockState
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
return appGroupPath + "/telegram-data"
|
||||
}
|
||||
|
||||
struct Provider: TimelineProvider {
|
||||
public typealias Entry = SimpleEntry
|
||||
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
return SimpleEntry(date: Date())
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
|
||||
let entry = SimpleEntry(date: Date())
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
|
||||
var entries: [SimpleEntry] = []
|
||||
|
||||
let currentDate = Date()
|
||||
for hourOffset in 0 ..< 1 {
|
||||
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
|
||||
let entry = SimpleEntry(date: entryDate)
|
||||
entries.append(entry)
|
||||
}
|
||||
|
||||
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
let date: Date
|
||||
}
|
||||
|
||||
enum PeersWidgetData {
|
||||
case placeholder
|
||||
case empty
|
||||
case locked
|
||||
case data(WidgetData)
|
||||
}
|
||||
|
||||
extension PeersWidgetData {
|
||||
static let previewData = PeersWidgetData.placeholder
|
||||
}
|
||||
|
||||
struct WidgetView: View {
|
||||
let data: PeersWidgetData
|
||||
|
||||
func placeholder(geometry: GeometryProxy) -> some View {
|
||||
let defaultItemSize: CGFloat = 60.0
|
||||
let defaultPaddingFraction: CGFloat = 0.36
|
||||
|
||||
let rowCount = Int(round(geometry.size.width / (defaultItemSize * (1.0 + defaultPaddingFraction))))
|
||||
let itemSize = floor(geometry.size.width / (CGFloat(rowCount) + defaultPaddingFraction * CGFloat(rowCount - 1)))
|
||||
|
||||
let firstRowY = itemSize / 2.0
|
||||
let secondRowY = itemSize / 2.0 + geometry.size.height - itemSize
|
||||
|
||||
return ZStack {
|
||||
ForEach(0 ..< rowCount * 2, content: { i in
|
||||
return Circle().frame(width: itemSize, height: itemSize).position(x: itemSize / 2.0 + floor(CGFloat(i % rowCount) * itemSize * (1.0 + defaultPaddingFraction)), y: i / rowCount == 0 ? firstRowY : secondRowY).foregroundColor(.gray)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func peersView(geometry: GeometryProxy, peers: WidgetDataPeers) -> some View {
|
||||
let defaultItemSize: CGFloat = 60.0
|
||||
let defaultPaddingFraction: CGFloat = 0.36
|
||||
|
||||
let rowCount = Int(round(geometry.size.width / (defaultItemSize * (1.0 + defaultPaddingFraction))))
|
||||
let itemSize = floor(geometry.size.width / (CGFloat(rowCount) + defaultPaddingFraction * CGFloat(rowCount - 1)))
|
||||
|
||||
let firstRowY = itemSize / 2.0
|
||||
let secondRowY = itemSize / 2.0 + geometry.size.height - itemSize
|
||||
|
||||
return ZStack {
|
||||
ForEach(0 ..< min(peers.peers.count, rowCount * 2), content: { i in
|
||||
Link(destination: URL(string: "\(buildConfig.appSpecificUrlScheme)://localpeer?id=\(peers.peers[i].id)")!, label: {
|
||||
Image(uiImage: avatarImage(accountPeerId: peers.accountPeerId, peer: peers.peers[i], size: CGSize(width: itemSize, height: itemSize)))
|
||||
.frame(width: itemSize, height: itemSize)
|
||||
}).frame(width: itemSize, height: itemSize)
|
||||
.position(x: itemSize / 2.0 + floor(CGFloat(i % rowCount) * itemSize * (1.0 + defaultPaddingFraction)), y: i / rowCount == 0 ? firstRowY : secondRowY)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func peerViews() -> AnyView {
|
||||
switch data {
|
||||
case .placeholder:
|
||||
return AnyView(GeometryReader { geometry in
|
||||
placeholder(geometry: geometry)
|
||||
})
|
||||
case .empty:
|
||||
return AnyView(VStack {
|
||||
Text(presentationData.applicationStartRequiredString)
|
||||
})
|
||||
case .locked:
|
||||
return AnyView(VStack {
|
||||
Text(presentationData.applicationLockedString)
|
||||
})
|
||||
case let .data(data):
|
||||
switch data {
|
||||
case let .peers(peers):
|
||||
return AnyView(GeometryReader { geometry in
|
||||
peersView(geometry: geometry, peers: peers)
|
||||
})
|
||||
default:
|
||||
return AnyView(ZStack {
|
||||
Circle()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(.systemBackground)
|
||||
peerViews()
|
||||
}
|
||||
.padding(.all)
|
||||
}
|
||||
}
|
||||
|
||||
private let buildConfig: BuildConfig = {
|
||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
preconditionFailure()
|
||||
}
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||
return buildConfig
|
||||
}()
|
||||
|
||||
private extension WidgetPresentationData {
|
||||
static var `default` = WidgetPresentationData(
|
||||
applicationLockedString: "Unlock the app to use the widget",
|
||||
applicationStartRequiredString: "Open the app to use the widget",
|
||||
widgetGalleryTitle: "Telegram",
|
||||
widgetGalleryDescription: ""
|
||||
)
|
||||
}
|
||||
|
||||
private let presentationData: WidgetPresentationData = {
|
||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
return WidgetPresentationData.default
|
||||
}
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
let appGroupName = "group.\(baseAppBundleId)"
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||
return WidgetPresentationData.default
|
||||
}
|
||||
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: widgetPresentationDataPath(rootPath: rootPath))), let value = try? JSONDecoder().decode(WidgetPresentationData.self, from: data) {
|
||||
return value
|
||||
} else {
|
||||
return WidgetPresentationData.default
|
||||
}
|
||||
}()
|
||||
|
||||
func getWidgetData() -> PeersWidgetData {
|
||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
return .placeholder
|
||||
}
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
let appGroupName = "group.\(baseAppBundleId)"
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||
return .placeholder
|
||||
}
|
||||
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) {
|
||||
return .locked
|
||||
}
|
||||
|
||||
let dataPath = rootPath + "/widget-data"
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
|
||||
return .data(widgetData)
|
||||
} else {
|
||||
return .placeholder
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct Static_Widget: Widget {
|
||||
private let kind: String = "Static_Widget"
|
||||
|
||||
public var body: some WidgetConfiguration {
|
||||
return StaticConfiguration(
|
||||
kind: kind,
|
||||
provider: Provider(),
|
||||
content: { entry in
|
||||
WidgetView(data: getWidgetData())
|
||||
}
|
||||
)
|
||||
.supportedFamilies([.systemMedium])
|
||||
.configurationDisplayName(presentationData.widgetGalleryTitle)
|
||||
.description(presentationData.widgetGalleryDescription)
|
||||
}
|
||||
}
|
||||
1
Telegram/WidgetKitWidget/ar.lproj/InfoPlist.strings
Normal file
@ -0,0 +1 @@
|
||||
"CFBundleDisplayName" = "الأشخاص";
|
||||
1
Telegram/WidgetKitWidget/de.lproj/InfoPlist.strings
Normal file
@ -0,0 +1 @@
|
||||
"CFBundleDisplayName" = "Leute";
|
||||
1
Telegram/WidgetKitWidget/en.lproj/InfoPlist.strings
Normal file
@ -0,0 +1 @@
|
||||
"CFBundleDisplayName" = "People";
|
||||
2
Telegram/WidgetKitWidget/en.lproj/Localizable.strings
Normal file
@ -0,0 +1,2 @@
|
||||
"Widget.NoUsers" = "No users here yet...";
|
||||
"Widget.AuthRequired" = "Open Telegram and log in.";
|
||||
1
Telegram/WidgetKitWidget/es.lproj/InfoPlist.strings
Normal file
@ -0,0 +1 @@
|
||||
"CFBundleDisplayName" = "Personas";
|
||||
1
Telegram/WidgetKitWidget/it.lproj/InfoPlist.strings
Normal file
@ -0,0 +1 @@
|
||||
"CFBundleDisplayName" = "Persone";
|
||||
1
Telegram/WidgetKitWidget/ko.lproj/InfoPlist.strings
Normal file
@ -0,0 +1 @@
|
||||
"CFBundleDisplayName" = "사람";
|
||||
1
Telegram/WidgetKitWidget/nl.lproj/InfoPlist.strings
Normal file
@ -0,0 +1 @@
|
||||
"CFBundleDisplayName" = "Mensen";
|
||||
1
Telegram/WidgetKitWidget/pt.lproj/InfoPlist.strings
Normal file
@ -0,0 +1 @@
|
||||
"CFBundleDisplayName" = "Pessoas";
|
||||
1
Telegram/WidgetKitWidget/ru.lproj/InfoPlist.strings
Normal file
@ -0,0 +1 @@
|
||||
"CFBundleDisplayName" = "Люди";
|
||||
@ -531,7 +531,7 @@ public protocol SharedAccountContext: class {
|
||||
func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem)
|
||||
func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?)
|
||||
func openChatMessage(_ params: OpenChatMessageParams) -> Bool
|
||||
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>
|
||||
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>
|
||||
func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController
|
||||
func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController?
|
||||
func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController?
|
||||
|
||||
@ -19,17 +19,17 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
public let automaticDownloadPeerType: MediaAutoDownloadPeerType
|
||||
public let automaticDownloadNetworkType: MediaAutoDownloadNetworkType
|
||||
public let isRecentActions: Bool
|
||||
public let isScheduledMessages: Bool
|
||||
public let subject: ChatControllerSubject?
|
||||
public let contactsPeerIds: Set<PeerId>
|
||||
public let channelDiscussionGroup: ChannelDiscussionGroupStatus
|
||||
public let animatedEmojiStickers: [String: [StickerPackItem]]
|
||||
public let forcedResourceStatus: FileMediaResourceStatus?
|
||||
|
||||
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, isScheduledMessages: Bool = false, contactsPeerIds: Set<PeerId> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) {
|
||||
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<PeerId> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) {
|
||||
self.automaticDownloadPeerType = automaticDownloadPeerType
|
||||
self.automaticDownloadNetworkType = automaticDownloadNetworkType
|
||||
self.isRecentActions = isRecentActions
|
||||
self.isScheduledMessages = isScheduledMessages
|
||||
self.subject = subject
|
||||
self.contactsPeerIds = contactsPeerIds
|
||||
self.channelDiscussionGroup = channelDiscussionGroup
|
||||
self.animatedEmojiStickers = animatedEmojiStickers
|
||||
@ -46,7 +46,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
if lhs.isRecentActions != rhs.isRecentActions {
|
||||
return false
|
||||
}
|
||||
if lhs.isScheduledMessages != rhs.isScheduledMessages {
|
||||
if lhs.subject != rhs.subject {
|
||||
return false
|
||||
}
|
||||
if lhs.contactsPeerIds != rhs.contactsPeerIds {
|
||||
@ -65,6 +65,16 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public extension ChatMessageItemAssociatedData {
|
||||
var isInPinnedListMode: Bool {
|
||||
if case .pinnedMessages = self.subject {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChatControllerInteractionLongTapAction {
|
||||
case url(String)
|
||||
case mention(String)
|
||||
@ -331,6 +341,7 @@ public struct ChatTextInputStateText: PostboxCoding, Equatable {
|
||||
public enum ChatControllerSubject: Equatable {
|
||||
case message(id: MessageId, highlight: Bool)
|
||||
case scheduledMessages
|
||||
case pinnedMessages(id: MessageId?)
|
||||
}
|
||||
|
||||
public enum ChatControllerPresentationMode: Equatable {
|
||||
|
||||
@ -78,6 +78,8 @@ public class AnimatedCountLabelNode: ASDisplayNode {
|
||||
|
||||
fileprivate var resolvedSegments: [ResolvedSegment.Key: (ResolvedSegment, TextNode)] = [:]
|
||||
|
||||
public var reverseAnimationDirection: Bool = false
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
@ -88,6 +90,7 @@ public class AnimatedCountLabelNode: ASDisplayNode {
|
||||
for (segmentKey, segmentAndTextNode) in self.resolvedSegments {
|
||||
segmentLayouts[segmentKey] = TextNode.asyncLayout(segmentAndTextNode.1)
|
||||
}
|
||||
let reverseAnimationDirection = self.reverseAnimationDirection
|
||||
|
||||
return { [weak self] size, initialSegments in
|
||||
var segments: [ResolvedSegment] = []
|
||||
@ -101,10 +104,12 @@ public class AnimatedCountLabelNode: ASDisplayNode {
|
||||
|
||||
var remainingValue = value
|
||||
|
||||
let insertPosition = segments.count
|
||||
|
||||
while true {
|
||||
let digitValue = remainingValue % 10
|
||||
|
||||
segments.insert(.number(id: 1000 - segments.count, value: value, string: NSAttributedString(string: "\(digitValue)", attributes: attributes)), at: 0)
|
||||
segments.insert(.number(id: 1000 - segments.count, value: value, string: NSAttributedString(string: "\(digitValue)", attributes: attributes)), at: insertPosition)
|
||||
remainingValue /= 10
|
||||
if remainingValue == 0 {
|
||||
break
|
||||
@ -163,18 +168,25 @@ public class AnimatedCountLabelNode: ASDisplayNode {
|
||||
var animation: (CGFloat, Double)?
|
||||
if let (currentSegment, currentTextNode) = strongSelf.resolvedSegments[segment.key] {
|
||||
if case let .number(_, currentValue, currentString) = currentSegment, case let .number(_, updatedValue, updatedString) = segment, animated, !wasEmpty, currentValue != updatedValue, currentString.string != updatedString.string, let snapshot = currentTextNode.layer.snapshotContentTree() {
|
||||
let offsetY: CGFloat
|
||||
var fromAlpha: CGFloat = 1.0
|
||||
if let presentation = currentTextNode.layer.presentation() {
|
||||
fromAlpha = CGFloat(presentation.opacity)
|
||||
}
|
||||
var offsetY: CGFloat
|
||||
if currentValue > updatedValue {
|
||||
offsetY = -floor(currentTextNode.bounds.height * 0.6)
|
||||
} else {
|
||||
offsetY = floor(currentTextNode.bounds.height * 0.6)
|
||||
}
|
||||
if reverseAnimationDirection {
|
||||
offsetY = -offsetY
|
||||
}
|
||||
animation = (-offsetY, 0.2)
|
||||
snapshot.frame = currentTextNode.frame
|
||||
strongSelf.layer.addSublayer(snapshot)
|
||||
snapshot.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offsetY), duration: 0.2, removeOnCompletion: false, additive: true)
|
||||
snapshot.animateScale(from: 1.0, to: 0.3, duration: 0.2, removeOnCompletion: false)
|
||||
snapshot.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshot] _ in
|
||||
snapshot.animateAlpha(from: fromAlpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshot] _ in
|
||||
snapshot?.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ static_library(
|
||||
"Sources/*.h",
|
||||
]),
|
||||
exported_headers = glob([
|
||||
"Sources/*.h",
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
deps = [
|
||||
],
|
||||
|
||||
@ -26,8 +26,11 @@ objc_library(
|
||||
"-DAPP_SPECIFIC_URL_SCHEME=\\\"{}\\\"".format(telegram_app_specific_url_scheme),
|
||||
],
|
||||
hdrs = glob([
|
||||
"Sources/*.h",
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
],
|
||||
deps = [
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#import "BuildConfig.h"
|
||||
#import <BuildConfig/BuildConfig.h>
|
||||
|
||||
static NSString *telegramApplicationSecretKey = @"telegramApplicationSecretKey_v3";
|
||||
API_AVAILABLE(ios(10))
|
||||
|
||||
@ -1770,7 +1770,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
strongSelf.interaction.dismissInput()
|
||||
strongSelf.interaction.present(controller, nil)
|
||||
} else {
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: MessageTags.music)
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: MessageTags.music)
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
14
submodules/Database/Buffers/BUILD
Normal file
@ -0,0 +1,14 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "Buffers",
|
||||
module_name = "Buffers",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
18
submodules/Database/MessageHistoryMetadataTable/BUILD
Normal file
@ -0,0 +1,18 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "MessageHistoryMetadataTable",
|
||||
module_name = "MessageHistoryMetadataTable",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/ValueBox:ValueBox",
|
||||
"//submodules/Database/Table:Table",
|
||||
"//submodules/Database/PostboxDataTypes:PostboxDataTypes",
|
||||
"//submodules/Database/PostboxCoding:PostboxCoding",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
17
submodules/Database/MessageHistoryReadStateTable/BUILD
Normal file
@ -0,0 +1,17 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "MessageHistoryReadStateTable",
|
||||
module_name = "MessageHistoryReadStateTable",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/ValueBox:ValueBox",
|
||||
"//submodules/Database/Table:Table",
|
||||
"//submodules/Database/PostboxDataTypes:PostboxDataTypes",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -13,6 +13,7 @@ static_library(
|
||||
"Sources/**/*.h",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/MurmurHash/Impl:MurMurHashObjC",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
||||
15
submodules/Database/MurmurHash/BUILD
Normal file
@ -0,0 +1,15 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "MurmurHash",
|
||||
module_name = "MurmurHash",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/MurmurHash/Impl:MurMurHashObjC",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
18
submodules/Database/MurmurHash/Impl/BUCK
Normal file
@ -0,0 +1,18 @@
|
||||
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||
|
||||
static_library(
|
||||
name = "MurMurHashObjC",
|
||||
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",
|
||||
],
|
||||
)
|
||||
22
submodules/Database/MurmurHash/Impl/BUILD
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
objc_library(
|
||||
name = "MurMurHashObjC",
|
||||
enable_modules = True,
|
||||
module_name = "MurMurHashObjC",
|
||||
srcs = glob([
|
||||
"Sources/**/*.m",
|
||||
"Sources/**/*.h",
|
||||
]),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -1,4 +1,4 @@
|
||||
#import "MurMurHash32.h"
|
||||
#import <MurMurHashObjC/MurMurHashObjC.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import MurMurHashObjC
|
||||
|
||||
public enum HashFunctions {
|
||||
public static func murMurHash32(_ s: String) -> Int32 {
|
||||
|
||||
18
submodules/Database/PeerTable/BUILD
Normal file
@ -0,0 +1,18 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PeerTable",
|
||||
module_name = "PeerTable",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/ValueBox:ValueBox",
|
||||
"//submodules/Database/Table:Table",
|
||||
"//submodules/Database/PostboxCoding:PostboxCoding",
|
||||
"//submodules/Database/PostboxDataTypes:PostboxDataTypes",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
16
submodules/Database/PostboxCoding/BUILD
Normal file
@ -0,0 +1,16 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PostboxCoding",
|
||||
module_name = "PostboxCoding",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/Buffers:Buffers",
|
||||
"//submodules/Database/MurmurHash:MurmurHash",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -26,11 +26,11 @@ private let typeStore = { () -> EncodableTypeStore in
|
||||
|
||||
public func declareEncodable(_ type: Any.Type, f: @escaping(PostboxDecoder) -> PostboxCoding) {
|
||||
let string = "\(type)"
|
||||
let hash = murMurHashString32(string)
|
||||
let hash = HashFunctions.murMurHash32(string)
|
||||
if typeStore.dict[hash] != nil {
|
||||
assertionFailure("Encodable type hash collision for \(type)")
|
||||
}
|
||||
typeStore.dict[murMurHashString32("\(type)")] = f
|
||||
typeStore.dict[HashFunctions.murMurHash32("\(type)")] = f
|
||||
}
|
||||
|
||||
public func declareEncodable(typeHash: Int32, _ f: @escaping(PostboxDecoder) -> PostboxCoding) {
|
||||
@ -41,7 +41,7 @@ public func declareEncodable(typeHash: Int32, _ f: @escaping(PostboxDecoder) ->
|
||||
}
|
||||
|
||||
public func persistentHash32(_ string: String) -> Int32 {
|
||||
return murMurHashString32(string)
|
||||
return HashFunctions.murMurHash32(string)
|
||||
}
|
||||
|
||||
private enum ValueType: Int8 {
|
||||
@ -172,7 +172,7 @@ public final class PostboxEncoder {
|
||||
self.buffer.write(&t, offset: 0, length: 1)
|
||||
|
||||
let string = "\(type(of: value))"
|
||||
var typeHash: Int32 = murMurHashString32(string)
|
||||
var typeHash: Int32 = HashFunctions.murMurHash32(string)
|
||||
self.buffer.write(&typeHash, offset: 0, length: 4)
|
||||
|
||||
let innerEncoder = PostboxEncoder()
|
||||
@ -189,7 +189,7 @@ public final class PostboxEncoder {
|
||||
self.buffer.write(&t, offset: 0, length: 1)
|
||||
|
||||
let string = "\(type(of: value))"
|
||||
var typeHash: Int32 = murMurHashString32(string)
|
||||
var typeHash: Int32 = HashFunctions.murMurHash32(string)
|
||||
self.buffer.write(&typeHash, offset: 0, length: 4)
|
||||
|
||||
let innerEncoder = PostboxEncoder()
|
||||
@ -232,7 +232,7 @@ public final class PostboxEncoder {
|
||||
self.buffer.write(&length, offset: 0, length: 4)
|
||||
let innerEncoder = PostboxEncoder()
|
||||
for object in value {
|
||||
var typeHash: Int32 = murMurHashString32("\(type(of: object))")
|
||||
var typeHash: Int32 = HashFunctions.murMurHash32("\(type(of: object))")
|
||||
self.buffer.write(&typeHash, offset: 0, length: 4)
|
||||
|
||||
innerEncoder.reset()
|
||||
@ -252,7 +252,7 @@ public final class PostboxEncoder {
|
||||
self.buffer.write(&length, offset: 0, length: 4)
|
||||
let innerEncoder = PostboxEncoder()
|
||||
for object in value {
|
||||
var typeHash: Int32 = murMurHashString32("\(type(of: object))")
|
||||
var typeHash: Int32 = HashFunctions.murMurHash32("\(type(of: object))")
|
||||
self.buffer.write(&typeHash, offset: 0, length: 4)
|
||||
|
||||
innerEncoder.reset()
|
||||
@ -272,7 +272,7 @@ public final class PostboxEncoder {
|
||||
self.buffer.write(&length, offset: 0, length: 4)
|
||||
let innerEncoder = PostboxEncoder()
|
||||
for object in value {
|
||||
var typeHash: Int32 = murMurHashString32("\(type(of: object))")
|
||||
var typeHash: Int32 = HashFunctions.murMurHash32("\(type(of: object))")
|
||||
self.buffer.write(&typeHash, offset: 0, length: 4)
|
||||
|
||||
innerEncoder.reset()
|
||||
@ -322,7 +322,7 @@ public final class PostboxEncoder {
|
||||
|
||||
let innerEncoder = PostboxEncoder()
|
||||
for record in value {
|
||||
var keyTypeHash: Int32 = murMurHashString32("\(type(of: record.0))")
|
||||
var keyTypeHash: Int32 = HashFunctions.murMurHash32("\(type(of: record.0))")
|
||||
self.buffer.write(&keyTypeHash, offset: 0, length: 4)
|
||||
innerEncoder.reset()
|
||||
record.0.encode(innerEncoder)
|
||||
@ -330,7 +330,7 @@ public final class PostboxEncoder {
|
||||
self.buffer.write(&keyLength, offset: 0, length: 4)
|
||||
self.buffer.write(innerEncoder.buffer.memory, offset: 0, length: Int(keyLength))
|
||||
|
||||
var valueTypeHash: Int32 = murMurHashString32("\(type(of: record.1))")
|
||||
var valueTypeHash: Int32 = HashFunctions.murMurHash32("\(type(of: record.1))")
|
||||
self.buffer.write(&valueTypeHash, offset: 0, length: 4)
|
||||
innerEncoder.reset()
|
||||
record.1.encode(innerEncoder)
|
||||
@ -349,7 +349,7 @@ public final class PostboxEncoder {
|
||||
|
||||
let innerEncoder = PostboxEncoder()
|
||||
for record in value {
|
||||
var keyTypeHash: Int32 = murMurHashString32("\(type(of: record.0))")
|
||||
var keyTypeHash: Int32 = HashFunctions.murMurHash32("\(type(of: record.0))")
|
||||
self.buffer.write(&keyTypeHash, offset: 0, length: 4)
|
||||
innerEncoder.reset()
|
||||
keyEncoder(record.0, innerEncoder)
|
||||
@ -357,7 +357,7 @@ public final class PostboxEncoder {
|
||||
self.buffer.write(&keyLength, offset: 0, length: 4)
|
||||
self.buffer.write(innerEncoder.buffer.memory, offset: 0, length: Int(keyLength))
|
||||
|
||||
var valueTypeHash: Int32 = murMurHashString32("\(type(of: record.1))")
|
||||
var valueTypeHash: Int32 = HashFunctions.murMurHash32("\(type(of: record.1))")
|
||||
self.buffer.write(&valueTypeHash, offset: 0, length: 4)
|
||||
innerEncoder.reset()
|
||||
record.1.encode(innerEncoder)
|
||||
|
||||
16
submodules/Database/PostboxDataTypes/BUILD
Normal file
@ -0,0 +1,16 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PostboxDataTypes",
|
||||
module_name = "PostboxDataTypes",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/ValueBox:ValueBox",
|
||||
"//submodules/Database/PostboxCoding:PostboxCoding",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
18
submodules/Database/PreferencesTable/BUILD
Normal file
@ -0,0 +1,18 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PreferencesTable",
|
||||
module_name = "PreferencesTable",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/ValueBox:ValueBox",
|
||||
"//submodules/Database/Table:Table",
|
||||
"//submodules/Database/PostboxCoding:PostboxCoding",
|
||||
"//submodules/Database/PostboxDataTypes:PostboxDataTypes",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
15
submodules/Database/Table/BUILD
Normal file
@ -0,0 +1,15 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "Table",
|
||||
module_name = "Table",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/ValueBox:ValueBox",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
17
submodules/Database/ValueBox/BUILD
Normal file
@ -0,0 +1,17 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ValueBox",
|
||||
module_name = "ValueBox",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/sqlcipher:sqlcipher",
|
||||
"//submodules/Database/Buffers:Buffers",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -38,7 +38,7 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
|
||||
globalMessageIdsPeerIdNamespaces.insert(GlobalMessageIdsNamespace(peerIdNamespace: peerIdNamespace, messageIdNamespace: Namespaces.Message.Cloud))
|
||||
}
|
||||
|
||||
return SeedConfiguration(globalMessageIdsPeerIdNamespaces: globalMessageIdsPeerIdNamespaces, initializeChatListWithHole: (topLevel: ChatListHole(index: MessageIndex(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.Empty, id: 0), namespace: Namespaces.Message.Cloud, id: 1), timestamp: Int32.max - 1)), groups: ChatListHole(index: MessageIndex(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.Empty, id: 0), namespace: Namespaces.Message.Cloud, id: 1), timestamp: Int32.max - 1))), messageHoles: messageHoles, upgradedMessageHoles: upgradedMessageHoles, messageThreadHoles: messageThreadHoles, existingMessageTags: MessageTags.all, messageTagsWithSummary: MessageTags.unseenPersonalMessage, existingGlobalMessageTags: GlobalMessageTags.all, peerNamespacesRequiringMessageTextIndex: [Namespaces.Peer.SecretChat], peerSummaryCounterTags: { peer, isContact in
|
||||
return SeedConfiguration(globalMessageIdsPeerIdNamespaces: globalMessageIdsPeerIdNamespaces, initializeChatListWithHole: (topLevel: ChatListHole(index: MessageIndex(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.Empty, id: 0), namespace: Namespaces.Message.Cloud, id: 1), timestamp: Int32.max - 1)), groups: ChatListHole(index: MessageIndex(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.Empty, id: 0), namespace: Namespaces.Message.Cloud, id: 1), timestamp: Int32.max - 1))), messageHoles: messageHoles, upgradedMessageHoles: upgradedMessageHoles, messageThreadHoles: messageThreadHoles, existingMessageTags: MessageTags.all, messageTagsWithSummary: [.unseenPersonalMessage, .pinned], existingGlobalMessageTags: GlobalMessageTags.all, peerNamespacesRequiringMessageTextIndex: [Namespaces.Peer.SecretChat], peerSummaryCounterTags: { peer, isContact in
|
||||
if let peer = peer as? TelegramUser {
|
||||
if peer.botInfo != nil {
|
||||
return .bot
|
||||
|
||||
@ -3781,14 +3781,15 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func requestProximityNotification(flags: Int32, peer: Api.InputPeer, msgId: Int32, maxDistance: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
public static func requestProximityNotification(flags: Int32, peer: Api.InputPeer, msgId: Int32, ownLocation: Api.InputGeoPoint?, maxDistance: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1322540260)
|
||||
buffer.appendInt32(-699657935)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {ownLocation!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxDistance!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "messages.requestProximityNotification", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("maxDistance", maxDistance)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
return (FunctionDescription(name: "messages.requestProximityNotification", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("ownLocation", ownLocation), ("maxDistance", maxDistance)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -3797,6 +3798,20 @@ public extension Api {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func unpinAllMessages(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-265962357)
|
||||
peer.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "messages.unpinAllMessages", parameters: [("peer", peer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.AffectedHistory?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct channels {
|
||||
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
|
||||
@ -570,7 +570,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
strongSelf.displayNode.view.window?.endEditing(true)
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
} else {
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: MessageTags.music)
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: MessageTags.music)
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -2790,6 +2790,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
}
|
||||
|
||||
var tags = currentMessage.tags
|
||||
let attributes = currentMessage.attributes
|
||||
if pinned {
|
||||
tags.insert(.pinned)
|
||||
} else {
|
||||
@ -2800,7 +2801,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
return .skip
|
||||
}
|
||||
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
case let .MergePeerPresences(statuses, explicit):
|
||||
|
||||
@ -616,12 +616,6 @@ extension StoreMessage {
|
||||
if replyPeerId == peerId {
|
||||
threadId = makeMessageThreadId(threadIdValue)
|
||||
}
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)
|
||||
threadMessageId = threadIdValue
|
||||
if replyPeerId == peerId {
|
||||
threadId = makeMessageThreadId(threadIdValue)
|
||||
}
|
||||
}
|
||||
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId))
|
||||
}
|
||||
|
||||
@ -108,3 +108,97 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func requestUnpinAllMessages(account: Account, peerId: PeerId) -> Signal<Never, UpdatePinnedMessageError> {
|
||||
return account.postbox.transaction { transaction -> (Peer?, CachedPeerData?) in
|
||||
return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId))
|
||||
}
|
||||
|> mapError { _ -> UpdatePinnedMessageError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { peer, cachedPeerData -> Signal<Never, UpdatePinnedMessageError> in
|
||||
guard let peer = peer, let inputPeer = apiInputPeer(peer) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
if let channel = peer as? TelegramChannel {
|
||||
let canManagePin = channel.hasPermission(.pinMessages)
|
||||
if !canManagePin {
|
||||
return .fail(.generic)
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
switch group.role {
|
||||
case .creator, .admin:
|
||||
break
|
||||
default:
|
||||
if let defaultBannedRights = group.defaultBannedRights {
|
||||
if defaultBannedRights.flags.contains(.banPinMessages) {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let _ = peer as? TelegramUser, let cachedPeerData = cachedPeerData as? CachedUserData {
|
||||
if !cachedPeerData.canPinMessages {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|
||||
enum InternalError {
|
||||
case error(String)
|
||||
case restart
|
||||
}
|
||||
|
||||
let request: Signal<Never, InternalError> = account.network.request(Api.functions.messages.unpinAllMessages(peer: inputPeer))
|
||||
|> mapError { error -> InternalError in
|
||||
return .error(error.errorDescription)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Bool, InternalError> in
|
||||
switch result {
|
||||
case let .affectedHistory(_, _, count):
|
||||
if count != 0 {
|
||||
return .fail(.restart)
|
||||
}
|
||||
}
|
||||
return .single(true)
|
||||
}
|
||||
|> retry(retryOnError: { error -> Bool in
|
||||
switch error {
|
||||
case .restart:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}, delayIncrement: 0.0, maxDelay: 0.0, maxRetries: 100, onQueue: .concurrentDefaultQueue())
|
||||
|> mapToSignal { _ -> Signal<Never, InternalError> in
|
||||
let signal: Signal<Never, InternalError> = account.postbox.transaction { transaction -> Void in
|
||||
for index in transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .pinned) {
|
||||
transaction.updateMessage(index.id, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(forwardInfo)
|
||||
}
|
||||
|
||||
var tags = currentMessage.tags
|
||||
tags.remove(.pinned)
|
||||
|
||||
if tags == currentMessage.tags {
|
||||
return .skip
|
||||
}
|
||||
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
}
|
||||
|> castError(InternalError.self)
|
||||
|> ignoreValues
|
||||
|
||||
return signal
|
||||
}
|
||||
|
||||
return request
|
||||
|> mapError { _ -> UpdatePinnedMessageError in
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -123,6 +123,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
case chatInfoItemBackgroundImageWithoutWallpaper
|
||||
|
||||
case chatInputPanelCloseIconImage
|
||||
case chatInputPanelPinnedListIconImage
|
||||
case chatInputPanelEncircledCloseIconImage
|
||||
case chatInputPanelVerticalSeparatorLineImage
|
||||
|
||||
|
||||
@ -209,6 +209,12 @@ public struct PresentationResourcesChat {
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatInputPanelPinnedListIconImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatInputPanelPinnedListIconImage.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/PinnedList"), color: theme.chat.inputPanel.panelControlAccentColor)
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatInputPanelEncircledCloseIconImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatInputPanelEncircledCloseIconImage.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/EncircledCloseButton"), color: theme.chat.inputPanel.panelControlAccentColor)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_pinnedlist.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -11,11 +11,13 @@ import AlertUI
|
||||
import PresentationDataUtils
|
||||
import PeerInfoUI
|
||||
|
||||
private enum SubscriberAction {
|
||||
private enum SubscriberAction: Equatable {
|
||||
case join
|
||||
case kicked
|
||||
case muteNotifications
|
||||
case unmuteNotifications
|
||||
case unpinMessages(Int)
|
||||
case hidePinnedMessages
|
||||
}
|
||||
|
||||
private func titleAndColorForAction(_ action: SubscriberAction, theme: PresentationTheme, strings: PresentationStrings) -> (String, UIColor) {
|
||||
@ -28,10 +30,38 @@ private func titleAndColorForAction(_ action: SubscriberAction, theme: Presentat
|
||||
return (strings.Conversation_Mute, theme.chat.inputPanel.panelControlAccentColor)
|
||||
case .unmuteNotifications:
|
||||
return (strings.Conversation_Unmute, theme.chat.inputPanel.panelControlAccentColor)
|
||||
case let .unpinMessages(count):
|
||||
return (strings.Chat_PanelUnpinAllMessages(Int32(count)), theme.chat.inputPanel.panelControlAccentColor)
|
||||
case .hidePinnedMessages:
|
||||
return (strings.Chat_PanelHidePinnedMessages, theme.chat.inputPanel.panelControlAccentColor)
|
||||
}
|
||||
}
|
||||
|
||||
private func actionForPeer(peer: Peer, isMuted: Bool) -> SubscriberAction? {
|
||||
private func actionForPeer(peer: Peer, interfaceState: ChatPresentationInterfaceState, isMuted: Bool) -> SubscriberAction? {
|
||||
if case .pinnedMessages = interfaceState.subject {
|
||||
var canManagePin = false
|
||||
if let channel = peer as? TelegramChannel {
|
||||
canManagePin = channel.hasPermission(.pinMessages)
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
switch group.role {
|
||||
case .creator, .admin:
|
||||
canManagePin = true
|
||||
default:
|
||||
if let defaultBannedRights = group.defaultBannedRights {
|
||||
canManagePin = !defaultBannedRights.flags.contains(.banPinMessages)
|
||||
} else {
|
||||
canManagePin = true
|
||||
}
|
||||
}
|
||||
} else if let _ = peer as? TelegramUser, interfaceState.explicitelyCanPinMessages {
|
||||
canManagePin = true
|
||||
}
|
||||
if canManagePin {
|
||||
return .unpinMessages(max(1, interfaceState.pinnedMessage?.totalCount ?? 1))
|
||||
} else {
|
||||
return .hidePinnedMessages
|
||||
}
|
||||
} else {
|
||||
if let channel = peer as? TelegramChannel {
|
||||
switch channel.participationStatus {
|
||||
case .kicked:
|
||||
@ -53,6 +83,7 @@ private func actionForPeer(peer: Peer, isMuted: Bool) -> SubscriberAction? {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let badgeFont = Font.regular(14.0)
|
||||
|
||||
@ -162,6 +193,8 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
if let context = self.context, let presentationInterfaceState = self.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer {
|
||||
self.actionDisposable.set(togglePeerMuted(account: context.account, peerId: peer.id).start())
|
||||
}
|
||||
case .hidePinnedMessages, .unpinMessages:
|
||||
self.interfaceInteraction?.unpinAllMessages()
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,54 +215,8 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0)
|
||||
}
|
||||
|
||||
/*if previousState?.peerDiscussionId != interfaceState.peerDiscussionId {
|
||||
let signal: Signal<Int?, NoError>
|
||||
if let peerDiscussionId = interfaceState.peerDiscussionId, let context = self.context {
|
||||
let key = PostboxViewKey.unreadCounts(items: [.peer(peerDiscussionId)])
|
||||
let inclusionKey = PostboxViewKey.peerChatInclusion(peerDiscussionId)
|
||||
signal = context.account.postbox.combinedView(keys: [key, inclusionKey])
|
||||
|> map { view -> Int? in
|
||||
guard let inclusionView = view.views[inclusionKey] as? PeerChatInclusionView, let countsView = view.views[key] as? UnreadMessageCountsView else {
|
||||
return nil
|
||||
}
|
||||
if !inclusionView.inclusion {
|
||||
return nil
|
||||
}
|
||||
if let count = countsView.count(for: .peer(peerDiscussionId)), count != 0 {
|
||||
return Int(count)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
signal = .single(nil)
|
||||
}
|
||||
self.badgeDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self, let interfaceState = strongSelf.presentationInterfaceState, let image = strongSelf.badgeBackground.image else {
|
||||
return
|
||||
}
|
||||
let text = compactNumericCountString(value ?? 0, decimalSeparator: interfaceState.dateTimeFormat.decimalSeparator)
|
||||
|
||||
strongSelf.badgeText.attributedText = NSAttributedString(string: text, font: badgeFont, textColor: interfaceState.theme.chatList.unreadBadgeActiveTextColor)
|
||||
let textSize = strongSelf.badgeText.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
let badgeSize = CGSize(width: max(image.size.width, textSize.width + 10.0), height: image.size.height)
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: strongSelf.discussButtonText.frame.maxX + 5.0, y: floor((strongSelf.discussButton.bounds.height - badgeSize.height) / 2.0)), size: badgeSize)
|
||||
strongSelf.badgeBackground.frame = badgeFrame
|
||||
strongSelf.badgeText.frame = CGRect(origin: CGPoint(x: badgeFrame.minX + floor((badgeSize.width - textSize.width) / 2.0), y: badgeFrame.minY + floor((badgeSize.height - textSize.height) / 2.0)), size: textSize)
|
||||
if value == nil || value == 0 {
|
||||
strongSelf.badgeBackground.isHidden = true
|
||||
strongSelf.badgeText.isHidden = true
|
||||
} else {
|
||||
strongSelf.badgeBackground.isHidden = false
|
||||
strongSelf.badgeText.isHidden = false
|
||||
}
|
||||
}))
|
||||
}*/
|
||||
|
||||
if let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted /*|| previousState?.peerDiscussionId != interfaceState.peerDiscussionId*/ {
|
||||
if let action = actionForPeer(peer: peer, isMuted: interfaceState.peerIsMuted) {
|
||||
if let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.pinnedMessage != interfaceState.pinnedMessage {
|
||||
if let action = actionForPeer(peer: peer, interfaceState: interfaceState, isMuted: interfaceState.peerIsMuted) {
|
||||
self.action = action
|
||||
let (title, color) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings)
|
||||
self.button.setTitle(title, with: Font.regular(17.0), with: color, for: [])
|
||||
@ -237,12 +224,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
self.action = nil
|
||||
}
|
||||
|
||||
/*if interfaceState.peerDiscussionId != nil {
|
||||
self.discussButtonText.attributedText = NSAttributedString(string: interfaceState.strings.Channel_DiscussionGroup_HeaderLabel, font: Font.regular(17.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor)
|
||||
self.discussButton.isHidden = false
|
||||
} else {*/
|
||||
self.discussButton.isHidden = true
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -369,6 +369,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
public var purposefulAction: (() -> Void)?
|
||||
var updatedClosedPinnedMessageId: ((MessageId) -> Void)?
|
||||
var updatedUnpinnedAllMessages: ((Int) -> Void)?
|
||||
|
||||
private let scrolledToMessageId = ValuePromise<ScrolledToMessageId?>(nil, ignoreRepeated: true)
|
||||
private var scrolledToMessageIdValue: ScrolledToMessageId? = nil {
|
||||
@ -414,13 +416,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
self.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: false)
|
||||
|
||||
var isScheduledMessages = false
|
||||
if let subject = subject, case .scheduledMessages = subject {
|
||||
self.canReadHistory.set(false)
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, isScheduledMessages: isScheduledMessages, peerNearbyData: peerNearbyData)
|
||||
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData)
|
||||
|
||||
var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none
|
||||
if case .standard = mode {
|
||||
@ -742,6 +738,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
gesture?.cancel()
|
||||
}, navigateToMessage: { [weak self] fromId, id in
|
||||
self?.navigateToMessage(from: fromId, to: .id(id), forceInCurrentChat: fromId.peerId == id.peerId)
|
||||
}, navigateToMessageStandalone: { [weak self] id in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
self?.navigateToMessage(from: nil, to: .id(id), forceInCurrentChat: false)
|
||||
}, tapMessage: nil, clickThroughMessage: { [weak self] in
|
||||
self?.chatDisplayNode.dismissInput()
|
||||
}, toggleMessagesSelection: { [weak self] ids, value in
|
||||
@ -773,7 +774,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) else {
|
||||
return
|
||||
}
|
||||
guard !strongSelf.presentationInterfaceState.isScheduledMessages else {
|
||||
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
guard !isScheduledMessages else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
@ -795,7 +802,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return false
|
||||
}
|
||||
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState, !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
||||
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceNode, sourceRect)
|
||||
return false
|
||||
}
|
||||
@ -826,7 +833,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return true
|
||||
}, sendGif: { [weak self] fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState, !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
||||
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceNode, sourceRect)
|
||||
return false
|
||||
}
|
||||
@ -850,7 +857,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState, !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
|
||||
return false
|
||||
}
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
||||
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceNode, sourceRect)
|
||||
return false
|
||||
}
|
||||
@ -860,7 +870,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return true
|
||||
}, requestMessageActionCallback: { [weak self] messageId, data, isGame, requiresPassword in
|
||||
if let strongSelf = self {
|
||||
guard !strongSelf.presentationInterfaceState.isScheduledMessages else {
|
||||
guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
@ -965,7 +975,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}, requestMessageActionUrlAuth: { [weak self] defaultUrl, messageId, buttonId in
|
||||
if let strongSelf = self {
|
||||
guard !strongSelf.presentationInterfaceState.isScheduledMessages else {
|
||||
guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
@ -1086,7 +1096,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard !strongSelf.presentationInterfaceState.isScheduledMessages else {
|
||||
guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
@ -1101,7 +1111,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}, shareCurrentLocation: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
guard !strongSelf.presentationInterfaceState.isScheduledMessages else {
|
||||
if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
|
||||
return
|
||||
}
|
||||
guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
@ -1122,7 +1135,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}, shareAccountContact: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
guard !strongSelf.presentationInterfaceState.isScheduledMessages else {
|
||||
if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
|
||||
return
|
||||
}
|
||||
|
||||
guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
@ -1826,7 +1843,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
|
||||
guard !strongSelf.presentationInterfaceState.isScheduledMessages else {
|
||||
guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_PollUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
@ -1988,7 +2005,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) }
|
||||
})
|
||||
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
@ -2267,9 +2284,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
self.controllerInteraction = controllerInteraction
|
||||
|
||||
if case let .peer(peerId) = chatLocation, peerId != context.account.peerId, subject != .scheduledMessages {
|
||||
if case let .peer(peerId) = chatLocation, peerId != context.account.peerId {
|
||||
switch subject {
|
||||
case .pinnedMessages, .scheduledMessages:
|
||||
break
|
||||
default:
|
||||
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
|
||||
}
|
||||
}
|
||||
self.navigationBar?.allowsCustomTransition = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
@ -2437,7 +2459,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
||||
}
|
||||
|
||||
if case .peer = chatLocation, !isScheduledMessages, peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
var isScheduledOrPinnedMessages = false
|
||||
switch subject {
|
||||
case .scheduledMessages, .pinnedMessages:
|
||||
isScheduledOrPinnedMessages = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if case .peer = chatLocation, !isScheduledOrPinnedMessages, peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
let chatLocationContextHolder = self.chatLocationContextHolder
|
||||
hasScheduledMessages = peerView.get()
|
||||
|> take(1)
|
||||
@ -2453,10 +2483,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, hasScheduledMessages, self.reportIrrelvantGeoNoticePromise.get())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, hasScheduledMessages, peerReportNotice in
|
||||
var pinnedCountSignal: Signal<Int?, NoError> = .single(nil)
|
||||
if case .pinnedMessages = subject {
|
||||
pinnedCountSignal = self.topPinnedMessageSignal(latest: true)
|
||||
|> map { message -> Int? in
|
||||
return message?.totalCount
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
|
||||
self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, hasScheduledMessages, self.reportIrrelvantGeoNoticePromise.get(), pinnedCountSignal)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount in
|
||||
if let strongSelf = self {
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
|
||||
strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationData.strings.Chat_TitlePinnedMessages(Int32(pinnedCount ?? 1)), false)
|
||||
} else {
|
||||
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages)
|
||||
let imageOverride: AvatarNodeImageOverride?
|
||||
if strongSelf.context.account.peerId == peer.id {
|
||||
@ -2471,6 +2518,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride)
|
||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil
|
||||
}
|
||||
}
|
||||
|
||||
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages {
|
||||
return
|
||||
@ -3311,7 +3359,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let context = self.context
|
||||
|
||||
func replyHistorySignal(anchorMessageId: MessageId?, count: Int) -> Signal<ChatHistoryViewUpdate, NoError> {
|
||||
func pinnedHistorySignal(anchorMessageId: MessageId?, count: Int) -> Signal<ChatHistoryViewUpdate, NoError> {
|
||||
let location: ChatHistoryLocation
|
||||
if let anchorMessageId = anchorMessageId {
|
||||
location = .InitialSearch(location: .id(anchorMessageId), count: count, highlight: false)
|
||||
@ -3319,7 +3367,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
location = .Initial(count: count)
|
||||
}
|
||||
|
||||
return (chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), context: context, chatLocation: .peer(peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tagMask: MessageTags.pinned, additionalData: [])
|
||||
return (chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), context: context, chatLocation: .peer(peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tagMask: MessageTags.pinned, additionalData: [], orderStatistics: .combinedLocation)
|
||||
|> castError(Bool.self)
|
||||
|> mapToSignal { update -> Signal<ChatHistoryViewUpdate, Bool> in
|
||||
switch update {
|
||||
@ -3337,7 +3385,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|> restartIfError
|
||||
}
|
||||
|
||||
let topMessage = replyHistorySignal(anchorMessageId: nil, count: 3)
|
||||
let topMessage = pinnedHistorySignal(anchorMessageId: nil, count: 3)
|
||||
|> map { update -> Message? in
|
||||
switch update {
|
||||
case .Loading:
|
||||
@ -3349,19 +3397,46 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let loadCount = 100
|
||||
|
||||
let adjustedReplyHistory: Signal<[Message], NoError>
|
||||
struct PinnedHistory {
|
||||
struct PinnedMessage {
|
||||
var message: Message
|
||||
var index: Int
|
||||
}
|
||||
|
||||
var messages: [PinnedMessage]
|
||||
var totalCount: Int
|
||||
}
|
||||
|
||||
let adjustedReplyHistory: Signal<PinnedHistory, NoError>
|
||||
if latest {
|
||||
adjustedReplyHistory = replyHistorySignal(anchorMessageId: nil, count: loadCount)
|
||||
|> map { view -> [Message] in
|
||||
adjustedReplyHistory = pinnedHistorySignal(anchorMessageId: nil, count: loadCount)
|
||||
|> map { view -> PinnedHistory in
|
||||
switch view {
|
||||
case .Loading:
|
||||
return []
|
||||
return PinnedHistory(messages: [], totalCount: 0)
|
||||
case let .HistoryView(viewValue, _, _, _, _, _, _):
|
||||
return viewValue.entries.map(\.message)
|
||||
var messages: [PinnedHistory.PinnedMessage] = []
|
||||
var totalCount = viewValue.entries.count
|
||||
for i in 0 ..< viewValue.entries.count {
|
||||
let index: Int
|
||||
if !viewValue.holeEarlier && viewValue.earlierId == nil {
|
||||
index = i
|
||||
} else if let location = viewValue.entries[i].location {
|
||||
index = location.index
|
||||
totalCount = location.count
|
||||
} else {
|
||||
index = i
|
||||
}
|
||||
messages.append(PinnedHistory.PinnedMessage(
|
||||
message: viewValue.entries[i].message,
|
||||
index: index
|
||||
))
|
||||
}
|
||||
return PinnedHistory(messages: messages, totalCount: totalCount)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
adjustedReplyHistory = (Signal<[Message], NoError> { subscriber in
|
||||
adjustedReplyHistory = (Signal<PinnedHistory, NoError> { subscriber in
|
||||
var referenceMessageValue: ReferenceMessage?
|
||||
var view: ChatHistoryViewUpdate?
|
||||
|
||||
@ -3370,33 +3445,44 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
guard case let .HistoryView(viewValue, _, _, _, _, _, _) = view else {
|
||||
subscriber.putNext([])
|
||||
subscriber.putNext(PinnedHistory(messages: [], totalCount: 0))
|
||||
return
|
||||
}
|
||||
|
||||
var messages: [PinnedHistory.PinnedMessage] = []
|
||||
for i in 0 ..< viewValue.entries.count {
|
||||
messages.append(PinnedHistory.PinnedMessage(
|
||||
message: viewValue.entries[i].message,
|
||||
index: i
|
||||
))
|
||||
}
|
||||
let result = PinnedHistory(messages: messages, totalCount: messages.count)
|
||||
|
||||
if let referenceId = referenceMessageValue?.id {
|
||||
if viewValue.entries.count < loadCount {
|
||||
subscriber.putNext(viewValue.entries.map(\.message))
|
||||
subscriber.putNext(result)
|
||||
} else if referenceId < viewValue.entries[1].message.id {
|
||||
if viewValue.earlierId != nil {
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putNext(viewValue.entries.map(\.message))
|
||||
subscriber.putNext(result)
|
||||
}
|
||||
} else if referenceId > viewValue.entries[viewValue.entries.count - 2].message.id {
|
||||
if viewValue.laterId != nil {
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putNext(viewValue.entries.map(\.message))
|
||||
subscriber.putNext(result)
|
||||
}
|
||||
} else {
|
||||
subscriber.putNext(viewValue.entries.map(\.message))
|
||||
subscriber.putNext(result)
|
||||
}
|
||||
} else {
|
||||
if viewValue.holeLater || viewValue.laterId != nil {
|
||||
if viewValue.isLoading {
|
||||
subscriber.putNext(result)
|
||||
} else if viewValue.holeLater || viewValue.laterId != nil {
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putNext(viewValue.entries.map(\.message))
|
||||
subscriber.putNext(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3410,7 +3496,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if !initializedView {
|
||||
initializedView = true
|
||||
//print("reload at \(String(describing: referenceMessage?.id)) disposable \(unsafeBitCast(viewDisposable, to: UInt64.self))")
|
||||
viewDisposable.set((replyHistorySignal(anchorMessageId: referenceMessage?.id, count: loadCount)
|
||||
viewDisposable.set((pinnedHistorySignal(anchorMessageId: referenceMessage?.id, count: loadCount)
|
||||
|> deliverOnMainQueue).start(next: { next in
|
||||
view = next
|
||||
updateState()
|
||||
@ -3434,27 +3520,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
topMessage,
|
||||
referenceMessage
|
||||
)
|
||||
|> map { messages, topMessage, referenceMessage -> ChatPinnedMessage? in
|
||||
|> map { pinnedMessages, topMessage, referenceMessage -> ChatPinnedMessage? in
|
||||
var message: ChatPinnedMessage?
|
||||
|
||||
let topMessageId: MessageId
|
||||
if messages.isEmpty {
|
||||
if pinnedMessages.messages.isEmpty {
|
||||
return nil
|
||||
}
|
||||
topMessageId = topMessage?.id ?? messages[messages.count - 1].id
|
||||
topMessageId = topMessage?.id ?? pinnedMessages.messages[pinnedMessages.messages.count - 1].message.id
|
||||
//print("reference: \(String(describing: referenceMessage?.id.id)) entries: \(view.entries.map(\.index.id.id))")
|
||||
for i in 0 ..< messages.count {
|
||||
let entry = messages[i]
|
||||
for i in 0 ..< pinnedMessages.messages.count {
|
||||
let entry = pinnedMessages.messages[i]
|
||||
var matches = false
|
||||
if message == nil {
|
||||
matches = true
|
||||
} else if let referenceMessage = referenceMessage {
|
||||
if referenceMessage.isScrolled {
|
||||
if entry.id < referenceMessage.id {
|
||||
if entry.message.id < referenceMessage.id {
|
||||
matches = true
|
||||
}
|
||||
} else {
|
||||
if entry.id <= referenceMessage.id {
|
||||
if entry.message.id <= referenceMessage.id {
|
||||
matches = true
|
||||
}
|
||||
}
|
||||
@ -3462,7 +3548,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
matches = true
|
||||
}
|
||||
if matches {
|
||||
message = ChatPinnedMessage(message: entry, topMessageId: topMessageId)
|
||||
message = ChatPinnedMessage(message: entry.message, index: entry.index, totalCount: pinnedMessages.totalCount, topMessageId: topMessageId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3508,6 +3594,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
if case .pinnedMessages = self.presentationInterfaceState.subject {
|
||||
self.chatDisplayNode.historyNode.setLoadStateUpdated({ [weak self] state, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if case .empty = state {
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.chatDisplayNode.peerView = self.peerView
|
||||
|
||||
let initialData = self.chatDisplayNode.historyNode.initialData
|
||||
@ -3547,7 +3644,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let pinnedMessageId = pinnedMessageId {
|
||||
if let cachedDataMessages = combinedInitialData.cachedDataMessages {
|
||||
if let message = cachedDataMessages[pinnedMessageId] {
|
||||
pinnedMessage = ChatPinnedMessage(message: message, topMessageId: message.id)
|
||||
pinnedMessage = ChatPinnedMessage(message: message, index: 1, totalCount: 1, topMessageId: message.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3699,10 +3796,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
if let pinnedMessageId = pinnedMessageId {
|
||||
if let message = messages?[pinnedMessageId] {
|
||||
pinnedMessage = ChatPinnedMessage(message: message, topMessageId: message.id)
|
||||
pinnedMessage = ChatPinnedMessage(message: message, index: 1, totalCount: 1, topMessageId: message.id)
|
||||
}
|
||||
}
|
||||
case let .peer(peerId):
|
||||
case .peer:
|
||||
pinnedMessageId = topPinnedMessage?.message.id
|
||||
pinnedMessage = topPinnedMessage
|
||||
}
|
||||
@ -3873,7 +3970,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self, let validLayout = strongSelf.validLayout {
|
||||
var mappedTransition: (ChatHistoryListViewTransition, ListViewUpdateSizeAndInsets?)?
|
||||
|
||||
let isScheduledMessages = strongSelf.presentationInterfaceState.isScheduledMessages
|
||||
let isScheduledMessages: Bool
|
||||
if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
} else {
|
||||
isScheduledMessages = false
|
||||
}
|
||||
strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut), listViewTransaction: { updateSizeAndInsets, _, _, _ in
|
||||
var options = transition.options
|
||||
let _ = options.insert(.Synchronous)
|
||||
@ -3997,7 +4099,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).start(next: { messageIds in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
|
||||
} else {
|
||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}
|
||||
@ -4020,7 +4122,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.presentationInterfaceState.interfaceState.editMessage == nil, let _ = strongSelf.presentationInterfaceState.slowmodeState, !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
if strongSelf.presentationInterfaceState.interfaceState.editMessage == nil, let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
||||
if let rect = strongSelf.chatDisplayNode.frameForAttachmentButton() {
|
||||
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(strongSelf.chatDisplayNode, rect)
|
||||
}
|
||||
@ -4062,7 +4164,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
self.chatDisplayNode.updateTypingActivity = { [weak self] value in
|
||||
if let strongSelf = self, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil && !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
if let strongSelf = self, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil && strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
||||
if value {
|
||||
strongSelf.typingActivityPromise.set(Signal<Bool, NoError>.single(true)
|
||||
|> then(
|
||||
@ -4674,7 +4776,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState, !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
||||
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(node, rect)
|
||||
return false
|
||||
}
|
||||
@ -4716,7 +4818,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}, botSwitchChatWithPayload: { [weak self] peerId, payload in
|
||||
if let strongSelf = self, case let .peer(currentPeerId) = strongSelf.chatLocation {
|
||||
strongSelf.openPeer(peerId: peerId, navigation: .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .automatic(returnToPeerId: currentPeerId, scheduled: strongSelf.presentationInterfaceState.isScheduledMessages))), fromMessage: nil)
|
||||
var isScheduled = false
|
||||
if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
|
||||
isScheduled = true
|
||||
}
|
||||
strongSelf.openPeer(peerId: peerId, navigation: .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .automatic(returnToPeerId: currentPeerId, scheduled: isScheduled))), fromMessage: nil)
|
||||
}
|
||||
}, beginMediaRecording: { [weak self] isVideo in
|
||||
guard let strongSelf = self else {
|
||||
@ -5095,9 +5201,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
if canManagePin {
|
||||
let action: () -> Void = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let strongSelf = self {
|
||||
let disposable: MetaDisposable
|
||||
if let current = strongSelf.unpinMessageDisposable {
|
||||
@ -5106,7 +5209,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
disposable = MetaDisposable()
|
||||
strongSelf.unpinMessageDisposable = disposable
|
||||
}
|
||||
disposable.set(requestUpdatePinnedMessage(account: strongSelf.context.account, peerId: peer.id, update: .clear(id: id)).start())
|
||||
disposable.set((requestUpdatePinnedMessage(account: strongSelf.context.account, peerId: peer.id, update: .clear(id: id))
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.present(
|
||||
UndoOverlayController(
|
||||
presentationData: strongSelf.presentationData,
|
||||
content: .messagesUnpinned(
|
||||
title: strongSelf.presentationData.strings.Chat_MessagesUnpinned(1),
|
||||
text: "",
|
||||
undo: false
|
||||
),
|
||||
elevatedLayout: false,
|
||||
action: { _ in return false }
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
if askForConfirmation {
|
||||
@ -5125,10 +5247,83 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return value
|
||||
}) })
|
||||
})
|
||||
strongSelf.present(
|
||||
UndoOverlayController(
|
||||
presentationData: strongSelf.presentationData,
|
||||
content: .messagesUnpinned(
|
||||
title: strongSelf.presentationData.strings.Chat_PinnedMessagesHiddenTitle,
|
||||
text: strongSelf.presentationData.strings.Chat_PinnedMessagesHiddenText,
|
||||
undo: false
|
||||
),
|
||||
elevatedLayout: false,
|
||||
action: { _ in return false }
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
strongSelf.updatedClosedPinnedMessageId?(pinnedMessage.topMessageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, unpinAllMessages: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
var canManagePin = false
|
||||
if let channel = peer as? TelegramChannel {
|
||||
canManagePin = channel.hasPermission(.pinMessages)
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
switch group.role {
|
||||
case .creator, .admin:
|
||||
canManagePin = true
|
||||
default:
|
||||
if let defaultBannedRights = group.defaultBannedRights {
|
||||
canManagePin = !defaultBannedRights.flags.contains(.banPinMessages)
|
||||
} else {
|
||||
canManagePin = true
|
||||
}
|
||||
}
|
||||
} else if let _ = peer as? TelegramUser, strongSelf.presentationInterfaceState.explicitelyCanPinMessages {
|
||||
canManagePin = true
|
||||
}
|
||||
|
||||
if canManagePin {
|
||||
let count = strongSelf.presentationInterfaceState.pinnedMessage?.totalCount ?? 1
|
||||
|
||||
let _ = (requestUnpinAllMessages(account: strongSelf.context.account, peerId: strongSelf.chatLocation.peerId)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
|
||||
}, completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
strongSelf.updatedUnpinnedAllMessages?(count)
|
||||
})
|
||||
} else {
|
||||
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError> = strongSelf.topPinnedMessageSignal(latest: true)
|
||||
|> take(1)
|
||||
|
||||
let _ = (topPinnedMessage
|
||||
|> deliverOnMainQueue).start(next: { value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let value = value {
|
||||
strongSelf.updatedClosedPinnedMessageId?(value.topMessageId)
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
})
|
||||
}
|
||||
}, openPinnedList: { [weak self] messageId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.openPinnedMessages(at: messageId)
|
||||
}, shareAccountContact: { [weak self] in
|
||||
self?.shareAccountContact()
|
||||
}, reportPeer: { [weak self] in
|
||||
@ -5611,7 +5806,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if inAppNotificationSettings.playSounds {
|
||||
serviceSoundManager.playMessageDeliveredSound()
|
||||
}
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages && namespace == Namespaces.Message.ScheduledCloud {
|
||||
if strongSelf.presentationInterfaceState.subject != .scheduledMessages && namespace == Namespaces.Message.ScheduledCloud {
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
@ -6638,7 +6833,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.chatDisplayNode.historyNode.historyAppearsCleared = true
|
||||
|
||||
let statusText: String
|
||||
if strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
|
||||
statusText = strongSelf.presentationData.strings.Undo_ScheduledMessagesCleared
|
||||
} else if case .forEveryone = type {
|
||||
statusText = strongSelf.presentationData.strings.Undo_ChatClearedForBothSides
|
||||
@ -6663,7 +6858,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
var items: [ActionSheetItem] = []
|
||||
|
||||
if self.presentationInterfaceState.isScheduledMessages {
|
||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ScheduledMessages_ClearAllConfirmation, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
@ -7109,7 +7304,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
slowModeEnabled = true
|
||||
}
|
||||
|
||||
let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: !strongSelf.presentationInterfaceState.isScheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, presentationData: strongSelf.presentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText.string, openGallery: {
|
||||
let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, presentationData: strongSelf.presentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText.string, openGallery: {
|
||||
self?.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals, silentPosting, scheduleTime in
|
||||
if !inputText.string.isEmpty {
|
||||
//strongSelf.clearInputText()
|
||||
@ -7122,7 +7317,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}, openCamera: { [weak self] cameraView, menuController in
|
||||
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
||||
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: !strongSelf.presentationInterfaceState.isScheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
|
||||
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
|
||||
if let strongSelf = self {
|
||||
if editMediaOptions != nil {
|
||||
strongSelf.editMessageMediaWithLegacySignals(signals!)
|
||||
@ -7146,7 +7341,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in
|
||||
if let strongSelf = self {
|
||||
done(time)
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
@ -7202,7 +7397,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in
|
||||
if let strongSelf = self {
|
||||
done(time)
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
@ -7397,7 +7592,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
legacyController.bind(controller: controller)
|
||||
legacyController.deferScreenEdgeGestures = [.top]
|
||||
|
||||
configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, initialCaption: inputText.string, hasSchedule: !strongSelf.presentationInterfaceState.isScheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: editingMedia ? nil : { [weak self, weak legacyController] in
|
||||
configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: editingMedia ? nil : { [weak self, weak legacyController] in
|
||||
if let strongSelf = self {
|
||||
let controller = WebSearchController(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, configuration: searchBotsConfiguration, mode: .media(completion: { results, selectionState, editingState, silentPosting in
|
||||
if let legacyController = legacyController {
|
||||
@ -7437,7 +7632,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in
|
||||
if let strongSelf = self {
|
||||
done(time)
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
@ -7531,7 +7726,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self, let selfPeer = selfPeer else {
|
||||
return
|
||||
}
|
||||
let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && !strongSelf.presentationInterfaceState.isScheduledMessages
|
||||
let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages
|
||||
let controller = LocationPickerController(context: strongSelf.context, mode: .share(peer: peer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -8054,12 +8249,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) {
|
||||
let peerId: PeerId = self.chatLocation.peerId
|
||||
|
||||
if commit || !self.presentationInterfaceState.isScheduledMessages {
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
if commit || !isScheduledMessages {
|
||||
self.commitPurposefulAction()
|
||||
|
||||
let _ = (enqueueMessages(account: self.context.account, peerId: peerId, messages: self.transformEnqueueMessages(messages))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
if let strongSelf = self, !strongSelf.presentationInterfaceState.isScheduledMessages {
|
||||
if let strongSelf = self, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}
|
||||
})
|
||||
@ -8256,7 +8456,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.recorderFeedback?.prepareImpact(.light)
|
||||
}
|
||||
|
||||
self.videoRecorder.set(.single(legacyInstantVideoController(theme: self.presentationData.theme, panelFrame: self.view.convert(currentInputPanelFrame, to: nil), context: self.context, peerId: peerId, slowmodeState: !self.presentationInterfaceState.isScheduledMessages ? self.presentationInterfaceState.slowmodeState : nil, hasSchedule: !self.presentationInterfaceState.isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat, send: { [weak self] message in
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
self.videoRecorder.set(.single(legacyInstantVideoController(theme: self.presentationData.theme, panelFrame: self.view.convert(currentInputPanelFrame, to: nil), context: self.context, peerId: peerId, slowmodeState: !isScheduledMessages ? self.presentationInterfaceState.slowmodeState : nil, hasSchedule: !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat, send: { [weak self] message in
|
||||
if let strongSelf = self {
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
@ -8276,7 +8481,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.presentScheduleTimePicker(completion: { [weak self] time in
|
||||
if let strongSelf = self {
|
||||
done(time)
|
||||
if !strongSelf.presentationInterfaceState.isScheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp {
|
||||
strongSelf.openScheduledMessages()
|
||||
}
|
||||
}
|
||||
@ -8289,7 +8494,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
private func dismissMediaRecorder(_ action: ChatFinishMediaRecordingAction) {
|
||||
var updatedAction = action
|
||||
if let _ = self.presentationInterfaceState.slowmodeState, !self.presentationInterfaceState.isScheduledMessages {
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
if let _ = self.presentationInterfaceState.slowmodeState, !isScheduledMessages {
|
||||
updatedAction = .preview
|
||||
}
|
||||
|
||||
@ -8431,7 +8641,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private func sendMediaRecording(silently: Bool) {
|
||||
self.chatDisplayNode.updateRecordedMediaDeleted(false)
|
||||
if let recordedMediaPreview = self.presentationInterfaceState.recordedMediaPreview {
|
||||
if let _ = self.presentationInterfaceState.slowmodeState, !self.presentationInterfaceState.isScheduledMessages {
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
if let _ = self.presentationInterfaceState.slowmodeState, !isScheduledMessages {
|
||||
if let rect = self.chatDisplayNode.frameForInputActionButton() {
|
||||
self.interfaceInteraction?.displaySlowmodeTooltip(self.chatDisplayNode, rect)
|
||||
}
|
||||
@ -8619,7 +8834,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
func scrollToEndOfHistory() {
|
||||
let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true, highlight: false), id: 0)
|
||||
|
||||
let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
||||
switch historyView {
|
||||
@ -8683,7 +8898,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
func scrollToStartOfHistory() {
|
||||
let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .lowerBound, anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true, highlight: false), id: 0)
|
||||
|
||||
let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
||||
switch historyView {
|
||||
@ -8878,11 +9093,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
var forceInCurrentChat = forceInCurrentChat
|
||||
if case let .peer(peerId) = self.chatLocation, messageLocation.peerId == peerId { forceInCurrentChat = true
|
||||
var isScheduledMessages = false
|
||||
var isPinnedMessages = false
|
||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
} else if case .pinnedMessages = self.presentationInterfaceState.subject {
|
||||
isPinnedMessages = true
|
||||
}
|
||||
|
||||
if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (self.presentationInterfaceState.isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) {
|
||||
var forceInCurrentChat = forceInCurrentChat
|
||||
if case let .peer(peerId) = self.chatLocation, messageLocation.peerId == peerId, !isPinnedMessages, !isScheduledMessages {
|
||||
forceInCurrentChat = true
|
||||
}
|
||||
|
||||
if isPinnedMessages, let messageId = messageLocation.messageId {
|
||||
if let navigationController = self.effectiveNavigationController {
|
||||
self.dismiss()
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true), keepStack: .always))
|
||||
}
|
||||
} else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) {
|
||||
if let navigationController = self.effectiveNavigationController {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true), keepStack: .always))
|
||||
}
|
||||
@ -8906,7 +9135,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.messageIndexDisposable.set(nil)
|
||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
|
||||
completion?()
|
||||
} else if case let .index(index) = messageLocation, index.id.id == 0 && index.timestamp > 0, self.presentationInterfaceState.isScheduledMessages {
|
||||
} else if case let .index(index) = messageLocation, index.id.id == 0, index.timestamp > 0, case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition)
|
||||
} else {
|
||||
self.loadingMessage.set(true)
|
||||
@ -8919,7 +9148,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .upperBound:
|
||||
searchLocation = .index(MessageIndex.upperBound(peerId: self.chatLocation.peerId))
|
||||
}
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
||||
switch historyView {
|
||||
@ -9011,7 +9240,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.historyNavigationStack.add(fromIndex)
|
||||
}
|
||||
self.loadingMessage.set(true)
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<MessageIndex?, NoError> in
|
||||
switch historyView {
|
||||
@ -10022,7 +10251,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
if options.contains(.deleteLocally) {
|
||||
var localOptionText = self.presentationData.strings.Conversation_DeleteMessagesForMe
|
||||
if self.presentationInterfaceState.isScheduledMessages {
|
||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
localOptionText = messageIds.count > 1 ? self.presentationData.strings.ScheduledMessages_DeleteMany : self.presentationData.strings.ScheduledMessages_Delete
|
||||
} else {
|
||||
if options.contains(.unsendPersonal) {
|
||||
@ -10483,6 +10712,60 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
|
||||
private func openPinnedMessages(at messageId: MessageId?) {
|
||||
guard let navigationController = self.effectiveNavigationController, navigationController.topViewController == self else {
|
||||
return
|
||||
}
|
||||
let controller = ChatControllerImpl(context: self.context, chatLocation: self.chatLocation, subject: .pinnedMessages(id: messageId))
|
||||
controller.navigationPresentation = .modal
|
||||
controller.updatedClosedPinnedMessageId = { [weak self] pinnedMessageId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedInterfaceState({ $0.withUpdatedMessageActionsState({ value in
|
||||
var value = value
|
||||
value.closedPinnedMessageId = pinnedMessageId
|
||||
return value
|
||||
}) })
|
||||
})
|
||||
|
||||
strongSelf.present(
|
||||
UndoOverlayController(
|
||||
presentationData: strongSelf.presentationData,
|
||||
content: .messagesUnpinned(
|
||||
title: strongSelf.presentationData.strings.Chat_PinnedMessagesHiddenTitle,
|
||||
text: strongSelf.presentationData.strings.Chat_PinnedMessagesHiddenText,
|
||||
undo: false
|
||||
),
|
||||
elevatedLayout: false,
|
||||
action: { _ in return false }
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
}
|
||||
controller.updatedUnpinnedAllMessages = { [weak self] count in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.present(
|
||||
UndoOverlayController(
|
||||
presentationData: strongSelf.presentationData,
|
||||
content: .messagesUnpinned(
|
||||
title: strongSelf.presentationData.strings.Chat_MessagesUnpinned(Int32(count)),
|
||||
text: "",
|
||||
undo: false
|
||||
),
|
||||
elevatedLayout: false,
|
||||
action: { _ in return false }
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
}
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
|
||||
private func presentScheduleTimePicker(style: ChatScheduleTimeControllerStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
|
||||
guard case let .peer(peerId) = self.chatLocation else {
|
||||
return
|
||||
|
||||
@ -54,6 +54,7 @@ public final class ChatControllerInteraction {
|
||||
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void
|
||||
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
|
||||
let navigateToMessage: (MessageId, MessageId) -> Void
|
||||
let navigateToMessageStandalone: (MessageId) -> Void
|
||||
let tapMessage: ((Message) -> Void)?
|
||||
let clickThroughMessage: () -> Void
|
||||
let toggleMessagesSelection: ([MessageId], Bool) -> Void
|
||||
@ -140,6 +141,7 @@ public final class ChatControllerInteraction {
|
||||
openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void,
|
||||
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
|
||||
navigateToMessage: @escaping (MessageId, MessageId) -> Void,
|
||||
navigateToMessageStandalone: @escaping (MessageId) -> Void,
|
||||
tapMessage: ((Message) -> Void)?,
|
||||
clickThroughMessage: @escaping () -> Void,
|
||||
toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void,
|
||||
@ -213,6 +215,7 @@ public final class ChatControllerInteraction {
|
||||
self.openMessageContextMenu = openMessageContextMenu
|
||||
self.openMessageContextActions = openMessageContextActions
|
||||
self.navigateToMessage = navigateToMessage
|
||||
self.navigateToMessageStandalone = navigateToMessageStandalone
|
||||
self.tapMessage = tapMessage
|
||||
self.clickThroughMessage = clickThroughMessage
|
||||
self.toggleMessagesSelection = toggleMessagesSelection
|
||||
@ -286,7 +289,7 @@ public final class ChatControllerInteraction {
|
||||
|
||||
static var `default`: ChatControllerInteraction {
|
||||
return ChatControllerInteraction(openMessage: { _, _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in }, navigationController: {
|
||||
return nil
|
||||
}, chatControllerNode: {
|
||||
|
||||
@ -616,7 +616,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
self.textInputPanelNode?.sendMessage = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.chatPresentationInterfaceState.isScheduledMessages && strongSelf.chatPresentationInterfaceState.editMessageState == nil {
|
||||
if case .scheduledMessages = strongSelf.chatPresentationInterfaceState.subject, strongSelf.chatPresentationInterfaceState.editMessageState == nil {
|
||||
strongSelf.controllerInteraction.scheduleCurrentMessage()
|
||||
} else {
|
||||
strongSelf.sendCurrentMessage()
|
||||
@ -2698,7 +2698,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if let _ = effectivePresentationInterfaceState.interfaceState.editMessage {
|
||||
self.interfaceInteraction?.editMessage()
|
||||
} else {
|
||||
if let _ = effectivePresentationInterfaceState.slowmodeState, !effectivePresentationInterfaceState.isScheduledMessages && scheduleTime == nil {
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = effectivePresentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
if let _ = effectivePresentationInterfaceState.slowmodeState, !isScheduledMessages && scheduleTime == nil {
|
||||
if let rect = self.frameForInputActionButton() {
|
||||
self.interfaceInteraction?.displaySlowmodeTooltip(self, rect)
|
||||
}
|
||||
|
||||
@ -42,7 +42,11 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod
|
||||
let text: String
|
||||
switch interfaceState.chatLocation {
|
||||
case .peer, .replyThread:
|
||||
text = interfaceState.isScheduledMessages ? interfaceState.strings.ScheduledMessages_EmptyPlaceholder : interfaceState.strings.Conversation_EmptyPlaceholder
|
||||
if case .scheduledMessages = interfaceState.subject {
|
||||
text = interfaceState.strings.ScheduledMessages_EmptyPlaceholder
|
||||
} else {
|
||||
text = interfaceState.strings.Conversation_EmptyPlaceholder
|
||||
}
|
||||
}
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: messageFont, textColor: serviceColor.primaryText)
|
||||
@ -645,10 +649,15 @@ final class ChatEmptyNode: ASDisplayNode {
|
||||
self.backgroundNode.image = graphics.chatEmptyItemBackgroundImage
|
||||
}
|
||||
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = interfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
let contentType: ChatEmptyNodeContentType
|
||||
if case .replyThread = interfaceState.chatLocation {
|
||||
contentType = .regular
|
||||
} else if let peer = interfaceState.renderedPeer?.peer, !interfaceState.isScheduledMessages {
|
||||
} else if let peer = interfaceState.renderedPeer?.peer, !isScheduledMessages {
|
||||
if peer.id == self.account.peerId {
|
||||
contentType = .cloud
|
||||
} else if let _ = peer as? TelegramSecretChat {
|
||||
|
||||
@ -358,7 +358,7 @@ private final class ChatHistoryTransactionOpaqueState {
|
||||
}
|
||||
}
|
||||
|
||||
private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], isScheduledMessages: Bool) -> ChatMessageItemAssociatedData {
|
||||
private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], subject: ChatControllerSubject?) -> ChatMessageItemAssociatedData {
|
||||
var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel
|
||||
var contactsPeerIds: Set<PeerId> = Set()
|
||||
var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown
|
||||
@ -406,7 +406,8 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist
|
||||
}
|
||||
}
|
||||
}
|
||||
let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, isScheduledMessages: isScheduledMessages, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers)
|
||||
|
||||
let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers)
|
||||
return associatedData
|
||||
}
|
||||
|
||||
@ -573,6 +574,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
private let clientId: Atomic<Int32>
|
||||
|
||||
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?, source: ChatHistoryListSource = .default, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles) {
|
||||
var tagMask = tagMask
|
||||
if case .pinnedMessages = subject {
|
||||
tagMask = .pinned
|
||||
}
|
||||
|
||||
self.context = context
|
||||
self.chatLocation = chatLocation
|
||||
self.chatLocationContextHolder = chatLocationContextHolder
|
||||
@ -846,6 +852,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
} else {
|
||||
if let subject = subject, case let .message(messageId, highlight) = subject {
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
} else if var chatHistoryLocation = strongSelf.chatHistoryLocationValue {
|
||||
chatHistoryLocation.id += 1
|
||||
strongSelf.chatHistoryLocationValue = chatHistoryLocation
|
||||
@ -910,12 +918,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
reverse = reverseValue
|
||||
}
|
||||
|
||||
var isScheduledMessages = false
|
||||
if let subject = subject, case .scheduledMessages = subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, isScheduledMessages: isScheduledMessages)
|
||||
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, subject: subject)
|
||||
|
||||
let filteredEntries = chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, associatedData: associatedData, updatingMedia: updatingMedia, customChannelDiscussionReadState: customChannelDiscussionReadState, customThreadOutgoingReadState: customThreadOutgoingReadState)
|
||||
let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0
|
||||
@ -1021,6 +1024,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
|
||||
if let subject = subject, case let .message(messageId, highlight) = subject {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: 0)
|
||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: 0)
|
||||
} else {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: 60), id: 0)
|
||||
}
|
||||
|
||||