Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-10-21 02:31:32 +04:00
commit 7eb824e11f
138 changed files with 6401 additions and 4587 deletions

View File

@ -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",

View File

@ -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 = [

View File

@ -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?

View 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",
],
)

View File

@ -1,6 +0,0 @@
#ifndef NotificationService_BridgingHeader_h
#define NotificationService_BridgingHeader_h
#import "NotificationService.h"
#endif

View 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",
],
)

View 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",
],
)

View File

@ -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:@"/"];

View File

@ -1,6 +1,7 @@
import Foundation
import UserNotifications
import SwiftSignalKit
import NotificationServiceObjC
private let queue = Queue()

View File

@ -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:))

View File

@ -1,8 +0,0 @@
import Foundation
import UserNotifications
@available(iOSApplicationExtension 10.0, *)
@objc(NotificationService)
public final class NotificationService: UNNotificationServiceExtension {
}

View File

@ -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

View File

@ -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 {

View File

@ -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
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -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.";

View File

@ -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) {

View 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>

View 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()
}
}
}

View 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)
}
}

View File

@ -0,0 +1 @@
"CFBundleDisplayName" = "الأشخاص";

View File

@ -0,0 +1 @@
"CFBundleDisplayName" = "Leute";

View File

@ -0,0 +1 @@
"CFBundleDisplayName" = "People";

View File

@ -0,0 +1,2 @@
"Widget.NoUsers" = "No users here yet...";
"Widget.AuthRequired" = "Open Telegram and log in.";

View File

@ -0,0 +1 @@
"CFBundleDisplayName" = "Personas";

View File

@ -0,0 +1 @@
"CFBundleDisplayName" = "Persone";

View File

@ -0,0 +1 @@
"CFBundleDisplayName" = "사람";

View File

@ -0,0 +1 @@
"CFBundleDisplayName" = "Mensen";

View File

@ -0,0 +1 @@
"CFBundleDisplayName" = "Pessoas";

View File

@ -0,0 +1 @@
"CFBundleDisplayName" = "Люди";

View File

@ -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?

View File

@ -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 {

View File

@ -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()
})
}

View File

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

View File

@ -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"],

View File

@ -1,4 +1,4 @@
#import "BuildConfig.h"
#import <BuildConfig/BuildConfig.h>
static NSString *telegramApplicationSecretKey = @"telegramApplicationSecretKey_v3";
API_AVAILABLE(ios(10))

View File

@ -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 }

View 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",
],
)

View 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",
],
)

View 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",
],
)

View File

@ -13,6 +13,7 @@ static_library(
"Sources/**/*.h",
]),
deps = [
"//submodules/Database/MurmurHash/Impl:MurMurHashObjC",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View 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",
],
)

View 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",
],
)

View 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",
],
)

View File

@ -1,4 +1,4 @@
#import "MurMurHash32.h"
#import <MurMurHashObjC/MurMurHashObjC.h>
#include <stdlib.h>
#include <string.h>

View File

@ -1,4 +1,5 @@
import Foundation
import MurMurHashObjC
public enum HashFunctions {
public static func murMurHash32(_ s: String) -> Int32 {

View 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",
],
)

View 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",
],
)

View File

@ -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)

View 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",
],
)

View 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",
],
)

View 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",
],
)

View 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",
],
)

View File

@ -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

View File

@ -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>) {

View File

@ -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 }

View File

@ -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):

View File

@ -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))
}

View File

@ -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
}
}
}

View File

@ -123,6 +123,7 @@ public enum PresentationResourceKey: Int32 {
case chatInfoItemBackgroundImageWithoutWallpaper
case chatInputPanelCloseIconImage
case chatInputPanelPinnedListIconImage
case chatInputPanelEncircledCloseIconImage
case chatInputPanelVerticalSeparatorLineImage

View File

@ -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)

View File

@ -1,9 +1,9 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_menu_pinnedlist.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

File diff suppressed because one or more lines are too long

View File

@ -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,28 +30,57 @@ 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? {
if let channel = peer as? TelegramChannel {
switch channel.participationStatus {
case .kicked:
return .kicked
case .left:
return .join
case .member:
if isMuted {
return .unmuteNotifications
} else {
return .muteNotifications
}
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 isMuted {
return .unmuteNotifications
if let channel = peer as? TelegramChannel {
switch channel.participationStatus {
case .kicked:
return .kicked
case .left:
return .join
case .member:
if isMuted {
return .unmuteNotifications
} else {
return .muteNotifications
}
}
} else {
return .muteNotifications
if isMuted {
return .unmuteNotifications
} else {
return .muteNotifications
}
}
}
}
@ -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
//}
self.discussButton.isHidden = true
}
}

View File

@ -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,8 +2284,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.controllerInteraction = controllerInteraction
if case let .peer(peerId) = chatLocation, peerId != context.account.peerId, subject != .scheduledMessages {
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
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 {
@ -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,23 +2483,41 @@ 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) {
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages)
let imageOverride: AvatarNodeImageOverride?
if strongSelf.context.account.peerId == peer.id {
imageOverride = .savedMessagesIcon
} else if peer.id.isReplies {
imageOverride = .repliesIcon
} else if peer.isDeleted {
imageOverride = .deletedIcon
if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
strongSelf.chatTitleView?.titleContent = .custom(strongSelf.presentationData.strings.Chat_TitlePinnedMessages(Int32(pinnedCount ?? 1)), false)
} else {
imageOverride = nil
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages)
let imageOverride: AvatarNodeImageOverride?
if strongSelf.context.account.peerId == peer.id {
imageOverride = .savedMessagesIcon
} else if peer.id.isReplies {
imageOverride = .repliesIcon
} else if peer.isDeleted {
imageOverride = .deletedIcon
} else {
imageOverride = nil
}
(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
}
(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 {
@ -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

View File

@ -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: {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)
}

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