Temporary

This commit is contained in:
Ali 2020-02-10 18:03:13 +01:00
parent 65b22f6ca5
commit d6188e2124
28 changed files with 1650 additions and 290 deletions

View File

@ -567,9 +567,9 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
completion(.success(with: .missed))
}
public func resolveCallType(for intent: INSearchCallHistoryIntent, with completion: @escaping (INCallRecordTypeResolutionResult) -> Void) {
/*public func resolveCallType(for intent: INSearchCallHistoryIntent, with completion: @escaping (INCallRecordTypeResolutionResult) -> Void) {
completion(.success(with: .missed))
}
}*/
public func handle(intent: INSearchCallHistoryIntent, completion: @escaping (INSearchCallHistoryIntentResponse) -> Void) {
self.actionDisposable.set((self.accountPromise.get()

View File

@ -0,0 +1,83 @@
#!/bin/bash
set -e
set -x
API_HOST="https://api.appcenter.ms"
IPA_PATH="build/Telegram_Signed.ipa"
DSYM_PATH="build/DSYM.zip"
upload_ipa() {
GROUP_DATA=$(curl \
-X GET \
--header "X-API-Token: $API_TOKEN" \
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/distribution_groups/Internal" \
)
GROUP_ID=$(echo "$GROUP_DATA" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["id"];')
UPLOAD_TOKEN=$(curl \
-X POST \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-API-Token: $API_TOKEN" \
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/release_uploads" \
)
UPLOAD_URL=$(echo "$UPLOAD_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["upload_url"];')
UPLOAD_ID=$(echo "$UPLOAD_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["upload_id"];')
curl --progress-bar -F "ipa=@${IPA_PATH}" "$UPLOAD_URL"
RELEASE_TOKEN=$(curl \
-X PATCH \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-API-Token: $API_TOKEN" \
-d '{ "status": "committed" }' \
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/release_uploads/$UPLOAD_ID" \
)
RELEASE_URL=$(echo "$RELEASE_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["release_url"];')
RELEASE_ID=$(echo "$RELEASE_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["release_id"];')
curl \
-X POST \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-API-Token: $API_TOKEN" \
-d "{ \"id\": \"$GROUP_ID\", \"mandatory_update\": false, \"notify_testers\": false }" \
"$API_HOST/$RELEASE_URL/groups"
}
upload_dsym() {
UPLOAD_DSYM_DATA=$(curl \
-X POST \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-API-Token: $API_TOKEN" \
-d "{ \"symbol_type\": \"Apple\"}" \
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/symbol_uploads" \
)
DSYM_UPLOAD_URL=$(echo "$UPLOAD_DSYM_DATA" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["upload_url"];')
DSYM_UPLOAD_ID=$(echo "$UPLOAD_DSYM_DATA" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["symbol_upload_id"];')
curl \
--progress-bar \
--header "x-ms-blob-type: BlockBlob" \
--upload-file "${DSYM_PATH}" \
"$DSYM_UPLOAD_URL"
curl \
-X PATCH \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-API-Token: $API_TOKEN" \
-d '{ "status": "committed" }' \
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/symbol_uploads/$DSYM_UPLOAD_ID"
}
upload_ipa

View File

@ -34,12 +34,10 @@ fi
if [ "$CONFIGURATION" == "hockeyapp" ]; then
FASTLANE_PASSWORD=""
FASTLANE_ITC_TEAM_NAME=""
FASTLANE_BUILD_CONFIGURATION="internalhockeyapp"
elif [ "$CONFIGURATION" == "appstore" ]; then
FASTLANE_PASSWORD="$TELEGRAM_BUILD_APPSTORE_PASSWORD"
FASTLANE_ITC_TEAM_NAME="$TELEGRAM_BUILD_APPSTORE_TEAM_NAME"
FASTLANE_ITC_USERNAME="$TELEGRAM_BUILD_APPSTORE_USERNAME"
FASTLANE_BUILD_CONFIGURATION="testflight_llc"
else
echo "Unknown configuration $CONFIGURATION"
exit 1
@ -62,7 +60,6 @@ fi
if [ "$1" == "appstore" ]; then
export DELIVER_ITMSTRANSPORTER_ADDITIONAL_UPLOAD_PARAMETERS="-t DAV"
FASTLANE_PASSWORD="$FASTLANE_PASSWORD" xcrun altool --upload-app --type ios --file "$IPA_PATH" --username "$FASTLANE_ITC_USERNAME" --password "@env:FASTLANE_PASSWORD"
#FASTLANE_PASSWORD="$FASTLANE_PASSWORD" FASTLANE_ITC_TEAM_NAME="$FASTLANE_ITC_TEAM_NAME" fastlane "$FASTLANE_BUILD_CONFIGURATION" build_number:"$BUILD_NUMBER" commit_hash:"$COMMIT_ID" commit_author:"$COMMIT_AUTHOR" skip_build:1 skip_pilot:1
else
FASTLANE_PASSWORD="$FASTLANE_PASSWORD" FASTLANE_ITC_TEAM_NAME="$FASTLANE_ITC_TEAM_NAME" fastlane "$FASTLANE_BUILD_CONFIGURATION" build_number:"$BUILD_NUMBER" commit_hash:"$COMMIT_ID" commit_author:"$COMMIT_AUTHOR" skip_build:1
else if [ "$1" == "hockeyapp" ]; then
API_USER_NAME="$API_USER_NAME" API_APP_NAME="$API_APP_NAME" API_TOKEN="$API_TOKEN" sh buildbox/deploy-appcenter.sh
fi

View File

@ -197,12 +197,13 @@ public final class NavigateToChatControllerParams {
public let keepStack: NavigateToChatKeepStack
public let purposefulAction: (() -> Void)?
public let scrollToEndIfExists: Bool
public let activateMessageSearch: Bool
public let animated: Bool
public let options: NavigationAnimationOptions
public let parentGroupId: PeerGroupId?
public let completion: () -> Void
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping () -> Void = {}) {
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: Bool = false, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping () -> Void = {}) {
self.navigationController = navigationController
self.chatController = chatController
self.context = context
@ -214,6 +215,7 @@ public final class NavigateToChatControllerParams {
self.keepStack = keepStack
self.purposefulAction = purposefulAction
self.scrollToEndIfExists = scrollToEndIfExists
self.activateMessageSearch = activateMessageSearch
self.animated = animated
self.options = options
self.parentGroupId = parentGroupId

View File

@ -191,6 +191,8 @@ public final class AvatarNode: ASDisplayNode {
private let imageReadyDisposable = MetaDisposable()
private var state: AvatarNodeState = .empty
public var unroundedImage: UIImage?
private let imageReady = Promise<Bool>(false)
public var ready: Signal<Void, NoError> {
let imageReady = self.imageReady
@ -283,7 +285,7 @@ public final class AvatarNode: ASDisplayNode {
self.imageNode.isHidden = true
}
public func setPeer(context: AccountContext, theme: PresentationTheme, peer: Peer?, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil, emptyColor: UIColor? = nil, clipStyle: AvatarNodeClipStyle = .round, synchronousLoad: Bool = false, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0)) {
public func setPeer(context: AccountContext, theme: PresentationTheme, peer: Peer?, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil, emptyColor: UIColor? = nil, clipStyle: AvatarNodeClipStyle = .round, synchronousLoad: Bool = false, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), storeUnrounded: Bool = false) {
var synchronousLoad = synchronousLoad
var representation: TelegramMediaImageRepresentation?
var icon = AvatarNodeIcon.none
@ -318,11 +320,18 @@ public final class AvatarNode: ASDisplayNode {
let parameters: AvatarNodeParameters
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad) {
if let peer = peer, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
self.contents = nil
self.displaySuspended = true
self.imageReady.set(self.imageNode.ready)
self.imageNode.setSignal(signal)
self.imageNode.setSignal(signal |> beforeNext { [weak self] next in
Queue.mainQueue().async {
self?.unroundedImage = next?.1
}
}
|> map { next -> UIImage? in
return next?.0
})
if case .editAvatarIcon = icon {
if self.editOverlayNode == nil {

View File

@ -64,15 +64,15 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
}
}
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false) -> Signal<UIImage?, NoError>? {
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
return imageData
|> mapToSignal { data -> Signal<UIImage?, NoError> in
let generate = deferred { () -> Signal<UIImage?, NoError> in
|> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in
let generate = deferred { () -> Signal<(UIImage, UIImage)?, NoError> in
if emptyColor == nil && data == nil {
return .single(nil)
}
return .single(generateImage(displayDimensions, contextGenerator: { size, context -> Void in
let roundedImage = generateImage(displayDimensions, contextGenerator: { size, context -> Void in
if let data = data {
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
@ -110,7 +110,41 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
}
}))
})
let unroundedImage: UIImage?
if provideUnrounded {
unroundedImage = generateImage(displayDimensions, contextGenerator: { size, context -> Void in
if let data = data {
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setBlendMode(.copy)
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else {
if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
}
} else if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
if round {
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else {
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
}
})
} else {
unroundedImage = roundedImage
}
if let roundedImage = roundedImage, let unroundedImage = unroundedImage {
return .single((roundedImage, unroundedImage))
} else {
return .single(nil)
}
}
if synchronousLoad {
return generate

View File

@ -5,6 +5,9 @@ private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> Bool {
if view.disablesInteractiveTransitionGestureRecognizer {
return true
}
if let disablesInteractiveTransitionGestureRecognizerNow = view.disablesInteractiveTransitionGestureRecognizerNow, disablesInteractiveTransitionGestureRecognizerNow() {
return true
}
if let point = point, let test = view.interactiveTransitionGestureRecognizerTest, test(point) {
return true

View File

@ -188,14 +188,14 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
bottomController.viewWillAppear(true)
let bottomNode = bottomController.displayNode
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self, topNode: topNode, topNavigationBar: topController.navigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: { [weak self] progress, transition, topFrame, bottomFrame in
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, isInteractive: true, container: self, topNode: topNode, topNavigationBar: topController.navigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: { [weak self, weak bottomController] progress, transition, topFrame, bottomFrame in
if let strongSelf = self {
if let top = strongSelf.state.top {
strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition)
var updatedStatusBarStyle = strongSelf.statusBarStyle
if let childTransition = strongSelf.state.transition, childTransition.coordinator.progress >= 0.3 {
updatedStatusBarStyle = childTransition.previous.value.statusBar.statusBarStyle
if let bottomController = bottomController, progress >= 0.3 {
updatedStatusBarStyle = bottomController.statusBar.statusBarStyle
} else {
updatedStatusBarStyle = top.value.statusBar.statusBarStyle
}
@ -355,8 +355,21 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
}
self.applyLayout(layout: updatedLayout, to: top, isMaster: true, transition: transition)
if let childTransition = self.state.transition, childTransition.coordinator.progress >= 0.3 {
updatedStatusBarStyle = childTransition.previous.value.statusBar.statusBarStyle
if let childTransition = self.state.transition, childTransition.coordinator.isInteractive {
switch childTransition.type {
case .push:
if childTransition.coordinator.progress >= 0.3 {
updatedStatusBarStyle = top.value.statusBar.statusBarStyle
} else {
updatedStatusBarStyle = childTransition.previous.value.statusBar.statusBarStyle
}
case .pop:
if childTransition.coordinator.progress >= 0.3 {
updatedStatusBarStyle = childTransition.previous.value.statusBar.statusBarStyle
} else {
updatedStatusBarStyle = top.value.statusBar.statusBarStyle
}
}
} else {
updatedStatusBarStyle = top.value.statusBar.statusBarStyle
}
@ -399,7 +412,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
toValue.value.setIgnoreAppearanceMethodInvocations(false)
let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, container: self, topNode: topController.displayNode, topNavigationBar: topController.navigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in
let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, isInteractive: false, container: self, topNode: topController.displayNode, topNavigationBar: topController.navigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in
guard let strongSelf = self else {
return
}

View File

@ -112,7 +112,7 @@ open class NavigationBar: ASDisplayNode {
public var backPressed: () -> () = { }
public var userInfo: Any?
public var makeCustomTransitionNode: ((NavigationBar) -> CustomNavigationTransitionNode?)?
public var makeCustomTransitionNode: ((NavigationBar, Bool) -> CustomNavigationTransitionNode?)?
private var collapsed: Bool {
get {

View File

@ -306,6 +306,13 @@ private final class NavigationButtonItemNode: ASTextNode {
public final class NavigationButtonNode: ASDisplayNode {
private var nodes: [NavigationButtonItemNode] = []
public var singleCustomNode: ASDisplayNode? {
for node in self.nodes {
return node.node
}
return nil
}
public var pressed: (Int) -> () = { _ in }
public var highlightChanged: (Int, Bool) -> () = { _, _ in }

View File

@ -35,6 +35,7 @@ final class NavigationTransitionCoordinator {
private let container: ASDisplayNode
private let transition: NavigationTransition
let isInteractive: Bool
let topNode: ASDisplayNode
let bottomNode: ASDisplayNode
private let topNavigationBar: NavigationBar?
@ -49,8 +50,9 @@ final class NavigationTransitionCoordinator {
private var currentCompletion: (() -> Void)?
private var didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)?
init(transition: NavigationTransition, container: ASDisplayNode, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) {
init(transition: NavigationTransition, isInteractive: Bool, container: ASDisplayNode, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) {
self.transition = transition
self.isInteractive = isInteractive
self.container = container
self.didUpdateProgress = didUpdateProgress
self.topNode = topNode
@ -65,11 +67,11 @@ final class NavigationTransitionCoordinator {
self.shadowNode.image = shadowImage
if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar {
if let customTransitionNode = topNavigationBar.makeCustomTransitionNode?(bottomNavigationBar) {
if let customTransitionNode = topNavigationBar.makeCustomTransitionNode?(bottomNavigationBar, isInteractive) {
self.inlineNavigationBarTransition = false
customTransitionNode.setup(topNavigationBar: topNavigationBar, bottomNavigationBar: bottomNavigationBar)
self.customTransitionNode = customTransitionNode
} else if let customTransitionNode = bottomNavigationBar.makeCustomTransitionNode?(topNavigationBar) {
} else if let customTransitionNode = bottomNavigationBar.makeCustomTransitionNode?(topNavigationBar, isInteractive) {
self.inlineNavigationBarTransition = false
customTransitionNode.setup(topNavigationBar: topNavigationBar, bottomNavigationBar: bottomNavigationBar)
self.customTransitionNode = customTransitionNode
@ -131,9 +133,16 @@ final class NavigationTransitionCoordinator {
let topFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(position * containerSize.width), y: 0.0), size: containerSize)
let bottomFrame = CGRect(origin: CGPoint(x: ((position - 1.0) * containerSize.width * 0.3), y: 0.0), size: containerSize)
var canInvokeCompletion = false
var hadEarlyCompletion = false
transition.updateFrame(node: self.topNode, frame: topFrame, completion: { _ in
completion()
if canInvokeCompletion {
completion()
} else {
hadEarlyCompletion = true
}
})
canInvokeCompletion = true
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, topFrame.minX), height: self.container.bounds.size.height - dimInset)))
transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: self.dimNode.frame.maxX - shadowWidth, y: dimInset), size: CGSize(width: shadowWidth, height: containerSize.height - dimInset)))
transition.updateAlpha(node: self.dimNode, alpha: (1.0 - position) * 0.15)
@ -149,6 +158,10 @@ final class NavigationTransitionCoordinator {
}
self.didUpdateProgress?(self.progress, transition, topFrame, bottomFrame)
if hadEarlyCompletion {
completion()
}
}
private func updateNavigationBarTransition(transition: ContainedViewLayoutTransition) {

View File

@ -68,6 +68,7 @@ public enum TapLongTapOrDoubleTapGestureRecognizerAction {
case waitForSingleTap
case waitForHold(timeout: Double, acceptTap: Bool)
case fail
case keepWithSingleTap
}
public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
@ -206,6 +207,8 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer,
}
switch tapAction {
case .keepWithSingleTap:
break
case .waitForSingleTap, .waitForDoubleTap:
self.timer?.invalidate()
let timer = Timer(timeInterval: 0.3, target: TapLongTapOrDoubleTapGestureRecognizerTimerTarget(target: self), selector: #selector(TapLongTapOrDoubleTapGestureRecognizerTimerTarget.longTapEvent), userInfo: nil, repeats: false)
@ -284,7 +287,7 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer,
}
switch tapAction {
case .waitForSingleTap:
case .waitForSingleTap, .keepWithSingleTap:
if let (touchLocation, _) = self.touchLocationAndTimestamp {
self.lastRecognizedGestureAndLocation = (.tap, touchLocation)
}

View File

@ -2020,10 +2020,10 @@ public func instantPageImageFile(account: Account, fileReference: FileMediaRefer
}
}
private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false, attemptSynchronously: Bool = false) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) {
let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource)
let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource, attemptSynchronously: attemptSynchronously)
let signal = maybeFullSize
|> take(1)
@ -2037,7 +2037,7 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR
let thumbnail = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource).start(next: { next in
let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: attemptSynchronously).start(next: { next in
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}, error: subscriber.putError, completed: subscriber.putCompletion)
@ -2052,7 +2052,7 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR
if autoFetchFullSize {
fullSizeData = Signal<Tuple2<Data?, Bool>, NoError> { subscriber in
let fetchedFullSizeDisposable = fetchedFullSize.start()
let fullSizeDisposable = account.postbox.mediaBox.resourceData(largestRepresentation.resource).start(next: { next in
let fullSizeDisposable = account.postbox.mediaBox.resourceData(largestRepresentation.resource, attemptSynchronously: attemptSynchronously).start(next: { next in
subscriber.putNext(Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete))
}, error: subscriber.putError, completed: subscriber.putCompletion)
@ -2082,8 +2082,8 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR
}
}
public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = avatarGalleryPhotoDatas(account: account, representations: representations, autoFetchFullSize: autoFetchFullSize)
public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false, attemptSynchronously: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = avatarGalleryPhotoDatas(account: account, representations: representations, autoFetchFullSize: autoFetchFullSize, attemptSynchronously: attemptSynchronously)
return signal
|> map { value in

View File

@ -1476,7 +1476,8 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let inset: CGFloat = 3.0
if let signal = peerAvatarImage(account: primary.0, peerReference: PeerReference(primary.1), authorOfMessage: nil, representation: primary.1.profileImageRepresentations.first, displayDimensions: size, inset: 3.0, emptyColor: nil, synchronousLoad: false) {
return signal
|> map { image -> (UIImage, UIImage)? in
|> map { imageVersions -> (UIImage, UIImage)? in
let image = imageVersions?.0
if let image = image, let selectedImage = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)

View File

@ -135,7 +135,8 @@ public func donateSendMessageIntent(account: Account, sharedContext: SharedAccou
signals.append(.single((peer, subject, savedMessagesAvatar)))
} else {
let peerAndAvatar = (peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.smallProfileImage, round: false) ?? .single(nil))
|> map { avatarImage in
|> map { imageVersions -> (Peer, SendMessageIntentSubject, UIImage?) in
let avatarImage = imageVersions?.0
return (peer, subject, avatarImage)
}
signals.append(peerAndAvatar)

View File

@ -70,8 +70,8 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
strongSelf.contextAction?(strongSelf.containerNode, gesture)
}
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 37.0, height: 37.0))
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 37.0, height: 37.0))
/*self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 37.0, height: 37.0))
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 37.0, height: 37.0))*/
}
override func didLoad() {
@ -80,8 +80,8 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
(self.view as? ChatAvatarNavigationNodeView)?.targetNode = self
(self.view as? ChatAvatarNavigationNodeView)?.chatController = self.chatController
let tapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.avatarTapGesture(_:)))
self.avatarNode.view.addGestureRecognizer(tapRecognizer)
/*let tapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.avatarTapGesture(_:)))
self.avatarNode.view.addGestureRecognizer(tapRecognizer)*/
}
@objc private func avatarTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
@ -106,7 +106,7 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
}
func onLayout() {
/*let bounds = self.bounds
let bounds = self.bounds
if self.bounds.size.height.isLessThanOrEqualTo(26.0) {
if !self.avatarNode.bounds.size.equalTo(bounds.size) {
self.avatarNode.font = smallFont
@ -119,6 +119,6 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
}
self.containerNode.frame = bounds.offsetBy(dx: 10.0, dy: 1.0)
self.avatarNode.frame = bounds
}*/
}
}
}

View File

@ -307,6 +307,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var updateSlowmodeStatusTimerValue: Int32?
private var isDismissed = false
private var focusOnSearchAfterAppearance: Bool = false
public override var customData: Any? {
return self.chatLocation
@ -1869,46 +1871,72 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if case let .peer(peerId) = chatLocation, peerId != context.account.peerId, subject != .scheduledMessages {
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
}
self.chatTitleView = ChatTitleView(account: self.context.account, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, displayAvatar: true)
if let avatarNode = self.chatTitleView?.avatarNode {
avatarNode.chatController = self
avatarNode.contextAction = { [weak self] node, gesture in
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, peer.smallProfileImage != nil else {
return
self.chatTitleView = ChatTitleView(account: self.context.account, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder)
self.navigationItem.titleView = self.chatTitleView
self.chatTitleView?.pressed = { [weak self] in
if let strongSelf = self {
if strongSelf.chatLocation == .peer(strongSelf.context.account.peerId) {
strongSelf.effectiveNavigationController?.pushViewController(PeerMediaCollectionController(context: strongSelf.context, peerId: strongSelf.context.account.peerId))
} else {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .chatInfo:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
} else {
var updatedContexts = $0
updatedContexts.append(.chatInfo)
return updatedContexts.sorted()
}
}
})
}
let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in
}, synchronousLoad: true)
galleryController.setHintWillBePresentedInPreviewingContext(true)
let items: [ContextMenuItem] = [
.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, icon: { _ in nil }, action: { _, f in
f(.dismissWithoutContent)
self?.navigationButtonAction(.openChatInfo(expandAvatar: false))
}))
]
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
avatarNode.tapped = { [weak self] in
guard let strongSelf = self else {
return
}
var expandAvatar = false
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer, peer.smallProfileImage != nil {
expandAvatar = true
}
strongSelf.navigationButtonAction(.openChatInfo(expandAvatar: expandAvatar))
}
}
let chatInfoButtonItem: UIBarButtonItem
switch chatLocation {
case .peer:
let avatarNode = ChatAvatarNavigationNode()
avatarNode.chatController = self
avatarNode.contextAction = { [weak self] node, gesture in
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, peer.smallProfileImage != nil else {
return
}
let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in
}, synchronousLoad: true)
galleryController.setHintWillBePresentedInPreviewingContext(true)
let items: [ContextMenuItem] = [
.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, icon: { _ in nil }, action: { _, f in
f(.dismissWithoutContent)
self?.navigationButtonAction(.openChatInfo(expandAvatar: true))
}))
]
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
}
chatInfoButtonItem.target = self
chatInfoButtonItem.action = #selector(self.rightNavigationButtonAction)
chatInfoButtonItem.accessibilityLabel = self.presentationData.strings.Conversation_Info
self.chatInfoNavigationButton = ChatNavigationButton(action: .openChatInfo(expandAvatar: true), buttonItem: chatInfoButtonItem)
self.navigationItem.titleView = self.chatTitleView
self.chatTitleView?.pressed = { [weak self] in
self?.navigationButtonAction(.openChatInfo(expandAvatar: false))
}
let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationMoreIcon(presentationInterfaceState.theme), style: .plain, target: self, action: #selector(self.rightNavigationButtonAction))
//buttonItem.accessibilityLabel = strings.Conversation_Search
chatInfoNavigationButton = ChatNavigationButton(action: .toggleInfoPanel, buttonItem: buttonItem)
self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
if let botStart = botStart, case .interactive = botStart.behavior {
return state.updatedBotStartPayload(botStart.payload)
@ -1994,8 +2022,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else {
imageOverride = nil
}
strongSelf.chatTitleView?.avatarNode?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride)
strongSelf.chatTitleView?.avatarNode?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && imageOverride == nil && peer.smallProfileImage != nil
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride)
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil
}
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages {
@ -4443,6 +4471,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
self.interfaceInteraction = interfaceInteraction
if self.focusOnSearchAfterAppearance {
self.focusOnSearchAfterAppearance = false
self.interfaceInteraction?.beginMessageSearch(.everything, "")
}
self.chatDisplayNode.interfaceInteraction = interfaceInteraction
self.context.sharedContext.mediaManager.galleryHiddenMediaManager.addTarget(self)
@ -4700,6 +4734,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})]), in: .window(.root))
}))
}
if self.focusOnSearchAfterAppearance {
self.focusOnSearchAfterAppearance = false
if let searchNode = self.navigationBar?.contentNode as? ChatSearchNavigationContentNode {
searchNode.activate()
}
}
}
override public func viewWillDisappear(_ animated: Bool) {
@ -5081,8 +5122,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.leftNavigationButton = nil
}
self.chatTitleView?.displayAvatar = updatedChatPresentationInterfaceState.interfaceState.selectionState == nil
if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton) {
if self.rightNavigationButton != button {
var animated = transition.isAnimated
@ -8456,6 +8495,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return nil
}
}
func activateSearch() {
self.focusOnSearchAfterAppearance = true
self.interfaceInteraction?.beginMessageSearch(.everything, "")
}
}
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {

View File

@ -1628,10 +1628,11 @@ private final class MergedAvatarsNode: ASDisplayNode {
if self.disposables[peer.peerId] == nil {
if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: mergedImageSize, height: mergedImageSize), synchronousLoad: synchronousLoad) {
let disposable = (signal
|> deliverOnMainQueue).start(next: { [weak self] image in
|> deliverOnMainQueue).start(next: { [weak self] imageVersions in
guard let strongSelf = self else {
return
}
let image = imageVersions?.0
if let image = image {
strongSelf.images[peer.peerId] = image
strongSelf.setNeedsDisplay()

View File

@ -101,8 +101,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
private let button: HighlightTrackingButtonNode
let avatarNode: ChatAvatarNavigationNode?
private var validLayout: (CGSize, CGRect)?
private var titleLeftIcon: ChatTitleIcon = .none
@ -179,15 +177,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
var pressed: (() -> Void)?
var displayAvatar: Bool = true {
didSet {
if self.displayAvatar != oldValue {
self.avatarNode?.isHidden = !self.displayAvatar
self.setNeedsLayout()
}
}
}
var titleContent: ChatTitleContent? {
didSet {
if let titleContent = self.titleContent {
@ -480,7 +469,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
}
}
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, displayAvatar: Bool) {
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder) {
self.account = account
self.theme = theme
self.strings = strings
@ -511,11 +500,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
self.activityNode = ChatTitleActivityNode()
self.button = HighlightTrackingButtonNode()
if displayAvatar {
self.avatarNode = ChatAvatarNavigationNode()
} else {
self.avatarNode = nil
}
super.init(frame: CGRect())
@ -526,7 +510,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
self.contentContainer.addSubnode(self.titleNode)
self.contentContainer.addSubnode(self.activityNode)
self.addSubnode(self.button)
self.avatarNode.flatMap(self.contentContainer.addSubnode)
self.presenceManager = PeerPresenceStatusManager(update: { [weak self] in
self?.updateStatus()
@ -541,16 +524,12 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
strongSelf.titleCredibilityIconNode.layer.removeAnimation(forKey: "opacity")
strongSelf.titleNode.alpha = 0.4
strongSelf.activityNode.alpha = 0.4
strongSelf.titleCredibilityIconNode.alpha = 0.4
} else {
strongSelf.titleNode.alpha = 1.0
strongSelf.activityNode.alpha = 1.0
strongSelf.titleCredibilityIconNode.alpha = 1.0
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.activityNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.titleLeftIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.titleRightIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.titleCredibilityIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
@ -587,6 +566,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
let transition: ContainedViewLayoutTransition = .immediate
self.button.frame = clearBounds
self.contentContainer.frame = clearBounds
var leftIconWidth: CGFloat = 0.0
@ -604,7 +584,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
if let image = self.titleCredibilityIconNode.image {
if self.titleCredibilityIconNode.supernode == nil {
self.contentContainer.addSubnode(self.titleCredibilityIconNode)
self.titleNode.addSubnode(self.titleCredibilityIconNode)
}
credibilityIconWidth = image.size.width + 3.0
} else if self.titleCredibilityIconNode.supernode != nil {
@ -620,47 +600,67 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
self.titleRightIconNode.removeFromSupernode()
}
var leftInset: CGFloat = 12.0
if let avatarNode = self.avatarNode {
let avatarSize = CGSize(width: 37.0, height: 37.0)
let avatarFrame = CGRect(origin: CGPoint(x: leftInset + 10.0, y: floor((size.height - avatarSize.height) / 2.0)), size: avatarSize)
avatarNode.frame = avatarFrame
if self.displayAvatar {
leftInset += avatarSize.width + 10.0 + 8.0
}
}
self.button.frame = CGRect(origin: CGPoint(x: leftInset - 20.0, y: 0.0), size: CGSize(width: clearBounds.width - leftInset, height: size.height))
let titleSideInset: CGFloat = 3.0
var titleSize = self.titleNode.updateLayout(CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0 - leftInset, height: size.height))
titleSize.width += credibilityIconWidth
let activitySize = self.activityNode.updateLayout(clearBounds.size, alignment: .left)
let titleInfoSpacing: CGFloat = 0.0
var titleFrame: CGRect
if activitySize.height.isZero {
titleFrame = CGRect(origin: CGPoint(x: leftInset + leftIconWidth, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
self.titleNode.frame = titleFrame
if size.height > 40.0 {
var titleSize = self.titleNode.updateLayout(CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height))
titleSize.width += credibilityIconWidth
let activitySize = self.activityNode.updateLayout(clearBounds.size, alignment: .center)
let titleInfoSpacing: CGFloat = 0.0
var titleFrame: CGRect
if activitySize.height.isZero {
titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
if titleFrame.size.width < size.width {
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
}
self.titleNode.frame = titleFrame
} else {
let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing
titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
if titleFrame.size.width < size.width {
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
}
titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth)
self.titleNode.frame = titleFrame
var activityFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - activitySize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize)
if activitySize.width < size.width {
activityFrame.origin.x = -clearBounds.minX + floor((size.width - activityFrame.width) / 2.0)
}
self.activityNode.frame = activityFrame
}
if let image = self.titleLeftIconNode.image {
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size)
}
if let image = self.titleCredibilityIconNode.image {
self.titleCredibilityIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width - image.size.width - 1.0, y: 2.0), size: image.size)
}
if let image = self.titleRightIconNode.image {
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0, y: 6.0), size: image.size)
}
} else {
let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing
let titleSize = self.titleNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0), height: size.height))
let activitySize = self.activityNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height), alignment: .center)
titleFrame = CGRect(origin: CGPoint(x: leftInset + leftIconWidth, y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
let titleInfoSpacing: CGFloat = 8.0
let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing
let titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
self.titleNode.frame = titleFrame
self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize)
var activityFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize)
self.activityNode.frame = activityFrame
}
if let image = self.titleLeftIconNode.image {
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size)
}
if let image = self.titleCredibilityIconNode.image {
self.titleCredibilityIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width - 1.0, y: titleFrame.minY + 2.0), size: image.size)
}
if let image = self.titleRightIconNode.image {
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0, y: 6.0), size: image.size)
if let image = self.titleLeftIconNode.image {
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size)
}
if let image = self.titleCredibilityIconNode.image {
self.titleCredibilityIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width - 1.0, y: titleFrame.minY + 6.0), size: image.size)
}
if let image = self.titleRightIconNode.image {
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width - 1.0, y: titleFrame.minY + 6.0), size: image.size)
}
}
}

