Merge branch 'master' into beta

This commit is contained in:
Ali 2021-11-26 15:51:34 +04:00
commit 1d6aca88b9
5631 changed files with 21905 additions and 1193449 deletions

View File

@ -377,12 +377,14 @@ official_apple_pay_merchants = [
"merchant.privatbank.test.telergramios",
"merchant.privatbank.prod.telergram",
"merchant.paymaster.test.telegramios",
"merchant.paymaster.prod.telegramios",
"merchant.smartglocal.prod.telegramios",
"merchant.smartglocal.test.telegramios",
"merchant.yoomoney.test.telegramios",
"merchant.yoomoney.prod.telegramios",
"merchant.org.telegram.Best2pay.test",
"merchant.psbank.test.telegramios",
"merchant.psbank.prod.telegramios",
]
official_bundle_ids = [

View File

@ -273,7 +273,7 @@ private extension CGSize {
private func convertLottieImage(data: Data) -> UIImage? {
let decompressedData = TGGUnzipData(data, 512 * 1024) ?? data
guard let animation = LottieInstance(data: decompressedData, cacheKey: "") else {
guard let animation = LottieInstance(data: decompressedData, fitzModifier: .none, cacheKey: "") else {
return nil
}
let size = animation.dimensions.fitted(CGSize(width: 200.0, height: 200.0))
@ -627,6 +627,13 @@ private final class NotificationServiceHandler {
} else {
isLockedMessage = nil
}
let incomingCallMessage: String
if let notificationsPresentationData = try? Data(contentsOf: URL(fileURLWithPath: notificationsPresentationDataPath(rootPath: rootPath))), let notificationsPresentationDataValue = try? JSONDecoder().decode(NotificationsPresentationData.self, from: notificationsPresentationData) {
incomingCallMessage = notificationsPresentationDataValue.incomingCallString
} else {
incomingCallMessage = "is calling you"
}
Logger.shared.log("NotificationService \(episode)", "Begin processing payload \(payload)")
@ -646,7 +653,7 @@ private final class NotificationServiceHandler {
let _ = (combineLatest(queue: self.queue,
self.accountManager.accountRecords(),
self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])
self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings, ApplicationSpecificSharedDataKeys.voiceCallSettings])
)
|> take(1)
|> deliverOn(self.queue)).start(next: { [weak self] records, sharedData in
@ -670,6 +677,13 @@ private final class NotificationServiceHandler {
}
let inAppNotificationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings]?.get(InAppNotificationSettings.self) ?? InAppNotificationSettings.defaultSettings
let voiceCallSettings: VoiceCallSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.voiceCallSettings]?.get(VoiceCallSettings.self) {
voiceCallSettings = value
} else {
voiceCallSettings = VoiceCallSettings.defaultSettings
}
guard let strongSelf = self, let recordId = recordId else {
Logger.shared.log("NotificationService \(episode)", "Couldn't find a matching decryption key")
@ -754,6 +768,7 @@ private final class NotificationServiceHandler {
var fromId: PeerId
var updates: String
var accountId: Int64
var peer: EnginePeer?
}
var callData: CallData?
@ -782,12 +797,27 @@ private final class NotificationServiceHandler {
if let callIdString = payloadJson["call_id"] as? String, let callAccessHashString = payloadJson["call_ah"] as? String, let peerId = peerId, let updates = payloadJson["updates"] as? String {
if let callId = Int64(callIdString), let callAccessHash = Int64(callAccessHashString) {
var peer: EnginePeer?
var updateString = updates
updateString = updateString.replacingOccurrences(of: "-", with: "+")
updateString = updateString.replacingOccurrences(of: "_", with: "/")
while updateString.count % 4 != 0 {
updateString.append("=")
}
if let updateData = Data(base64Encoded: updateString) {
if let callUpdate = AccountStateManager.extractIncomingCallUpdate(data: updateData) {
peer = callUpdate.peer
}
}
callData = CallData(
id: callId,
accessHash: callAccessHash,
fromId: peerId,
updates: updates,
accountId: recordId.int64
accountId: recordId.int64,
peer: peer
)
}
}
@ -937,17 +967,27 @@ private final class NotificationServiceHandler {
"updates": callData.updates,
"accountId": "\(callData.accountId)"
]
Logger.shared.log("NotificationService \(episode)", "Will report voip notification")
let content = NotificationContent(isLockedMessage: isLockedMessage)
updateCurrentContent(content)
if #available(iOS 14.5, *) {
if #available(iOS 14.5, *), voiceCallSettings.enableSystemIntegration {
Logger.shared.log("NotificationService \(episode)", "Will report voip notification")
let content = NotificationContent(isLockedMessage: isLockedMessage)
updateCurrentContent(content)
CXProvider.reportNewIncomingVoIPPushPayload(voipPayload, completion: { error in
Logger.shared.log("NotificationService \(episode)", "Did report voip notification, error: \(String(describing: error))")
completed()
})
} else {
var content = NotificationContent(isLockedMessage: nil)
if let peer = callData.peer {
content.title = peer.debugDisplayTitle
content.body = incomingCallMessage
} else {
content.body = "Incoming Call"
}
updateCurrentContent(content)
completed()
}
case .logout:

View File

@ -49,7 +49,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
}
if !isMuted && hasUnread {
signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(index.messageIndex.id.peerId), anchor: .upperBound, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tagMask: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: .combinedLocation)
signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(index.messageIndex.id.peerId), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tagMask: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: .combinedLocation)
|> take(1)
|> map { view -> [INMessage] in
var messages: [INMessage] = []

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2657,6 +2657,7 @@ Unused sets are archived when you add more.";
"Channel.AdminLog.CaptionEdited" = "%@ edited caption:";
"Channel.AdminLog.MessageDeleted" = "%@ deleted message:";
"Channel.AdminLog.MessagePinned" = "%@ pinned message:";
"Channel.AdminLog.MessageUnpinnedExtended" = "%@ unpinned message:";
"Channel.AdminLog.MessageInvitedName" = "invited %1$@";
"Channel.AdminLog.MessageInvitedNameUsername" = "invited %1$@ (%2$@)";
@ -2891,6 +2892,7 @@ Unused sets are archived when you add more.";
"FastTwoStepSetup.EmailHelp" = "Please add your valid e-mail. It is the only way to recover a forgotten password.";
"Conversation.ViewMessage" = "VIEW MESSAGE";
"Conversation.ViewPost" = "VIEW POST";
"GroupInfo.GroupHistory" = "History For New Members";
"GroupInfo.GroupHistoryVisible" = "Visible";
@ -4848,6 +4850,7 @@ Sorry for the inconvenience.";
"ChatList.Context.Mute" = "Mute";
"ChatList.Context.Unmute" = "Unmute";
"ChatList.Context.JoinChannel" = "Join Channel";
"ChatList.Context.JoinChat" = "Join Chat";
"ChatList.Context.Delete" = "Delete";
"ContactList.Context.SendMessage" = "Send Message";
@ -7046,3 +7049,55 @@ Sorry for the inconvenience.";
"Themes.CreateNewTheme" = "Create a New Theme";
"Chat.JumpToDate" = "Jump to Date";
"VoiceChat.DiscussionGroup" = "discussion group";
"Group.Edit.PrivatePublicLinkAlert" = "Please note that if you choose a public link for your group, anyone will be able to find it in search and join.\n\nDo not create this link if you want your group to stay private.";
"Conversation.CopyProtectionInfoGroup" = "Admins restricted members to copy or forward content from this group.";
"Conversation.CopyProtectionInfoChannel" = "Admins restricted members to copy or forward content from this channel.";
"Conversation.CopyProtectionForwardingDisabledGroup" = "Forwards from this group are restricted";
"Conversation.CopyProtectionForwardingDisabledChannel" = "Forwards from this channel are restricted";
"Conversation.CopyProtectionSavingDisabledGroup" = "Saving from this group is restricted";
"Conversation.CopyProtectionSavingDisabledChannel" = "Saving from this channel is restricted";
"Channel.AdminLog.MessageToggleNoForwardsOn" = "%@ restricted message forwarding";
"Channel.AdminLog.MessageToggleNoForwardsOff" = "%@ allowed message forwarding";
"Group.Setup.ForwardingGroupTitle" = "Forwarding From This Group";
"Group.Setup.ForwardingChannelTitle" = "Forwarding From This Channel";
"Group.Setup.ForwardingEnabled" = "Allow Forwarding";
"Group.Setup.ForwardingDisabled" = "Restrict Forwarding";
"Group.Setup.ForwardingGroupInfo" = "Participants can forward messages from this group and save media files.";
"Group.Setup.ForwardingChannelInfo" = "Participants can forward messages from this channel and save media files.";
"Group.Setup.ForwardingGroupInfoDisabled" = "Participants can't forward messages from this group and save media files.";
"Group.Setup.ForwardingChannelInfoDisabled" = "Participants can't forward messages from this channel and save media files.";
"AuthSessions.TerminateIfAwayTitle" = "Automatically Terminate Old Sessions";
"AuthSessions.TerminateIfAwayFor" = "If Inactive For";
"AuthSessions.View.LocationInfo" = "This location estimate is based on the IP address and may not always be accurate.";
"AuthSessions.View.AcceptSecretChatsTitle" = "Incoming Secret Chats";
"AuthSessions.View.AcceptSecretChats" = "Accept on This Device";
"AuthSessions.View.AcceptSecretChatsInfo" = "You can disable the acception of incoming secret chats on this device.";
"Conversation.SendMesageAs" = "Send Message As...";
"Conversation.InviteRequestAdminGroup" = "%1$@ is an admin of %2$@, a group you requested to join.";
"Conversation.InviteRequestAdminChannel" = "%1$@ is an admin of %2$@, a channel you requested to join.";
"Conversation.InviteRequestInfo" = "You received this message because you requested to join %1$@ on %2$@.";
"Conversation.InviteRequestInfoConfirm" = "I understand";
"AuthSessions.HeaderInfo" = "Link [Telegram Desktop](desktop) or [Telegram Web](web) by scanning a QR code.";
"AuthSessions.LinkDesktopDevice" = "Link Desktop Device";
"AuthSessions.AddDevice.ScanInstallInfo" = "Go to [getdesktop.telegram.org](desktop) or [web.telegram.org](web) to get the QR code";
"Channel.AdminLog.MessageSent" = "%@ sent message:";
"ChatList.ClearSearchHistory" = "Are you sure you want to clear your search history?";
"AuthSessions.TerminateSessionText" = "Are you sure you want to terminate this session?";
"AuthSessions.TerminateOtherSessionsText" = "Are you sure you want to terminate all other sessions?";
"Notifications.ResetAllNotificationsText" = "Are you sure you want to reset all notification settings to default?";

0
Tests/BUILD Normal file
View File

16
Tests/Common/BUILD Normal file
View File

@ -0,0 +1,16 @@
filegroup(
name = "LaunchScreen",
srcs = [
"Base.lproj/LaunchScreen.xib",
],
visibility = ["//visibility:public"],
)
objc_library(
name = "Main",
srcs = [
"Main/main.m"
],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" interfaceStyle="light" id="O8c-13-3vw">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<point key="canvasLocation" x="139" y="117"/>
</view>
</objects>
</document>

7
Tests/Common/Main/main.m Normal file
View File

@ -0,0 +1,7 @@
#import <UIKit/UIKit.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, @"Application", @"AppDelegate");
}
}

150
Tests/LottieMesh/BUILD Normal file
View File

@ -0,0 +1,150 @@
load("@build_bazel_rules_apple//apple:ios.bzl",
"ios_application",
)
load("@build_bazel_rules_swift//swift:swift.bzl",
"swift_library",
)
load("//build-system/bazel-utils:plist_fragment.bzl",
"plist_fragment",
)
filegroup(
name = "AppResources",
srcs = glob([
"Resources/**/*",
], exclude = ["Resources/**/.*"]),
)
swift_library(
name = "Lib",
srcs = glob([
"Sources/**/*.swift",
]),
data = [
":AppResources",
],
deps = [
"//submodules/LottieMeshSwift:LottieMeshSwift",
],
)
plist_fragment(
name = "BuildNumberInfoPlist",
extension = "plist",
template =
"""
<key>CFBundleVersion</key>
<string>1</string>
"""
)
plist_fragment(
name = "VersionInfoPlist",
extension = "plist",
template =
"""
<key>CFBundleShortVersionString</key>
<string>1.0</string>
"""
)
plist_fragment(
name = "AppNameInfoPlist",
extension = "plist",
template =
"""
<key>CFBundleDisplayName</key>
<string>Test</string>
"""
)
plist_fragment(
name = "AppInfoPlist",
extension = "plist",
template =
"""
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Test</string>
<key>CFBundleIdentifier</key>
<string>org.telegram.LottieMesh</string>
<key>CFBundleName</key>
<string>Telegram</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIFileSharingEnabled</key>
<false/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDefault</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIViewEdgeAntialiasing</key>
<false/>
<key>UIViewGroupOpacity</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
"""
)
ios_application(
name = "LottieMesh",
bundle_id = "org.telegram.LottieMesh",
families = ["iphone", "ipad"],
minimum_os_version = "9.0",
provisioning_profile = "@build_configuration//provisioning:Wildcard.mobileprovision",
infoplists = [
":AppInfoPlist",
":BuildNumberInfoPlist",
":VersionInfoPlist",
],
resources = [
"//Tests/Common:LaunchScreen",
],
frameworks = [
],
deps = [
"//Tests/Common:Main",
":Lib",
],
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,21 @@
import Foundation
import UIKit
@objc(Application)
public final class Application: UIApplication {
}
@objc(AppDelegate)
public final class AppDelegate: NSObject, UIApplicationDelegate {
public var window: UIWindow?
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let window = UIWindow()
self.window = window
window.rootViewController = ViewController()
window.makeKeyAndVisible()
return true
}
}

View File

@ -0,0 +1,37 @@
import Foundation
import UIKit
import LottieMeshSwift
import Postbox
public final class ViewController: UIViewController {
override public func viewDidLoad() {
super.viewDidLoad()
TempBox.initializeShared(basePath: NSTemporaryDirectory(), processType: "test", launchSpecificId: Int64.random(in: Int64.min ..< Int64.max))
self.view.backgroundColor = .black
//let path = Bundle.main.path(forResource: "SUPER Fire", ofType: "json")!
let path = Bundle.main.path(forResource: "Fireworks", ofType: "json")!
//let path = Bundle.main.path(forResource: "Cat", ofType: "json")!
/*for _ in 0 ..< 100 {
let _ = generateMeshAnimation(data: try! Data(contentsOf: URL(fileURLWithPath: path)))!
}*/
if #available(iOS 13.0, *) {
let startTime = CFAbsoluteTimeGetCurrent()
let animationFile = generateMeshAnimation(data: try! Data(contentsOf: URL(fileURLWithPath: path)))!
print("Time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0)")
let buffer = MeshReadBuffer(data: try! Data(contentsOf: URL(fileURLWithPath: animationFile.path)))
let animation = MeshAnimation.read(buffer: buffer)
let renderer = MeshRenderer(wireframe: false)!
renderer.frame = CGRect(origin: CGPoint(x: 0.0, y: 50.0), size: CGSize(width: 300.0, height: 300.0))
self.view.addSubview(renderer)
renderer.add(mesh: animation, offset: CGPoint(), loop: true)
}
}
}

View File

