mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' into beta
This commit is contained in:
commit
1d6aca88b9
@ -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 = [
|
||||
|
@ -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:
|
||||
|
@ -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] = []
|
||||
|
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Balloon.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Balloon.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Fireworks.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Fireworks.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Hearts.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Hearts.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Joy.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Joy.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Money.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Money.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Party.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Party.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Poo.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/DemoAnimations/Poo.tgs
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Devices.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Devices.tgs
Normal file
Binary file not shown.
@ -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
0
Tests/BUILD
Normal file
16
Tests/Common/BUILD
Normal file
16
Tests/Common/BUILD
Normal 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"],
|
||||
)
|
19
Tests/Common/Base.lproj/LaunchScreen.xib
Normal file
19
Tests/Common/Base.lproj/LaunchScreen.xib
Normal 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
7
Tests/Common/Main/main.m
Normal 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
150
Tests/LottieMesh/BUILD
Normal 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",
|
||||
],
|
||||
)
|
1
Tests/LottieMesh/Resources/Cat.json
Normal file
1
Tests/LottieMesh/Resources/Cat.json
Normal file
File diff suppressed because one or more lines are too long
1
Tests/LottieMesh/Resources/Fireworks.json
Normal file
1
Tests/LottieMesh/Resources/Fireworks.json
Normal file
File diff suppressed because one or more lines are too long
1
Tests/LottieMesh/Resources/SUPER Fire.json
Normal file
1
Tests/LottieMesh/Resources/SUPER Fire.json
Normal file
File diff suppressed because one or more lines are too long
21
Tests/LottieMesh/Sources/AppDelegate.swift
Normal file
21
Tests/LottieMesh/Sources/AppDelegate.swift
Normal 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
|
||||
}
|
||||
}
|
37
Tests/LottieMesh/Sources/ViewController.swift
Normal file
37
Tests/LottieMesh/Sources/ViewController.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -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])
|
||||
|
@ -20,7 +20,7 @@ swift_library(
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
|
||||
#"//submodules/MeshAnimationCache:MeshAnimationCache"
|
||||
"//submodules/MeshAnimationCache:MeshAnimationCache"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
// }
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
21
submodules/CodeInputView/BUILD
Normal file
21
submodules/CodeInputView/BUILD
Normal 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",
|
||||
],
|
||||
)
|
293
submodules/CodeInputView/Sources/CodeInputView.swift
Normal file
293
submodules/CodeInputView/Sources/CodeInputView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ swift_library(
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/TextSelectionNode:TextSelectionNode",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/ReactionSelectionNode:ReactionSelectionNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
32
submodules/Crc32/Package.swift
Normal file
32
submodules/Crc32/Package.swift
Normal 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")
|
||||
]),
|
||||
]
|
||||
)
|
32
submodules/CryptoUtils/Package.swift
Normal file
32
submodules/CryptoUtils/Package.swift
Normal 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")
|
||||
]),
|
||||
]
|
||||
)
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
32
submodules/EncryptionProvider/Package.swift
Normal file
32
submodules/EncryptionProvider/Package.swift
Normal 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"),
|
||||
]),
|
||||
]
|
||||
)
|
@ -0,0 +1 @@
|
||||
#import <EncryptionProvider/EncryptionProvider.h>
|
33
submodules/FFMpegBinding/Package.swift
Normal file
33
submodules/FFMpegBinding/Package.swift
Normal 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"])
|
||||
]),
|
||||
]
|
||||
)
|
@ -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])?
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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>
|
27
submodules/GraphCore/Package.swift
Normal file
27
submodules/GraphCore/Package.swift
Normal 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"),
|
||||
]
|
||||
)
|
@ -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>
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user