View File

@ -0,0 +1,75 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
private final class MultiScaleTextStateNode: ASDisplayNode {
let textNode: ImmediateTextNode
var currentLayout: MultiScaleTextLayout?
override init() {
self.textNode = ImmediateTextNode()
super.init()
self.addSubnode(self.textNode)
}
}
final class MultiScaleTextState {
let attributedText: NSAttributedString
let constrainedSize: CGSize
init(attributedText: NSAttributedString, constrainedSize: CGSize) {
self.attributedText = attributedText
self.constrainedSize = constrainedSize
}
}
struct MultiScaleTextLayout {
var size: CGSize
}
final class MultiScaleTextNode: ASDisplayNode {
private let stateNodes: [AnyHashable: MultiScaleTextStateNode]
init(stateKeys: [AnyHashable]) {
self.stateNodes = Dictionary(stateKeys.map { ($0, MultiScaleTextStateNode()) }, uniquingKeysWith: { lhs, _ in lhs })
super.init()
for (_, node) in self.stateNodes {
self.addSubnode(node)
}
}
func updateLayout(states: [AnyHashable: MultiScaleTextState]) -> [AnyHashable: MultiScaleTextLayout] {
assert(Set(states.keys) == Set(self.stateNodes.keys))
var result: [AnyHashable: MultiScaleTextLayout] = [:]
for (key, state) in states {
if let node = self.stateNodes[key] {
node.textNode.attributedText = state.attributedText
let nodeSize = node.textNode.updateLayout(state.constrainedSize)
let nodeLayout = MultiScaleTextLayout(size: nodeSize)
node.currentLayout = nodeLayout
node.textNode.frame = CGRect(origin: CGPoint(x: -nodeSize.width / 2.0, y: -nodeSize.height / 2.0), size: nodeSize)
result[key] = nodeLayout
}
}
return result
}
func update(stateFractions: [AnyHashable: CGFloat], transition: ContainedViewLayoutTransition) {
var fractionSum: CGFloat = 0.0
for (_, fraction) in stateFractions {
fractionSum += fraction
}
for (key, fraction) in stateFractions {
if let node = self.stateNodes[key], let nodeLayout = node.currentLayout {
transition.updateAlpha(node: node, alpha: fraction / fractionSum)
}
}
}
}