@ -377,6 +377,7 @@ def generate_project(arguments):
disable_extensions = False
disable_provisioning_profiles = False
generate_dsym = False
target_name = "Telegram"
if arguments.disableExtensions is not None:
disable_extensions = arguments.disableExtensions
@ -384,6 +385,8 @@ def generate_project(arguments):
disable_provisioning_profiles = arguments.disableProvisioningProfiles
if arguments.generateDsym is not None:
generate_dsym = arguments.generateDsym
if arguments.target is not None:
target_name = arguments.target
call_executable(['killall', 'Xcode'], check_result=False)
@ -394,6 +397,7 @@ def generate_project(arguments):
generate_dsym=generate_dsym,
configuration_path=bazel_command_line.configuration_path,
bazel_app_arguments=bazel_command_line.get_project_generation_arguments(),
target_name=target_name
)
@ -559,6 +563,13 @@ if __name__ == '__main__':
'''
)
generateProjectParser.add_argument(
'--target',
type=str,
help='A custom bazel target name to build.',
metavar='target_name'
)
buildParser = subparsers.add_parser('build', help='Build the app')
buildParser.add_argument(
'--buildNumber',

View File

@ -10,13 +10,21 @@ def remove_directory(path):
shutil.rmtree(path)
def generate(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments):
def generate(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments, target_name):
project_path = os.path.join(build_environment.base_path, 'build-input/gen/project')
app_target = 'Telegram'
if '/' in target_name:
app_target_spec = target_name.split('/')[0] + '/' + target_name.split('/')[1] + ':' + target_name.split('/')[1]
app_target = target_name
app_target_clean = app_target.replace('/', '_')
else:
app_target_spec = '{target}:{target}'.format(target=target_name)
app_target = target_name
app_target_clean = app_target.replace('/', '_')
os.makedirs(project_path, exist_ok=True)
remove_directory('{}/Tulsi.app'.format(project_path))
remove_directory('{project}/{target}.tulsiproj'.format(project=project_path, target=app_target))
remove_directory('{project}/{target}.tulsiproj'.format(project=project_path, target=app_target_clean))
tulsi_path = os.path.join(project_path, 'Tulsi.app/Contents/MacOS/Tulsi')
@ -78,9 +86,9 @@ def generate(build_environment: BuildEnvironment, disable_extensions, disable_pr
bazel_build_arguments = []
bazel_build_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)]
if disable_extensions:
bazel_build_arguments += ['--//Telegram:disableExtensions']
bazel_build_arguments += ['--//{}:disableExtensions'.format(app_target)]
if disable_provisioning_profiles:
bazel_build_arguments += ['--//Telegram:disableProvisioningProfiles']
bazel_build_arguments += ['--//{}:disableProvisioningProfiles'.format(app_target)]
if generate_dsym:
bazel_build_arguments += ['--apple_generate_dsym']
@ -88,11 +96,11 @@ def generate(build_environment: BuildEnvironment, disable_extensions, disable_pr
tulsi_path,
'--',
'--verbose',
'--create-tulsiproj', app_target,
'--create-tulsiproj', app_target_clean,
'--workspaceroot', './',
'--bazel', bazel_wrapper_path,
'--outputfolder', project_path,
'--target', '{target}:{target}'.format(target=app_target),
'--target', '{target_spec}'.format(target_spec=app_target_spec),
'--build-options', ' '.join(bazel_build_arguments)
])
@ -100,17 +108,17 @@ def generate(build_environment: BuildEnvironment, disable_extensions, disable_pr
additional_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)]
additional_arguments += bazel_app_arguments
if disable_extensions:
additional_arguments += ['--//Telegram:disableExtensions']
additional_arguments += ['--//{}:disableExtensions'.format(app_target)]
additional_arguments_string = ' '.join(additional_arguments)
tulsi_config_path = 'build-input/gen/project/{target}.tulsiproj/Configs/{target}.tulsigen'.format(target=app_target)
tulsi_config_path = 'build-input/gen/project/{target}.tulsiproj/Configs/{target}.tulsigen'.format(target=app_target_clean)
with open(tulsi_config_path, 'rb') as tulsi_config:
tulsi_config_json = json.load(tulsi_config)
for category in ['BazelBuildOptionsDebug', 'BazelBuildOptionsRelease']:
tulsi_config_json['optionSet'][category]['p'] += ' {}'.format(additional_arguments_string)
tulsi_config_json['sourceFilters'] = [
'Telegram/...',
'{}/...'.format(app_target),
'submodules/...',
'third-party/...'
]
@ -121,12 +129,12 @@ def generate(build_environment: BuildEnvironment, disable_extensions, disable_pr
tulsi_path,
'--',
'--verbose',
'--genconfig', '{project}/{target}.tulsiproj:{target}'.format(project=project_path, target=app_target),
'--genconfig', '{project}/{target}.tulsiproj:{target}'.format(project=project_path, target=app_target_clean),
'--bazel', bazel_wrapper_path,
'--outputfolder', project_path,
'--no-open-xcode'
])
xcodeproj_path = '{project}/{target}.xcodeproj'.format(project=project_path, target=app_target)
xcodeproj_path = '{project}/{target}.xcodeproj'.format(project=project_path, target=app_target_clean)
call_executable(['open', xcodeproj_path])

View File

@ -20,7 +20,7 @@ swift_library(
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
#"//submodules/MeshAnimationCache:MeshAnimationCache"
"//submodules/MeshAnimationCache:MeshAnimationCache"
],
visibility = [
"//visibility:public",

View File

@ -10,7 +10,7 @@ import AsyncDisplayKit
import Display
import DeviceLocationManager
import TemporaryCachedPeerDataManager
//import MeshAnimationCache
import MeshAnimationCache
public final class TelegramApplicationOpenUrlCompletion {
public let completion: (Bool) -> Void
@ -147,11 +147,13 @@ public struct ChatAvailableMessageActions {
public var options: ChatAvailableMessageActionOptions
public var banAuthor: Peer?
public var disableDelete: Bool
public var isCopyProtected: Bool
public init(options: ChatAvailableMessageActionOptions, banAuthor: Peer?, disableDelete: Bool) {
public init(options: ChatAvailableMessageActionOptions, banAuthor: Peer?, disableDelete: Bool, isCopyProtected: Bool) {
self.options = options
self.banAuthor = banAuthor
self.disableDelete = disableDelete
self.isCopyProtected = isCopyProtected
}
}
@ -737,7 +739,7 @@ public protocol AccountContext: AnyObject {
var currentAppConfiguration: Atomic<AppConfiguration> { get }
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
//var meshAnimationCache: MeshAnimationCache { get }
var meshAnimationCache: MeshAnimationCache { get }
func storeSecureIdPassword(password: String)
func getStoredSecureIdPassword() -> String?

View File

@ -25,8 +25,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
public let additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]]
public let forcedResourceStatus: FileMediaResourceStatus?
public let currentlyPlayingMessageId: EngineMessage.Index?
public let isCopyProtectionEnabled: Bool
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil) {
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false) {
self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadNetworkType = automaticDownloadNetworkType
self.isRecentActions = isRecentActions
@ -37,6 +38,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
self.additionalAnimatedEmojiStickers = additionalAnimatedEmojiStickers
self.forcedResourceStatus = forcedResourceStatus
self.currentlyPlayingMessageId = currentlyPlayingMessageId
self.isCopyProtectionEnabled = isCopyProtectionEnabled
}
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
@ -67,6 +69,12 @@ public final class ChatMessageItemAssociatedData: Equatable {
if lhs.forcedResourceStatus != rhs.forcedResourceStatus {
return false
}
if lhs.currentlyPlayingMessageId != rhs.currentlyPlayingMessageId {
return false
}
if lhs.isCopyProtectionEnabled != rhs.isCopyProtectionEnabled {
return false
}
return true
}
}

View File

@ -13,15 +13,18 @@ public enum SharedMediaPlaybackDataType {
}
public enum SharedMediaPlaybackDataSource: Equatable {
case telegramFile(FileMediaReference)
case telegramFile(reference: FileMediaReference, isCopyProtected: Bool)
public static func ==(lhs: SharedMediaPlaybackDataSource, rhs: SharedMediaPlaybackDataSource) -> Bool {
switch lhs {
case let .telegramFile(lhsFileReference):
if case let .telegramFile(rhsFileReference) = rhs {
case let .telegramFile(lhsFileReference, lhsIsCopyProtected):
if case let .telegramFile(rhsFileReference, rhsIsCopyProtected) = rhs {
if !lhsFileReference.media.isEqual(to: rhsFileReference.media) {
return false
}
if lhsIsCopyProtected != rhsIsCopyProtected {
return false
}
return true
} else {
return false

View File

@ -8,6 +8,23 @@ import GZip
import YuvConversion
import MediaResources
public extension EmojiFitzModifier {
var lottieFitzModifier: LottieFitzModifier {
switch self {
case .type12:
return .type12
case .type3:
return .type3
case .type4:
return .type4
case .type5:
return .type5
case .type6:
return .type6
}
}
}
private let sharedQueue = Queue()
private let sharedStoreQueue = Queue.concurrentDefaultQueue()
@ -666,7 +683,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
let rawData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data
let decompressedData = transformedWithFitzModifier(data: rawData, fitzModifier: fitzModifier)
guard let animation = LottieInstance(data: decompressedData, cacheKey: "") else {
guard let animation = LottieInstance(data: decompressedData, fitzModifier: fitzModifier?.lottieFitzModifier ?? .none, cacheKey: "") else {
return nil
}
self.animation = animation

View File

@ -5,72 +5,73 @@ import MediaResources
let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]")
public func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data {
if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) {
let colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) }
let replacementColors: [UIColor]
switch fitzModifier {
case .type12:
replacementColors = [0xcb7b55, 0xf6b689, 0xffcda7, 0xffdfc5].map { UIColor(rgb: $0) }
case .type3:
replacementColors = [0xa45a38, 0xdf986b, 0xedb183, 0xf4c3a0].map { UIColor(rgb: $0) }
case .type4:
replacementColors = [0x703a17, 0xab673d, 0xc37f4e, 0xd89667].map { UIColor(rgb: $0) }
case .type5:
replacementColors = [0x4a2409, 0x7d3e0e, 0x965529, 0xa96337].map { UIColor(rgb: $0) }
case .type6:
replacementColors = [0x200f0a, 0x412924, 0x593d37, 0x63453f].map { UIColor(rgb: $0) }
}
func colorToString(_ color: UIColor) -> String {
var r: CGFloat = 0.0
var g: CGFloat = 0.0
var b: CGFloat = 0.0
if color.getRed(&r, green: &g, blue: &b, alpha: nil) {
return "\"k\":[\(r),\(g),\(b),1]"
}
return ""
}
func match(_ a: Double, _ b: Double, eps: Double) -> Bool {
return abs(a - b) < eps
}
var replacements: [(NSTextCheckingResult, String)] = []
if let colorKeyRegex = colorKeyRegex {
let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string))
for result in results.reversed() {
if let range = Range(result.range, in: string) {
let substring = String(string[range])
let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)]
let components = color.split(separator: ",")
if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) {
if match(a, 1.0, eps: 0.01) {
for i in 0 ..< colors.count {
let color = colors[i]
var cr: CGFloat = 0.0
var cg: CGFloat = 0.0
var cb: CGFloat = 0.0
if color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) {
if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) {
replacements.append((result, colorToString(replacementColors[i])))
}
}
}
}
}
}
}
}
for (result, text) in replacements {
if let range = Range(result.range, in: string) {
string = string.replacingCharacters(in: range, with: text)
}
}
return string.data(using: .utf8) ?? data
} else {
return data
}
return data
// if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) {
// let colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) }
// let replacementColors: [UIColor]
// switch fitzModifier {
// case .type12:
// replacementColors = [0xcb7b55, 0xf6b689, 0xffcda7, 0xffdfc5].map { UIColor(rgb: $0) }
// case .type3:
// replacementColors = [0xa45a38, 0xdf986b, 0xedb183, 0xf4c3a0].map { UIColor(rgb: $0) }
// case .type4:
// replacementColors = [0x703a17, 0xab673d, 0xc37f4e, 0xd89667].map { UIColor(rgb: $0) }
// case .type5:
// replacementColors = [0x4a2409, 0x7d3e0e, 0x965529, 0xa96337].map { UIColor(rgb: $0) }
// case .type6:
// replacementColors = [0x200f0a, 0x412924, 0x593d37, 0x63453f].map { UIColor(rgb: $0) }
// }
//
// func colorToString(_ color: UIColor) -> String {
// var r: CGFloat = 0.0
// var g: CGFloat = 0.0
// var b: CGFloat = 0.0
// if color.getRed(&r, green: &g, blue: &b, alpha: nil) {
// return "\"k\":[\(r),\(g),\(b),1]"
// }
// return ""
// }
//
// func match(_ a: Double, _ b: Double, eps: Double) -> Bool {
// return abs(a - b) < eps
// }
//
// var replacements: [(NSTextCheckingResult, String)] = []
//
// if let colorKeyRegex = colorKeyRegex {
// let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string))
// for result in results.reversed() {
// if let range = Range(result.range, in: string) {
// let substring = String(string[range])
// let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)]
// let components = color.split(separator: ",")
// if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) {
// if match(a, 1.0, eps: 0.01) {
// for i in 0 ..< colors.count {
// let color = colors[i]
// var cr: CGFloat = 0.0
// var cg: CGFloat = 0.0
// var cb: CGFloat = 0.0
// if color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) {
// if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) {
// replacements.append((result, colorToString(replacementColors[i])))
// }
// }
// }
// }
// }
// }
// }
// }
//
// for (result, text) in replacements {
// if let range = Range(result.range, in: string) {
// string = string.replacingCharacters(in: range, with: text)
// }
// }
//
// return string.data(using: .utf8) ?? data
// } else {
// return data
// }
}

View File

@ -24,11 +24,12 @@ swift_library(
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/Markdown:Markdown",
"//submodules/AnimationUI:AnimationUI",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/DeviceAccess:DeviceAccess",
"//submodules/UndoUI:UndoUI",
"//submodules/TextFormat:TextFormat",
"//submodules/Markdown:Markdown",
],
visibility = [
"//visibility:public",

View File

@ -11,6 +11,8 @@ import AlertUI
import TelegramPresentationData
import TelegramCore
import UndoUI
import Markdown
import TextFormat
private func parseAuthTransferUrl(_ url: URL) -> Data? {
var tokenString: String?
@ -160,7 +162,7 @@ public final class AuthTransferScanScreen: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = AuthTransferScanScreenNode(presentationData: self.presentationData)
self.displayNode = AuthTransferScanScreenNode(context: self.context, presentationData: self.presentationData)
self.displayNodeDidLoad()
@ -210,6 +212,7 @@ public final class AuthTransferScanScreen: ViewController {
}
private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
private let context: AccountContext
private var presentationData: PresentationData
private let previewNode: CameraPreviewNode
@ -246,7 +249,10 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
}
}
init(presentationData: PresentationData) {
private var highlightViews: [UIVisualEffectView] = []
init(context: AccountContext, presentationData: PresentationData) {
self.context = context
self.presentationData = presentationData
self.previewNode = CameraPreviewNode()
@ -287,11 +293,22 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
let textFont = Font.regular(17.0)
let boldFont = Font.bold(17.0)
var text = presentationData.strings.AuthSessions_AddDevice_ScanInstallInfo
text = text.replacingOccurrences(of: " [", with: " [").replacingOccurrences(of: ") ", with: ") ")
let attributedText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: .white), bold: MarkdownAttributeSet(font: boldFont, textColor: .white), link: MarkdownAttributeSet(font: boldFont, textColor: .white), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})))
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: presentationData.strings.AuthSessions_AddDevice_ScanInfo, font: Font.regular(16.0), textColor: .white)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 0
self.textNode.textAlignment = .center
self.textNode.lineSpacing = 0.5
self.errorTextNode = ImmediateTextNode()
self.errorTextNode.displaysAsynchronously = false
@ -377,6 +394,39 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
strongSelf.updateFocusedRect(nil)
}
}))
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.tapActionAtPoint = { _ in
return .waitForSingleTap
}
self.textNode.view.addGestureRecognizer(recognizer)
}
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .tap:
if let (_, attributes) = self.textNode.attributesAtPoint(location) {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
switch url {
case "desktop":
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: "https://getdesktop.telegram.org", forceExternal: true, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
case "web":
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: "https://web.telegram.org", forceExternal: true, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
default:
break
}
}
}
default:
break
}
}
default:
break
}
}
func updateFocusedRect(_ rect: CGRect?) {
@ -454,7 +504,7 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: layout.size.height))
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: layout.size.height))
let errorTextSize = self.errorTextNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: layout.size.height))
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: dimHeight - textSize.height - titleSpacing), size: textSize)
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: min(dimHeight - textSize.height - titleSpacing, navigationHeight + floorToScreenPixels((dimHeight - navigationHeight - textSize.height) / 2.0) + 5.0)), size: textSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: textFrame.minY - 18.0 - titleSize.height), size: titleSize)
var errorTextFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - errorTextSize.width) / 2.0), y: dimHeight + frameSide + 48.0), size: errorTextSize)
errorTextFrame.origin.y += floor(additionalTorchOffset / 2.0)
@ -466,6 +516,19 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
transition.updateFrameAdditive(node: self.errorTextNode, frame: errorTextFrame)
if self.highlightViews.isEmpty {
let urlAttributesAndRects = self.textNode.cachedLayout?.allAttributeRects(name: "UrlAttributeT") ?? []
for (_, rect) in urlAttributesAndRects {
let view = UIVisualEffectView(effect: UIBlurEffect(style: .light))
view.clipsToBounds = true
view.layer.cornerRadius = 5.0
view.frame = rect.offsetBy(dx: self.textNode.frame.minX, dy: self.textNode.frame.minY).insetBy(dx: -4.0, dy: -2.0)
self.view.insertSubview(view, belowSubview: self.textNode.view)
self.highlightViews.append(view)
}
}
}
@objc private func torchPressed() {

View File

@ -144,7 +144,8 @@ public func layoutAuthorizationItems(bounds: CGRect, items: [AuthorizationLayout
}
var verticalOrigin: CGFloat = bounds.minY + floor((bounds.size.height - totalHeight) / 2.0)
for item in solvedItems {
for i in 0 ..< solvedItems.count {
let item = solvedItems[i]
verticalOrigin += item.spacingBefore!
transition.updateFrame(node: item.item.node, frame: CGRect(origin: CGPoint(x: floor((bounds.size.width - item.item.size.width) / 2.0), y: verticalOrigin), size: item.item.size))
verticalOrigin += item.item.size.height

View File

@ -14,6 +14,10 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, st
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor)
return parseMarkdownIntoAttributedString(strings.Login_CodeSentInternal, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center)
case .missedCall:
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor)
return parseMarkdownIntoAttributedString("Within a few seconds you should\nreceive a short call from:", attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center)
case .call, .flashCall:
return NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center)
}
@ -31,7 +35,7 @@ public func authorizationNextOptionText(currentType: SentAuthorizationCodeType,
let timeString = NSString(format: "%d:%.02d", Int(minutes), Int(seconds))
return (NSAttributedString(string: strings.Login_WillSendSms(timeString as String).string, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false)
}
case .call, .flashCall:
case .call, .flashCall, .missedCall:
if timeout <= 0 {
return (NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false)
} else {

View File

@ -260,7 +260,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: contentSize.height))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel))
}
})
}

View File

@ -12,14 +12,34 @@ import PhotoResources
import DirectMediaImageCache
import TelegramStringFormatting
private final class MediaPreviewView: UIView {
private final class NullActionClass: NSObject, CAAction {
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
}
}
private let nullAction = NullActionClass()
private class SimpleLayer: CALayer {
override func action(forKey event: String) -> CAAction? {
return nullAction
}
func update(size: CGSize) {
}
}
private enum SelectionTransition {
case begin
case change
case end
}
private final class MediaPreviewView: SimpleLayer {
private let context: AccountContext
private let message: EngineMessage
private let media: EngineMedia
private let imageCache: DirectMediaImageCache
private let imageView: UIImageView
private var requestedImage: Bool = false
private var disposable: Disposable?
@ -29,12 +49,9 @@ private final class MediaPreviewView: UIView {
self.media = media
self.imageCache = imageCache
self.imageView = UIImageView()
self.imageView.contentMode = .scaleToFill
super.init()
super.init(frame: CGRect())
self.addSubview(self.imageView)
self.contentsGravity = .resize
}
required init?(coder: NSCoder) {
@ -62,7 +79,7 @@ private final class MediaPreviewView: UIView {
self.requestedImage = true
if let result = self.imageCache.getImage(message: self.message._asMessage(), media: self.media._asMedia(), width: 100, possibleWidths: [100], synchronous: false) {
if let image = result.image {
self.imageView.image = processImage(image)
self.contents = processImage(image).cgImage
}
if let signal = result.loadSignal {
self.disposable = (signal
@ -74,49 +91,22 @@ private final class MediaPreviewView: UIView {
return
}
if let image = image {
if strongSelf.imageView.image != nil {
let tempView = UIImageView()
tempView.image = strongSelf.imageView.image
tempView.frame = strongSelf.imageView.frame
tempView.contentMode = strongSelf.imageView.contentMode
strongSelf.addSubview(tempView)
tempView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempView] _ in
tempView?.removeFromSuperview()
if strongSelf.contents != nil {
let tempView = SimpleLayer()
tempView.contents = strongSelf.contents
tempView.frame = strongSelf.bounds
tempView.contentsGravity = strongSelf.contentsGravity
strongSelf.addSublayer(tempView)
tempView.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempView] _ in
tempView?.removeFromSuperlayer()
})
}
strongSelf.imageView.image = image
strongSelf.contents = image.cgImage
}
})
}
}
}
self.imageView.frame = CGRect(origin: CGPoint(), size: size)
/*var dimensions = CGSize(width: 100.0, height: 100.0)
if case let .image(image) = self.media {
if let largest = largestImageRepresentation(image.representations) {
dimensions = largest.dimensions.cgSize
if !self.requestedImage {
self.requestedImage = true
let signal = mediaGridMessagePhoto(account: self.context.account, photoReference: .message(message: MessageReference(self.message._asMessage()), media: image), fullRepresentationSize: CGSize(width: 36.0, height: 36.0), synchronousLoad: synchronousLoads)
self.imageView.setSignal(signal, attemptSynchronously: synchronousLoads)
}
}
} else if case let .file(file) = self.media {
if let mediaDimensions = file.dimensions {
dimensions = mediaDimensions.cgSize
if !self.requestedImage {
self.requestedImage = true
let signal = mediaGridMessageVideo(postbox: self.context.account.postbox, videoReference: .message(message: MessageReference(self.message._asMessage()), media: file), synchronousLoad: synchronousLoads, autoFetchFullSizeThumbnail: true, useMiniThumbnailIfAvailable: true)
self.imageView.setSignal(signal, attemptSynchronously: synchronousLoads)
}
}
}
let makeLayout = self.imageView.asyncLayout()
self.imageView.frame = CGRect(origin: CGPoint(), size: size)
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
apply()*/
}
}
@ -298,6 +288,7 @@ private final class ImageCache: Equatable {
private final class DayEnvironment: Equatable {
let imageCache: ImageCache
let directImageCache: DirectMediaImageCache
var selectionDelayCoordination: Int = 0
init(imageCache: ImageCache, directImageCache: DirectMediaImageCache) {
self.imageCache = imageCache
@ -425,45 +416,39 @@ private final class DayComponent: Component {
return true
}
final class View: UIView {
private let button: HighlightTrackingButton
private let highlightView: UIImageView
private var selectionView: UIImageView?
private let titleView: UIImageView
final class View: HighlightTrackingButton {
private let highlightView: SimpleLayer
private var selectionView: SimpleLayer?
private let titleView: SimpleLayer
private var mediaPreviewView: MediaPreviewView?
private var action: (() -> Void)?
private var currentMedia: DayMedia?
private var currentSelection: DaySelection?
private(set) var timestamp: Int32?
private(set) var index: MessageIndex?
private var isHighlightingEnabled: Bool = false
init() {
self.button = HighlightTrackingButton()
self.highlightView = UIImageView()
self.highlightView.isUserInteractionEnabled = false
self.titleView = UIImageView()
self.titleView.isUserInteractionEnabled = false
self.highlightView = SimpleLayer()
self.titleView = SimpleLayer()
super.init(frame: CGRect())
self.button.addSubview(self.highlightView)
self.button.addSubview(self.titleView)
self.layer.addSublayer(self.highlightView)
self.layer.addSublayer(self.titleView)
self.addSubview(self.button)
self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.button.highligthedChanged = { [weak self] highligthed in
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.highligthedChanged = { [weak self] highligthed in
guard let strongSelf = self, let mediaPreviewView = strongSelf.mediaPreviewView else {
return
}
if strongSelf.isHighlightingEnabled && highligthed {
mediaPreviewView.alpha = 0.8
mediaPreviewView.opacity = 0.8
} else {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
transition.updateAlpha(layer: mediaPreviewView.layer, alpha: 1.0)
transition.updateAlpha(layer: mediaPreviewView, alpha: 1.0)
}
}
}
@ -483,33 +468,38 @@ private final class DayComponent: Component {
self.timestamp = component.timestamp
self.index = component.media?.message.index
self.isHighlightingEnabled = component.isEnabled && component.media != nil && !component.isSelecting
let previousSelection = self.currentSelection ?? component.selection
let previousSelected = previousSelection != .none
let isSelected = component.selection != .none
self.currentSelection = component.selection
let diameter = min(availableSize.width, availableSize.height)
let contentFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - diameter) / 2.0), y: floor((availableSize.height - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter))
let dayEnvironment = environment[DayEnvironment.self].value
if component.media != nil {
self.highlightView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2))
self.highlightView.contents = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2)).cgImage
} else {
self.highlightView.image = nil
self.highlightView.contents = nil
}
var animateTitle = false
var animateMediaIn = false
if self.currentMedia != component.media {
self.currentMedia = component.media
if let mediaPreviewView = self.mediaPreviewView {
self.mediaPreviewView = nil
mediaPreviewView.removeFromSuperview()
mediaPreviewView.removeFromSuperlayer()
} else {
animateMediaIn = !isFirstTime
}
if let media = component.media {
let mediaPreviewView = MediaPreviewView(context: component.context, message: media.message, media: media.media, imageCache: dayEnvironment.directImageCache)
mediaPreviewView.isUserInteractionEnabled = false
self.mediaPreviewView = mediaPreviewView
self.button.insertSubview(mediaPreviewView, belowSubview: self.highlightView)
self.layer.insertSublayer(mediaPreviewView, below: self.highlightView)
}
}
@ -552,65 +542,125 @@ private final class DayComponent: Component {
switch component.selection {
case .edge:
let selectionView: UIImageView
let selectionView: SimpleLayer
if let current = self.selectionView {
selectionView = current
} else {
selectionView = UIImageView()
selectionView = SimpleLayer()
self.selectionView = selectionView
self.button.insertSubview(selectionView, belowSubview: self.titleView)
self.layer.insertSublayer(selectionView, below: self.titleView)
}
selectionView.frame = contentFrame
if self.mediaPreviewView != nil {
selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor)
selectionView.contents = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor).cgImage
} else {
selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor)
selectionView.contents = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor).cgImage
}
case .middle, .none:
if let selectionView = self.selectionView {
self.selectionView = nil
selectionView.removeFromSuperview()
if let _ = transition.userData(SelectionTransition.self), previousSelected != isSelected {
selectionView.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak selectionView] _ in
selectionView?.removeFromSuperlayer()
})
} else {
selectionView.removeFromSuperlayer()
}
}
}
let minimizedContentScale: CGFloat = (contentFrame.width - 8.0) / contentFrame.width
let contentScale: CGFloat
switch component.selection {
case .edge, .middle:
contentScale = (contentFrame.width - 8.0) / contentFrame.width
contentScale = minimizedContentScale
case .none:
contentScale = 1.0
}
let titleImage = dayEnvironment.imageCache.text(fontSize: titleFontSize, isSemibold: titleFontIsSemibold, color: titleColor, string: component.title)
if animateMediaIn {
let previousTitleView = UIImageView(image: self.titleView.image)
previousTitleView.frame = self.titleView.frame
self.titleView.superview?.insertSubview(previousTitleView, aboveSubview: self.titleView)
previousTitleView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousTitleView] _ in
previousTitleView?.removeFromSuperview()
})
self.titleView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.16)
animateTitle = true
}
self.titleView.image = titleImage
let titleSize = titleImage.size
transition.setFrame(view: self.highlightView, frame: CGRect(origin: CGPoint(x: contentFrame.midX - contentFrame.width * contentScale / 2.0, y: contentFrame.midY - contentFrame.width * contentScale / 2.0), size: CGSize(width: contentFrame.width * contentScale, height: contentFrame.height * contentScale)))
self.titleView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)
self.button.frame = CGRect(origin: CGPoint(), size: availableSize)
self.highlightView.bounds = CGRect(origin: CGPoint(), size: contentFrame.size)
self.highlightView.position = CGPoint(x: contentFrame.midX, y: contentFrame.midY)
if let mediaPreviewView = self.mediaPreviewView {
mediaPreviewView.frame = contentFrame
mediaPreviewView.bounds = CGRect(origin: CGPoint(), size: contentFrame.size)
mediaPreviewView.position = CGPoint(x: contentFrame.midX, y: contentFrame.midY)
mediaPreviewView.updateLayout(size: contentFrame.size, synchronousLoads: false)
mediaPreviewView.layer.sublayerTransform = CATransform3DMakeScale(contentScale, contentScale, 1.0)
mediaPreviewView.transform = CATransform3DMakeScale(contentScale, contentScale, 1.0)
if animateMediaIn {
mediaPreviewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.highlightView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
mediaPreviewView.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.highlightView.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
self.highlightView.transform = CATransform3DMakeScale(contentScale, contentScale, 1.0)
if let _ = transition.userData(SelectionTransition.self), previousSelected != isSelected {
if self.mediaPreviewView == nil {
animateTitle = true
}
if isSelected {
if component.selection == .edge {
let scaleIn = self.layer.makeAnimation(from: 1.0 as NSNumber, to: 0.75 as NSNumber, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.1)
let scaleOut = self.layer.springAnimation(from: 0.75 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
self.layer.animateGroup([scaleIn, scaleOut], key: "selection")
if let selectionView = self.selectionView {
if self.mediaPreviewView != nil {
let shapeLayer = CAShapeLayer()
let lineWidth: CGFloat = 2.0
shapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: diameter / 2.0, y: diameter / 2.0), radius: diameter / 2.0 - lineWidth / 2.0, startAngle: -CGFloat.pi / 2.0, endAngle: 2 * CGFloat.pi - CGFloat.pi / 2.0, clockwise: true).cgPath
shapeLayer.frame = selectionView.frame
shapeLayer.strokeColor = component.theme.list.itemCheckColors.fillColor.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.lineCap = .round
selectionView.isHidden = true
self.layer.insertSublayer(shapeLayer, above: selectionView)
shapeLayer.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "strokeEnd", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.25, delay: 0.1, completion: { [weak selectionView, weak shapeLayer] _ in
shapeLayer?.removeFromSuperlayer()
selectionView?.isHidden = false
})
} else {
selectionView.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
} else {
if let mediaPreviewView = self.mediaPreviewView {
mediaPreviewView.animateScale(from: 1.0, to: contentScale, duration: 0.2)
}
self.highlightView.animateScale(from: 1.0, to: contentScale, duration: 0.2)
}
} else {
if let mediaPreviewView = self.mediaPreviewView {
mediaPreviewView.animateScale(from: minimizedContentScale, to: contentScale, duration: 0.2)
}
self.highlightView.animateScale(from: minimizedContentScale, to: contentScale, duration: 0.2)
}
}
if animateTitle {
let previousTitleView = SimpleLayer()
previousTitleView.contents = self.titleView.contents
previousTitleView.frame = self.titleView.frame
self.titleView.superlayer?.insertSublayer(previousTitleView, above: self.titleView)
previousTitleView.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousTitleView] _ in
previousTitleView?.removeFromSuperlayer()
})
self.titleView.animateAlpha(from: 0.0, to: 1.0, duration: 0.16)
}
self.titleView.contents = titleImage.cgImage
let titleSize = titleImage.size
self.highlightView.frame = CGRect(origin: CGPoint(x: contentFrame.midX - contentFrame.width * contentScale / 2.0, y: contentFrame.midY - contentFrame.width * contentScale / 2.0), size: CGSize(width: contentFrame.width * contentScale, height: contentFrame.height * contentScale))
self.titleView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - titleSize.height) / 2.0)), size: titleSize)
return availableSize
}
@ -634,6 +684,7 @@ private final class MonthComponent: CombinedComponent {
let strings: PresentationStrings
let theme: PresentationTheme
let dayAction: (Int32) -> Void
let monthAction: (ClosedRange<Int32>) -> Void
let selectedDays: ClosedRange<Int32>?
init(
@ -643,6 +694,7 @@ private final class MonthComponent: CombinedComponent {
strings: PresentationStrings,
theme: PresentationTheme,
dayAction: @escaping (Int32) -> Void,
monthAction: @escaping (ClosedRange<Int32>) -> Void,
selectedDays: ClosedRange<Int32>?
) {
self.context = context
@ -651,6 +703,7 @@ private final class MonthComponent: CombinedComponent {
self.strings = strings
self.theme = theme
self.dayAction = dayAction
self.monthAction = monthAction
self.selectedDays = selectedDays
}
@ -758,21 +811,30 @@ private final class MonthComponent: CombinedComponent {
selection: daySelection,
isSelecting: context.component.selectedDays != nil,
action: {
dayAction(dayTimestamp)
if isEnabled {
dayAction(dayTimestamp)
}
}
)),
environment: {
context.environment[DayEnvironment.self]
},
availableSize: CGSize(width: usableWeekdayWidth, height: weekdaySize),
transition: .immediate
transition: context.transition
)
}
let titleFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - title.size.width) / 2.0), y: 0.0), size: title.size)
let monthAction = context.component.monthAction
let firstDayStart = Int32(context.component.model.firstDay.timeIntervalSince1970)
let lastDayStart = firstDayStart + 24 * 60 * 60 * Int32(context.component.model.numberOfDays - 1)
context.add(title
.position(CGPoint(x: titleFrame.midX, y: titleFrame.midY))
.gesture(.tap {
monthAction(firstDayStart ... lastDayStart)
})
)
let baseWeekdayTitleY = titleFrame.maxY + titleWeekdaysSpacing
@ -827,7 +889,10 @@ private final class MonthComponent: CombinedComponent {
}
if let selectedDays = context.component.selectedDays {
for (lineIndex, selection) in selectionsByLine {
for (lineIndex, selection) in selectionsByLine.sorted(by: { $0.key < $1.key }) {
if selection.leftTimestamp == selection.rightTimestamp && selection.leftTimestamp == selectedDays.lowerBound && selection.rightTimestamp == selectedDays.upperBound {
continue
}
let dayEnvironment = context.environment[DayEnvironment.self].value
let dayItemSize = updatedDays[0].size
@ -838,18 +903,8 @@ private final class MonthComponent: CombinedComponent {
let minY = baseDayY + CGFloat(lineIndex) * (weekdaySize + weekdaySpacing) + deltaHeight
let maxY = minY + dayItemSize.width
let leftRadius: CGFloat
if selectedDays.lowerBound == selection.leftTimestamp {
leftRadius = dayItemSize.width
} else {
leftRadius = 10.0
}
let rightRadius: CGFloat
if selectedDays.upperBound == selection.rightTimestamp {
rightRadius = dayItemSize.width
} else {
rightRadius = 10.0
}
let leftRadius: CGFloat = dayItemSize.width
let rightRadius: CGFloat = dayItemSize.width
let monthSelectionColor = context.component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.1)
@ -859,9 +914,28 @@ private final class MonthComponent: CombinedComponent {
availableSize: selectionRect.size,
transition: .immediate
)
let delayIndex = dayEnvironment.selectionDelayCoordination
context.add(selection
.position(CGPoint(x: selectionRect.midX, y: selectionRect.midY))
.appear(Transition.Appear { _, view, transition in
if case .none = transition.animation {
return
}
let delay = Double(min(delayIndex, 6)) * 0.1
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.05, delay: delay)
view.layer.animateFrame(from: CGRect(origin: view.frame.origin, size: CGSize(width: leftRadius, height: view.frame.height)), to: view.frame, duration: 0.25, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
})
.disappear(Transition.Disappear { view, transition, completion in
if case .none = transition.animation {
completion()
return
}
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
completion()
})
})
)
dayEnvironment.selectionDelayCoordination += 1
}
}
@ -1054,7 +1128,7 @@ public final class CalendarMessageScreen: ViewController {
return false
}
guard let dayView = result.superview as? DayComponent.View else {
guard let dayView = result as? DayComponent.View else {
return false
}
@ -1187,26 +1261,31 @@ public final class CalendarMessageScreen: ViewController {
}
func toggleSelectionMode() {
var transition: Transition = .immediate
if self.selectionState == nil {
self.selectionState = SelectionState(dayRange: nil)
} else {
self.selectionState = nil
transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
transition = transition.withUserData(SelectionTransition.end)
}
self.contextGestureContainerNode.isGestureEnabled = self.selectionState == nil
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.5, curve: .spring))
}
self.updateSelectionState(transition: transition)
}
func selectDay(timestamp: Int32) {
self.selectionState = SelectionState(dayRange: timestamp ... timestamp)
if let selectionState = self.selectionState, selectionState.dayRange == timestamp ... timestamp {
self.selectionState = SelectionState(dayRange: nil)
} else {
self.selectionState = SelectionState(dayRange: timestamp ... timestamp)
}
self.contextGestureContainerNode.isGestureEnabled = self.selectionState == nil
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.5, curve: .spring))
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.5, curve: .spring), componentsTransition: .immediate)
}
}
@ -1215,7 +1294,7 @@ public final class CalendarMessageScreen: ViewController {
self.selectionToolbarActionSelected()
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition, componentsTransition: Transition) {
let isFirstLayout = self.validLayout == nil
self.validLayout = (layout, navigationHeight)
@ -1233,13 +1312,39 @@ public final class CalendarMessageScreen: ViewController {
let tabBarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))
if let _ = self.selectionState {
if let selectionState = self.selectionState {
let selectionToolbarNode: ToolbarNode
if let currrent = self.selectionToolbarNode {
selectionToolbarNode = currrent
var selectedCount = 0
if let dayRange = selectionState.dayRange {
for i in 0 ..< self.months.count {
let firstDayTimestamp = Int32(self.months[i].firstDay.timeIntervalSince1970)
for day in 0 ..< self.months[i].numberOfDays {
let dayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day)
if dayRange.contains(dayTimestamp) {
selectedCount += 1
}
}
}
}
let toolbarText: String
if selectedCount == 0 {
toolbarText = self.presentationData.strings.DialogList_ClearHistoryConfirmation
} else if selectedCount == 1 {
//TODO:localize
toolbarText = "Clear History For This Day"
} else {
//TODO:localize
toolbarText = "Clear History For These Days"
}
transition.updateFrame(node: selectionToolbarNode, frame: tabBarFrame)
selectionToolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: self.presentationData.strings.DialogList_ClearHistoryConfirmation, isEnabled: self.selectionState?.dayRange != nil, color: .custom(self.presentationData.theme.list.itemDestructiveColor))), transition: transition)
selectionToolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: toolbarText, isEnabled: self.selectionState?.dayRange != nil, color: .custom(self.presentationData.theme.list.itemDestructiveColor))), transition: transition)
} else {
selectionToolbarNode = ToolbarNode(
theme: TabBarControllerTheme(
@ -1316,7 +1421,7 @@ public final class CalendarMessageScreen: ViewController {
}
updateMonthViews()
updateMonthViews(transition: componentsTransition)
}
private func selectionToolbarActionSelected() {
@ -1441,15 +1546,9 @@ public final class CalendarMessageScreen: ViewController {
guard let strongSelf = self else {
return
}
let _ = strongSelf.calendarSource.removeMessagesInRange(minTimestamp: minTimestampValue, maxTimestamp: maxTimestampValue, type: type, completion: {
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
strongSelf.controller?.dismiss(completion: nil)
}
})
strongSelf.controller?.completedWithRemoveMessagesInRange?(minTimestampValue ... maxTimestampValue, type, selectedCount, strongSelf.calendarSource)
strongSelf.controller?.dismiss(completion: nil)
}
if let _ = info.canClearForMyself ?? info.canClearForEveryone {
@ -1496,8 +1595,6 @@ public final class CalendarMessageScreen: ViewController {
strongSelf.controller?.present(actionSheet, in: .window(.root))
})
self.controller?.toggleSelectPressed()
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
@ -1510,7 +1607,7 @@ public final class CalendarMessageScreen: ViewController {
indicator.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
}
self.updateMonthViews()
self.updateMonthViews(transition: .immediate)
}
}
@ -1537,6 +1634,8 @@ public final class CalendarMessageScreen: ViewController {
theme: self.presentationData.theme,
dayAction: { _ in
},
monthAction: { _ in
},
selectedDays: nil
)),
environment: {
@ -1562,15 +1661,17 @@ public final class CalendarMessageScreen: ViewController {
return true
}
func updateMonthViews() {
func updateMonthViews(transition: Transition) {
guard let (width, _, frames) = self.scrollLayout else {
return
}
self.dayEnvironment.selectionDelayCoordination = 0
let visibleRect = self.scrollView.bounds.insetBy(dx: 0.0, dy: -200.0)
var validMonths = Set<Int>()
for i in 0 ..< self.months.count {
for i in (0 ..< self.months.count).reversed() {
guard let monthFrame = frames[i] else {
continue
}
@ -1579,17 +1680,19 @@ public final class CalendarMessageScreen: ViewController {
}
validMonths.insert(i)
var monthTransition = transition
let monthView: ComponentHostView<DayEnvironment>
if let current = self.monthViews[i] {
monthView = current
} else {
monthTransition = .immediate
monthView = ComponentHostView()
monthView.layer.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
self.monthViews[i] = monthView
self.scrollView.addSubview(monthView)
}
let _ = monthView.update(
transition: .immediate,
transition: monthTransition,
component: AnyComponent(MonthComponent(
context: self.context,
model: self.months[i],
@ -1601,22 +1704,29 @@ public final class CalendarMessageScreen: ViewController {
return
}
if var selectionState = strongSelf.selectionState {
var transition = Transition(animation: .curve(duration: 0.2, curve: .spring))
if let dayRange = selectionState.dayRange {
if dayRange.lowerBound == dayRange.upperBound {
if dayRange.lowerBound == timestamp || dayRange.upperBound == timestamp {
selectionState.dayRange = nil
transition = transition.withUserData(SelectionTransition.end)
} else if dayRange.lowerBound == dayRange.upperBound {
if timestamp < dayRange.lowerBound {
selectionState.dayRange = timestamp ... dayRange.upperBound
} else {
selectionState.dayRange = dayRange.lowerBound ... timestamp
}
transition = transition.withUserData(SelectionTransition.change)
} else {
selectionState.dayRange = timestamp ... timestamp
transition = transition.withUserData(SelectionTransition.change)
}
} else {
selectionState.dayRange = timestamp ... timestamp
transition = transition.withUserData(SelectionTransition.begin)
}
strongSelf.selectionState = selectionState
strongSelf.updateSelectionState()
strongSelf.updateSelectionState(transition: transition)
} else if let calendarState = strongSelf.calendarState {
outer: for month in strongSelf.months {
let firstDayTimestamp = Int32(month.firstDay.timeIntervalSince1970)
@ -1642,6 +1752,30 @@ public final class CalendarMessageScreen: ViewController {
}
}
},
monthAction: { [weak self] range in
guard let strongSelf = self else {
return
}
guard var selectionState = strongSelf.selectionState else {
return
}
var transition = Transition(animation: .curve(duration: 0.2, curve: .spring))
if let dayRange = selectionState.dayRange {
if dayRange == range {
selectionState.dayRange = nil
transition = transition.withUserData(SelectionTransition.end)
} else {
selectionState.dayRange = range
transition = transition.withUserData(SelectionTransition.change)
}
} else {
selectionState.dayRange = range
transition = transition.withUserData(SelectionTransition.begin)
}
strongSelf.selectionState = selectionState
strongSelf.updateSelectionState(transition: transition)
},
selectedDays: self.selectionState?.dayRange
)),
environment: {
@ -1664,7 +1798,7 @@ public final class CalendarMessageScreen: ViewController {
}
}
private func updateSelectionState() {
private func updateSelectionState(transition: Transition) {
var title = self.presentationData.strings.MessageCalendar_Title
if let selectionState = self.selectionState, let dayRange = selectionState.dayRange {
var selectedCount = 0
@ -1687,7 +1821,7 @@ public final class CalendarMessageScreen: ViewController {
self.controller?.navigationItem.title = title
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.5, curve: .spring))
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.5, curve: .spring), componentsTransition: transition)
}
}
@ -1733,7 +1867,7 @@ public final class CalendarMessageScreen: ViewController {
self.months[monthIndex].mediaByDay = mediaByDay
}
self.updateMonthViews()
self.updateMonthViews(transition: .immediate)
}
}
@ -1751,6 +1885,8 @@ public final class CalendarMessageScreen: ViewController {
private let previewDay: (Int32, MessageIndex?, ASDisplayNode, CGRect, ContextGesture) -> Void
private var presentationData: PresentationData
public var completedWithRemoveMessagesInRange: ((ClosedRange<Int32>, InteractiveHistoryClearingType, Int, SparseMessageCalendar) -> Void)?
public init(
context: AccountContext,
@ -1845,6 +1981,6 @@ public final class CalendarMessageScreen: ViewController {
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.node.containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
self.node.containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, componentsTransition: .immediate)
}
}

View File

@ -190,6 +190,11 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let containerNode: ASDisplayNode
override var controlsContainer: ASDisplayNode {
return self.containerNode
}
private let avatarNode: AvatarNode
private let titleNode: TextNode
private let statusNode: TextNode
@ -207,6 +212,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.containerNode = ASDisplayNode()
self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false
@ -239,12 +246,13 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.avatarNode)
self.addSubnode(self.typeIconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.statusNode)
self.addSubnode(self.dateNode)
self.addSubnode(self.infoButtonNode)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.avatarNode)
self.containerNode.addSubnode(self.typeIconNode)
self.containerNode.addSubnode(self.titleNode)
self.containerNode.addSubnode(self.statusNode)
self.containerNode.addSubnode(self.dateNode)
self.containerNode.addSubnode(self.infoButtonNode)
self.addSubnode(self.accessibilityArea)
self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside)
@ -631,6 +639,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size)
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset))
strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)

View File

@ -328,8 +328,14 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
}
} else {
if case .search = source {
if let _ = peer as? TelegramChannel {
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_JoinChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
if let peer = peer as? TelegramChannel {
let text: String
if case .broadcast = peer.info {
text = strings.ChatList_Context_JoinChannel
} else {
text = strings.ChatList_Context_JoinChat
}
items.append(.action(ContextMenuActionItem(text: text, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
var createSignal = context.peerChannelMemberCategoriesContextsManager.join(engine: context.engine, peerId: peerId, hash: nil)
var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in

View File

@ -485,7 +485,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
strongSelf.tabContainerNode.cancelAnimations()
strongSelf.chatListDisplayNode.inlineTabContainerNode.cancelAnimations()
}
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
}
self.reloadFilters()
@ -549,7 +549,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
if let layout = self.validLayout {
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate)
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate)
self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate)
}
@ -1414,7 +1414,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let navigationBarHeight = self.navigationBar?.frame.maxY ?? 0.0
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0)))
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
if let tabContainerData = self.tabContainerData {
self.chatListDisplayNode.inlineTabContainerNode.isHidden = !tabContainerData.1 || tabContainerData.0.count <= 1
} else {
@ -1621,7 +1621,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
strongSelf.containerLayoutUpdated(layout, transition: transition)
(strongSelf.parent as? TabBarController)?.updateLayout(transition: transition)
} else {
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
}
}

View File

@ -109,6 +109,11 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let containerNode: ASDisplayNode
override var controlsContainer: ASDisplayNode {
return self.containerNode
}
private let titleNode: TextNode
private let labelNode: TextNode
private let arrowNode: ASImageNode
@ -138,6 +143,8 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.containerNode = ASDisplayNode()
self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false
@ -161,9 +168,10 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.arrowNode)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.titleNode)
self.containerNode.addSubnode(self.labelNode)
self.containerNode.addSubnode(self.arrowNode)
self.addSubnode(self.activateArea)
self.activateArea.activate = { [weak self] in
@ -345,6 +353,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size)
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))

View File

@ -83,6 +83,7 @@ private final class ItemNode: ASDisplayNode {
private(set) var unreadCount: Int = 0
private var isReordering: Bool = false
private var isEditing: Bool = false
private var theme: PresentationTheme?
@ -192,6 +193,8 @@ private final class ItemNode: ASDisplayNode {
}
func updateText(strings: PresentationStrings, title: String, shortTitle: String, unreadCount: Int, unreadHasUnmuted: Bool, isNoFilter: Bool, selectionFraction: CGFloat, isEditing: Bool, isAllChats: Bool, isReordering: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
self.isEditing = isEditing
if self.theme !== presentationData.theme {
self.theme = presentationData.theme
@ -212,7 +215,7 @@ private final class ItemNode: ASDisplayNode {
self.selectionFraction = selectionFraction
self.unreadCount = unreadCount
transition.updateAlpha(node: self.containerNode, alpha: isReordering && isAllChats ? 0.5 : 1.0)
transition.updateAlpha(node: self.containerNode, alpha: isEditing || (isReordering && isAllChats) ? 0.5 : 1.0)
if isReordering && !isAllChats {
if self.deleteButtonNode == nil {
@ -234,7 +237,7 @@ private final class ItemNode: ASDisplayNode {
})
}
transition.updateAlpha(node: self.badgeContainerNode, alpha: (isReordering || unreadCount == 0) ? 0.0 : 1.0)
transition.updateAlpha(node: self.badgeContainerNode, alpha: (isEditing || isReordering || unreadCount == 0) ? 0.0 : 1.0)
let selectionAlpha: CGFloat = selectionFraction * selectionFraction
let deselectionAlpha: CGFloat = 1.0// - selectionFraction
@ -302,7 +305,7 @@ private final class ItemNode: ASDisplayNode {
self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((badgeBackgroundFrame.width - badgeSize.width) / 2.0), y: floor((badgeBackgroundFrame.height - badgeSize.height) / 2.0)), size: badgeSize)
let width: CGFloat
if self.unreadCount == 0 || self.isReordering {
if self.unreadCount == 0 || self.isReordering || self.isEditing {
if !self.isReordering {
self.badgeContainerNode.alpha = 0.0
}
@ -636,6 +639,11 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
let isFirstTime = self.currentParams == nil
let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition
var isEditing = isEditing
if isReordering {
isEditing = false
}
var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter
let previousScrollBounds = self.scrollNode.bounds
let previousContentWidth = self.scrollNode.view.contentSize.width
@ -674,7 +682,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
self.currentParams = (size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering, isEditing, transitionFraction, presentationData: presentationData)
self.reorderingGesture?.isEnabled = isEditing || isReordering
self.reorderingGesture?.isEnabled = isReordering
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
@ -754,7 +762,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
selectionFraction = 0.0
}
itemNode.updateText(strings: presentationData.strings, title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, selectionFraction: selectionFraction, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition)
itemNode.updateText(strings: presentationData.strings, title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, selectionFraction: selectionFraction, isEditing: isEditing, isAllChats: isNoFilter, isReordering: isReordering, presentationData: presentationData, transition: itemNodeTransition)
}
var removeKeys: [ChatListFilterTabEntryId] = []
for (id, _) in self.itemNodes {

View File

@ -115,6 +115,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var selectedFilterKeyPromise = Promise<ChatListSearchFilterEntryId?>()
private var transitionFraction: CGFloat = 0.0
private weak var copyProtectionTooltipController: TooltipController?
private var didSetReady: Bool = false
private let _ready = Promise<Void>()
public override func ready() -> Signal<Void, NoError> {
@ -183,6 +185,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let presentationData = strongSelf.presentationData
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.ChatList_ClearSearchHistory),
ActionSheetButtonItem(title: presentationData.strings.WebSearch_RecentSectionClear, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
@ -417,6 +420,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.presentationDataDisposable?.dispose()
self.suggestedFiltersDisposable.dispose()
self.shareStatusDisposable?.dispose()
self.copyProtectionTooltipController?.dismiss()
}
private func updateState(_ f: (ChatListSearchContainerNodeSearchState) -> ChatListSearchContainerNodeSearchState) {
@ -588,6 +593,53 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return
}
strongSelf.forwardMessages(messageIds: nil)
}, displayCopyProtectionTip: { [weak self] node, save in
guard let strongSelf = self, let messageIds = strongSelf.stateValue.selectedMessageIds, !messageIds.isEmpty else {
return
}
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [EngineMessage] in
var messages: [EngineMessage] = []
for id in messageIds {
if let message = transaction.getMessage(id) {
messages.append(EngineMessage(message))
}
}
return messages
}
|> deliverOnMainQueue).start(next: { messages in
if let strongSelf = self, !messages.isEmpty {
var isChannel = false
for message in messages {
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
isChannel = true
break
}
}
let text: String
if save {
text = isChannel ? strongSelf.presentationData.strings.Conversation_CopyProtectionSavingDisabledChannel : strongSelf.presentationData.strings.Conversation_CopyProtectionSavingDisabledGroup
} else {
text = isChannel ? strongSelf.presentationData.strings.Conversation_CopyProtectionForwardingDisabledChannel : strongSelf.presentationData.strings.Conversation_CopyProtectionForwardingDisabledGroup
}
strongSelf.copyProtectionTooltipController?.dismiss()
let tooltipController = TooltipController(content: .text(text), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
strongSelf.copyProtectionTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.copyProtectionTooltipController === tooltipController {
strongSelf.copyProtectionTooltipController = nil
}
}
strongSelf.present?(tooltipController, TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self {
let rect = node.view.convert(node.view.bounds, to: strongSelf.view).offsetBy(dx: 0.0, dy: 3.0)
return (strongSelf, rect)
}
return nil
}))
}
})
})
selectionPanelNode.chatAvailableMessageActions = { [weak self] messageIds -> Signal<ChatAvailableMessageActions, NoError> in
guard let strongSelf = self else {
@ -763,13 +815,17 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
})
})))
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.forwardMessages(messageIds: [message.id])
}
})
})))
if let peer = message.peers[message.id.peerId], peer.isCopyProtectionEnabled {
} else {
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.forwardMessages(messageIds: [message.id])
}
})
})))
}
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuSelect, icon: { theme in

View File

@ -17,9 +17,10 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
private let deleteMessages: () -> Void
private let shareMessages: () -> Void
private let forwardMessages: () -> Void
private let displayCopyProtectionTip: (ASDisplayNode, Bool) -> Void
private let separatorNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let backgroundNode: NavigationBackgroundNode
private let deleteButton: HighlightableButtonNode
private let forwardButton: HighlightableButtonNode
private let shareButton: HighlightableButtonNode
@ -60,11 +61,12 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
}
}
init(context: AccountContext, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void) {
init(context: AccountContext, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void, displayCopyProtectionTip: @escaping (ASDisplayNode, Bool) -> Void) {
self.context = context
self.deleteMessages = deleteMessages
self.shareMessages = shareMessages
self.forwardMessages = forwardMessages
self.displayCopyProtectionTip = displayCopyProtectionTip
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.theme = presentationData.theme
@ -72,11 +74,9 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = presentationData.theme.chat.inputPanel.panelSeparatorColor
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = presentationData.theme.chat.inputPanel.panelBackgroundColor
self.backgroundNode = NavigationBackgroundNode(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
self.deleteButton = HighlightableButtonNode(pointerStyle: .default)
self.deleteButton.isEnabled = false
self.deleteButton.isAccessibilityElement = true
self.deleteButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextDelete
@ -85,7 +85,6 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
self.forwardButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextForward
self.shareButton = HighlightableButtonNode(pointerStyle: .default)
self.shareButton.isEnabled = false
self.shareButton.isAccessibilityElement = true
self.shareButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextShare
@ -104,7 +103,9 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
self.addSubnode(self.shareButton)
self.addSubnode(self.separatorNode)
self.forwardButton.isEnabled = false
self.deleteButton.isEnabled = false
self.forwardButton.isImplicitlyDisabled = true
self.shareButton.isImplicitlyDisabled = true
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), forControlEvents: .touchUpInside)
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside)
@ -120,7 +121,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
if presentationData.theme !== self.theme {
self.theme = presentationData.theme
self.backgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
self.backgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
@ -145,18 +146,15 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
if let actions = self.actions {
self.deleteButton.isEnabled = false
self.forwardButton.isEnabled = actions.options.contains(.forward)
self.shareButton.isEnabled = false
self.forwardButton.isImplicitlyDisabled = !actions.options.contains(.forward)
self.deleteButton.isEnabled = !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty
self.shareButton.isEnabled = !actions.options.intersection([.forward]).isEmpty
self.deleteButton.isHidden = !self.deleteButton.isEnabled
self.shareButton.isImplicitlyDisabled = actions.options.intersection([.forward]).isEmpty
} else {
self.deleteButton.isEnabled = false
self.deleteButton.isHidden = true
self.forwardButton.isEnabled = false
self.shareButton.isEnabled = false
self.forwardButton.isImplicitlyDisabled = true
self.shareButton.isImplicitlyDisabled = true
}
self.deleteButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 57.0, height: panelHeight))
@ -166,6 +164,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
let panelHeightWithInset = panelHeight + layout.intrinsicInsets.bottom
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeightWithInset)))
self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition)
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
return panelHeightWithInset
@ -176,10 +175,18 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
}
@objc func forwardButtonPressed() {
self.forwardMessages()
if let actions = self.actions, actions.isCopyProtected {
self.displayCopyProtectionTip(self.forwardButton, false)
} else {
self.forwardMessages()
}
}
@objc func shareButtonPressed() {
self.shareMessages()
if let actions = self.actions, actions.isCopyProtected {
self.displayCopyProtectionTip(self.shareButton, true)
} else {
self.shareMessages()
}
}
}

View File

@ -1020,7 +1020,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let messagePeer = itemPeer.chatMainPeer {
peerText = messagePeer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
}
} else if let message = messages.last, case let .user(author) = message.author, let peer = itemPeer.chatMainPeer, !isUser {
} else if let message = messages.last, let author = message.author?._asPeer(), let peer = itemPeer.chatMainPeer, !isUser {
if case let .channel(peer) = peer, case .broadcast = peer.info {
} else if !displayAsMessage {
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature {

View File

@ -0,0 +1,21 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "CodeInputView",
module_name = "CodeInputView",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,293 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import PhoneNumberFormat
public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
public struct Theme: Equatable {
public var inactiveBorder: UInt32
public var activeBorder: UInt32
public var foreground: UInt32
public var isDark: Bool
public init(
inactiveBorder: UInt32,
activeBorder: UInt32,
foreground: UInt32,
isDark: Bool
) {
self.inactiveBorder = inactiveBorder
self.activeBorder = activeBorder
self.foreground = foreground
self.isDark = isDark
}
}
private final class ItemView: ASDisplayNode {
private let backgroundView: UIImageView
private let textNode: ImmediateTextNode
private var borderColorValue: UInt32?
private var text: String = ""
override init() {
self.backgroundView = UIImageView()
self.textNode = ImmediateTextNode()
super.init()
self.addSubnode(self.textNode)
self.view.addSubview(self.backgroundView)
self.clipsToBounds = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(borderColor: UInt32) {
if self.borderColorValue != borderColor {
self.borderColorValue = borderColor
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: 10.0, color: nil, strokeColor: UIColor(argb: borderColor), strokeWidth: 1.0, backgroundColor: nil)
}
}
func update(textColor: UInt32, text: String, size: CGSize, animated: Bool) {
let previousText = self.text
self.text = text
if animated && previousText.isEmpty != text.isEmpty {
if !text.isEmpty {
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
self.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: size.height / 2.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
} else {
if let copyView = self.textNode.view.snapshotContentTree() {
self.view.insertSubview(copyView, at: 0)
copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyView] _ in
copyView?.removeFromSuperview()
})
copyView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: size.height / 2.0), duration: 0.2, removeOnCompletion: false, additive: true)
}
}
}
self.textNode.attributedText = NSAttributedString(string: text, font: Font.monospace(21.0), textColor: UIColor(argb: textColor))
let textSize = self.textNode.updateLayout(size)
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
self.backgroundView.frame = CGRect(origin: CGPoint(), size: size)
}
}
private let prefixLabel: ImmediateTextNode
private let textField: UITextField
private var focusIndex: Int?
private var itemViews: [ItemView] = []
public var updated: (() -> Void)?
private var theme: Theme?
private var count: Int?
private var textValue: String = ""
public var text: String {
get {
return self.textValue
} set(value) {
self.textValue = value
self.textField.text = value
}
}
override public init() {
self.prefixLabel = ImmediateTextNode()
self.textField = UITextField()
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
self.textField.keyboardType = .asciiCapableNumberPad
} else {
self.textField.keyboardType = .numberPad
}
if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
self.textField.textContentType = .oneTimeCode
}
self.textField.returnKeyType = .done
self.textField.disableAutomaticKeyboardHandling = [.forward, .backward]
super.init()
self.addSubnode(self.prefixLabel)
self.view.addSubview(self.textField)
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
self.textField.delegate = self
self.textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
}
required public init?(coder: NSCoder) {
preconditionFailure()
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.textField.becomeFirstResponder()
}
}
@objc func textFieldChanged(_ textField: UITextField) {
self.textValue = textField.text ?? ""
self.updateItemViews(animated: true)
self.updated?()
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let count = self.count else {
return false
}
var text = textField.text ?? ""
guard let stringRange = Range(range, in: text) else {
return false
}
text.replaceSubrange(stringRange, with: string)
if !text.allSatisfy({ $0.isNumber && $0.isASCII }) {
return false
}
if text.count > count {
return false
}
return true
}
private func currentCaretIndex() -> Int? {
if let selectedTextRange = self.textField.selectedTextRange {
let index = self.textField.offset(from: self.textField.beginningOfDocument, to: selectedTextRange.end)
return index
} else {
return nil
}
}
public func textFieldDidBeginEditing(_ textField: UITextField) {
self.focusIndex = self.currentCaretIndex()
self.updateItemViews(animated: true)
}
public func textFieldDidEndEditing(_ textField: UITextField) {
self.focusIndex = nil
self.updateItemViews(animated: true)
}
public func textFieldDidChangeSelection(_ textField: UITextField) {
self.focusIndex = self.currentCaretIndex()
self.updateItemViews(animated: true)
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
return false
}
private func updateItemViews(animated: Bool) {
guard let theme = self.theme else {
return
}
for i in 0 ..< self.itemViews.count {
let itemView = self.itemViews[i]
let itemSize = itemView.bounds.size
itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder)
let itemText: String
if i < self.textValue.count {
itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)])
} else {
itemText = ""
}
itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, animated: animated)
}
}
public func update(theme: Theme, prefix: String, count: Int, width: CGFloat) -> CGSize {
self.theme = theme
self.count = count
if theme.isDark {
self.textField.keyboardAppearance = .dark
} else {
self.textField.keyboardAppearance = .light
}
let height: CGFloat = 28.0
self.prefixLabel.attributedText = NSAttributedString(string: prefix, font: Font.monospace(21.0), textColor: UIColor(argb: theme.foreground))
let prefixSize = self.prefixLabel.updateLayout(CGSize(width: width, height: 100.0))
let prefixSpacing: CGFloat = prefix.isEmpty ? 0.0 : 8.0
let itemSize = CGSize(width: 25.0, height: height)
let itemSpacing: CGFloat = 5.0
let itemsWidth: CGFloat = itemSize.width * CGFloat(count) + itemSpacing * CGFloat(count - 1)
let contentWidth: CGFloat = prefixSize.width + prefixSpacing + itemsWidth
let contentOriginX: CGFloat = floor((width - contentWidth) / 2.0)
self.prefixLabel.frame = CGRect(origin: CGPoint(x: contentOriginX, y: floorToScreenPixels((height - prefixSize.height) / 2.0)), size: prefixSize)
for i in 0 ..< count {
let itemView: ItemView
if self.itemViews.count > i {
itemView = self.itemViews[i]
} else {
itemView = ItemView()
self.itemViews.append(itemView)
self.addSubnode(itemView)
}
itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder)
let itemText: String
if i < self.textValue.count {
itemText = String(self.textValue[self.textValue.index(self.textValue.startIndex, offsetBy: i)])
} else {
itemText = ""
}
itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, animated: false)
itemView.frame = CGRect(origin: CGPoint(x: contentOriginX + prefixSize.width + prefixSpacing + CGFloat(i) * (itemSize.width + itemSpacing), y: 0.0), size: itemSize)
}
if self.itemViews.count > count {
for i in count ..< self.itemViews.count {
self.itemViews[i].removeFromSupernode()
}
self.itemViews.removeSubrange(count...)
}
return CGSize(width: width, height: height)
}
public override func becomeFirstResponder() -> Bool {
return self.textField.becomeFirstResponder()
}
public override func canBecomeFirstResponder() -> Bool {
return self.textField.canBecomeFirstResponder
}
public override func resignFirstResponder() -> Bool {
return self.textField.resignFirstResponder()
}
public override func canResignFirstResponder() -> Bool {
return self.textField.canResignFirstResponder
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.bounds.contains(point) {
return self.view
} else {
return nil
}
}
}

View File

@ -1,12 +1,12 @@
import Foundation
import UIKit
final class Button: CombinedComponent, Equatable {
let content: AnyComponent<Empty>
let insets: UIEdgeInsets
let action: () -> Void
public final class Button: CombinedComponent, Equatable {
public let content: AnyComponent<Empty>
public let insets: UIEdgeInsets
public let action: () -> Void
init(
public init(
content: AnyComponent<Empty>,
insets: UIEdgeInsets,
action: @escaping () -> Void
@ -16,7 +16,7 @@ final class Button: CombinedComponent, Equatable {
self.action = action
}
static func ==(lhs: Button, rhs: Button) -> Bool {
public static func ==(lhs: Button, rhs: Button) -> Bool {
if lhs.content != rhs.content {
return false
}
@ -26,7 +26,7 @@ final class Button: CombinedComponent, Equatable {
return true
}
final class State: ComponentState {
public final class State: ComponentState {
var isHighlighted = false
override init() {
@ -34,11 +34,11 @@ final class Button: CombinedComponent, Equatable {
}
}
func makeState() -> State {
public func makeState() -> State {
return State()
}
static var body: Body {
public static var body: Body {
let content = Child(environment: Empty.self)
return { context in

View File

@ -30,7 +30,7 @@ public final class Text: Component {
public final class View: UIView {
private var measureState: MeasureState?
func update(component: Text, availableSize: CGSize) -> CGSize {
public func update(component: Text, availableSize: CGSize) -> CGSize {
let attributedText = NSAttributedString(string: component.text, attributes: [
NSAttributedString.Key.font: component.font,
NSAttributedString.Key.foregroundColor: component.color

View File

@ -219,7 +219,7 @@ class CreatePollOptionActionItemNode: ListViewItemNode, ItemListItemNode {
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
}
})
}

View File

@ -16,6 +16,8 @@ swift_library(
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TextSelectionNode:TextSelectionNode",
"//submodules/AppBundle:AppBundle",
"//submodules/AccountContext:AccountContext",
"//submodules/ReactionSelectionNode:ReactionSelectionNode",
],
visibility = [
"//visibility:public",

View File

@ -4,7 +4,7 @@ import Display
import TelegramPresentationData
import SwiftSignalKit
enum ContextActionSibling {
public enum ContextActionSibling {
case none
case item
case separator
@ -13,10 +13,11 @@ enum ContextActionSibling {
public protocol ContextActionNodeProtocol: ASDisplayNode {
func setIsHighlighted(_ value: Bool)
func performAction()
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol
var isActionEnabled: Bool { get }
}
final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
private var presentationData: PresentationData
private(set) var action: ContextMenuActionItem
private let getController: () -> ContextControllerProtocol?
@ -37,11 +38,11 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
private var pointerInteraction: PointerInteraction?
var isActionEnabled: Bool {
public var isActionEnabled: Bool {
return true
}
init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void, requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void) {
public init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void, requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void) {
self.presentationData = presentationData
self.action = action
self.getController = getController
@ -81,7 +82,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
titleFont = customFont
}
let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
self.textNode.attributedText = NSAttributedString(string: action.text, font: titleFont, textColor: textColor)
@ -181,7 +182,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
self.iconDisposable?.dispose()
}
override func didLoad() {
public override func didLoad() {
super.didLoad()
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
@ -195,7 +196,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
})
}
func updateLayout(constrainedWidth: CGFloat, previous: ContextActionSibling, next: ContextActionSibling) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
public func updateLayout(constrainedWidth: CGFloat, previous: ContextActionSibling, next: ContextActionSibling) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 16.0
let iconSideInset: CGFloat = 12.0
let verticalInset: CGFloat = 12.0
@ -272,7 +273,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
}
}
func updateTheme(presentationData: PresentationData) {
public func updateTheme(presentationData: PresentationData) {
self.presentationData = presentationData
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
@ -350,7 +351,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
self.requestLayout()
}
func performAction() {
public func performAction() {
guard let controller = self.getController() else {
return
}
@ -368,11 +369,15 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
))
}
func setIsHighlighted(_ value: Bool) {
public func setIsHighlighted(_ value: Bool) {
if value && self.buttonNode.isUserInteractionEnabled {
self.highlightedBackgroundNode.alpha = 1.0
} else {
self.highlightedBackgroundNode.alpha = 0.0
}
}
public func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
return self
}
}

View File

@ -189,7 +189,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
gesture.isEnabled = self.panSelectionGestureEnabled
}
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, minimalWidth: CGFloat?, transition: ContainedViewLayoutTransition) -> CGSize {
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, minimalWidth: CGFloat?, transition: ContainedViewLayoutTransition) -> CGSize {
var minActionsWidth: CGFloat = 250.0
if let minimalWidth = minimalWidth, minimalWidth > minActionsWidth {
minActionsWidth = minimalWidth
@ -250,7 +250,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
heightsAndCompletions.append((minSize.height, complete))
contentHeight += minSize.height
case let .custom(itemNode):
let (minSize, complete) = itemNode.updateLayout(constrainedWidth: constrainedWidth)
let (minSize, complete) = itemNode.updateLayout(constrainedWidth: constrainedWidth, constrainedHeight: constrainedHeight)
maxWidth = max(maxWidth, minSize.width)
heightsAndCompletions.append((minSize.height, complete))
contentHeight += minSize.height
@ -327,7 +327,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
}
case let .custom(node):
if let node = node as? ContextActionNodeProtocol, node.frame.contains(point) {
return node
return node.actionNode(at: self.convert(point, to: node))
}
default:
break
@ -351,6 +351,7 @@ private final class InnerTextSelectionTipContainerNode: ASDisplayNode {
self.presentationData = presentationData
self.textNode = TextNode()
var icon: UIImage?
switch tip {
case .textSelection:
var rawText = self.presentationData.strings.ChatContextMenu_TextSelectionTip
@ -362,15 +363,21 @@ private final class InnerTextSelectionTipContainerNode: ASDisplayNode {
self.text = rawText
self.targetSelectionIndex = 1
}
icon = UIImage(bundleImageName: "Chat/Context Menu/Tip")
case .messageViewsPrivacy:
self.text = self.presentationData.strings.ChatContextMenu_MessageViewsPrivacyTip
self.targetSelectionIndex = nil
icon = UIImage(bundleImageName: "Chat/Context Menu/Tip")
case let .messageCopyProtection(isChannel):
self.text = isChannel ? self.presentationData.strings.Conversation_CopyProtectionInfoChannel : self.presentationData.strings.Conversation_CopyProtectionInfoGroup
self.targetSelectionIndex = nil
icon = UIImage(bundleImageName: "Chat/Context Menu/ReportCopyright")
}
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tip"), color: presentationData.theme.contextMenu.primaryColor)
self.iconNode.image = generateTintedImage(image: icon, color: presentationData.theme.contextMenu.primaryColor)
super.init()
@ -543,17 +550,17 @@ final class ContextActionsContainerNode: ASDisplayNode {
self.addSubnode(self.scrollNode)
}
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
var widthClass = widthClass
if !self.blurBackground {
widthClass = .regular
}
var contentSize = CGSize()
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, minimalWidth: nil, transition: transition)
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, constrainedHeight: constrainedHeight, minimalWidth: nil, transition: transition)
if let additionalActionsNode = self.additionalActionsNode, let additionalShadowNode = self.additionalShadowNode {
let additionalActionsSize = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsSize.width, minimalWidth: actionsSize.width, transition: transition)
let additionalActionsSize = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsSize.width, constrainedHeight: constrainedHeight, minimalWidth: actionsSize.width, transition: transition)
contentSize = additionalActionsSize
let bounds = CGRect(origin: CGPoint(), size: additionalActionsSize)

View File

@ -4,8 +4,10 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
import TextSelectionNode
import ReactionSelectionNode
import TelegramCore
import SwiftSignalKit
import AccountContext
private let animationDurationFactor: Double = 1.0
@ -142,7 +144,7 @@ public final class ContextMenuActionItem {
}
public protocol ContextMenuCustomNode: ASDisplayNode {
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void)
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void)
func updateTheme(presentationData: PresentationData)
}
@ -201,11 +203,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
private var contentAreaInScreenSpace: CGRect?
private let contentContainerNode: ContextContentContainerNode
private var actionsContainerNode: ContextActionsContainerNode
private var reactionContextNode: ReactionContextNode?
private var reactionContextNodeIsAnimatingOut = false
private var didCompleteAnimationIn = false
private var initialContinueGesturePoint: CGPoint?
private var didMoveFromInitialGesturePoint = false
private var highlightedActionNode: ContextActionNodeProtocol?
private var highlightedReaction: ReactionContextItem.Reaction?
private let hapticFeedback = HapticFeedback()
@ -216,7 +221,18 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
private let blurBackground: Bool
init(account: Account, controller: ContextController, presentationData: PresentationData, source: ContextContentSource, items: Signal<ContextController.Items, NoError>, beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, gesture: ContextGesture?, beganAnimatingOut: @escaping () -> Void, attemptTransitionControllerIntoNavigation: @escaping () -> Void) {
init(
account: Account,
controller: ContextController,
presentationData: PresentationData,
source: ContextContentSource,
items: Signal<ContextController.Items, NoError>,
beginDismiss: @escaping (ContextMenuActionResult) -> Void,
recognizer: TapLongTapOrDoubleTapGestureRecognizer?,
gesture: ContextGesture?,
beganAnimatingOut: @escaping () -> Void,
attemptTransitionControllerIntoNavigation: @escaping () -> Void
) {
self.presentationData = presentationData
self.source = source
self.items = items
@ -653,6 +669,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
localContentSourceFrame = localSourceFrame
}
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateIn(from: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size))
}
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y + actionsOffset)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: actionsDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
let contentContainerOffset = CGPoint(x: localContentSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localContentSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY)
self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: contentDuration, initialVelocity: 0.0, damping: springDamping, additive: true, completion: { [weak self] _ in
@ -823,8 +843,13 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
updatedContentAreaInScreenSpace.size.width = self.bounds.width
self.clippingNode.view.mask = putBackInfo.maskView
self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: updatedContentAreaInScreenSpace, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
let previousFrame = self.clippingNode.frame
self.clippingNode.position = updatedContentAreaInScreenSpace.center
self.clippingNode.bounds = CGRect(origin: CGPoint(), size: updatedContentAreaInScreenSpace.size)
self.clippingNode.layer.animatePosition(from: previousFrame.center, to: updatedContentAreaInScreenSpace.center, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: true)
self.clippingNode.layer.animateBounds(from: CGRect(origin: CGPoint(), size: previousFrame.size), to: CGRect(origin: CGPoint(), size: updatedContentAreaInScreenSpace.size), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: true)
//self.clippingNode.layer.animateFrame(from: previousFrame, to: updatedContentAreaInScreenSpace, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
//self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
}
let intermediateCompletion: () -> Void = { [weak self, weak contentParentNode] in
@ -922,6 +947,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
contentParentNode.updateAbsoluteRect?(self.contentContainerNode.frame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y + contentContainerOffset.y), self.bounds.size)
contentParentNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: -contentContainerOffset.y), transitionCurve, transitionDuration)
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateOut(to: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size), animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
}
contentParentNode.willUpdateIsExtractedToContextPreview?(false, .animated(duration: 0.2, curve: .easeInOut))
} else {
if let snapshotView = contentParentNode.contentNode.view.snapshotContentTree(keepTransform: true) {
@ -940,6 +969,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
contentParentNode.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
contentParentNode.willUpdateIsExtractedToContextPreview?(false, .animated(duration: 0.2, curve: .easeInOut))
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
}
}
case let .controller(source):
guard let maybeContentNode = self.contentContainerNode.contentNode, case let .controller(controller) = maybeContentNode else {
@ -1078,9 +1111,53 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
completedContentNode = true
intermediateCompletion()
})
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
}
}
}
}
func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
if self.reactionContextNodeIsAnimatingOut, let reactionContextNode = self.reactionContextNode {
reactionContextNode.bounds = reactionContextNode.bounds.offsetBy(dx: 0.0, dy: offset.y)
transition.animateOffsetAdditive(node: reactionContextNode, offset: -offset.y)
}
}
func animateOutToReaction(value: String, targetEmptyNode: ASDisplayNode, targetFilledNode: ASDisplayNode, hideNode: Bool, completion: @escaping () -> Void) {
guard let reactionContextNode = self.reactionContextNode else {
self.animateOut(result: .default, completion: completion)
return
}
var contentCompleted = false
var reactionCompleted = false
let intermediateCompletion: () -> Void = {
if contentCompleted && reactionCompleted {
completion()
}
}
self.reactionContextNodeIsAnimatingOut = true
reactionContextNode.willAnimateOutToReaction(value: value)
reactionContextNode.animateOutToReaction(value: value, targetEmptyNode: targetEmptyNode, targetFilledNode: targetFilledNode, hideNode: hideNode, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.reactionContextNode?.removeFromSupernode()
strongSelf.reactionContextNode = nil
reactionCompleted = true
intermediateCompletion()
})
self.animateOut(result: .default, completion: {
contentCompleted = true
intermediateCompletion()
})
self.isUserInteractionEnabled = false
}
func getActionsMinHeight() -> ContextController.ActionsHeight? {
if !self.actionsContainerNode.bounds.height.isZero {
@ -1111,6 +1188,24 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.currentItems = items
self.currentActionsMinHeight = minHeight
if let reactionContextNode = self.reactionContextNode {
self.reactionContextNode = nil
reactionContextNode.removeFromSupernode()
}
if !items.reactionItems.isEmpty, let context = items.context {
let reactionContextNode = ReactionContextNode(context: context, theme: self.presentationData.theme, items: items.reactionItems)
self.reactionContextNode = reactionContextNode
self.addSubnode(reactionContextNode)
reactionContextNode.reactionSelected = { [weak self] reaction in
guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else {
return
}
controller.reactionSelected?(reaction)
}
}
let previousActionsContainerNode = self.actionsContainerNode
let previousActionsContainerFrame = previousActionsContainerNode.view.convert(previousActionsContainerNode.bounds, to: self.view)
@ -1202,7 +1297,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let actionsSideInset: CGFloat = layout.safeInsets.left + 11.0
let contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0)
var contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0)
if let _ = self.reactionContextNode {
contentTopInset += 34.0
}
let actionsBottomInset: CGFloat = 11.0
@ -1214,7 +1313,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition)
let adjustedActionsSize = realActionsSize
self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize)
@ -1232,12 +1331,19 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
var originalContentFrame = CGRect(origin: CGPoint(x: originalContentX, y: originalContentY), size: originalProjectedContentViewFrame.1.size)
let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0)
let bottomEdge = min(layout.size.height - layout.intrinsicInsets.bottom, self.contentAreaInScreenSpace?.maxY ?? layout.size.height)
if originalContentFrame.minY < topEdge {
let requiredOffset = topEdge - originalContentFrame.minY
let availableOffset = max(0.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - originalActionsFrame.maxY)
let offset = min(requiredOffset, availableOffset)
originalActionsFrame = originalActionsFrame.offsetBy(dx: 0.0, dy: offset)
originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset)
} else if originalActionsFrame.maxY > bottomEdge {
let requiredOffset = bottomEdge - originalActionsFrame.maxY
let offset = requiredOffset
originalActionsFrame = originalActionsFrame.offsetBy(dx: 0.0, dy: offset)
originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset)
}
var contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset)
@ -1294,7 +1400,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition)
let adjustedActionsSize = realActionsSize
self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize)
@ -1425,6 +1531,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y)
contentParentNode.updateAbsoluteRect?(absoluteContentRect, layout.size)
if let reactionContextNode = self.reactionContextNode {
let insets = layout.insets(options: [.statusBar])
transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size))
reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX + contentParentNode.contentRect.minX, y: absoluteContentRect.minY + contentParentNode.contentRect.minY), size: contentParentNode.contentRect.size), transition: transition)
}
}
case let .controller(contentParentNode):
var projectedFrame: CGRect = convertFrame(contentParentNode.sourceNode.bounds, from: contentParentNode.sourceNode.view, to: self.view)
@ -1453,7 +1565,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
constrainedWidth = floor(layout.size.width / 2.0)
}
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: constrainedWidth - actionsSideInset * 2.0, transition: actionsContainerTransition)
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: constrainedWidth - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition)
let contentScale = (constrainedWidth - actionsSideInset * 2.0) / constrainedWidth
var contentUnscaledSize: CGSize
if case .compact = layout.metrics.widthClass {
@ -1555,6 +1667,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
transition.animateOffsetAdditive(node: self.scrollNode, offset: currentContainerFrame.minY - previousContainerFrame.minY)
}
}
let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y)
if let reactionContextNode = self.reactionContextNode {
let insets = layout.insets(options: [.statusBar])
transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size))
reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX, y: absoluteContentRect.minY), size: contentSize), transition: transition)
}
}
}
}
@ -1639,6 +1759,16 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
if !self.bounds.contains(point) {
return nil
}
if !self.isUserInteractionEnabled {
return nil
}
if let reactionContextNode = self.reactionContextNode {
if let result = reactionContextNode.hitTest(self.view.convert(point, to: reactionContextNode.view), with: event) {
return result
}
}
let mappedPoint = self.view.convert(point, to: self.scrollNode.view)
var maybePassthrough: ContextController.HandledTouchEvent?
if let maybeContentNode = self.contentContainerNode.contentNode {
@ -1800,15 +1930,21 @@ public enum ContextContentSource {
public final class ContextController: ViewController, StandalonePresentableController, ContextControllerProtocol {
public struct Items {
public var items: [ContextMenuItem]
public var context: AccountContext?
public var reactionItems: [ReactionContextItem]
public var tip: Tip?
public init(items: [ContextMenuItem], tip: Tip? = nil) {
public init(items: [ContextMenuItem], context: AccountContext? = nil, reactionItems: [ReactionContextItem] = [], tip: Tip? = nil) {
self.items = items
self.context = context
self.reactionItems = reactionItems
self.tip = tip
}
public init() {
self.items = []
self.context = nil
self.reactionItems = []
self.tip = nil
}
}
@ -1821,6 +1957,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
public enum Tip {
case textSelection
case messageViewsPrivacy
case messageCopyProtection(isChannel: Bool)
}
public final class ActionsHeight {
@ -1872,6 +2009,8 @@ public final class ContextController: ViewController, StandalonePresentableContr
private var shouldBeDismissedDisposable: Disposable?
public var reactionSelected: ((ReactionContextItem) -> Void)?
public init(account: Account, presentationData: PresentationData, source: ContextContentSource, items: Signal<ContextController.Items, NoError>, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil) {
self.account = account
self.presentationData = presentationData
@ -2014,4 +2153,19 @@ public final class ContextController: ViewController, StandalonePresentableContr
override public func dismiss(completion: (() -> Void)? = nil) {
self.dismiss(result: .default, completion: completion)
}
public func dismissWithReaction(value: String, targetEmptyNode: ASDisplayNode, targetFilledNode: ASDisplayNode, hideNode: Bool, completion: (() -> Void)?) {
if !self.wasDismissed {
self.wasDismissed = true
self.controllerNode.animateOutToReaction(value: value, targetEmptyNode: targetEmptyNode, targetFilledNode: targetFilledNode, hideNode: hideNode, completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
completion?()
})
self.dismissed?()
}
}
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
self.controllerNode.addRelativeContentOffset(offset, transition: transition)
}
}

View File

@ -158,7 +158,7 @@ final class PeekControllerNode: ViewControllerTracingNode {
}
let actionsSideInset: CGFloat = layout.safeInsets.left + 11.0
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: .immediate)
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: .immediate)
let containerFrame: CGRect
let actionsFrame: CGRect

View File

@ -0,0 +1,32 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Crc32",
platforms: [.macOS(.v10_11)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "Crc32",
targets: ["Crc32"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "Crc32",
dependencies: [],
path: ".",
exclude: ["BUILD"],
publicHeadersPath: "PublicHeaders",
cSettings: [
.headerSearchPath("PublicHeaders")
]),
]
)

View File

@ -0,0 +1,32 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "CryptoUtils",
platforms: [.macOS(.v10_11)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "CryptoUtils",
targets: ["CryptoUtils"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "CryptoUtils",
dependencies: [],
path: ".",
exclude: ["BUILD"],
publicHeadersPath: "PublicHeaders",
cSettings: [
.headerSearchPath("PublicHeaders")
]),
]
)

View File

@ -1,5 +1,6 @@
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
private let containerInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
@ -59,6 +60,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.bottomDimView.isUserInteractionEnabled = false
self.itemGroupsContainerNode = ActionSheetItemGroupsContainerNode(theme: self.theme)
self.itemGroupsContainerNode.isUserInteractionEnabled = false
super.init()
@ -128,6 +130,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.updateScrollDimViews(size: layout.size, insets: insets, transition: transition)
}
func animateIn(completion: @escaping () -> Void) {
let tempDimView = UIView()
tempDimView.backgroundColor = self.theme.dimColor
@ -144,6 +147,10 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
tempDimView?.removeFromSuperview()
completion()
})
Queue.mainQueue().after(0.3, {
self.itemGroupsContainerNode.isUserInteractionEnabled = true
})
}
func animateOut(cancelled: Bool) {
@ -170,7 +177,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
@objc func dimNodeTap(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if case .ended = recognizer.state, self.itemGroupsContainerNode.isUserInteractionEnabled {
self.view.window?.endEditing(true)
self.animateOut(cancelled: true)
}

View File

@ -84,6 +84,9 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode {
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard self.isUserInteractionEnabled else {
return nil
}
for groupNode in self.groupNodes {
if groupNode.frame.contains(point) {
return groupNode.hitTest(self.convert(point, to: groupNode), with: event)

View File

@ -147,15 +147,19 @@ public extension CALayer {
self.add(animation, forKey: additive ? nil : keyPath)
}
func animateGroup(_ animations: [CAAnimation], key: String) {
func animateGroup(_ animations: [CAAnimation], key: String, completion: ((Bool) -> Void)? = nil) {
let animationGroup = CAAnimationGroup()
var timeOffset = 0.0
for animation in animations {
animation.beginTime = animation.beginTime + timeOffset
animation.beginTime = self.convertTime(animation.beginTime, from: nil) + timeOffset
timeOffset += animation.duration / Double(animation.speed)
}
animationGroup.animations = animations
animationGroup.duration = timeOffset
if let completion = completion {
animationGroup.delegate = CALayerAnimationDelegate(animation: animationGroup, completion: completion)
}
self.add(animationGroup, forKey: key)
}
@ -194,6 +198,35 @@ public extension CALayer {
self.add(animation, forKey: keyPath)
}
func springAnimation(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double = 0.0, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false) -> CABasicAnimation {
let animation: CABasicAnimation
if #available(iOS 9.0, *) {
animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping)
} else {
animation = makeSpringAnimation(keyPath)
}
animation.fromValue = from
animation.toValue = to
animation.isRemovedOnCompletion = removeOnCompletion
animation.fillMode = .forwards
let k = Float(UIView.animationDurationFactor())
var speed: Float = 1.0
if k != 0 && k != 1 {
speed = Float(1.0) / k
}
if !delay.isZero {
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
animation.fillMode = .both
}
animation.speed = speed * Float(animation.duration / duration)
animation.isAdditive = additive
return animation
}
func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double = 0.0, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
let animation: CABasicAnimation
@ -217,7 +250,7 @@ public extension CALayer {
}
if !delay.isZero {
animation.beginTime = CACurrentMediaTime() + delay * UIView.animationDurationFactor()
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
animation.fillMode = .both
}

View File

@ -389,6 +389,17 @@ public extension ContainedViewLayoutTransition {
}
}
func animatePositionWithKeyframes(node: ASDisplayNode, keyframes: [AnyObject], removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
completion?(true)
case let .animated(duration, curve):
node.layer.animateKeyframes(values: keyframes, duration: duration, keyPath: "position", timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { value in
completion?(value)
})
}
}
func animateFrame(node: ASDisplayNode, from frame: CGRect, to toFrame: CGRect? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:

View File

@ -17,6 +17,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
case iPhone12
case iPhone12ProMax
case iPad
case iPadMini
case iPad102Inch
case iPadPro10Inch
case iPadPro11Inch
@ -37,6 +38,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
.iPhone12,
.iPhone12ProMax,
.iPad,
.iPadMini,
.iPad102Inch,
.iPadPro10Inch,
.iPadPro11Inch,
@ -111,6 +113,8 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return CGSize(width: 428.0, height: 926.0)
case .iPad:
return CGSize(width: 768.0, height: 1024.0)
case .iPadMini:
return CGSize(width: 744.0, height: 1133.0)
case .iPad102Inch:
return CGSize(width: 810.0, height: 1080.0)
case .iPadPro10Inch:
@ -162,7 +166,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return inLandscape ? 21.0 : 34.0
case .iPadPro3rdGen, .iPadPro11Inch:
return 21.0
case .iPad, .iPadPro, .iPadPro10Inch:
case .iPad, .iPadPro, .iPadPro10Inch, .iPadMini:
if let systemOnScreenNavigationHeight = systemOnScreenNavigationHeight, !systemOnScreenNavigationHeight.isZero {
return 21.0
} else {
@ -192,7 +196,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
switch self {
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax:
return 44.0
case .iPadPro11Inch, .iPadPro3rdGen:
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini:
return 24.0
case let .unknown(_, statusBarHeight, _):
return statusBarHeight
@ -212,7 +216,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return 172.0
case .iPad, .iPad102Inch, .iPadPro10Inch:
return 348.0
case .iPadPro11Inch:
case .iPadPro11Inch, .iPadMini:
return 368.0
case .iPadPro:
return 421.0
@ -235,7 +239,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return 263.0
case .iPadPro11Inch:
return 283.0
case .iPadPro:
case .iPadPro, .iPadMini:
return 328.0
case .iPadPro3rdGen:
return 348.0
@ -250,7 +254,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
switch self {
case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax:
return 37.0
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen:
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini:
return 50.0
case .unknown:
return 37.0
@ -263,7 +267,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return 44.0
case .iPhone6Plus:
return 45.0
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen:
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini:
return 50.0
case .unknown:
return 44.0

View File

@ -2,7 +2,7 @@ import Foundation
import UIKit
import AsyncDisplayKit
public class EditableTextNode: ASEditableTextNode {
open class EditableTextNode: ASEditableTextNode {
override public var keyboardAppearance: UIKeyboardAppearance {
get {
return super.keyboardAppearance

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import Accelerate
import AsyncDisplayKit
import CoreMedia
public let deviceColorSpace: CGColorSpace = {
if #available(iOSApplicationExtension 9.3, iOS 9.3, *) {
@ -581,6 +582,30 @@ public class DrawingContext {
return nil
}
}
public func generatePixelBuffer() -> CVPixelBuffer? {
if self.scaledSize.width.isZero || self.scaledSize.height.isZero {
return nil
}
if self.hasGeneratedImage {
preconditionFailure()
}
let ioSurfaceProperties = NSMutableDictionary()
let options = NSMutableDictionary()
options.setObject(ioSurfaceProperties, forKey: kCVPixelBufferIOSurfacePropertiesKey as NSString)
var pixelBuffer: CVPixelBuffer?
CVPixelBufferCreateWithBytes(nil, Int(self.scaledSize.width), Int(self.scaledSize.height), kCVPixelFormatType_32BGRA, self.bytes, self.bytesPerRow, { pointer, _ in
if let pointer = pointer {
Unmanaged<ASCGImageBuffer>.fromOpaque(pointer).release()
}
}, Unmanaged.passRetained(self.imageBuffer).toOpaque(), options as CFDictionary, &pixelBuffer)
self.hasGeneratedImage = true
return pixelBuffer
}
public func colorAt(_ point: CGPoint) -> UIColor {
let x = Int(point.x * self.scale)
@ -649,6 +674,76 @@ public class DrawingContext {
}
}
public extension UIImage {
var cvPixelBuffer: CVPixelBuffer? {
guard let cgImage = self.cgImage else {
return nil
}
let _ = cgImage
var maybePixelBuffer: CVPixelBuffer? = nil
let ioSurfaceProperties = NSMutableDictionary()
let options = NSMutableDictionary()
options.setObject(ioSurfaceProperties, forKey: kCVPixelBufferIOSurfacePropertiesKey as NSString)
let _ = CVPixelBufferCreate(kCFAllocatorDefault, Int(size.width * self.scale), Int(size.height * self.scale), kCVPixelFormatType_32ARGB, options as CFDictionary, &maybePixelBuffer)
guard let pixelBuffer = maybePixelBuffer else {
return nil
}
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
defer {
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
}
let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)
let context = CGContext(
data: baseAddress,
width: Int(self.size.width * self.scale),
height: Int(self.size.height * self.scale),
bitsPerComponent: 8,
bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue,
releaseCallback: nil,
releaseInfo: nil
)!
context.clear(CGRect(origin: .zero, size: CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale)))
context.draw(cgImage, in: CGRect(origin: .zero, size: CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale)))
return pixelBuffer
}
var cmSampleBuffer: CMSampleBuffer? {
guard let pixelBuffer = self.cvPixelBuffer else {
return nil
}
var newSampleBuffer: CMSampleBuffer? = nil
var timingInfo = CMSampleTimingInfo(
duration: CMTimeMake(value: 1, timescale: 30),
presentationTimeStamp: CMTimeMake(value: 0, timescale: 30),
decodeTimeStamp: CMTimeMake(value: 0, timescale: 30)
)
var videoInfo: CMVideoFormatDescription? = nil
CMVideoFormatDescriptionCreateForImageBuffer(allocator: nil, imageBuffer: pixelBuffer, formatDescriptionOut: &videoInfo)
guard let videoInfo = videoInfo else {
return nil
}
CMSampleBufferCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: videoInfo, sampleTiming: &timingInfo, sampleBufferOut: &newSampleBuffer)
if let newSampleBuffer = newSampleBuffer {
let attachments = CMSampleBufferGetSampleAttachmentsArray(newSampleBuffer, createIfNecessary: true)! as NSArray
let dict = attachments[0] as! NSMutableDictionary
dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DisplayImmediately as NSString as String)
}
return newSampleBuffer
}
}
public enum ParsingError: Error {
case Generic
}

View File

@ -89,7 +89,7 @@ open class HighlightableButtonNode: HighlightTrackingButtonNode {
super.init(pointerStyle: pointerStyle)
self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if let strongSelf = self, !strongSelf.isImplicitlyDisabled {
if highlighted {
strongSelf.layer.removeAnimation(forKey: "opacity")
strongSelf.alpha = 0.4

View File

@ -150,6 +150,41 @@ private func cancelContextGestures(view: UIView) {
}
open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGestureRecognizerDelegate {
public struct ScrollingIndicatorState {
public struct Item {
public var index: Int
public var offset: CGFloat
public var height: CGFloat
public init(
index: Int,
offset: CGFloat,
height: CGFloat
) {
self.index = index
self.offset = offset
self.height = height
}
}
public var insets: UIEdgeInsets
public var topItem: Item
public var bottomItem: Item
public var itemCount: Int
public init(
insets: UIEdgeInsets,
topItem: Item,
bottomItem: Item,
itemCount: Int
) {
self.insets = insets
self.topItem = topItem
self.bottomItem = bottomItem
self.itemCount = itemCount
}
}
public final let scroller: ListViewScroller
public private(set) final var visibleSize: CGSize = CGSize()
public private(set) final var insets = UIEdgeInsets()
@ -214,14 +249,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
public final var snapToBottomInsetUntilFirstInteraction: Bool = false
public final var updateFloatingHeaderOffset: ((CGFloat, ContainedViewLayoutTransition) -> Void)? {
didSet {
}
}
public final var updateFloatingHeaderOffset: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
public final var didScrollWithOffset: ((CGFloat, ContainedViewLayoutTransition, ListViewItemNode?) -> Void)?
public final var addContentOffset: ((CGFloat, ListViewItemNode?) -> Void)?
public final var updateScrollingIndicator: ((ScrollingIndicatorState?, ContainedViewLayoutTransition) -> Void)?
private var topItemOverscrollBackground: ListViewOverscrollBackgroundNode?
private var bottomItemOverscrollBackground: ASDisplayNode?
@ -252,6 +284,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
public private(set) var isTracking = false
public private(set) var trackingOffset: CGFloat = 0.0
public private(set) var beganTrackingAtTopOrigin = false
public private(set) var isDragging = false
public private(set) var isDeceleratingAfterTracking = false
private final var transactionQueue: ListViewTransactionQueue
@ -738,6 +771,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.snapToBottomInsetUntilFirstInteraction = false
}
self.scrolledToItem = nil
self.isDragging = true
self.beganInteractiveDragging(self.touchesPosition)
@ -749,6 +784,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
self.isDragging = false
if decelerate {
self.lastContentOffsetTimestamp = CACurrentMediaTime()
self.isDeceleratingAfterTracking = true
@ -3766,33 +3802,51 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
break
}
}
if let topIndexAndBoundary = topIndexAndBoundary, let bottomIndexAndBoundary = bottomIndexAndBoundary {
var scrollingIndicatorStateValue: ScrollingIndicatorState?
if let topIndexAndBoundaryValue = topIndexAndBoundary, let bottomIndexAndBoundaryValue = bottomIndexAndBoundary {
let scrollingIndicatorState = ScrollingIndicatorState(
insets: self.insets,
topItem: ScrollingIndicatorState.Item(
index: topIndexAndBoundaryValue.0,
offset: topIndexAndBoundaryValue.1,
height: topIndexAndBoundaryValue.2
),
bottomItem: ScrollingIndicatorState.Item(
index: bottomIndexAndBoundaryValue.0,
offset: bottomIndexAndBoundaryValue.1,
height: bottomIndexAndBoundaryValue.2
),
itemCount: self.items.count
)
scrollingIndicatorStateValue = scrollingIndicatorState
let averageRangeItemHeight: CGFloat = 44.0
var upperItemsHeight = floor(averageRangeItemHeight * CGFloat(topIndexAndBoundary.0))
var approximateContentHeight = CGFloat(self.items.count) * averageRangeItemHeight
if topIndexAndBoundary.0 >= 0 && self.items[topIndexAndBoundary.0].approximateHeight.isZero {
var upperItemsHeight = floor(averageRangeItemHeight * CGFloat(scrollingIndicatorState.topItem.index))
var approximateContentHeight = CGFloat(scrollingIndicatorState.itemCount) * averageRangeItemHeight
if scrollingIndicatorState.topItem.index >= 0 && self.items[scrollingIndicatorState.topItem.index].approximateHeight.isZero {
upperItemsHeight -= averageRangeItemHeight
approximateContentHeight -= averageRangeItemHeight
}
var convertedTopBoundary: CGFloat
if topIndexAndBoundary.1 < self.insets.top {
convertedTopBoundary = (topIndexAndBoundary.1 - self.insets.top) * averageRangeItemHeight / topIndexAndBoundary.2
if scrollingIndicatorState.topItem.offset < self.insets.top {
convertedTopBoundary = (scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top) * averageRangeItemHeight / scrollingIndicatorState.topItem.height
} else {
convertedTopBoundary = topIndexAndBoundary.1 - self.insets.top
convertedTopBoundary = scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top
}
convertedTopBoundary -= upperItemsHeight
let approximateOffset = -convertedTopBoundary
var convertedBottomBoundary: CGFloat = 0.0
if bottomIndexAndBoundary.1 > self.visibleSize.height - self.insets.bottom {
convertedBottomBoundary = ((self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1) * averageRangeItemHeight / bottomIndexAndBoundary.2
if scrollingIndicatorState.bottomItem.offset > self.visibleSize.height - self.insets.bottom {
convertedBottomBoundary = ((self.visibleSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset) * averageRangeItemHeight / scrollingIndicatorState.bottomItem.height
} else {
convertedBottomBoundary = (self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1
convertedBottomBoundary = (self.visibleSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset
}
convertedBottomBoundary += CGFloat(bottomIndexAndBoundary.0 + 1) * averageRangeItemHeight
convertedBottomBoundary += CGFloat(scrollingIndicatorState.bottomItem.index + 1) * averageRangeItemHeight
let approximateVisibleHeight = max(0.0, convertedBottomBoundary - approximateOffset)
@ -3801,8 +3855,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let indicatorSideInset: CGFloat = 3.0
var indicatorTopInset: CGFloat = 3.0
if self.verticalScrollIndicatorFollowsOverscroll {
if topIndexAndBoundary.0 == 0 {
indicatorTopInset = max(topIndexAndBoundary.1 + 3.0 - self.insets.top, 3.0)
if scrollingIndicatorState.topItem.index == 0 {
indicatorTopInset = max(scrollingIndicatorState.topItem.offset + 3.0 - self.insets.top, 3.0)
}
}
let indicatorBottomInset: CGFloat = 3.0
@ -3814,7 +3868,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
if approximateContentHeight <= 0 {
indicatorHeight = 0.0
} else {
indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - self.insets.top - self.insets.bottom) / approximateContentHeight))
indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - scrollingIndicatorState.insets.top - scrollingIndicatorState.insets.bottom) / approximateContentHeight))
}
let upperBound = self.scrollIndicatorInsets.top + indicatorTopInset
@ -3852,6 +3906,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} else {
verticalScrollIndicator.isHidden = true
}
self.updateScrollingIndicator?(scrollingIndicatorStateValue, transition)
}
}
@ -4638,7 +4694,3 @@ private func findAccessibilityFocus(_ node: ASDisplayNode) -> Bool {
}
return false
}
public func randomfqweeqwf() {
print("t")
}

View File

@ -6,6 +6,12 @@ private var backArrowImageCache: [Int32: UIImage] = [:]
public final class SparseNode: ASDisplayNode {
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.alpha.isZero {
return nil
}
if !self.bounds.contains(point) {
return nil
}
for view in self.view.subviews {
if let result = view.hitTest(self.view.convert(point, to: view), with: event), result.isUserInteractionEnabled {
return result

View File

@ -298,8 +298,9 @@ open class ASButtonNode: ASControlNode {
override open var isHighlighted: Bool {
didSet {
if self.isHighlighted != oldValue {
if self.isHighlighted {
if self.isHighlighted != oldValue && !self.isImplicitlyDisabled {
let isHighlighted = self.isHighlighted
if isHighlighted {
if self.highlightedTitleNode.attributedText != nil {
self.highlightedTitleNode.isHidden = false
self.titleNode.isHidden = true
@ -349,28 +350,42 @@ open class ASButtonNode: ASControlNode {
}
}
open var isImplicitlyDisabled: Bool = false {
didSet {
if self.isImplicitlyDisabled != oldValue {
self.updateIsEnabled()
}
}
}
override open var isEnabled: Bool {
didSet {
if self.isEnabled != oldValue {
if self.isEnabled || self.disabledTitleNode.attributedText == nil {
self.titleNode.isHidden = false
self.disabledTitleNode.isHidden = true
} else {
self.titleNode.isHidden = true
self.disabledTitleNode.isHidden = false
}
if self.isEnabled || self.disabledImageNode.image == nil {
self.imageNode.isHidden = false
self.disabledImageNode.isHidden = true
} else {
self.imageNode.isHidden = true
self.disabledImageNode.isHidden = false
}
self.updateIsEnabled()
}
}
}
private func updateIsEnabled() {
let isEnabled = self.isEnabled && !self.isImplicitlyDisabled
if isEnabled || self.disabledTitleNode.attributedText == nil {
self.titleNode.isHidden = false
self.disabledTitleNode.isHidden = true
} else {
self.titleNode.isHidden = true
self.disabledTitleNode.isHidden = false
}
if isEnabled || self.disabledImageNode.image == nil {
self.imageNode.isHidden = false
self.disabledImageNode.isHidden = true
} else {
self.imageNode.isHidden = true
self.disabledImageNode.isHidden = false
}
}
override open func layout() {
let size = self.bounds.size

View File

@ -564,8 +564,16 @@ public final class TextNodeLayout: NSObject {
rightOffset = ceil(secondaryOffset)
}
}
var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size)
lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout)
switch self.resolvedAlignment {
case .center:
lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0)
case .natural:
lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout)
default:
break
}
let rect = CGRect(origin: CGPoint(x: lineFrame.minX + min(leftOffset, rightOffset) + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: abs(rightOffset - leftOffset), height: lineFrame.size.height))
if coveringRect.isEmpty {

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
import AVFoundation
public struct TransformImageNodeContentAnimations: OptionSet {
public var rawValue: Int32
@ -21,10 +22,69 @@ open class TransformImageNode: ASDisplayNode {
private var currentTransform: ((TransformImageArguments) -> DrawingContext?)?
private var currentArguments: TransformImageArguments?
public private(set) var image: UIImage?
private var argumentsPromise = ValuePromise<TransformImageArguments>(ignoreRepeated: true)
private var overlayColor: UIColor?
private var overlayNode: ASDisplayNode?
private var captureProtectedContentLayer: CaptureProtectedContentLayer?
public var captureProtected: Bool = false {
didSet {
if self.captureProtected != oldValue {
if self.captureProtected {
if self.captureProtectedContentLayer == nil {
let captureProtectedContentLayer = CaptureProtectedContentLayer()
self.captureProtectedContentLayer = captureProtectedContentLayer
if #available(iOS 13.0, *) {
captureProtectedContentLayer.preventsCapture = true
captureProtectedContentLayer.preventsDisplaySleepDuringVideoPlayback = false
}
captureProtectedContentLayer.frame = self.bounds
self.layer.addSublayer(captureProtectedContentLayer)
var hasImage = false
if let image = self.image {
hasImage = true
if let cmSampleBuffer = image.cmSampleBuffer {
captureProtectedContentLayer.enqueue(cmSampleBuffer)
}
}
if hasImage {
Queue.mainQueue().after(0.1) {
self.contents = nil
}
} else {
self.contents = nil
}
}
} else if let captureProtectedContentLayer = self.captureProtectedContentLayer {
self.captureProtectedContentLayer = nil
captureProtectedContentLayer.removeFromSuperlayer()
self.contents = self.image?.cgImage
}
}
}
}
open override var bounds: CGRect {
didSet {
if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size {
captureProtectedContentLayer.frame = super.bounds
}
}
}
open override var frame: CGRect {
didSet {
if let overlayNode = self.overlayNode {
overlayNode.frame = self.bounds
}
if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size {
captureProtectedContentLayer.frame = super.bounds
}
}
}
deinit {
self.disposable.dispose()
@ -38,19 +98,12 @@ open class TransformImageNode: ASDisplayNode {
}
}
override open var frame: CGRect {
didSet {
if let overlayNode = self.overlayNode {
overlayNode.frame = self.bounds
}
}
}
public func reset() {
self.disposable.set(nil)
self.currentArguments = nil
self.currentTransform = nil
self.contents = nil
self.image = nil
}
public func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, attemptSynchronously: Bool = false, dispatchOnDisplayLink: Bool = true) {
@ -85,21 +138,31 @@ open class TransformImageNode: ASDisplayNode {
strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
} else if strongSelf.contentAnimations.contains(.subsequentUpdates) {
let tempLayer = CALayer()
tempLayer.frame = strongSelf.bounds
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
tempLayer.contents = strongSelf.contents
strongSelf.layer.addSublayer(tempLayer)
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
tempLayer?.removeFromSuperlayer()
})
if let _ = strongSelf.captureProtectedContentLayer {
} else {
let tempLayer = CALayer()
tempLayer.frame = strongSelf.bounds
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
tempLayer.contents = strongSelf.contents
strongSelf.layer.addSublayer(tempLayer)
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
tempLayer?.removeFromSuperlayer()
})
}
}
var imageUpdate: UIImage?
if let (transform, arguments, image) = next {
strongSelf.currentTransform = transform
strongSelf.currentArguments = arguments
strongSelf.contents = image?.cgImage
if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer {
if let cmSampleBuffer = image?.cmSampleBuffer {
captureProtectedContentLayer.enqueue(cmSampleBuffer)
}
} else {
strongSelf.contents = image?.cgImage
}
strongSelf.image = image
imageUpdate = image
}
if let _ = strongSelf.overlayColor {
@ -135,7 +198,14 @@ open class TransformImageNode: ASDisplayNode {
return
}
if let image = updatedImage {
strongSelf.contents = image.cgImage
if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer {
if let cmSampleBuffer = image.cmSampleBuffer {
captureProtectedContentLayer.enqueue(cmSampleBuffer)
}
} else {
strongSelf.contents = image.cgImage
}
strongSelf.image = image
strongSelf.currentArguments = arguments
if let _ = strongSelf.overlayColor {
strongSelf.applyOverlayColor(animated: false)
@ -207,6 +277,19 @@ open class TransformImageNode: ASDisplayNode {
}
}
private final class NullActionClass: NSObject, CAAction {
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
}
}
private let nullAction = NullActionClass()
private class CaptureProtectedContentLayer: AVSampleBufferDisplayLayer {
override func action(forKey event: String) -> CAAction? {
return nullAction
}
}
open class TransformImageView: UIView {
public var imageUpdated: ((UIImage?) -> Void)?
public var contentAnimations: TransformImageNodeContentAnimations = []
@ -215,9 +298,55 @@ open class TransformImageView: UIView {
private var currentTransform: ((TransformImageArguments) -> DrawingContext?)?
private var currentArguments: TransformImageArguments?
private var argumentsPromise = ValuePromise<TransformImageArguments>(ignoreRepeated: true)
public private(set) var image: UIImage?
private var captureProtectedContentLayer: CaptureProtectedContentLayer?
private var overlayColor: UIColor?
private var overlayView: UIView?
open override var bounds: CGRect {
didSet {
if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size {
captureProtectedContentLayer.frame = super.bounds
}
}
}
open override var frame: CGRect {
didSet {
if let overlayView = self.overlayView {
overlayView.frame = self.bounds
}
if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size {
captureProtectedContentLayer.frame = super.bounds
}
}
}
public var captureProtected: Bool = false {
didSet {
if self.captureProtected != oldValue {
if self.captureProtected {
if self.captureProtectedContentLayer == nil {
let captureProtectedContentLayer = CaptureProtectedContentLayer()
captureProtectedContentLayer.frame = self.bounds
self.layer.addSublayer(captureProtectedContentLayer)
if let image = self.image {
if let cmSampleBuffer = image.cmSampleBuffer {
captureProtectedContentLayer.enqueue(cmSampleBuffer)
}
}
self.layer.contents = nil
}
} else if let captureProtectedContentLayer = self.captureProtectedContentLayer {
self.captureProtectedContentLayer = nil
captureProtectedContentLayer.removeFromSuperlayer()
self.layer.contents = self.image?.cgImage
}
}
}
}
override public init(frame: CGRect) {
super.init(frame: frame)
@ -235,19 +364,13 @@ open class TransformImageView: UIView {
self.disposable.dispose()
}
override open var frame: CGRect {
didSet {
if let overlayView = self.overlayView {
overlayView.frame = self.bounds
}
}
}
public func reset() {
self.disposable.set(nil)
self.currentArguments = nil
self.currentTransform = nil
self.layer.contents = nil
self.image = nil
self.captureProtectedContentLayer?.flushAndRemoveImage()
}
public func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, attemptSynchronously: Bool = false, dispatchOnDisplayLink: Bool = true) {
@ -277,26 +400,36 @@ open class TransformImageView: UIView {
self.disposable.set((result |> deliverOnMainQueue).start(next: { [weak self] next in
let apply: () -> Void = {
if let strongSelf = self {
if strongSelf.layer.contents == nil {
if strongSelf.image == nil {
if strongSelf.contentAnimations.contains(.firstUpdate) && !attemptSynchronously {
strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
} else if strongSelf.contentAnimations.contains(.subsequentUpdates) {
let tempLayer = CALayer()
tempLayer.frame = strongSelf.bounds
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
tempLayer.contents = strongSelf.layer.contents
strongSelf.layer.addSublayer(tempLayer)
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
tempLayer?.removeFromSuperlayer()
})
if let _ = strongSelf.captureProtectedContentLayer {
} else {
let tempLayer = CALayer()
tempLayer.frame = strongSelf.bounds
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
tempLayer.contents = strongSelf.layer.contents
strongSelf.layer.addSublayer(tempLayer)
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
tempLayer?.removeFromSuperlayer()
})
}
}
var imageUpdate: UIImage?
if let (transform, arguments, image) = next {
strongSelf.currentTransform = transform
strongSelf.currentArguments = arguments
strongSelf.layer.contents = image?.cgImage
if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer {
if let cmSampleBuffer = image?.cmSampleBuffer {
captureProtectedContentLayer.enqueue(cmSampleBuffer)
}
} else {
strongSelf.layer.contents = image?.cgImage
}
strongSelf.image = image
imageUpdate = image
}
if let _ = strongSelf.overlayColor {
@ -369,13 +502,13 @@ open class TransformImageView: UIView {
private func applyOverlayColor(animated: Bool) {
if let overlayColor = self.overlayColor {
if let contents = self.layer.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID {
if let image = self.image {
if let overlayView = self.overlayView {
(overlayView as! UIImageView).image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
(overlayView as! UIImageView).image = UIImage(cgImage: image.cgImage!).withRenderingMode(.alwaysTemplate)
overlayView.tintColor = overlayColor
} else {
let overlayView = UIImageView()
overlayView.image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
overlayView.image = UIImage(cgImage: image.cgImage!).withRenderingMode(.alwaysTemplate)
overlayView.tintColor = overlayColor
overlayView.frame = self.bounds
self.addSubview(overlayView)

View File

@ -1,5 +1,6 @@
import UIKit
import UIKitRuntimeUtils
import AVFoundation
public extension UIView {
static func animationDurationFactor() -> Double {
@ -399,6 +400,9 @@ public extension UIImage {
}
private func makeSubtreeSnapshot(layer: CALayer, keepTransform: Bool = false) -> UIView? {
if layer is AVSampleBufferDisplayLayer {
return nil
}
let view = UIView()
view.layer.isHidden = layer.isHidden
view.layer.opacity = layer.opacity
@ -441,7 +445,7 @@ private func makeSubtreeSnapshot(layer: CALayer, keepTransform: Bool = false) ->
}
view.addSubview(subtree)
} else {
return nil
continue
}
}
}
@ -449,6 +453,9 @@ private func makeSubtreeSnapshot(layer: CALayer, keepTransform: Bool = false) ->
}
private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? {
if layer is AVSampleBufferDisplayLayer {
return nil
}
let view = CALayer()
view.isHidden = layer.isHidden
view.opacity = layer.opacity

View File

@ -0,0 +1,32 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "EncryptionProvider",
platforms: [.macOS(.v10_11)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "EncryptionProvider",
targets: ["EncryptionProvider"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "EncryptionProvider",
dependencies: [],
path: ".",
exclude: ["BUILD"],
publicHeadersPath: "PublicHeaders",
cSettings: [
.headerSearchPath("PublicHeaders"),
]),
]
)

View File

@ -0,0 +1 @@
#import <EncryptionProvider/EncryptionProvider.h>

View File

@ -0,0 +1,33 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "FFMpegBinding",
platforms: [.macOS(.v10_11)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "FFMpegBinding",
targets: ["FFMpegBinding"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "FFMpegBinding",
dependencies: [],
path: ".",
exclude: ["BUILD"],
publicHeadersPath: "Public",
cSettings: [
.headerSearchPath("Public"),
.unsafeFlags(["-I../../../../core-xprojects/ffmpeg/build/ffmpeg/include"])
]),
]
)

View File

@ -90,6 +90,11 @@ public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galle
}
public func chatMessageGalleryControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, source: GalleryControllerItemSource?, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? {
var standalone = standalone
if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.namespace != Namespaces.Message.Cloud {
standalone = true
}
var galleryMedia: Media?
var otherMedia: Media?
var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])?

View File

@ -634,6 +634,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
canEdit = false
}
if message.isCopyProtected() {
canShare = false
canEdit = false
}
var authorNameText: String?
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature {
@ -1192,7 +1196,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
shareController.actionCompleted = { [weak self] in
if let strongSelf = self, let actionCompletionText = actionCompletionText {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .mediaSaved(text: actionCompletionText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
strongSelf.controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .mediaSaved(text: actionCompletionText), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return true }), nil)
}
}
shareController.completed = { [weak self] peerIds in

View File

@ -154,10 +154,10 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
if file.isVideo {
let content: UniversalVideoContent
if file.isAnimated {
content = NativeVideoContent(id: .message(message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), loopVideo: true, enableSound: false, tempFilePath: tempFilePath)
content = NativeVideoContent(id: .message(message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), loopVideo: true, enableSound: false, tempFilePath: tempFilePath, captureProtected: message.isCopyProtected())
} else {
if true || (file.mimeType == "video/mpeg4" || file.mimeType == "video/mov" || file.mimeType == "video/mp4") {
content = NativeVideoContent(id: .message(message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath)
content = NativeVideoContent(id: .message(message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath, captureProtected: message.isCopyProtected())
} else {
content = PlatformVideoContent(id: .message(message.id, message.stableId, file.fileId), content: .file(.message(message: MessageReference(message), media: file)), streamVideo: streamVideos, loopVideo: loopVideos)
}
@ -202,11 +202,11 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
var content: UniversalVideoContent?
switch websiteType(of: webpageContent.websiteName) {
case .instagram where webpageContent.file != nil && webpageContent.image != nil && webpageContent.file!.isVideo:
content = NativeVideoContent(id: .message(message.stableId, webpageContent.file?.id ?? webpage.webpageId), fileReference: .message(message: MessageReference(message), media: webpageContent.file!), imageReference: webpageContent.image.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, enableSound: true)
content = NativeVideoContent(id: .message(message.stableId, webpageContent.file?.id ?? webpage.webpageId), fileReference: .message(message: MessageReference(message), media: webpageContent.file!), imageReference: webpageContent.image.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, enableSound: true, captureProtected: message.isCopyProtected())
default:
if let embedUrl = webpageContent.embedUrl, let image = webpageContent.image {
if let file = webpageContent.file, file.isVideo {
content = NativeVideoContent(id: .message(message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath)
content = NativeVideoContent(id: .message(message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath, captureProtected: message.isCopyProtected())
} else if URL(string: embedUrl)?.pathExtension == "mp4" {
content = SystemVideoContent(url: embedUrl, imageReference: .webPage(webPage: WebpageReference(webpage), media: image), dimensions: webpageContent.embedSize?.cgSize ?? CGSize(width: 640.0, height: 640.0), duration: Int32(webpageContent.duration ?? 0))
}
@ -448,7 +448,7 @@ public class GalleryController: ViewController, StandalonePresentableController
} else {
namespaces = .not(Namespaces.Message.allScheduled)
}
return context.account.postbox.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: [.combinedLocation])
return context.account.postbox.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(message!.index), ignoreMessagesInTimestampRange: nil, count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
@ -1139,7 +1139,7 @@ public class GalleryController: ViewController, StandalonePresentableController
} else {
namespaces = .not(Namespaces.Message.allScheduled)
}
let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: [.combinedLocation])
let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), ignoreMessagesInTimestampRange: nil, count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)

View File

@ -329,15 +329,18 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
open func animateIn(animateContent: Bool, useSimpleAnimation: Bool) {
let duration: Double = animateContent ? 0.2 : 0.3
self.backgroundNode.backgroundColor = self.backgroundNode.backgroundColor?.withAlphaComponent(0.0)
let fadeDuration: Double = 0.2
let backgroundColor = self.backgroundNode.backgroundColor ?? .black
self.statusBar?.alpha = 0.0
self.navigationBar?.alpha = 0.0
self.footerNode.alpha = 0.0
self.currentThumbnailContainerNode?.alpha = 0.0
UIView.animate(withDuration: duration, animations: {
self.backgroundNode.backgroundColor = self.backgroundNode.backgroundColor?.withAlphaComponent(1.0)
self.backgroundNode.layer.animate(from: backgroundColor.withAlphaComponent(0.0).cgColor, to: backgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: fadeDuration)
UIView.animate(withDuration: fadeDuration, delay: 0.0, options: [.curveLinear], animations: {
if !self.areControlsHidden {
self.statusBar?.alpha = 1.0
self.navigationBar?.alpha = 1.0

View File

@ -122,6 +122,7 @@ class ChatImageGalleryItem: GalleryItem {
func node(synchronous: Bool) -> GalleryItemNode {
let node = ChatImageGalleryItemNode(context: self.context, presentationData: self.presentationData, performAction: self.performAction, openActionOptions: self.openActionOptions, present: self.present)
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop)
for media in self.message.media {
if let image = media as? TelegramMediaImage {
node.setImage(imageReference: .message(message: MessageReference(self.message), media: image))
@ -147,7 +148,6 @@ class ChatImageGalleryItem: GalleryItem {
if self.displayInfoOnTop {
node.titleContentView?.setMessage(self.message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId)
}
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop)
return node
}
@ -258,6 +258,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
}
fileprivate func setMessage(_ message: Message, displayInfo: Bool) {
self.message = message
self.imageNode.captureProtected = message.isCopyProtected()
self.footerContentNode.setMessage(message, displayInfo: displayInfo)
}
@ -455,6 +457,9 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
let wasCaptureProtected = self.imageNode.captureProtected
self.imageNode.captureProtected = false
let contentNode = self.tilingNode ?? self.imageNode
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: contentNode.view)
@ -496,8 +501,14 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
let positionDuration: Double = 0.21
copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak copyView] _ in
copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak copyView, weak self] _ in
copyView?.removeFromSuperview()
if wasCaptureProtected {
Queue.mainQueue().after(0.2) {
self?.imageNode.captureProtected = true
}
}
})
let scale = CGSize(width: transformedCopyViewFinalFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewFinalFrame.size.height / transformedSelfFrame.size.height)
copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)

View File

@ -2423,8 +2423,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
let url = content.url
let item = OpenInItem.url(url: url)
let canOpenIn = availableOpenInOptions(context: strongSelf.context, item: item).count > 1
let openText = canOpenIn ? strongSelf.presentationData.strings.Conversation_FileOpenIn : strongSelf.presentationData.strings.Conversation_LinkDialogOpen
let openText = strongSelf.presentationData.strings.Conversation_FileOpenIn
items.append(.action(ContextMenuActionItem(text: openText, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
@ -2446,7 +2445,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile {
if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveVideo, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in
f(.default)

View File

@ -145,7 +145,7 @@ final class GameControllerNode: ViewControllerTracingNode {
if eventName == "share_score" {
self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, text, account in
if let strongSelf = self {
let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: strongSelf.message.id, to: $0) }
let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: strongSelf.message.id, to: $0, as: nil) }
return .single(.preparing)
|> then(
combineLatest(signals)

View File

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

View File

@ -0,0 +1,27 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "GraphCore",
platforms: [.macOS(.v10_11)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "GraphCore",
targets: ["GraphCore"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "GraphCore",
dependencies: [],
path: "Sources"),
]
)

View File

@ -1,19 +0,0 @@
//
// GraphCore.h
// GraphCore
//
// Created by Mikhail Filimonov on 03.02.2020.
// Copyright © 2020 Telegram. All rights reserved.
//
#import <Cocoa/Cocoa.h>
//! Project version number for GraphCore.
FOUNDATION_EXPORT double GraphCoreVersionNumber;
//! Project version string for GraphCore.
FOUNDATION_EXPORT const unsigned char GraphCoreVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <GraphCore/PublicHeader.h>

View File

@ -46,13 +46,13 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem {
switch attribute {
case let .Audio(isVoice, _, _, _, _):
if isVoice {
return SharedMediaPlaybackData(type: .voice, source: .telegramFile(.webPage(webPage: WebpageReference(self.webPage), media: file)))
return SharedMediaPlaybackData(type: .voice, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false))
} else {
return SharedMediaPlaybackData(type: .music, source: .telegramFile(.webPage(webPage: WebpageReference(self.webPage), media: file)))
return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false))
}
case let .Video(_, _, flags):
if flags.contains(.instantRoundVideo) {
return SharedMediaPlaybackData(type: .instantVideo, source: .telegramFile(.webPage(webPage: WebpageReference(self.webPage), media: file)))
return SharedMediaPlaybackData(type: .instantVideo, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false))
} else {
return nil
}
@ -61,12 +61,12 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem {
}
}
if file.mimeType.hasPrefix("audio/") {
return SharedMediaPlaybackData(type: .music, source: .telegramFile(.webPage(webPage: WebpageReference(self.webPage), media: file)))
return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false))
}
if let fileName = file.fileName {
let ext = (fileName as NSString).pathExtension.lowercased()
if ext == "wav" || ext == "opus" {
return SharedMediaPlaybackData(type: .music, source: .telegramFile(.webPage(webPage: WebpageReference(self.webPage), media: file)))
return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false))
}
}
}

View File

@ -162,7 +162,6 @@ public final class InviteLinkQRCodeController: ViewController {
private let backgroundNode: ASDisplayNode
private let contentBackgroundNode: ASDisplayNode
private let titleNode: ASTextNode
private let subtitleNode: ASTextNode
private let cancelButton: HighlightableButtonNode
private let textNode: ImmediateTextNode
@ -209,9 +208,6 @@ public final class InviteLinkQRCodeController: ViewController {
self.titleNode = ASTextNode()
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_QRCode_Title, font: Font.bold(17.0), textColor: textColor)
self.subtitleNode = ASTextNode()
self.subtitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_QRCode_Title, font: Font.regular(13.0), textColor: secondaryTextColor)
self.cancelButton = HighlightableButtonNode()
self.cancelButton.setTitle(self.presentationData.strings.Common_Done, with: Font.bold(17.0), with: accentColor, for: .normal)
@ -254,10 +250,8 @@ public final class InviteLinkQRCodeController: ViewController {
self.contentContainerNode.addSubnode(self.qrImageNode)
self.contentContainerNode.addSubnode(self.qrIconNode)
self.contentContainerNode.addSubnode(self.qrButtonNode)
let textFont = Font.regular(13.0)
self.textNode.attributedText = NSAttributedString(string: isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel, font: textFont, textColor: secondaryTextColor)
self.textNode.attributedText = NSAttributedString(string: isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel, font: Font.regular(13.0), textColor: secondaryTextColor)
self.buttonNode.title = self.presentationData.strings.InviteLink_QRCode_Share
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
@ -304,6 +298,7 @@ public final class InviteLinkQRCodeController: ViewController {
self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor)
if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)

View File

@ -9,6 +9,7 @@ import PresentationDataUtils
public enum ItemListPeerActionItemHeight {
case generic
case compactPeerList
case peerList
}
@ -162,17 +163,25 @@ class ItemListPeerActionItemNode: ListViewItemNode {
updatedTheme = item.presentationData.theme
}
let leftInset: CGFloat
let iconOffset: CGFloat
let verticalInset: CGFloat
let verticalOffset: CGFloat
switch item.height {
case .generic:
iconOffset = 1.0
verticalInset = 11.0
verticalOffset = 0.0
leftInset = (item.icon == nil ? 16.0 : 59.0) + params.leftInset
case .peerList:
iconOffset = 3.0
verticalInset = 14.0
verticalOffset = 0.0
leftInset = 65.0 + params.leftInset
case .compactPeerList:
iconOffset = 3.0
verticalInset = 11.0
verticalOffset = 0.0
leftInset = 65.0 + params.leftInset
}
let editingOffset: CGFloat = (item.editing ? 38.0 : 0.0)
@ -222,7 +231,7 @@ class ItemListPeerActionItemNode: ListViewItemNode {
strongSelf.iconNode.image = item.icon
if let image = item.icon {
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + 3.0, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + iconOffset, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
}
if strongSelf.backgroundNode.supernode == nil {

View File

@ -449,6 +449,9 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
private let maskNode: ASImageNode
private let containerNode: ContextControllerSourceNode
public override var controlsContainer: ASDisplayNode {
return self.containerNode
}
fileprivate let avatarNode: AvatarNode
private let titleNode: TextNode
@ -1012,6 +1015,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
case .sameSection(false):
bottomStripeInset = leftInset + editingOffset
bottomStripeOffset = -separatorHeight
strongSelf.bottomStripeNode.isHidden = !item.displayDecorations
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0

View File

@ -151,6 +151,11 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
private var disabledOverlayNode: ASDisplayNode?
private let maskNode: ASImageNode
private let containerNode: ASDisplayNode
override var controlsContainer: ASDisplayNode {
return self.containerNode
}
fileprivate let imageNode: TransformImageNode
private var animationNode: AnimatedStickerNode?
private var placeholderNode: StickerShimmerEffectNode?
@ -203,6 +208,8 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.containerNode = ASDisplayNode()
self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false
@ -245,17 +252,19 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
if let placeholderNode = self.placeholderNode {
self.addSubnode(placeholderNode)
}
self.addSubnode(self.imageNode)
self.addSubnode(self.containerNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.statusNode)
self.addSubnode(self.unreadNode)
self.addSubnode(self.installationActionImageNode)
self.addSubnode(self.installationActionNode)
self.addSubnode(self.selectionIconNode)
if let placeholderNode = self.placeholderNode {
self.containerNode.addSubnode(placeholderNode)
}
self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.titleNode)
self.containerNode.addSubnode(self.statusNode)
self.containerNode.addSubnode(self.unreadNode)
self.containerNode.addSubnode(self.installationActionImageNode)
self.containerNode.addSubnode(self.installationActionNode)
self.containerNode.addSubnode(self.selectionIconNode)
self.addSubnode(self.activateArea)
self.installationActionNode.addTarget(self, action: #selector(self.installationActionPressed), forControlEvents: .touchUpInside)
@ -689,6 +698,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size)
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
@ -744,7 +754,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
strongSelf.imageNode.setSignal(updatedImageSignal)
}
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 59.0 + UIScreenPixel + UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)

View File

@ -471,13 +471,11 @@ open class ItemListControllerNode: ASDisplayNode {
insets.top += navigationBarHeight
insets.bottom = max(insets.bottom, additionalInsets.bottom)
var addedInsets: UIEdgeInsets?
let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
if layout.size.width >= 375.0 {
insets.left += inset
insets.right += inset
}
addedInsets = UIEdgeInsets(top: 0.0, left: inset, bottom: 0.0, right: inset)
if self.rightOverlayNode.supernode == nil {
self.insertSubnode(self.rightOverlayNode, aboveSubnode: self.listNode)
@ -551,10 +549,6 @@ open class ItemListControllerNode: ASDisplayNode {
self.rightOverlayNode.frame = CGRect(x: layout.size.width - insets.right, y: 0.0, width: insets.right, height: layout.size.height)
if let emptyStateNode = self.emptyStateNode {
var layout = layout
if let addedInsets = addedInsets {
layout = layout.addedInsets(insets: addedInsets)
}
emptyStateNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition)
}

View File

@ -253,7 +253,7 @@ public class ItemListCheckboxItemNode: ListViewItemNode {
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
}
})
}

View File

@ -23,14 +23,16 @@ public class InfoListItem: ListViewItem {
let title: String
let text: InfoListItemText
let style: ItemListStyle
let hasDecorations: Bool
let linkAction: ((InfoListItemLinkAction) -> Void)?
let closeAction: (() -> Void)?
public init(presentationData: ItemListPresentationData, title: String, text: InfoListItemText, style: ItemListStyle, linkAction: ((InfoListItemLinkAction) -> Void)? = nil, closeAction: (() -> Void)?) {
public init(presentationData: ItemListPresentationData, title: String, text: InfoListItemText, style: ItemListStyle, hasDecorations: Bool = true, linkAction: ((InfoListItemLinkAction) -> Void)? = nil, closeAction: (() -> Void)?) {
self.presentationData = presentationData
self.title = title
self.text = text
self.style = style
self.hasDecorations = hasDecorations
self.linkAction = linkAction
self.closeAction = closeAction
}
@ -301,7 +303,7 @@ public class InfoItemNode: ListViewItemNode {
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
strongSelf.topStripeNode.isHidden = hasCorners || !item.hasDecorations
}
}
let bottomStripeInset: CGFloat
@ -312,10 +314,13 @@ public class InfoItemNode: ListViewItemNode {
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
strongSelf.bottomStripeNode.isHidden = hasCorners || !item.hasDecorations
}
} else {
bottomStripeInset = leftInset
if !item.hasDecorations {
strongSelf.topStripeNode.isHidden = true
}
}
strongSelf.closeButton.isHidden = item.closeAction == nil

View File

@ -100,6 +100,7 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode {
private let highlightedBackgroundNode: ASDisplayNode
private var linkHighlightingNode: LinkHighlightingNode?
private var selectionNode: ItemListSelectableControlNode?
private let maskNode: ASImageNode
public var item: ItemListTextWithLabelItem?
@ -111,6 +112,9 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
@ -285,7 +289,9 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode {
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
}
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
case .blocks:
leftInset = 16.0 + params.leftInset
@ -299,11 +305,19 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode {
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
strongSelf.topStripeNode.isHidden = false
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
@ -314,8 +328,14 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode {
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
}

View File

@ -301,7 +301,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.topStripeNode.removeFromSupernode()
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.addSubnode(strongSelf.bottomStripeNode)
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
}
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()

View File

@ -46,14 +46,13 @@ typedef enum {
@property (nonatomic, strong) id<TGPhotoPaintStickersContext> stickersContext;
@property (nonatomic, assign) bool shortcut;
@property (nonatomic, strong) NSString *forcedCaption;
@property (nonatomic, strong) NSArray *forcedEntities;
@property (nonatomic, strong) NSAttributedString *forcedCaption;
@property (nonatomic, strong) NSString *recipientName;
@property (nonatomic, copy) void(^finishedWithResults)(TGOverlayController *controller, TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem, bool silentPosting, int32_t scheduleTime);
@property (nonatomic, copy) void(^finishedWithPhoto)(TGOverlayController *controller, UIImage *resultImage, NSString *caption, NSArray *entities, NSArray *stickers, NSNumber *timer);
@property (nonatomic, copy) void(^finishedWithVideo)(TGOverlayController *controller, NSURL *videoURL, UIImage *previewImage, NSTimeInterval duration, CGSize dimensions, TGVideoEditAdjustments *adjustments, NSString *caption, NSArray *entities, NSArray *stickers, NSNumber *timer);
@property (nonatomic, copy) void(^finishedWithPhoto)(TGOverlayController *controller, UIImage *resultImage, NSAttributedString *caption, NSArray *stickers, NSNumber *timer);
@property (nonatomic, copy) void(^finishedWithVideo)(TGOverlayController *controller, NSURL *videoURL, UIImage *previewImage, NSTimeInterval duration, CGSize dimensions, TGVideoEditAdjustments *adjustments, NSAttributedString *caption, NSArray *stickers, NSNumber *timer);
@property (nonatomic, copy) void(^recognizedQRCode)(NSString *code);
@ -68,7 +67,7 @@ typedef enum {
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia intent:(TGCameraControllerIntent)intent;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia camera:(PGCamera *)camera previewView:(TGCameraPreviewView *)previewView intent:(TGCameraControllerIntent)intent;
+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext currentItem:(id<TGMediaSelectableItem>)currentItem storeAssets:(bool)storeAssets saveEditedPhotos:(bool)saveEditedPhotos descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator;
+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext currentItem:(id<TGMediaSelectableItem>)currentItem storeAssets:(bool)storeAssets saveEditedPhotos:(bool)saveEditedPhotos descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *))descriptionGenerator;
- (void)beginTransitionInFromRect:(CGRect)rect;
- (void)_dismissTransitionForResultController:(TGOverlayController *)resultController;

View File

@ -16,6 +16,7 @@
+ (TGMenuSheetController *)presentInParentController:(TGViewController *)parentController context:(id<LegacyComponentsContext>)context images:(NSArray *)images allowGrouping:(bool)allowGrouping hasCaption:(bool)hasCaption hasTimer:(bool)hasTimer hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder recipientName:(NSString *)recipientName suggestionContext:(TGSuggestionContext *)suggestionContext stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext presentScheduleController:(void (^)(void(^)(int32_t)))presentScheduleController presentTimerController:(void (^)(void(^)(int32_t)))presentTimerController completed:(void (^)(TGMediaSelectionContext *selectionContext, TGMediaEditingContext *editingContext, id<TGMediaSelectableItem> currentItem, bool silentPosting, int32_t scheduleTime))completed dismissed:(void (^)(void))dismissed sourceView:(UIView *)sourceView sourceRect:(CGRect (^)(void))sourceRect;
+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext currentItem:(id<TGMediaSelectableItem>)currentItem descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator;
+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext currentItem:(id<TGMediaSelectableItem>)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *,
NSString *))descriptionGenerator;
@end

View File

@ -74,7 +74,7 @@ typedef enum
@property (nonatomic, strong) NSString *recipientName;
@property (nonatomic, copy) NSDictionary *(^descriptionGenerator)(id, NSString *, NSArray *, NSString *, NSString *);
@property (nonatomic, copy) NSDictionary *(^descriptionGenerator)(id, NSAttributedString *, NSString *, NSString *);
@property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image);
@property (nonatomic, copy) void (^completionBlock)(NSArray *signals, bool silentPosting, int32_t scheduleTime);
@property (nonatomic, copy) void (^avatarVideoCompletionBlock)(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments);
@ -92,7 +92,7 @@ typedef enum
- (UIBarButtonItem *)rightBarButtonItem;
- (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *, NSString *))descriptionGenerator;
- (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator;
- (void)completeWithAvatarImage:(UIImage *)image;
- (void)completeWithAvatarVideo:(id)asset adjustments:(TGVideoEditAdjustments *)adjustments image:(UIImage *)image;
@ -105,6 +105,6 @@ typedef enum
+ (TGMediaAssetType)assetTypeForIntent:(TGMediaAssetsControllerIntent)intent;
+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(TGMediaAsset *)currentItem storeAssets:(bool)storeAssets convertToJpeg:(bool)convertToJpeg descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *, NSString *))descriptionGenerator saveEditedPhotos:(bool)saveEditedPhotos;
+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(TGMediaAsset *)currentItem storeAssets:(bool)storeAssets convertToJpeg:(bool)convertToJpeg descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator saveEditedPhotos:(bool)saveEditedPhotos;
@end

View File

@ -62,14 +62,13 @@
- (SSignal *)fullSizeImageUrlForItem:(id<TGMediaEditableItem>)item;
- (NSString *)captionForItem:(NSObject<TGMediaEditableItem> *)item;
- (NSArray *)entitiesForItem:(NSObject<TGMediaEditableItem> *)item;
- (NSAttributedString *)captionForItem:(NSObject<TGMediaEditableItem> *)item;
- (SSignal *)captionSignalForItem:(NSObject<TGMediaEditableItem> *)item;
- (void)setCaption:(NSString *)caption entities:(NSArray *)entities forItem:(NSObject<TGMediaEditableItem> *)item;
- (void)setCaption:(NSAttributedString *)caption forItem:(NSObject<TGMediaEditableItem> *)item;
- (bool)isForcedCaption;
- (void)setForcedCaption:(NSString *)caption entities:(NSArray *)entities;
- (void)setForcedCaption:(NSAttributedString *)caption;
- (NSObject<TGMediaEditAdjustments> *)adjustmentsForItem:(NSObject<TGMediaEditableItem> *)item;
- (SSignal *)adjustmentsSignalForItem:(NSObject<TGMediaEditableItem> *)item;

View File

@ -5,6 +5,7 @@
#import <LegacyComponents/LegacyComponentsContext.h>
@protocol TGPhotoPaintStickersContext;
@class TGMediaSelectionContext;
@class TGMediaEditingContext;
@class TGSuggestionContext;
@ -12,7 +13,7 @@
@interface TGMediaPickerGalleryInterfaceView : UIView <TGModernGalleryInterfaceView>
@property (nonatomic, copy) void (^captionSet)(id<TGModernGalleryItem>, NSString *, NSArray *);
@property (nonatomic, copy) void (^captionSet)(id<TGModernGalleryItem>, NSAttributedString *);
@property (nonatomic, copy) void (^donePressed)(id<TGModernGalleryItem>);
@property (nonatomic, copy) void (^doneLongPressed)(id<TGModernGalleryItem>);
@ -37,7 +38,7 @@
@property (nonatomic, readonly) UIView *timerButton;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasSelectionPanel:(bool)hasSelectionPanel hasCameraButton:(bool)hasCameraButton recipientName:(NSString *)recipientName;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext hasSelectionPanel:(bool)hasSelectionPanel hasCameraButton:(bool)hasCameraButton recipientName:(NSString *)recipientName;
- (void)setSelectedItemsModel:(TGMediaPickerGallerySelectedItemsModel *)selectedItemsModel;
- (void)setEditorTabPressed:(void (^)(TGPhotoEditorTab tab))editorTabPressed;

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