View File

@ -33,6 +33,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
controller.scrollToEndOfHistory()
let _ = params.navigationController.popToViewController(controller, animated: params.animated)
params.completion()
} else if params.activateMessageSearch {
controller.activateSearch()
let _ = params.navigationController.popToViewController(controller, animated: params.animated)
params.completion()
} else {
let _ = params.navigationController.popToViewController(controller, animated: params.animated)
params.completion()
@ -65,6 +69,9 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, subject: params.subject, botStart: params.botStart)
}
controller.purposefulAction = params.purposefulAction
if params.activateMessageSearch {
controller.activateSearch()
}
let resolvedKeepStack: Bool
switch params.keepStack {
case .default:

View File

@ -0,0 +1,57 @@
import AsyncDisplayKit
import Display
import TelegramPresentationData
final class PeerInfoScreenCommentItem: PeerInfoScreenItem {
let id: AnyHashable
let text: String
init(id: AnyHashable, text: String) {
self.id = id
self.text = text
}
func node() -> PeerInfoScreenItemNode {
return PeerInfoScreenCommentItemNode()
}
}
private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode {
private let textNode: ImmediateTextNode
private var item: PeerInfoScreenCommentItem?
override init() {
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.textNode)
}
override func update(width: CGFloat, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat {
guard let item = item as? PeerInfoScreenCommentItem else {
return 10.0
}
self.item = item
let sideInset: CGFloat = 16.0
let verticalInset: CGFloat = 7.0
self.textNode.maximumNumberOfLines = 0
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(14.0), textColor: presentationData.theme.list.freeTextColor)
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: textSize)
let height = textSize.height + verticalInset * 2.0
transition.updateFrame(node: self.textNode, frame: textFrame)
return height
}
}

View File

@ -372,6 +372,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
)
self.itemInteraction.selectedMessageIds = chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.showsVerticalScrollIndicator = false
if #available(iOS 11.0, *) {
self.scrollNode.view.contentInsetAdjustmentBehavior = .never

View File

@ -9,34 +9,47 @@ import PeerPresenceStatusManager
import TelegramStringFormatting
import TelegramPresentationData
enum PeerInfoUpdatingAvatar {
case none
case image(TelegramMediaImageRepresentation)
}
final class PeerInfoState {
let isEditing: Bool
let isSearching: Bool
let selectedMessageIds: Set<MessageId>?
let updatingAvatar: PeerInfoUpdatingAvatar?
init(
isEditing: Bool,
isSearching: Bool,
selectedMessageIds: Set<MessageId>?
selectedMessageIds: Set<MessageId>?,
updatingAvatar: PeerInfoUpdatingAvatar?
) {
self.isEditing = isEditing
self.isSearching = isSearching
self.selectedMessageIds = selectedMessageIds
self.updatingAvatar = updatingAvatar
}
func withIsEditing(_ isEditing: Bool) -> PeerInfoState {
return PeerInfoState(
isEditing: isEditing,
isSearching: self.isSearching,
selectedMessageIds: self.selectedMessageIds
selectedMessageIds: self.selectedMessageIds,
updatingAvatar: self.updatingAvatar
)
}
func withSelectedMessageIds(_ selectedMessageIds: Set<MessageId>?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
isSearching: self.isSearching,
selectedMessageIds: selectedMessageIds
selectedMessageIds: selectedMessageIds,
updatingAvatar: self.updatingAvatar
)
}
func withUpdatingAvatar(_ updatingAvatar: PeerInfoUpdatingAvatar?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
updatingAvatar: updatingAvatar
)
}
}
@ -50,6 +63,7 @@ final class PeerInfoScreenData {
let isContact: Bool
let availablePanes: [PeerInfoPaneKey]
let groupsInCommon: [Peer]?
let linkedDiscussionPeer: Peer?
init(
peer: Peer?,
@ -59,7 +73,8 @@ final class PeerInfoScreenData {
globalNotificationSettings: GlobalNotificationSettings?,
isContact: Bool,
availablePanes: [PeerInfoPaneKey],
groupsInCommon: [Peer]?
groupsInCommon: [Peer]?,
linkedDiscussionPeer: Peer?
) {
self.peer = peer
self.cachedData = cachedData
@ -69,12 +84,21 @@ final class PeerInfoScreenData {
self.isContact = isContact
self.availablePanes = availablePanes
self.groupsInCommon = groupsInCommon
self.linkedDiscussionPeer = linkedDiscussionPeer
}
}
enum PeerInfoScreenInputUserKind {
case user
case bot
case support
}
enum PeerInfoScreenInputData: Equatable {
case none
case user(userId: PeerId, secretChatId: PeerId?, isBot: Bool)
case user(userId: PeerId, secretChatId: PeerId?, kind: PeerInfoScreenInputUserKind)
case channel
case group(isSupergroup: Bool)
}
func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<[PeerInfoPaneKey], NoError> {
@ -82,7 +106,7 @@ func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId) -> Sig
(.photoOrVideo, .media),
(.file, .files),
(.music, .music),
(.voiceOrInstantVideo, .voice),
//(.voiceOrInstantVideo, .voice),
(.webPage, .links)
]
return combineLatest(tags.map { tagAndKey -> Signal<PeerInfoPaneKey?, NoError> in
@ -127,9 +151,25 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
return .none
}
if let user = peer as? TelegramUser {
return .user(userId: user.id, secretChatId: nil, isBot: user.botInfo != nil)
let kind: PeerInfoScreenInputUserKind
if user.flags.contains(.isSupport) {
kind = .support
} else if user.botInfo != nil {
kind = .bot
} else {
kind = .user
}
return .user(userId: user.id, secretChatId: nil, kind: kind)
} else if let channel = peer as? TelegramChannel {
if case .group = channel.info {
return .group(isSupergroup: true)
} else {
return .channel
}
} else if let _ = peer as? TelegramGroup {
return .group(isSupergroup: false)
} else {
preconditionFailure()
return .none
}
}
|> distinctUntilChanged
@ -144,23 +184,26 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
globalNotificationSettings: nil,
isContact: false,
availablePanes: [],
groupsInCommon: nil
groupsInCommon: nil,
linkedDiscussionPeer: nil
))
case let .user(peerId, secretChatId, isBot):
case let .user(peerId, secretChatId, kind):
let groupsInCommonSignal: Signal<[Peer]?, NoError>
if isBot {
groupsInCommonSignal = .single([])
} else {
switch kind {
case .user:
groupsInCommonSignal = .single(nil)
|> then(
groupsInCommon(account: context.account, peerId: peerId)
|> map(Optional.init)
)
default:
groupsInCommonSignal = .single([])
}
enum StatusInputData: Equatable {
case none
case presence(TelegramUserPresence)
case bot
case support
}
let status = Signal<PeerInfoStatusData?, NoError> { subscriber in
class Manager {
@ -188,12 +231,12 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
if user.isDeleted {
return .none
}
if user.flags.contains(.isSupport) {
return .support
}
if user.botInfo != nil {
return .bot
}
if user.flags.contains(.isSupport) {
return .none
}
guard let presence = view.peerPresences[peerId] as? TelegramUserPresence else {
return .none
}
@ -203,6 +246,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
switch inputData {
case .bot:
subscriber.putNext(PeerInfoStatusData(text: strings.Bot_GenericBotStatus, isActivity: false))
case .support:
subscriber.putNext(PeerInfoStatusData(text: strings.Bot_GenericSupportStatus, isActivity: false))
default:
var presence: TelegramUserPresence?
if case let .presence(value) = inputData {
@ -268,13 +313,124 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
globalNotificationSettings: globalNotificationSettings,
isContact: peerView.peerIsContact,
availablePanes: availablePanes,
groupsInCommon: groupsInCommon
groupsInCommon: groupsInCommon,
linkedDiscussionPeer: nil
)
}
case .channel:
let status = context.account.viewTracker.peerView(peerId, updateData: false)
|> map { peerView -> PeerInfoStatusData? in
guard let channel = peerView.peers[peerId] as? TelegramChannel else {
return PeerInfoStatusData(text: strings.Channel_Status, isActivity: false)
}
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount != 0 {
return PeerInfoStatusData(text: strings.Conversation_StatusSubscribers(memberCount), isActivity: false)
} else {
return PeerInfoStatusData(text: strings.Channel_Status, isActivity: false)
}
}
|> distinctUntilChanged
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
var combinedKeys: [PostboxViewKey] = []
combinedKeys.append(globalNotificationsKey)
return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId),
context.account.postbox.combinedView(keys: combinedKeys),
status
)
|> map { peerView, availablePanes, combinedView, status -> PeerInfoScreenData in
var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
globalNotificationSettings = settings
}
}
var discussionPeer: Peer?
if let linkedDiscussionPeerId = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
discussionPeer = peer
}
return PeerInfoScreenData(
peer: peerView.peers[peerId],
cachedData: peerView.cachedData,
status: status,
notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings,
globalNotificationSettings: globalNotificationSettings,
isContact: peerView.peerIsContact,
availablePanes: availablePanes,
groupsInCommon: [],
linkedDiscussionPeer: discussionPeer
)
}
case .group:
let status = context.account.viewTracker.peerView(peerId, updateData: false)
|> map { peerView -> PeerInfoStatusData? in
guard let channel = peerView.peers[peerId] as? TelegramChannel else {
return PeerInfoStatusData(text: strings.Channel_Status, isActivity: false)
}
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount != 0 {
return PeerInfoStatusData(text: strings.Conversation_StatusMembers(memberCount), isActivity: false)
} else {
return PeerInfoStatusData(text: strings.Group_Status, isActivity: false)
}
}
|> distinctUntilChanged
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
var combinedKeys: [PostboxViewKey] = []
combinedKeys.append(globalNotificationsKey)
return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId),
context.account.postbox.combinedView(keys: combinedKeys),
status
)
|> map { peerView, availablePanes, combinedView, status -> PeerInfoScreenData in
var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
globalNotificationSettings = settings
}
}
return PeerInfoScreenData(
peer: peerView.peers[peerId],
cachedData: peerView.cachedData,
status: status,
notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings,
globalNotificationSettings: globalNotificationSettings,
isContact: peerView.peerIsContact,
availablePanes: availablePanes,
groupsInCommon: [],
linkedDiscussionPeer: nil
)
}
}
}
}
func canEditPeerInfo(peer: Peer?) -> Bool {
if let channel = peer as? TelegramChannel {
if channel.hasPermission(.changeInfo) {
return true
}
} else if let group = peer as? TelegramGroup {
switch group.role {
case .admin, .creator:
return true
case .member:
break
}
if !group.hasBannedPermission(.banChangeInfo) {
return true
}
}
return false
}
func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?) -> [PeerInfoHeaderButtonKey] {
var result: [PeerInfoHeaderButtonKey] = []
if let user = peer as? TelegramUser {
@ -291,6 +447,62 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?) -> [PeerInf
if !user.isDeleted, user.botInfo == nil && !user.flags.contains(.isSupport) {
result.append(.more)
}
} else if let channel = peer as? TelegramChannel {
var canEditGroupInfo = false
var canEditMembers = false
var canAddMembers = false
var isPublic = false
var isCreator = false
isPublic = channel.username != nil
if !isPublic, let cachedChannelData = cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
isPublic = true
}
isCreator = channel.flags.contains(.isCreator)
if channel.hasPermission(.changeInfo) {
canEditGroupInfo = true
}
if channel.hasPermission(.banMembers) {
canEditMembers = true
}
if channel.hasPermission(.inviteMembers) {
canAddMembers = true
}
result.append(.mute)
result.append(.more)
} else if let group = peer as? TelegramGroup {
var canEditGroupInfo = false
var canEditMembers = false
var canAddMembers = false
var isPublic = false
var isCreator = false
if case .creator = group.role {
isCreator = true
}
switch group.role {
case .admin, .creator:
canEditGroupInfo = true
canEditMembers = true
canAddMembers = true
case .member:
break
}
if !group.hasBannedPermission(.banChangeInfo) {
canEditGroupInfo = true
}
if !group.hasBannedPermission(.banAddMembers) {
canAddMembers = true
}
if canAddMembers {
result.append(.addMember)
}
result.append(.mute)
result.append(.more)
}
return result
}
@ -301,6 +513,8 @@ func peerInfoCanEdit(peer: Peer?, cachedData: CachedPeerData?) -> Bool {
return false
}
return true
} else if peer is TelegramChannel || peer is TelegramGroup {
return true
}
return false
}

View File

@ -12,6 +12,7 @@ import TelegramPresentationData
import PhotoResources
import PeerAvatarGalleryUI
import TelegramStringFormatting
import ActivityIndicator
enum PeerInfoHeaderButtonKey: Hashable {
case message
@ -154,25 +155,19 @@ enum PeerInfoAvatarListItem: Equatable {
}
final class PeerInfoAvatarListItemNode: ASDisplayNode {
private let context: AccountContext
let imageNode: TransformImageNode
let isReady = Promise<Bool>()
private var didSetReady: Bool = false
init(context: AccountContext, item: PeerInfoAvatarListItem) {
init(context: AccountContext) {
self.context = context
self.imageNode = TransformImageNode()
super.init()
self.addSubnode(self.imageNode)
let representations: [ImageRepresentationWithReference]
switch item {
case let .topImage(topRepresentations):
representations = topRepresentations
case let .image(_, imageRepresentations):
representations = imageRepresentations
}
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: context.account, representations: representations, autoFetchFullSize: true), dispatchOnDisplayLink: false)
self.imageNode.imageUpdated = { [weak self] _ in
guard let strongSelf = self else {
@ -185,6 +180,17 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
}
}
func setup(item: PeerInfoAvatarListItem, synchronous: Bool) {
let representations: [ImageRepresentationWithReference]
switch item {
case let .topImage(topRepresentations):
representations = topRepresentations
case let .image(_, imageRepresentations):
representations = imageRepresentations
}
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, autoFetchFullSize: true, attemptSynchronously: synchronous), attemptSynchronously: synchronous, dispatchOnDisplayLink: false)
}
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
let imageSize = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
let makeLayout = self.imageNode.asyncLayout()
@ -198,13 +204,23 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
private let context: AccountContext
let controlsContainerNode: ASDisplayNode
let controlsContainerTransformNode: ASDisplayNode
let shadowNode: ASDisplayNode
let controlsClippingNode: ASDisplayNode
let controlsClippingOffsetNode: ASDisplayNode
let shadowNode: ASImageNode
let contentNode: ASDisplayNode
let leftHighlightNode: ASImageNode
let rightHighlightNode: ASImageNode
var highlightedSide: Bool?
let stripContainerNode: ASDisplayNode
let highlightContainerNode: ASDisplayNode
private(set) var galleryEntries: [AvatarGalleryEntry] = []
private var items: [PeerInfoAvatarListItem] = []
private var itemNodes: [WrappedMediaResourceId: PeerInfoAvatarListItemNode] = [:]
private var stripNodes: [ASImageNode] = []
private let inactiveStripImage: UIImage
private let activeStripImage: UIImage
private var appliedStripNodeCurrentIndex: Int?
private var currentIndex: Int = 0
private var transitionFraction: CGFloat = 0.0
@ -229,14 +245,99 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.contentNode = ASDisplayNode()
self.leftHighlightNode = ASImageNode()
self.leftHighlightNode.displaysAsynchronously = false
self.leftHighlightNode.displayWithoutProcessing = true
self.leftHighlightNode.contentMode = .scaleToFill
self.leftHighlightNode.image = generateImage(CGSize(width: 88.0, height: 1.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let topColor = UIColor(rgb: 0x000000, alpha: 0.5)
let bottomColor = UIColor(rgb: 0x000000, alpha: 0.0)
var locations: [CGFloat] = [0.0, 1.0]
let colors: [CGColor] = [topColor.cgColor, bottomColor.cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
})
self.leftHighlightNode.isHidden = true
self.rightHighlightNode = ASImageNode()
self.rightHighlightNode.displaysAsynchronously = false
self.rightHighlightNode.displayWithoutProcessing = true
self.rightHighlightNode.contentMode = .scaleToFill
self.rightHighlightNode.image = generateImage(CGSize(width: 88.0, height: 1.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let topColor = UIColor(rgb: 0x000000, alpha: 0.5)
let bottomColor = UIColor(rgb: 0x000000, alpha: 0.0)
var locations: [CGFloat] = [0.0, 1.0]
let colors: [CGColor] = [topColor.cgColor, bottomColor.cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: size.width, y: 0.0), end: CGPoint(x: 0.0, y: 0.0), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
})
self.rightHighlightNode.isHidden = true
self.stripContainerNode = ASDisplayNode()
self.contentNode.addSubnode(self.stripContainerNode)
self.inactiveStripImage = generateStretchableFilledCircleImage(diameter: 3.0, color: UIColor(white: 1.0, alpha: 0.2))!
self.activeStripImage = generateStretchableFilledCircleImage(diameter: 3.0, color: .white)!
self.highlightContainerNode = ASDisplayNode()
self.highlightContainerNode.addSubnode(self.leftHighlightNode)
self.highlightContainerNode.addSubnode(self.rightHighlightNode)
self.controlsContainerNode = ASDisplayNode()
self.controlsContainerNode.isUserInteractionEnabled = false
self.controlsContainerTransformNode = ASDisplayNode()
self.controlsContainerTransformNode.isUserInteractionEnabled = false
self.controlsClippingOffsetNode = ASDisplayNode()
self.shadowNode = ASDisplayNode()
//self.shadowNode.backgroundColor = .green
self.controlsClippingNode = ASDisplayNode()
self.controlsClippingNode.isUserInteractionEnabled = false
self.controlsClippingNode.clipsToBounds = true
self.shadowNode = ASImageNode()
self.shadowNode.displaysAsynchronously = false
self.shadowNode.displayWithoutProcessing = true
self.shadowNode.contentMode = .scaleToFill
do {
let size = CGSize(width: 88.0, height: 88.0)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
if let context = UIGraphicsGetCurrentContext() {
context.clip(to: CGRect(origin: CGPoint(), size: size))
let topColor = UIColor(rgb: 0x000000, alpha: 0.4)
let bottomColor = UIColor(rgb: 0x000000, alpha: 0.0)
var locations: [CGFloat] = [0.0, 1.0]
let colors: [CGColor] = [topColor.cgColor, bottomColor.cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let image = image {
self.shadowNode.image = generateImage(image.size, contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.rotate(by: -CGFloat.pi / 2.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size))
})
}
}
}
super.init()
@ -244,12 +345,52 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.addSubnode(self.contentNode)
self.controlsContainerNode.addSubnode(self.highlightContainerNode)
self.controlsContainerNode.addSubnode(self.shadowNode)
self.controlsContainerTransformNode.addSubnode(self.controlsContainerNode)
self.addSubnode(self.controlsContainerTransformNode)
self.controlsContainerNode.addSubnode(self.stripContainerNode)
self.controlsClippingNode.addSubnode(self.controlsContainerNode)
self.controlsClippingOffsetNode.addSubnode(self.controlsClippingNode)
self.view.disablesInteractiveTransitionGestureRecognizer = true
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
guard let strongSelf = self else {
return false
}
return strongSelf.currentIndex != 0
}
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.tapActionAtPoint = { _ in
return .keepWithSingleTap
}
recognizer.highlight = { [weak self] point in
guard let strongSelf = self, let size = strongSelf.validLayout else {
return
}
var highlightedSide: Bool?
if let point = point {
if point.x < size.width * 1.0 / 5.0 {
if strongSelf.currentIndex != 0 {
highlightedSide = false
}
} else if point.x > size.width * 4.0 / 5.0 {
if strongSelf.currentIndex < strongSelf.items.count - 1 || strongSelf.items.count > 1 {
highlightedSide = true
}
}
}
if strongSelf.highlightedSide != highlightedSide {
strongSelf.highlightedSide = highlightedSide
if let highlightedSide = highlightedSide {
strongSelf.leftHighlightNode.isHidden = highlightedSide
strongSelf.rightHighlightNode.isHidden = !highlightedSide
} else {
strongSelf.leftHighlightNode.isHidden = true
strongSelf.rightHighlightNode.isHidden = true
}
}
}
self.view.addGestureRecognizer(recognizer)
}
deinit {
@ -267,6 +408,32 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
}
}
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
if let size = self.validLayout, case .tap = gesture {
if location.x < size.width * 1.0 / 5.0 {
if self.currentIndex != 0 {
self.currentIndex -= 1
self.updateItems(size: size, transition: .immediate)
}
} else if location.x > size.width * 4.0 / 5.0 {
if self.currentIndex < self.items.count - 1 {
self.currentIndex += 1
self.updateItems(size: size, transition: .immediate)
} else if self.items.count > 1 {
self.currentIndex = 0
self.updateItems(size: size, transition: .immediate, synchronous: true)
}
}
}
}
default:
break
}
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .changed:
@ -309,6 +476,10 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
func update(size: CGSize, peer: Peer?, transition: ContainedViewLayoutTransition) {
self.validLayout = size
self.leftHighlightNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: floor(size.width * 1.0 / 5.0), height: size.height))
self.rightHighlightNode.frame = CGRect(origin: CGPoint(x: size.width - floor(size.width * 1.0 / 5.0), y: 0.0), size: CGSize(width: floor(size.width * 1.0 / 5.0), height: size.height))
if let peer = peer, !self.initializedList {
self.initializedList = true
self.disposable.set((fetchedAvatarGalleryEntries(account: self.context.account, peer: peer)
@ -341,7 +512,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.updateItems(size: size, transition: transition)
}
private func updateItems(size: CGSize, transition: ContainedViewLayoutTransition) {
private func updateItems(size: CGSize, transition: ContainedViewLayoutTransition, synchronous: Bool = false) {
var validIds: [WrappedMediaResourceId] = []
var addedItemNodesForAdditiveTransition: [PeerInfoAvatarListItemNode] = []
var additiveTransitionOffset: CGFloat = 0.0
@ -354,7 +525,8 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
itemNode = current
} else {
wasAdded = true
itemNode = PeerInfoAvatarListItemNode(context: self.context, item: self.items[i])
itemNode = PeerInfoAvatarListItemNode(context: self.context)
itemNode.setup(item: self.items[i], synchronous: synchronous && i == self.currentIndex)
self.itemNodes[self.items[i].id] = itemNode
self.contentNode.addSubnode(itemNode)
}
@ -387,6 +559,52 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
}
}
if self.stripNodes.count != self.items.count {
if self.stripNodes.count < self.items.count {
for _ in 0 ..< self.items.count - self.stripNodes.count {
let stripNode = ASImageNode()
stripNode.displaysAsynchronously = false
stripNode.displayWithoutProcessing = true
if stripNodes.count == self.currentIndex {
stripNode.image = self.activeStripImage
} else {
stripNode.image = self.inactiveStripImage
}
self.stripNodes.append(stripNode)
self.stripContainerNode.addSubnode(stripNode)
}
} else {
for i in (self.items.count ..< self.stripNodes.count).reversed() {
self.stripNodes[i].removeFromSupernode()
self.stripNodes.remove(at: i)
}
}
}
if self.appliedStripNodeCurrentIndex != self.currentIndex {
if let appliedStripNodeCurrentIndex = self.appliedStripNodeCurrentIndex {
if appliedStripNodeCurrentIndex >= 0 && appliedStripNodeCurrentIndex < self.stripNodes.count {
self.stripNodes[appliedStripNodeCurrentIndex].image = self.inactiveStripImage
}
}
self.appliedStripNodeCurrentIndex = self.currentIndex
if self.currentIndex >= 0 && self.currentIndex < self.stripNodes.count {
self.stripNodes[self.currentIndex].image = self.activeStripImage
}
}
let stripInset: CGFloat = 5.0
let stripSpacing: CGFloat = 4.0
let stripWidth: CGFloat = floor((size.width - stripInset * 2.0 - stripSpacing * CGFloat(self.stripNodes.count - 1)) / CGFloat(self.stripNodes.count))
var stripX: CGFloat = stripInset
for i in 0 ..< self.stripNodes.count {
if i == 0 && self.stripNodes.count == 1 {
self.stripNodes[i].isHidden = true
} else {
self.stripNodes[i].isHidden = false
}
self.stripNodes[i].frame = CGRect(origin: CGPoint(x: stripX, y: 0.0), size: CGSize(width: stripWidth, height: 3.0))
stripX += stripWidth + stripSpacing
}
if let item = self.items.first, let itemNode = self.itemNodes[item.id] {
if !self.didSetReady {
self.didSetReady = true
@ -429,7 +647,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if peer.isDeleted {
overrideImage = .deletedIcon
}
self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: 100.0, height: 100.0))
self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: 100.0, height: 100.0), storeUnrounded: true)
self.isFirstAvatarLoading = false
}
}
@ -439,6 +657,9 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
let context: AccountContext
let avatarNode: AvatarNode
private let updatingAvatarOverlay: ASImageNode
private let activityIndicator: ActivityIndicator
var tapped: (() -> Void)?
init(context: AccountContext) {
@ -446,10 +667,24 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
let avatarFont = avatarPlaceholderFont(size: floor(100.0 * 16.0 / 37.0))
self.avatarNode = AvatarNode(font: avatarFont)
self.updatingAvatarOverlay = ASImageNode()
self.updatingAvatarOverlay.displayWithoutProcessing = true
self.updatingAvatarOverlay.displaysAsynchronously = false
self.updatingAvatarOverlay.isHidden = true
self.activityIndicator = ActivityIndicator(type: .custom(.white, 22.0, 1.0, false))
self.activityIndicator.isHidden = true
super.init()
self.addSubnode(self.avatarNode)
self.avatarNode.frame = CGRect(origin: CGPoint(x: -50.0, y: -50.0), size: CGSize(width: 100.0, height: 100.0))
self.updatingAvatarOverlay.frame = self.avatarNode.frame
let indicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
self.activityIndicator.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(self.avatarNode.frame.midX - indicatorSize.width / 2.0), y: floorToScreenPixels(self.avatarNode.frame.midY - indicatorSize.height / 2.0)), size: indicatorSize)
self.addSubnode(self.updatingAvatarOverlay)
self.addSubnode(self.activityIndicator)
self.avatarNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
@ -460,11 +695,42 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
}
}
func update(peer: Peer?, theme: PresentationTheme) {
func update(peer: Peer?, updatingAvatar: PeerInfoUpdatingAvatar?, theme: PresentationTheme) {
if let peer = peer {
self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, synchronousLoad: false, displayDimensions: CGSize(width: 100.0, height: 100.0))
let overrideImage: AvatarNodeImageOverride?
if canEditPeerInfo(peer: peer) {
if let updatingAvatar = updatingAvatar {
switch updatingAvatar {
case let .image(representation):
overrideImage = .image(representation)
case .none:
overrideImage = .none
}
self.activityIndicator.isHidden = false
self.updatingAvatarOverlay.isHidden = false
if self.updatingAvatarOverlay.image == nil {
self.updatingAvatarOverlay.image = generateFilledCircleImage(diameter: 100.0, color: UIColor(white: 0.0, alpha: 0.4), backgroundColor: nil)
}
} else {
overrideImage = .editAvatarIcon
self.activityIndicator.isHidden = true
self.updatingAvatarOverlay.isHidden = true
}
} else {
overrideImage = nil
self.activityIndicator.isHidden = true
self.updatingAvatarOverlay.isHidden = true
}
self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, synchronousLoad: false, displayDimensions: CGSize(width: 100.0, height: 100.0))
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.avatarNode.frame.contains(point) {
return self.avatarNode.view
}
return super.hitTest(point, with: event)
}
}
final class PeerInfoAvatarListNode: ASDisplayNode {
@ -534,7 +800,10 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
}
func animateAvatarCollapse(transition: ContainedViewLayoutTransition) {
if let currentItemNode = self.listContainerNode.currentItemNode, let avatarCopyView = self.avatarContainerNode.avatarNode.view.snapshotContentTree(), case let .animated(duration, curve) = transition {
if let currentItemNode = self.listContainerNode.currentItemNode, let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage, case let .animated(duration, curve) = transition {
let avatarCopyView = UIImageView()
avatarCopyView.image = unroundedImage
avatarCopyView.frame = self.avatarContainerNode.avatarNode.frame
avatarCopyView.center = currentItemNode.imageNode.position
currentItemNode.view.addSubview(avatarCopyView)
let scale = currentItemNode.imageNode.bounds.height / avatarCopyView.bounds.height
@ -854,7 +1123,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
return self.itemNodes[key]?.text
}
func update(width: CGFloat, safeInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, peer: Peer?, isContact: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat {
func update(width: CGFloat, safeInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, peer: Peer?, cachedData: CachedPeerData?, isContact: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat {
let avatarSize: CGFloat = 100.0
let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 10.0), size: CGSize(width: avatarSize, height: avatarSize))
transition.updateFrameAdditiveToCenter(node: self.avatarNode, frame: CGRect(origin: avatarFrame.center, size: CGSize()))
@ -869,6 +1138,16 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
fieldKeys.append(.lastName)
}
}
} else if let _ = peer as? TelegramGroup {
fieldKeys.append(.title)
if canEditPeerInfo(peer: peer) {
fieldKeys.append(.description)
}
} else if let _ = peer as? TelegramChannel {
fieldKeys.append(.title)
if canEditPeerInfo(peer: peer) {
fieldKeys.append(.description)
}
}
var hasPrevious = false
for key in fieldKeys {
@ -883,9 +1162,15 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
case .lastName:
updateText = (peer as? TelegramUser)?.lastName ?? ""
case .title:
updateText = (peer as? TelegramUser)?.debugDisplayTitle ?? ""
updateText = peer?.debugDisplayTitle ?? ""
case .description:
break
if let cachedData = cachedData as? CachedChannelData {
updateText = cachedData.about ?? ""
} else if let cachedData = cachedData as? CachedGroupData {
updateText = cachedData.about ?? ""
} else {
updateText = ""
}
}
itemNode = PeerInfoHeaderSingleLineTextFieldNode()
self.itemNodes[key] = itemNode
@ -902,8 +1187,10 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
isEnabled = isContact
case .title:
placeholder = "Title"
isEnabled = canEditPeerInfo(peer: peer)
case .description:
placeholder = "Description"
isEnabled = canEditPeerInfo(peer: peer)
}
let itemHeight = itemNode.update(width: width, safeInset: safeInset, hasPrevious: hasPrevious, placeholder: placeholder, isEnabled: isEnabled, presentationData: presentationData, updateText: updateText)
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: itemHeight)))
@ -951,6 +1238,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var performButtonAction: ((PeerInfoHeaderButtonKey) -> Void)?
var requestAvatarExpansion: (([AvatarGalleryEntry], (ASDisplayNode, CGRect, () -> (UIView?, UIView?))) -> Void)?
var requestOpenAvatarForEditing: (() -> Void)?
var navigationTransition: PeerInfoHeaderNavigationTransition?
@ -999,6 +1287,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.subtitleNodeContainer.addSubnode(self.subtitleNode)
self.regularContentNode.addSubnode(self.subtitleNodeContainer)
self.regularContentNode.addSubnode(self.avatarListNode)
self.regularContentNode.addSubnode(self.avatarListNode.listContainerNode.controlsClippingOffsetNode)
self.addSubnode(self.regularContentNode)
self.addSubnode(self.editingContentNode)
self.addSubnode(self.navigationButtonContainer)
@ -1012,6 +1301,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
return (avatarNode?.view.snapshotContentTree(unhide: true), nil)
}))
}
self.editingContentNode.avatarNode.tapped = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.requestOpenAvatarForEditing?()
}
}
func updateAvatarIsHidden(_ isHidden: Bool) {
@ -1029,7 +1324,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.regularContentNode.alpha = state.isEditing ? 0.0 : 1.0
self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0
let editingContentHeight = self.editingContentNode.update(width: width, safeInset: containerInset, statusBarHeight: statusBarHeight, navigationHeight: navigationHeight, peer: state.isEditing ? peer : nil, isContact: isContact, presentationData: presentationData, transition: transition)
let editingContentHeight = self.editingContentNode.update(width: width, safeInset: containerInset, statusBarHeight: statusBarHeight, navigationHeight: navigationHeight, peer: state.isEditing ? peer : nil, cachedData: cachedData, isContact: isContact, presentationData: presentationData, transition: transition)
transition.updateFrame(node: self.editingContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -contentOffset), size: CGSize(width: width, height: editingContentHeight)))
var transitionSourceHeight: CGFloat = 0.0
@ -1041,7 +1336,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.backgroundNode.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
self.expandedBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
if let navigationTransition = self.navigationTransition, let sourceAvatarNode = navigationTransition.sourceTitleView.avatarNode?.avatarNode {
if let navigationTransition = self.navigationTransition, let sourceAvatarNode = (navigationTransition.sourceNavigationBar.rightButtonNode.singleCustomNode as? ChatAvatarNavigationNode)?.avatarNode {
transitionSourceHeight = navigationTransition.sourceNavigationBar.bounds.height
transitionFraction = navigationTransition.fraction
transitionSourceAvatarFrame = sourceAvatarNode.view.convert(sourceAvatarNode.view.bounds, to: navigationTransition.sourceNavigationBar.view)
@ -1049,6 +1344,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transitionSourceSubtitleFrame = navigationTransition.sourceSubtitleFrame
transition.updateAlpha(node: self.expandedBackgroundNode, alpha: transitionFraction)
if self.isAvatarExpanded, case .animated = transition, transitionFraction == 1.0 {
self.avatarListNode.animateAvatarCollapse(transition: transition)
}
} else {
let backgroundTransitionFraction: CGFloat = max(0.0, min(1.0, contentOffset / (212.0)))
transition.updateAlpha(node: self.expandedBackgroundNode, alpha: backgroundTransitionFraction)
@ -1139,10 +1438,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.avatarListNode.listContainerNode.isHidden = false
if !transitionSourceAvatarFrame.width.isZero {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: transitionFraction * transitionSourceAvatarFrame.width / 2.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: transitionFraction * transitionSourceAvatarFrame.width / 2.0)
} else {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: 0.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: 0.0)
}
} else if self.avatarListNode.listContainerNode.cornerRadius != 50.0 {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: 50.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: 50.0, completion: { [weak self] _ in
guard let strongSelf = self else {
return
@ -1152,18 +1454,26 @@ final class PeerInfoHeaderNode: ASDisplayNode {
}
self.avatarListNode.update(size: CGSize(), isExpanded: self.isAvatarExpanded, peer: peer, theme: presentationData.theme, transition: transition)
self.editingContentNode.avatarNode.update(peer: peer, theme: presentationData.theme)
self.editingContentNode.avatarNode.update(peer: peer, updatingAvatar: state.updatingAvatar, theme: presentationData.theme)
if additive {
transition.updateSublayerTransformScaleAdditive(node: self.avatarListNode.avatarContainerNode, scale: avatarScale)
} else {
transition.updateSublayerTransformScale(node: self.avatarListNode.avatarContainerNode, scale: avatarScale)
}
let apparentAvatarFrame: CGRect
let controlsClippingFrame: CGRect
if self.isAvatarExpanded {
let expandedAvatarCenter = CGPoint(x: expandedAvatarListSize.width / 2.0, y: expandedAvatarListSize.height / 2.0 - contentOffset / 2.0)
apparentAvatarFrame = CGRect(origin: CGPoint(x: expandedAvatarCenter.x * (1.0 - transitionFraction) + transitionFraction * avatarCenter.x, y: expandedAvatarCenter.y * (1.0 - transitionFraction) + transitionFraction * avatarCenter.y), size: CGSize())
if !transitionSourceAvatarFrame.width.isZero {
let expandedFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedAvatarListSize)
controlsClippingFrame = CGRect(origin: CGPoint(x: transitionFraction * transitionSourceAvatarFrame.minX + (1.0 - transitionFraction) * expandedFrame.minX, y: transitionFraction * transitionSourceAvatarFrame.minY + (1.0 - transitionFraction) * expandedFrame.minY), size: CGSize(width: transitionFraction * transitionSourceAvatarFrame.width + (1.0 - transitionFraction) * expandedFrame.width, height: transitionFraction * transitionSourceAvatarFrame.height + (1.0 - transitionFraction) * expandedFrame.height))
} else {
controlsClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedAvatarListSize)
}
} else {
apparentAvatarFrame = CGRect(origin: CGPoint(x: avatarCenter.x - avatarFrame.width / 2.0, y: -contentOffset + avatarOffset + avatarCenter.y - avatarFrame.height / 2.0), size: avatarFrame.size)
controlsClippingFrame = apparentAvatarFrame
}
if case let .animated(duration, curve) = transition, !transitionSourceAvatarFrame.width.isZero, false {
let previousFrame = self.avatarListNode.frame
@ -1205,10 +1515,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transition.updateSublayerTransformScale(node: self.avatarListNode.listContainerNode, scale: innerScale)
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.contentNode, frame: CGRect(origin: CGPoint(x: innerDeltaX + expandedAvatarListSize.width / 2.0, y: innerDeltaY + expandedAvatarListSize.height / 2.0), size: CGSize()))
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.controlsContainerTransformNode, frame: CGRect(origin: CGPoint(x: expandedAvatarListSize.width / 2.0, y: expandedAvatarListSize.height / 2.0 - innerDeltaY), size: CGSize()))
transition.updateSublayerTransformScale(node: self.avatarListNode.listContainerNode.controlsContainerNode, scale: 1.0 / innerScale)
transition.updateSublayerTransformScaleAdditive(node: self.avatarListNode.listContainerNode.controlsContainerTransformNode, scale: 1.0 / avatarListContainerScale)
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.shadowNode, frame: CGRect(origin: CGPoint(x: -apparentAvatarFrame.minX, y: -apparentAvatarFrame.minY), size: CGSize(width: expandedAvatarListSize.width, height: navigationHeight)))
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.controlsClippingOffsetNode, frame: CGRect(origin: controlsClippingFrame.center, size: CGSize()))
transition.updateFrame(node: self.avatarListNode.listContainerNode.controlsClippingNode, frame: CGRect(origin: CGPoint(x: -controlsClippingFrame.width / 2.0, y: -controlsClippingFrame.height / 2.0), size: controlsClippingFrame.size))
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.controlsContainerNode, frame: CGRect(origin: CGPoint(x: -controlsClippingFrame.minX, y: -controlsClippingFrame.minY), size: CGSize(width: expandedAvatarListSize.width, height: expandedAvatarListSize.height)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.shadowNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: expandedAvatarListSize.width, height: navigationHeight + 20.0)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.stripContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusBarHeight + 2.0), size: CGSize(width: expandedAvatarListSize.width, height: 3.0)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.highlightContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: expandedAvatarListSize.width, height: expandedAvatarListSize.height)))
transition.updateAlpha(node: self.avatarListNode.listContainerNode.controlsContainerNode, alpha: self.isAvatarExpanded ? (1.0 - transitionFraction) : 0.0)
if additive {
transition.updateSublayerTransformScaleAdditive(node: self.avatarListNode.listContainerTransformNode, scale: avatarListContainerScale)
@ -1247,16 +1561,16 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let titleScale = (transitionFraction * transitionSourceTitleFrame.height + (1.0 - transitionFraction) * titleFrame.height * neutralTitleScale) / (titleFrame.height)
let subtitleScale = max(0.01, min(10.0, (transitionFraction * transitionSourceSubtitleFrame.height + (1.0 - transitionFraction) * subtitleFrame.height * neutralSubtitleScale) / (subtitleFrame.height)))
let titleOrigin = CGPoint(x: transitionFraction * transitionSourceTitleFrame.minX + (1.0 - transitionFraction) * titleFrame.minX, y: transitionFraction * transitionSourceTitleFrame.minY + (1.0 - transitionFraction) * titleFrame.minY)
let subtitleOrigin = CGPoint(x: transitionFraction * transitionSourceSubtitleFrame.minX + (1.0 - transitionFraction) * subtitleFrame.minX, y: transitionFraction * transitionSourceSubtitleFrame.minY + (1.0 - transitionFraction) * subtitleFrame.minY)
let titleCenter = CGPoint(x: transitionFraction * transitionSourceTitleFrame.midX + (1.0 - transitionFraction) * titleFrame.midX, y: transitionFraction * transitionSourceTitleFrame.midY + (1.0 - transitionFraction) * titleFrame.midY)
let subtitleCenter = CGPoint(x: transitionFraction * transitionSourceSubtitleFrame.midX + (1.0 - transitionFraction) * subtitleFrame.midX, y: transitionFraction * transitionSourceSubtitleFrame.midY + (1.0 - transitionFraction) * subtitleFrame.midY)
let rawTitleFrame = CGRect(origin: titleOrigin, size: titleFrame.size)
let rawTitleFrame = CGRect(origin: CGPoint(x: titleCenter.x - titleFrame.size.width * titleScale / 2.0, y: titleCenter.y - titleFrame.size.height * titleScale / 2.0), size: CGSize(width: titleFrame.size.width * titleScale, height: titleFrame.size.height * titleScale))
self.titleNodeRawContainer.frame = rawTitleFrame
transition.updateFrameAdditiveToCenter(node: self.titleNodeContainer, frame: rawTitleFrame.offsetBy(dx: rawTitleFrame.width * 0.5 * (titleScale - 1.0), dy: titleOffset + rawTitleFrame.height * 0.5 * (titleScale - 1.0)))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size))
let rawSubtitleFrame = CGRect(origin: subtitleOrigin, size: subtitleFrame.size)
transition.updateFrameAdditiveToCenter(node: self.titleNodeContainer, frame: CGRect(origin: rawTitleFrame.center, size: CGSize()))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: -titleFrame.size.width * 1.0 / titleScale / 2.0, y: -titleFrame.size.height * 1.0 / titleScale / 2.0), size: titleFrame.size))
let rawSubtitleFrame = CGRect(origin: CGPoint(x: subtitleCenter.x - subtitleFrame.size.width / 2.0, y: subtitleCenter.y - subtitleFrame.size.height / 2.0), size: subtitleFrame.size)
self.subtitleNodeRawContainer.frame = rawSubtitleFrame
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: rawSubtitleFrame.offsetBy(dx: rawSubtitleFrame.width * 0.5 * (subtitleScale - 1.0), dy: titleOffset + rawSubtitleFrame.height * 0.5 * (subtitleScale - 1.0)))
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: rawSubtitleFrame)
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(), size: subtitleFrame.size))
transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale)
transition.updateSublayerTransformScale(node: self.subtitleNodeContainer, scale: subtitleScale)

View File

@ -128,7 +128,12 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
super.init()
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
guard let strongSelf = self else {
return false
}
return strongSelf.scrollNode.view.contentOffset.x > .ulpOfOne
}
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.scrollsToTop = false
if #available(iOS 11.0, *) {
@ -249,7 +254,7 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
}
leftOffset += paneNodeSize.width + spacing
}
self.scrollNode.view.contentSize = CGSize(width: leftOffset + sideInset, height: size.height)
self.scrollNode.view.contentSize = CGSize(width: leftOffset - spacing + sideInset, height: size.height)
}
if let selectedFrame = selectedFrame {

View File

@ -28,6 +28,10 @@ import ContextUI
import OpenInExternalAppUI
import SafariServices
import GalleryUI
import LegacyUI
import MapResourceToAvatarSizes
import LegacyComponents
import WebSearchUI
protocol PeerInfoScreenItem: class {
var id: AnyHashable { get }
@ -77,6 +81,7 @@ private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode {
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
var contentHeight: CGFloat = 0.0
var contentWithBackgroundHeight: CGFloat = 0.0
for i in 0 ..< items.count {
let item = items[i]
@ -100,13 +105,28 @@ private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode {
let itemTransition: ContainedViewLayoutTransition = wasAdded ? .immediate : transition
let itemHeight = itemNode.update(width: width, presentationData: presentationData, item: item, topItem: i == 0 ? nil : items[i - 1], bottomItem: (i == items.count - 1) ? nil : items[i + 1], transition: itemTransition)
let bottomItem: PeerInfoScreenItem?
if i == items.count - 1 {
bottomItem = nil
} else if items[i + 1] is PeerInfoScreenCommentItem {
bottomItem = nil
} else {
bottomItem = items[i + 1]
}
let itemHeight = itemNode.update(width: width, presentationData: presentationData, item: item, topItem: i == 0 ? nil : items[i - 1], bottomItem: bottomItem, transition: itemTransition)
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: itemHeight))
itemTransition.updateFrame(node: itemNode, frame: itemFrame)
if wasAdded {
itemNode.alpha = 0.0
transition.updateAlpha(node: itemNode, alpha: 1.0)
}
if item is PeerInfoScreenCommentItem {
} else {
contentWithBackgroundHeight += itemHeight
}
contentHeight += itemHeight
}
@ -124,9 +144,9 @@ private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode {
}
}
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight)))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentWithBackgroundHeight)))
transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)))
transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: UIScreenPixel)))
transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundHeight), size: CGSize(width: width, height: UIScreenPixel)))
if contentHeight.isZero {
transition.updateAlpha(node: self.topSeparatorNode, alpha: 0.0)
@ -280,6 +300,12 @@ private enum PeerInfoBotCommand {
case privacy
}
private enum PeerInfoParticipantsSection {
case members
case admins
case banned
}
private final class PeerInfoInteraction {
let openUsername: (String) -> Void
let openPhone: (String) -> Void
@ -293,6 +319,10 @@ private final class PeerInfoInteraction {
let openShareBot: () -> Void
let openAddBotToGroup: () -> Void
let performBotCommand: (PeerInfoBotCommand) -> Void
let editingOpenPublicLinkSetup: () -> Void
let editingOpenDiscussionGroupSetup: () -> Void
let editingToggleMessageSignatures: (Bool) -> Void
let openParticipantsSection: (PeerInfoParticipantsSection) -> Void
init(
openUsername: @escaping (String) -> Void,
@ -306,7 +336,11 @@ private final class PeerInfoInteraction {
openReport: @escaping () -> Void,
openShareBot: @escaping () -> Void,
openAddBotToGroup: @escaping () -> Void,
performBotCommand: @escaping (PeerInfoBotCommand) -> Void
performBotCommand: @escaping (PeerInfoBotCommand) -> Void,
editingOpenPublicLinkSetup: @escaping () -> Void,
editingOpenDiscussionGroupSetup: @escaping () -> Void,
editingToggleMessageSignatures: @escaping (Bool) -> Void,
openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void
) {
self.openUsername = openUsername
self.openPhone = openPhone
@ -320,6 +354,10 @@ private final class PeerInfoInteraction {
self.openShareBot = openShareBot
self.openAddBotToGroup = openAddBotToGroup
self.performBotCommand = performBotCommand
self.editingOpenPublicLinkSetup = editingOpenPublicLinkSetup
self.editingOpenDiscussionGroupSetup = editingOpenDiscussionGroupSetup
self.editingToggleMessageSignatures = editingToggleMessageSignatures
self.openParticipantsSection = openParticipantsSection
}
}
@ -341,38 +379,11 @@ private func peerInfoSectionItems(data: PeerInfoScreenData?, presentationData: P
}
if let cachedData = data.cachedData as? CachedUserData {
if let about = cachedData.about {
items.append(PeerInfoScreenLabeledValueItem(id: 0, label: "bio", text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
items.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
}
}
if !data.isContact {
if let botInfo = user.botInfo {
if botInfo.flags.contains(.worksWithGroups) {
items.append(PeerInfoScreenActionItem(id: 6, text: presentationData.strings.UserInfo_InviteBotToGroup, action: {
interaction.openAddBotToGroup()
}))
}
items.append(PeerInfoScreenActionItem(id: 7, text: presentationData.strings.UserInfo_ShareBot, action: {
interaction.openShareBot()
}))
if let cachedData = data.cachedData as? CachedUserData, let botInfo = cachedData.botInfo {
for command in botInfo.commands {
if command.text == "settings" {
items.append(PeerInfoScreenActionItem(id: 8, text: presentationData.strings.UserInfo_BotSettings, action: {
interaction.performBotCommand(.settings)
}))
} else if command.text == "help" {
items.append(PeerInfoScreenActionItem(id: 9, text: presentationData.strings.UserInfo_BotHelp, action: {
interaction.performBotCommand(.help)
}))
} else if command.text == "privacy" {
items.append(PeerInfoScreenActionItem(id: 10, text: presentationData.strings.UserInfo_BotPrivacy, action: {
interaction.performBotCommand(.privacy)
}))
}
}
}
} else {
if user.botInfo == nil {
items.append(PeerInfoScreenActionItem(id: 3, text: "Add Contact", action: {
interaction.openAddContact()
}))
@ -397,6 +408,54 @@ private func peerInfoSectionItems(data: PeerInfoScreenData?, presentationData: P
}))
}
}
} else if let channel = data.peer as? TelegramChannel {
let ItemUsername = 1
let ItemAbout = 2
let ItemAdmins = 3
let ItemMembers = 4
let ItemBanned = 5
let ItemReport = 6
if let username = channel.username {
items.append(PeerInfoScreenLabeledValueItem(id: ItemUsername, label: presentationData.strings.Channel_LinkItem, text: "https://t.me/\(username)", textColor: .accent, action: {
interaction.openUsername(username)
}))
}
if let cachedData = data.cachedData as? CachedChannelData {
if let about = cachedData.about {
items.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
}
if case .broadcast = channel.info {
var canEditMembers = false
if channel.hasPermission(.banMembers) {
canEditMembers = true
}
if canEditMembers {
if channel.adminRights != nil || channel.flags.contains(.isCreator) {
let adminCount = cachedData.participantsSummary.adminCount ?? 0
let memberCount = cachedData.participantsSummary.memberCount ?? 0
let bannedCount = cachedData.participantsSummary.kickedCount ?? 0
items.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: "\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")", text: presentationData.strings.GroupInfo_Administrators, action: {
interaction.openParticipantsSection(.admins)
}))
items.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: "\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")", text: presentationData.strings.Channel_Info_Subscribers, action: {
interaction.openParticipantsSection(.members)
}))
items.append(PeerInfoScreenDisclosureItem(id: ItemBanned, label: "\(bannedCount == 0 ? "" : "\(presentationStringsFormattedNumber(bannedCount, presentationData.dateTimeFormat.groupingSeparator))")", text: presentationData.strings.GroupInfo_Permissions_Removed, action: {
interaction.openParticipantsSection(.banned)
}))
}
}
}
}
} else if let group = data.peer as? TelegramGroup {
if let cachedData = data.cachedData as? CachedGroupData {
if let about = cachedData.about {
items.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
}
}
}
return items
}
@ -407,34 +466,32 @@ private func editingInfoSectionItems(data: PeerInfoScreenData?, presentationData
}
var items: [PeerInfoScreenItem] = []
if let _ = data.peer as? TelegramUser {
if let notificationSettings = data.notificationSettings {
let notificationsLabel: String
let soundLabel: String
let notificationSettings = notificationSettings as? TelegramPeerNotificationSettings ?? TelegramPeerNotificationSettings.defaultSettings
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
if until < Int32.max - 1 {
notificationsLabel = stringForRemainingMuteInterval(strings: presentationData.strings, muteInterval: until)
} else {
notificationsLabel = presentationData.strings.UserInfo_NotificationsDisabled
}
if let notificationSettings = data.notificationSettings {
let notificationsLabel: String
let soundLabel: String
let notificationSettings = notificationSettings as? TelegramPeerNotificationSettings ?? TelegramPeerNotificationSettings.defaultSettings
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
if until < Int32.max - 1 {
notificationsLabel = stringForRemainingMuteInterval(strings: presentationData.strings, muteInterval: until)
} else {
notificationsLabel = presentationData.strings.UserInfo_NotificationsEnabled
notificationsLabel = presentationData.strings.UserInfo_NotificationsDisabled
}
let globalNotificationSettings: GlobalNotificationSettings = data.globalNotificationSettings ?? GlobalNotificationSettings.defaultSettings
soundLabel = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.privateChats.sound)
items.append(PeerInfoScreenDisclosureItem(id: 0, label: notificationsLabel, text: "Notifications", action: {
interaction.editingOpenNotificationSettings()
}))
items.append(PeerInfoScreenDisclosureItem(id: 1, label: soundLabel, text: "Sound", action: {
interaction.editingOpenSoundSettings()
}))
items.append(PeerInfoScreenSwitchItem(id: 2, text: "Show Message Text", value: notificationSettings.displayPreviews != .hide, toggled: { value in
interaction.editingToggleShowMessageText(value)
}))
} else {
notificationsLabel = presentationData.strings.UserInfo_NotificationsEnabled
}
let globalNotificationSettings: GlobalNotificationSettings = data.globalNotificationSettings ?? GlobalNotificationSettings.defaultSettings
soundLabel = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.privateChats.sound)
items.append(PeerInfoScreenDisclosureItem(id: 0, label: notificationsLabel, text: "Notifications", action: {
interaction.editingOpenNotificationSettings()
}))
items.append(PeerInfoScreenDisclosureItem(id: 1, label: soundLabel, text: "Sound", action: {
interaction.editingOpenSoundSettings()
}))
items.append(PeerInfoScreenSwitchItem(id: 2, text: "Show Message Text", value: notificationSettings.displayPreviews != .hide, toggled: { value in
interaction.editingToggleShowMessageText(value)
}))
}
return items
@ -443,10 +500,62 @@ private func editingInfoSectionItems(data: PeerInfoScreenData?, presentationData
private func editingActionsSectionItems(data: PeerInfoScreenData?, presentationData: PresentationData, interaction: PeerInfoInteraction) -> [PeerInfoScreenItem] {
var items: [PeerInfoScreenItem] = []
if let data = data {
if data.isContact {
items.append(PeerInfoScreenActionItem(id: 0, text: "Delete Contact", color: .destructive, action: {
interaction.requestDeleteContact()
}))
if let user = data.peer as? TelegramUser {
if data.isContact {
items.append(PeerInfoScreenActionItem(id: 0, text: presentationData.strings.UserInfo_DeleteContact, color: .destructive, action: {
interaction.requestDeleteContact()
}))
}
} else if let channel = data.peer as? TelegramChannel {
if channel.flags.contains(.isCreator) {
let linkText: String
if let username = channel.username {
linkText = "@\(username)"
} else {
linkText = presentationData.strings.Channel_Setup_TypePrivate
}
items.append(PeerInfoScreenDisclosureItem(id: 1, label: linkText, text: presentationData.strings.Channel_TypeSetup_Title, action: {
interaction.editingOpenPublicLinkSetup()
}))
let discussionGroupTitle: String
if let cachedData = data.cachedData as? CachedChannelData {
if let peer = data.linkedDiscussionPeer {
if let addressName = peer.addressName, !addressName.isEmpty {
discussionGroupTitle = "@\(addressName)"
} else {
discussionGroupTitle = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
}
} else {
discussionGroupTitle = presentationData.strings.Channel_DiscussionGroupAdd
}
} else {
discussionGroupTitle = "..."
}
items.append(PeerInfoScreenDisclosureItem(id: 2, label: discussionGroupTitle, text: presentationData.strings.Channel_DiscussionGroup, action: {
interaction.editingOpenDiscussionGroupSetup()
}))
//items.append(PeerInfoScreenCommentItem(id: 3, text: presentationData.strings.Channel_DiscussionGroupInfo))
let messagesShouldHaveSignatures: Bool
switch channel.info {
case let .broadcast(info):
messagesShouldHaveSignatures = info.flags.contains(.messagesShouldHaveSignatures)
default:
messagesShouldHaveSignatures = false
}
items.append(PeerInfoScreenSwitchItem(id: 4, text: presentationData.strings.Channel_SignMessages, value: messagesShouldHaveSignatures, toggled: { value in
interaction.editingToggleMessageSignatures(value)
}))
items.append(PeerInfoScreenCommentItem(id: 5, text: presentationData.strings.Channel_SignMessages_Help))
}
} else if let group = data.peer as? TelegramGroup {
if case .creator = group.role {
items.append(PeerInfoScreenDisclosureItem(id: 1, label: presentationData.strings.Group_Setup_TypePrivate, text: presentationData.strings.Channel_TypeSetup_Title, action: {
interaction.editingOpenPublicLinkSetup()
}))
}
}
}
return items
@ -486,13 +595,17 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private(set) var data: PeerInfoScreenData?
private(set) var state = PeerInfoState(
isEditing: false,
isSearching: false,
selectedMessageIds: nil
selectedMessageIds: nil,
updatingAvatar: nil
)
private var dataDisposable: Disposable?
private let activeActionDisposable = MetaDisposable()
private let resolveUrlDisposable = MetaDisposable()
private let toggleShouldChannelMessagesSignaturesDisposable = MetaDisposable()
private let updateAvatarDisposable = MetaDisposable()
private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
private let _ready = Promise<Bool>()
var ready: Promise<Bool> {
@ -507,6 +620,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.scrollNode = ASScrollNode()
self.scrollNode.view.delaysContentTouches = false
self.headerNode = PeerInfoHeaderNode(context: context, avatarInitiallyExpanded: avatarInitiallyExpanded)
self.infoSection = PeerInfoScreenItemSectionContainerNode(id: 0)
@ -552,6 +666,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
},
performBotCommand: { [weak self] command in
self?.performBotCommand(command: command)
},
editingOpenPublicLinkSetup: { [weak self] in
self?.editingOpenPublicLinkSetup()
},
editingOpenDiscussionGroupSetup: { [weak self] in
self?.editingOpenDiscussionGroupSetup()
},
editingToggleMessageSignatures: { [weak self] value in
self?.editingToggleMessageSignatures(value: value)
},
openParticipantsSection: { [weak self] section in
self?.openParticipantsSection(section: section)
}
)
@ -950,6 +1076,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}))
}
self.headerNode.requestOpenAvatarForEditing = { [weak self] in
self?.openAvatarForEditing()
}
self.headerNode.navigationButtonContainer.performAction = { [weak self] key in
guard let strongSelf = self else {
return
@ -970,8 +1100,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
strongSelf.controller?.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, style: .plain, target: strongSelf, action: #selector(strongSelf.editingCancelPressed)), animated: true)
case .done, .cancel:
if case .done = key {
if let data = strongSelf.data, data.isContact {
if let peer = data.peer as? TelegramUser {
guard let data = strongSelf.data else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
return
}
if let peer = data.peer as? TelegramUser {
if data.isContact {
let firstName = strongSelf.headerNode.editingContentNode.editingTextForKey(.firstName) ?? ""
let lastName = strongSelf.headerNode.editingContentNode.editingTextForKey(.lastName) ?? ""
@ -1012,7 +1146,44 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
}
} else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
}
} else if let group = data.peer as? TelegramGroup, canEditPeerInfo(peer: group) {
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
} else if let channel = data.peer as? TelegramChannel, canEditPeerInfo(peer: channel) {
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? ""
var updateDataSignals: [Signal<Never, Void>] = []
if title != channel.title {
updateDataSignals.append(
updatePeerTitle(account: strongSelf.context.account, peerId: channel.id, title: title)
|> ignoreValues
|> mapError { _ in return Void() }
)
}
if description != (data.cachedData as? CachedChannelData)?.about {
updateDataSignals.append(
updatePeerDescription(account: strongSelf.context.account, peerId: channel.id, description: description.isEmpty ? nil : description)
|> ignoreValues
|> mapError { _ in return Void() }
)
}
strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals)
|> deliverOnMainQueue).start(error: { _ in
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
}, completed: {
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
}))
} else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
}
@ -1060,6 +1231,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.activeActionDisposable.dispose()
self.resolveUrlDisposable.dispose()
self.hiddenAvatarRepresentationDisposable.dispose()
self.toggleShouldChannelMessagesSignaturesDisposable.dispose()
self.updateAvatarDisposable.dispose()
}
override func didLoad() {
@ -1242,16 +1415,86 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let dismissAction: () -> Void = { [weak actionSheet] in
actionSheet?.dismissAnimated()
}
var reportSpam = false
var deleteChat = false
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.ChatSearch_SearchPlaceholder, color: .accent, action: { [weak self] in
dismissAction()
self?.openChatWithMessageSearch()
}))
if let user = peer as? TelegramUser {
if let botInfo = user.botInfo {
if botInfo.flags.contains(.worksWithGroups) {
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_InviteBotToGroup, color: .accent, action: { [weak self] in
dismissAction()
self?.openAddBotToGroup()
}))
}
if user.username != nil {
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_ShareBot, color: .accent, action: { [weak self] in
dismissAction()
self?.openShareBot()
}))
}
if let cachedData = data.cachedData as? CachedUserData, let botInfo = cachedData.botInfo {
for command in botInfo.commands {
if command.text == "settings" {
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_BotSettings, color: .accent, action: { [weak self] in
dismissAction()
self?.performBotCommand(command: .settings)
}))
} else if command.text == "help" {
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_BotHelp, color: .accent, action: { [weak self] in
dismissAction()
self?.performBotCommand(command: .help)
}))
} else if command.text == "privacy" {
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_BotPrivacy, color: .accent, action: { [weak self] in
dismissAction()
self?.performBotCommand(command: .privacy)
}))
}
}
}
}
if user.botInfo == nil && !user.flags.contains(.isSupport) {
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_StartSecretChat, color: .accent, action: { [weak self] in
dismissAction()
self?.openStartSecretChat()
}))
}
} else if let channel = peer as? TelegramChannel {
var canReport = true
if channel.isVerified {
canReport = false
}
if channel.adminRights != nil {
canReport = false
}
if channel.flags.contains(.isCreator) {
canReport = false
}
if canReport {
items.append(ActionSheetButtonItem(title: presentationData.strings.ReportPeer_Report, color: .destructive, action: { [weak self] in
dismissAction()
self?.openReport()
}))
}
if channel.flags.contains(.isCreator) {
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_DeleteChannel, color: .destructive, action: { [weak self] in
dismissAction()
self?.openDeleteChannel()
}))
} else {
if case .member = channel.participationStatus {
items.append(ActionSheetButtonItem(title: presentationData.strings.Channel_LeaveChannel, color: .destructive, action: { [weak self] in
dismissAction()
self?.openLeaveChannel()
}))
}
}
} else if let group = peer as? TelegramGroup {
}
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
@ -1263,6 +1506,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
}
private func openChatWithMessageSearch() {
if let navigationController = (self.controller?.navigationController as? NavigationController) {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(self.peerId), activateMessageSearch: true))
}
}
private func openStartSecretChat() {
let peerId = self.peerId
let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, PeerId?) in
@ -1710,6 +1959,235 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
})
}
private func editingOpenPublicLinkSetup() {
self.controller?.push(channelVisibilityController(context: self.context, peerId: self.peerId, mode: .generic, upgradedToSupergroup: { _, f in f() }))
}
private func editingOpenDiscussionGroupSetup() {
self.controller?.push(channelDiscussionGroupSetupController(context: self.context, peerId: self.peerId))
}
private func editingToggleMessageSignatures(value: Bool) {
self.toggleShouldChannelMessagesSignaturesDisposable.set(toggleShouldChannelMessagesSignatures(account: self.context.account, peerId: self.peerId, enabled: value).start())
}
private func openParticipantsSection(section: PeerInfoParticipantsSection) {
switch section {
case .members:
self.controller?.push(channelMembersController(context: self.context, peerId: self.peerId))
case .admins:
self.controller?.push(channelAdminsController(context: self.context, peerId: self.peerId))
case .banned:
self.controller?.push(channelBlacklistController(context: self.context, peerId: self.peerId))
}
}
private func openDeleteChannel() {
let peerId = self.peerId
let _ = (self.context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self, let peer = peer else {
return
}
let presentationData = strongSelf.presentationData
let actionSheet = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak actionSheet] in
actionSheet?.dismissAnimated()
}
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.ChannelInfo_DeleteChannelConfirmation),
ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_DeleteChannel, color: .destructive, action: {
dismissAction()
self?.deletePeerChat(peer: peer, globally: true)
}),
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
strongSelf.controller?.present(actionSheet, in: .window(.root))
})
}
private func openLeaveChannel() {
let peerId = self.peerId
let _ = (self.context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self, let peer = peer else {
return
}
let presentationData = strongSelf.presentationData
let actionSheet = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak actionSheet] in
actionSheet?.dismissAnimated()
}
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Channel_LeaveChannel, color: .destructive, action: {
dismissAction()
self?.deletePeerChat(peer: peer, globally: false)
}),
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
strongSelf.controller?.present(actionSheet, in: .window(.root))
})
}
private func deletePeerChat(peer: Peer, globally: Bool) {
guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else {
return
}
guard let tabController = navigationController.viewControllers.first as? TabBarController else {
return
}
for childController in tabController.controllers {
if let chatListController = childController as? ChatListController {
chatListController.maybeAskForPeerChatRemoval(peer: RenderedPeer(peer: peer), deleteGloballyIfPossible: globally, completion: { [weak navigationController] deleted in
if deleted {
navigationController?.popToRoot(animated: true)
}
}, removed: {
})
break
}
}
}
private func openAvatarForEditing() {
guard let peer = self.data?.peer, canEditPeerInfo(peer: peer) else {
return
}
let peerId = self.peerId
let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return (transaction.getPeer(peerId), currentSearchBotsConfiguration(transaction: transaction))
}
|> deliverOnMainQueue).start(next: { [weak self] peer, searchBotsConfiguration in
guard let strongSelf = self, let peer = peer else {
return
}
let presentationData = strongSelf.presentationData
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
legacyController.statusBar.statusBarStyle = .Ignore
let emptyController = LegacyEmptyController(context: legacyController.context)!
let navigationController = makeLegacyNavigationController(rootController: emptyController)
navigationController.setNavigationBarHidden(true, animated: false)
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
legacyController.bind(controller: navigationController)
strongSelf.controller?.present(legacyController, in: .window(.root))
var hasPhotos = false
if !peer.profileImageRepresentations.isEmpty {
hasPhotos = true
}
let completedImpl: (UIImage) -> Void = { image in
guard let strongSelf = self, let data = image.jpegData(compressionQuality: 0.6) else {
return
}
let resource = LocalFileMediaResource(fileId: arc4random64())
strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
strongSelf.state = strongSelf.state.withUpdatingAvatar(.image(representation))
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
let postbox = strongSelf.context.account.postbox
strongSelf.updateAvatarDisposable.set((updatePeerPhoto(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, stateManager: strongSelf.context.account.stateManager, accountPeerId: strongSelf.context.account.peerId, peerId: strongSelf.peerId, photo: uploadedPeerPhoto(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
}
switch result {
case .complete:
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
case .progress:
break
}
}))
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = strongSelf.currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { assetsController in
guard let strongSelf = self else {
return
}
let controller = WebSearchController(context: strongSelf.context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { result in
assetsController?.dismiss()
completedImpl(result)
}))
strongSelf.controller?.present(controller, in: .window(.root))
}
mixin.didFinishWithImage = { image in
if let image = image {
completedImpl(image)
}
}
mixin.didFinishWithDelete = {
guard let strongSelf = self else {
return
}
let _ = strongSelf.currentAvatarMixin.swap(nil)
if let profileImage = peer.smallProfileImage {
strongSelf.state = strongSelf.state.withUpdatingAvatar(.none)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
}
let postbox = strongSelf.context.account.postbox
strongSelf.updateAvatarDisposable.set((updatePeerPhoto(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, stateManager: strongSelf.context.account.stateManager, accountPeerId: strongSelf.context.account.peerId, peerId: strongSelf.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
}
switch result {
case .complete:
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
case .progress:
break
}
}))
}
mixin.didDismiss = { [weak legacyController] in
guard let strongSelf = self else {
return
}
let _ = strongSelf.currentAvatarMixin.swap(nil)
legacyController?.dismiss()
}
let menuController = mixin.present()
if let menuController = menuController {
menuController.customRemoveFromParentViewController = { [weak legacyController] in
legacyController?.dismiss()
}
}
})
}
func deleteMessages(messageIds: Set<MessageId>?) {
if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty {
self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds)
@ -2353,7 +2831,7 @@ public final class PeerInfoScreen: ViewController {
badgeStrokeColor: baseNavigationBarPresentationData.theme.badgeStrokeColor,
badgeTextColor: baseNavigationBarPresentationData.theme.badgeTextColor
), strings: baseNavigationBarPresentationData.strings))
self.navigationBar?.makeCustomTransitionNode = { [weak self] other in
self.navigationBar?.makeCustomTransitionNode = { [weak self] other, isInteractive in
guard let strongSelf = self else {
return nil
}
@ -2363,7 +2841,10 @@ public final class PeerInfoScreen: ViewController {
if strongSelf.controllerNode.scrollNode.view.contentOffset.y > .ulpOfOne {
return nil
}
if strongSelf.controllerNode.headerNode.isAvatarExpanded {
if isInteractive && strongSelf.controllerNode.headerNode.isAvatarExpanded {
return nil
}
if other.contentNode != nil {
return nil
}
if let tag = other.userInfo as? PeerInfoNavigationSourceTag, tag.peerId == peerId {
@ -2456,7 +2937,6 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
private var previousBackButtonArrow: ASDisplayNode?
private var currentBackButtonArrow: ASDisplayNode?
private var previousBackButtonBadge: ASDisplayNode?
private var previousRightButton: ASDisplayNode?
private var currentBackButton: ASDisplayNode?
private var previousTitleNode: (ASDisplayNode, TextNode)?
@ -2500,10 +2980,6 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
self.previousBackButtonBadge = previousBackButtonBadge
self.addSubnode(previousBackButtonBadge)
}
if let previousRightButton = bottomNavigationBar.makeTransitionRightButtonNode(accentColor: self.presentationData.theme.rootController.navigationBar.accentTextColor) {
self.previousRightButton = previousRightButton
self.addSubnode(previousRightButton)
}
if let currentBackButton = topNavigationBar.makeTransitionBackButtonNode(accentColor: self.screenNode.headerNode.isAvatarExpanded ? .white : self.presentationData.theme.rootController.navigationBar.accentTextColor) {
self.currentBackButton = currentBackButton
self.addSubnode(currentBackButton)
@ -2512,12 +2988,14 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
let previousTitleNode = previousTitleView.titleNode.makeCopy()
let previousTitleContainerNode = ASDisplayNode()
previousTitleContainerNode.addSubnode(previousTitleNode)
previousTitleNode.frame = previousTitleNode.frame.offsetBy(dx: -previousTitleNode.frame.width / 2.0, dy: -previousTitleNode.frame.height / 2.0)
self.previousTitleNode = (previousTitleContainerNode, previousTitleNode)
self.addSubnode(previousTitleContainerNode)
let previousStatusNode = previousTitleView.activityNode.makeCopy()
let previousStatusContainerNode = ASDisplayNode()
previousStatusContainerNode.addSubnode(previousStatusNode)
previousStatusNode.frame = previousStatusNode.frame.offsetBy(dx: -previousStatusNode.frame.width / 2.0, dy: -previousStatusNode.frame.height / 2.0)
self.previousStatusNode = (previousStatusContainerNode, previousStatusNode)
self.addSubnode(previousStatusContainerNode)
}
@ -2553,12 +3031,6 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
transition.updateAlpha(node: previousBackButtonBadge, alpha: fraction)
}
if let previousRightButton = self.previousRightButton {
let previousRightButtonFrame = bottomNavigationBar.rightButtonNode.view.convert(bottomNavigationBar.rightButtonNode.view.bounds, to: bottomNavigationBar.view)
previousRightButton.frame = previousRightButtonFrame
transition.updateAlpha(node: previousRightButton, alpha: fraction)
}
if let currentBackButton = self.currentBackButton {
let currentBackButtonFrame = topNavigationBar.backButtonNode.view.convert(topNavigationBar.backButtonNode.view.bounds, to: topNavigationBar.view)
transition.updateFrame(node: currentBackButton, frame: currentBackButtonFrame.offsetBy(dx: fraction * 12.0, dy: 0.0))
@ -2566,7 +3038,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
transition.updateAlpha(node: currentBackButton, alpha: (1.0 - fraction))
}
if let previousTitleView = bottomNavigationBar.titleView as? ChatTitleView, let avatarNode = previousTitleView.avatarNode, let (previousTitleContainerNode, previousTitleNode) = self.previousTitleNode, let (previousStatusContainerNode, previousStatusNode) = self.previousStatusNode {
if let previousTitleView = bottomNavigationBar.titleView as? ChatTitleView, let _ = (bottomNavigationBar.rightButtonNode.singleCustomNode as? ChatAvatarNavigationNode)?.avatarNode, let (previousTitleContainerNode, previousTitleNode) = self.previousTitleNode, let (previousStatusContainerNode, previousStatusNode) = self.previousStatusNode {
let previousTitleFrame = previousTitleView.titleNode.view.convert(previousTitleView.titleNode.bounds, to: bottomNavigationBar.view)
let previousStatusFrame = previousTitleView.activityNode.view.convert(previousTitleView.activityNode.bounds, to: bottomNavigationBar.view)
@ -2578,10 +3050,10 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
let titleScale = (fraction * previousTitleNode.bounds.height + (1.0 - fraction) * self.headerNode.titleNode.bounds.height) / previousTitleNode.bounds.height
let subtitleScale = max(0.01, min(10.0, (fraction * previousStatusNode.bounds.height + (1.0 - fraction) * self.headerNode.subtitleNode.bounds.height) / previousStatusNode.bounds.height))
transition.updateFrame(node: previousTitleContainerNode, frame: CGRect(origin: self.headerNode.titleNodeRawContainer.frame.origin.offsetBy(dx: previousTitleFrame.size.width * 0.5 * (titleScale - 1.0), dy: previousTitleFrame.size.height * 0.5 * (titleScale - 1.0)), size: previousTitleFrame.size))
transition.updateFrame(node: previousTitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: previousTitleFrame.size))
transition.updateFrame(node: previousStatusContainerNode, frame: CGRect(origin: self.headerNode.subtitleNodeRawContainer.frame.origin.offsetBy(dx: previousStatusFrame.size.width * 0.5 * (subtitleScale - 1.0), dy: previousStatusFrame.size.height * 0.5 * (subtitleScale - 1.0)), size: previousStatusFrame.size))
transition.updateFrame(node: previousStatusNode, frame: CGRect(origin: CGPoint(), size: previousStatusFrame.size))
transition.updateFrame(node: previousTitleContainerNode, frame: CGRect(origin: self.headerNode.titleNodeRawContainer.frame.center, size: CGSize()))
transition.updateFrame(node: previousTitleNode, frame: CGRect(origin: CGPoint(x: -previousTitleFrame.width / 2.0, y: -previousTitleFrame.height / 2.0), size: previousTitleFrame.size))
transition.updateFrame(node: previousStatusContainerNode, frame: CGRect(origin: self.headerNode.subtitleNodeRawContainer.frame.center, size: CGSize()))
transition.updateFrame(node: previousStatusNode, frame: CGRect(origin: CGPoint(x: -previousStatusFrame.size.width / 2.0, y: -previousStatusFrame.size.height / 2.0), size: previousStatusFrame.size))
transition.updateSublayerTransformScale(node: previousTitleContainerNode, scale: titleScale)
transition.updateSublayerTransformScale(node: previousStatusContainerNode, scale: subtitleScale)

View File

@ -1246,9 +1246,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
private let defaultChatControllerInteraction = ChatControllerInteraction.default
private func peerInfoControllerImpl(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool) -> ViewController? {
if let _ = peer as? TelegramGroup {
if let _ = peer as? TelegramGroup {
return PeerInfoScreen(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded)
return groupInfoController(context: context, peerId: peer.id)
} else if let channel = peer as? TelegramChannel {
return PeerInfoScreen(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded)
if case .group = channel.info {
return groupInfoController(context: context, peerId: peer.id)
} else {