Various improvements

This commit is contained in:
Ilya Laktyushin 2022-10-19 00:23:15 +03:00
parent 85ac817816
commit 3d5218b3f6
31 changed files with 1225 additions and 120 deletions

View File

@ -8142,3 +8142,5 @@ Sorry for the inconvenience.";
"Channel.AdminLog.MessageChangedGroupUsernames" = "%@ changed group links:";
"Channel.AdminLog.MessageChangedChannelUsernames" = "%@ changed channel links:";
"Channel.AdminLog.MessagePreviousLinks" = "Previous links";
"ShareMenu.SelectTopic" = "Select topic";

View File

@ -2018,7 +2018,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let fileId = threadInfo.info.icon, fileId != 0 {
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever)
} else {
avatarIconContent = .topic(title: String(threadInfo.info.title.prefix(1)), colorIndex: Int(clamping: abs(threadInfo.id)), size: CGSize(width: 32.0, height: 32.0))
avatarIconContent = .topic(title: String(threadInfo.info.title.prefix(1)), color: threadInfo.info.iconColor, size: CGSize(width: 32.0, height: 32.0))
}
let avatarIconComponent = EmojiStatusComponent(

View File

@ -1053,7 +1053,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
if let fileId = icon, fileId != 0 {
avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever)
} else {
avatarIconContent = .topic(title: String(title.prefix(1)), colorIndex: Int(clamping: abs(color)), size: CGSize(width: 32.0, height: 32.0))
avatarIconContent = .topic(title: String(title.prefix(1)), color: color, size: CGSize(width: 32.0, height: 32.0))
}
let avatarIconComponent = EmojiStatusComponent(

View File

@ -21,7 +21,9 @@ public enum DeviceMetrics: CaseIterable, Equatable {
case iPhone13Pro
case iPhone13ProMax
case iPhone14Pro
case iPhone14ProZoomed
case iPhone14ProMax
case iPhone14ProMaxZoomed
case iPad
case iPadMini
case iPad102Inch
@ -49,7 +51,9 @@ public enum DeviceMetrics: CaseIterable, Equatable {
.iPhone13Pro,
.iPhone13ProMax,
.iPhone14Pro,
.iPhone14ProZoomed,
.iPhone14ProMax,
.iPhone14ProMaxZoomed,
.iPad,
.iPadMini,
.iPad102Inch,
@ -83,7 +87,9 @@ public enum DeviceMetrics: CaseIterable, Equatable {
let width = device.screenSize.width
let height = device.screenSize.height
if ((screenSize.width.isEqual(to: width) && screenSize.height.isEqual(to: height)) || (additionalSize.width.isEqual(to: width) && additionalSize.height.isEqual(to: height))) {
if case .iPhoneXSMax = device, scale == 2.0 {
if case .iPhoneX = device, statusBarHeight == 47.0 {
self = .iPhone14ProMaxZoomed
} else if case .iPhoneXSMax = device, scale == 2.0 {
self = .iPhoneXr
} else {
self = device
@ -135,8 +141,12 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return CGSize(width: 428.0, height: 926.0)
case .iPhone14Pro:
return CGSize(width: 393.0, height: 852.0)
case .iPhone14ProZoomed:
return CGSize(width: 320.0, height: 693.0)
case .iPhone14ProMax:
return CGSize(width: 430.0, height: 932.0)
case .iPhone14ProMaxZoomed:
return CGSize(width: 375.0, height: 812.0)
case .iPad:
return CGSize(width: 768.0, height: 1024.0)
case .iPadMini:
@ -164,9 +174,9 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return 41.0 + UIScreenPixel
case .iPhone12Mini:
return 44.0
case .iPhone12, .iPhone13, .iPhone13Pro, .iPhone14Pro:
case .iPhone12, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed:
return 47.0 + UIScreenPixel
case .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax:
case .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax, .iPhone14ProMaxZoomed:
return 53.0 + UIScreenPixel
case let .unknown(_, _, onScreenNavigationHeight):
if let _ = onScreenNavigationHeight {
@ -181,7 +191,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
func safeInsets(inLandscape: Bool) -> UIEdgeInsets {
switch self {
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProMax:
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
return inLandscape ? UIEdgeInsets(top: 0.0, left: 44.0, bottom: 0.0, right: 44.0) : UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0)
default:
return UIEdgeInsets.zero
@ -192,6 +202,10 @@ public enum DeviceMetrics: CaseIterable, Equatable {
switch self {
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProMax:
return inLandscape ? 21.0 : 34.0
case .iPhone14ProZoomed:
return inLandscape ? 21.0 : 28.0
case .iPhone14ProMaxZoomed:
return inLandscape ? 21.0 : 31.0
case .iPadPro3rdGen, .iPadPro11Inch:
return 21.0
case .iPad, .iPadPro, .iPadPro10Inch, .iPadMini, .iPadMini6thGen:
@ -224,6 +238,8 @@ public enum DeviceMetrics: CaseIterable, Equatable {
switch self {
case .iPhone14Pro, .iPhone14ProMax:
return 54.0
case .iPhone14ProMaxZoomed:
return 47.0
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax:
return 44.0
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
@ -242,7 +258,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return 162.0
case .iPhone6, .iPhone6Plus:
return 163.0
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProMax:
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
return 172.0
case .iPad, .iPad102Inch, .iPadPro10Inch:
return 348.0
@ -261,7 +277,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return 216.0
case .iPhone6Plus:
return 226.0
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro:
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed:
return 292.0
case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax:
return 302.0
@ -282,7 +298,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
func predictiveInputHeight(inLandscape: Bool) -> CGFloat {
if inLandscape {
switch self {
case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProMax:
case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
return 37.0
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
return 50.0
@ -293,7 +309,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
switch self {
case .iPhone4, .iPhone5:
return 37.0
case .iPhone6, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProMax:
case .iPhone6, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
return 44.0
case .iPhone6Plus:
return 45.0
@ -316,7 +332,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
public var hasDynamicIsland: Bool {
switch self {
case .iPhone14Pro, .iPhone14ProMax:
case .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
return true
default:
return false

View File

@ -85,12 +85,20 @@ private final class WindowRootViewController: UIViewController, UIViewController
didSet {
if oldValue != self.orientations {
if self.orientations == .portrait {
if UIDevice.current.orientation != .portrait {
if #available(iOSApplicationExtension 16.0, iOS 16.0, *) {
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait))
self.setNeedsUpdateOfSupportedInterfaceOrientations()
} else if UIDevice.current.orientation != .portrait {
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
} else {
UIViewController.attemptRotationToDeviceOrientation()
if #available(iOSApplicationExtension 16.0, iOS 16.0, *) {
self.setNeedsUpdateOfSupportedInterfaceOrientations()
} else {
UIViewController.attemptRotationToDeviceOrientation()
}
}
}
}

View File

@ -160,7 +160,7 @@ open class NavigationController: UINavigationController, ContainableController,
public private(set) var validLayout: ContainerViewLayout?
private var validStatusBarStyle: NavigationStatusBarStyle?
private var validStatusBarHidden: Bool = false
private var validStatusBarHidden: Bool?
private var ignoreInputHeight: Bool = false
private var currentStatusBarExternalHidden: Bool = false

View File

@ -270,7 +270,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
self.contentOffsetUpdated?(-size.height + nodeHeight, transition)
}
func updateSelectedPeers() {
func updateSelectedPeers(animated: Bool) {
}
}
@ -318,6 +318,6 @@ public final class JoinLinkPreviewLoadingContainerNode: ASDisplayNode, ShareCont
self.contentOffsetUpdated?(-size.height + nodeHeight, transition)
}
public func updateSelectedPeers() {
public func updateSelectedPeers(animated: Bool) {
}
}

View File

@ -110,6 +110,6 @@ final class LanguageLinkPreviewContentNode: ASDisplayNode, ShareContentContainer
self.contentOffsetUpdated?(-size.height + nodeHeight - 64.0, transition)
}
func updateSelectedPeers() {
func updateSelectedPeers(animated: Bool) {
}
}

View File

@ -67,7 +67,7 @@ struct PasscodeKeyboardLayout {
self.topOffset = 226.0
self.biometricsOffset = 30.0
self.deleteOffset = 20.0
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro:
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed:
self.buttonSize = 75.0
self.horizontalSecond = 103.0
self.horizontalThird = 206.0
@ -151,7 +151,7 @@ public struct PasscodeLayout {
self.titleOffset = 112.0
self.subtitleOffset = -6.0
self.inputFieldOffset = 156.0
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro:
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed:
self.titleOffset = 162.0
self.subtitleOffset = 0.0
self.inputFieldOffset = 206.0

View File

@ -22,6 +22,8 @@ swift_library(
"//submodules/LocalizedPeerData:LocalizedPeerData",
"//submodules/AccountContext:AccountContext",
"//submodules/CheckNode:CheckNode",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent",
],
visibility = [
"//visibility:public",

View File

@ -12,6 +12,8 @@ import ContextUI
import LocalizedPeerData
import AccountContext
import CheckNode
import ComponentFlow
import EmojiStatusComponent
private let avatarFont = avatarPlaceholderFont(size: 24.0)
private let textFont = Font.regular(11.0)
@ -72,6 +74,8 @@ public final class SelectablePeerNode: ASDisplayNode {
private let onlineNode: PeerOnlineMarkerNode
private var checkNode: CheckNode?
private let textNode: ASTextNode
private let iconView: ComponentView<Empty>
public var toggleSelection: (() -> Void)?
public var contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? {
@ -119,6 +123,8 @@ public final class SelectablePeerNode: ASDisplayNode {
self.onlineNode = PeerOnlineMarkerNode()
self.iconView = ComponentView<Empty>()
super.init()
self.addSubnode(self.contextContainer)
@ -137,7 +143,7 @@ public final class SelectablePeerNode: ASDisplayNode {
}
}
public func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
public func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
let isFirstTime = self.peer == nil
self.peer = peer
guard let mainPeer = peer.chatMainPeer else {
@ -166,7 +172,7 @@ public final class SelectablePeerNode: ASDisplayNode {
}
}
self.textNode.maximumNumberOfLines = numberOfLines
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : defaultColor, paragraphAlignment: .center)
self.textNode.attributedText = NSAttributedString(string: customTitle ?? text, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : defaultColor, paragraphAlignment: .center)
self.avatarNode.setPeer(context: context, theme: theme, peer: mainPeer, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: isForum ? .roundedRect : .round, synchronousLoad: synchronousLoad)
let onlineLayout = self.onlineNode.asyncLayout()
@ -176,6 +182,40 @@ public final class SelectablePeerNode: ASDisplayNode {
self.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(theme, state: .panel), color: nil, transition: .immediate)
self.onlineNode.frame = CGRect(origin: CGPoint(), size: onlineSize)
let iconContent: EmojiStatusComponent.Content?
if let fileId = iconId {
iconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.actionSheet.disabledActionTextColor, themeColor: theme.actionSheet.primaryTextColor, loopMode: .count(2))
} else if let customTitle = customTitle {
iconContent = .topic(title: String(customTitle.prefix(1)), color: iconColor ?? 0, size: CGSize(width: 18.0, height: 18.0))
} else {
iconContent = nil
}
if let iconContent = iconContent {
let iconSize = self.iconView.update(
transition: .easeInOut(duration: 0.2),
component: AnyComponent(EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: iconContent,
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: CGSize(width: 18.0, height: 18.0)
)
if let iconComponentView = self.iconView.view {
if iconComponentView.superview == nil {
self.view.addSubview(iconComponentView)
}
iconComponentView.frame = CGRect(origin: .zero, size: iconSize)
}
} else if let iconComponentView = self.iconView.view {
iconComponentView.removeFromSuperview()
}
self.setNeedsLayout()
}
@ -267,7 +307,18 @@ public final class SelectablePeerNode: ASDisplayNode {
self.contextContainer.frame = bounds
self.avatarNodeContainer.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - 60.0) / 2.0), y: 4.0), size: CGSize(width: 60.0, height: 60.0))
self.textNode.frame = CGRect(origin: CGPoint(x: 2.0, y: 4.0 + 60.0 + 4.0), size: CGSize(width: bounds.size.width - 4.0, height: 34.0))
let iconSize = CGSize(width: 18.0, height: 18.0)
let textSize = self.textNode.calculateSizeThatFits(bounds.size)
var totalWidth = textSize.width
var leftOrigin = floorToScreenPixels((bounds.width - textSize.width) / 2.0)
if let iconView = self.iconView.view, iconView.superview != nil {
totalWidth += iconView.frame.width + 2.0
leftOrigin = floorToScreenPixels((bounds.width - totalWidth) / 2.0)
iconView.frame = CGRect(origin: CGPoint(x: leftOrigin, y: 4.0 + 60.0 + 1.0), size: iconSize)
leftOrigin += iconSize.width + 2.0
}
self.textNode.frame = CGRect(origin: CGPoint(x: leftOrigin, y: 4.0 + 60.0 + 4.0), size: CGSize(width: textSize.width, height: 34.0))
let avatarFrame = self.avatarNode.frame
let avatarContainerFrame = self.avatarNodeContainer.frame

View File

@ -35,6 +35,8 @@ swift_library(
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent",
],
visibility = [
"//visibility:public",

View File

@ -10,5 +10,5 @@ public protocol ShareContentContainerNode: AnyObject {
func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?)
func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?)
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition)
func updateSelectedPeers()
func updateSelectedPeers(animated: Bool)
}

View File

@ -1011,12 +1011,14 @@ public final class ShareController: ViewController {
return (resultPeers, accountPeer)
}
})
var animatedIn = false
self.peersDisposable.set((self.peers.get()
|> deliverOnMainQueue).start(next: { [weak self] next in
if let strongSelf = self {
strongSelf.controllerNode.updatePeers(context: strongSelf.sharedContext.makeTempAccountContext(account: strongSelf.currentAccount), switchableAccounts: strongSelf.switchableAccounts, peers: next.0, accountPeer: next.1, defaultAction: strongSelf.defaultAction)
if animateIn {
if animateIn && !animatedIn {
animatedIn = true
strongSelf.readyDisposable.set((strongSelf.controllerNode.ready.get()
|> filter({ $0 })
|> take(1)

View File

@ -73,6 +73,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
private var controllerInteraction: ShareControllerInteraction?
private var peersContentNode: SharePeersContainerNode?
private var topicsContentNode: ShareTopicsContainerNode?
private var scheduledLayoutTransitionRequestId: Int = 0
private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)?
@ -244,15 +245,28 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer, search in
if let strongSelf = self {
var added = false
var openedTopic = false
if strongSelf.controllerInteraction!.selectedPeerIds.contains(peer.peerId) {
strongSelf.controllerInteraction!.selectedTopics[peer.peerId] = nil
strongSelf.peersContentNode?.update()
strongSelf.controllerInteraction!.selectedPeerIds.remove(peer.peerId)
strongSelf.controllerInteraction!.selectedPeers = strongSelf.controllerInteraction!.selectedPeers.filter({ $0.peerId != peer.peerId })
} else {
strongSelf.controllerInteraction!.selectedPeerIds.insert(peer.peerId)
strongSelf.controllerInteraction!.selectedPeers.append(peer)
if case let .channel(channel) = peer.peer, channel.flags.contains(.isForum) {
if strongSelf.controllerInteraction!.selectedTopics[peer.peerId] != nil {
strongSelf.controllerInteraction!.selectedTopics[peer.peerId] = nil
strongSelf.peersContentNode?.update()
} else {
strongSelf.transitionToPeerTopics(peer)
openedTopic = true
}
} else {
strongSelf.controllerInteraction!.selectedPeerIds.insert(peer.peerId)
strongSelf.controllerInteraction!.selectedPeers.append(peer)
added = true
}
strongSelf.contentNode?.setEnsurePeerVisibleOnLayout(peer.peerId)
added = true
}
if search && added {
@ -263,12 +277,14 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
strongSelf.peersContentNode?.updateFoundPeers()
}
strongSelf.setActionNodesHidden(strongSelf.controllerInteraction!.selectedPeers.isEmpty && strongSelf.presetText == nil, inputField: true, actions: strongSelf.defaultAction == nil)
strongSelf.updateButton()
strongSelf.peersContentNode?.updateSelectedPeers()
strongSelf.contentNode?.updateSelectedPeers()
if !openedTopic {
strongSelf.setActionNodesHidden(strongSelf.controllerInteraction!.selectedPeers.isEmpty && strongSelf.presetText == nil, inputField: true, actions: strongSelf.defaultAction == nil)
strongSelf.updateButton()
strongSelf.peersContentNode?.updateSelectedPeers(animated: true)
strongSelf.contentNode?.updateSelectedPeers(animated: true)
}
if let (layout, navigationBarHeight, _) = strongSelf.containerLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
@ -280,6 +296,17 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
}
}, selectTopic: { [weak self] peer, threadId, threadData in
if let strongSelf = self {
strongSelf.controllerInteraction?.selectedPeerIds.insert(peer)
strongSelf.controllerInteraction?.selectedTopics[peer] = (threadId, threadData)
strongSelf.peersContentNode?.update()
strongSelf.peersContentNode?.updateSelectedPeers(animated: false)
strongSelf.updateButton()
Queue.mainQueue().after(0.01, {
strongSelf.closePeerTopics(peer)
})
}
})
self.backgroundColor = nil
@ -324,7 +351,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
deinit {
self.shareDisposable.dispose()
}
override func didLoad() {
super.didLoad()
@ -333,6 +360,70 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
func transitionToPeerTopics(_ peer: EngineRenderedPeer) {
guard let context = self.context, let mainPeer = peer.chatMainPeer else {
return
}
let _ = (threadList(context: context, peerId: mainPeer.id)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] threads in
guard let strongSelf = self, let context = strongSelf.context, let controllerInteraction = strongSelf.controllerInteraction else {
return
}
let topicsContentNode = ShareTopicsContainerNode(
sharedContext: strongSelf.sharedContext,
context: context,
theme: strongSelf.presentationData.theme,
strings: strongSelf.presentationData.strings,
peer: mainPeer,
topics: threads.items.reversed(),
controllerInteraction: controllerInteraction
)
topicsContentNode.backPressed = { [weak self] in
if let strongSelf = self {
strongSelf.closePeerTopics(peer.peerId)
}
}
strongSelf.topicsContentNode = topicsContentNode
strongSelf.contentNode?.supernode?.addSubnode(topicsContentNode)
topicsContentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
})
if let (layout, navigationBarHeight, _) = strongSelf.containerLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
if let sourceFrame = strongSelf.peersContentNode?.animateOut(peerId: peer.peerId) {
topicsContentNode.animateIn(sourceFrame: sourceFrame)
}
})
}
func closePeerTopics(_ peerId: EnginePeer.Id) {
if let topicsContentNode = self.topicsContentNode, let peersContentNode = self.peersContentNode {
topicsContentNode.supernode?.insertSubnode(topicsContentNode, belowSubnode: peersContentNode)
}
self.peersContentNode?.setContentOffsetUpdated({ [weak self] contentOffset, transition in
self?.contentNodeOffsetUpdated(contentOffset, transition: transition)
})
if let (layout, navigationBarHeight, _) = self.containerLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
if let targetFrame = self.peersContentNode?.animateIn(peerId: peerId), let topicsContentNode = self.topicsContentNode {
topicsContentNode.animateOut(targetFrame: targetFrame, completion: { [weak self] in
if let topicsContentNode = self?.topicsContentNode {
topicsContentNode.removeFromSupernode()
self?.topicsContentNode = nil
}
})
}
}
func updatePresentationData(_ presentationData: PresentationData) {
guard self.presentationData !== presentationData else {
return
@ -540,6 +631,11 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
contentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: transition)
}
if let topicsContentNode = self.topicsContentNode {
transition.updateFrame(node: topicsContentNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
topicsContentNode.updateLayout(size: gridSize, isLandscape: layout.size.width > layout.size.height, bottomInset: bottomGridInset, transition: transition)
}
}
private func contentNodeOffsetUpdated(_ contentOffset: CGFloat, transition: ContainedViewLayoutTransition) {
@ -967,7 +1063,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
private func updateButton() {
if self.controllerInteraction!.selectedPeers.isEmpty {
let count = self.controllerInteraction!.selectedPeerIds.count
if count == 0 {
if self.presetText != nil {
self.actionButtonNode.setTitle(self.presentationData.strings.ShareMenu_Send, with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.disabledActionTextColor, for: .normal)
self.actionButtonNode.isEnabled = false
@ -990,13 +1087,13 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
let text: String
if let segmentedValues = self.segmentedValues {
let value = segmentedValues[self.selectedSegmentedIndex]
text = value.formatSendTitle(self.controllerInteraction!.selectedPeers.count)
text = value.formatSendTitle(count)
} else {
text = self.presentationData.strings.ShareMenu_Send
}
self.actionButtonNode.isEnabled = true
self.actionButtonNode.setTitle(text, with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal)
self.actionButtonNode.badge = "\(self.controllerInteraction!.selectedPeers.count)"
self.actionButtonNode.badge = "\(count)"
}
}
@ -1098,3 +1195,63 @@ private final class ShareContextReferenceContentSource: ContextReferenceContentS
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds, customPosition: self.customPosition)
}
}
private func threadList(context: AccountContext, peerId: EnginePeer.Id) -> Signal<EngineChatList, NoError> {
let viewKey: PostboxViewKey = .messageHistoryThreadIndex(
id: peerId,
summaryComponents: ChatListEntrySummaryComponents(
components: [:]
)
)
return context.account.postbox.combinedView(keys: [viewKey])
|> map { views -> EngineChatList in
guard let view = views.views[viewKey] as? MessageHistoryThreadIndexView else {
preconditionFailure()
}
var items: [EngineChatList.Item] = []
for item in view.items {
guard let peer = view.peer else {
continue
}
guard let data = item.info.get(MessageHistoryThreadData.self) else {
continue
}
let pinnedIndex: EngineChatList.Item.PinnedIndex
if let index = item.pinnedIndex {
pinnedIndex = .index(index)
} else {
pinnedIndex = .none
}
items.append(EngineChatList.Item(
id: .forum(item.id),
index: .forum(pinnedIndex: pinnedIndex, timestamp: item.index.timestamp, threadId: item.id, namespace: item.index.id.namespace, id: item.index.id.id),
messages: item.topMessage.flatMap { [EngineMessage($0)] } ?? [],
readCounters: EnginePeerReadCounters(state: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: data.incomingUnreadCount, markedUnread: false))])),
isMuted: false,
draft: nil,
threadData: data,
renderedPeer: EngineRenderedPeer(peer: EnginePeer(peer)),
presence: nil,
hasUnseenMentions: false,
hasUnseenReactions: false,
forumTopicData: nil,
hasFailed: false,
isContact: false
))
}
let list = EngineChatList(
items: items.reversed(),
groupItems: [],
additionalItems: [],
hasEarlier: false,
hasLater: false,
isLoading: view.isLoading
)
return list
}
}

View File

@ -16,10 +16,15 @@ final class ShareControllerInteraction {
var foundPeers: [EngineRenderedPeer] = []
var selectedPeerIds = Set<PeerId>()
var selectedPeers: [EngineRenderedPeer] = []
let togglePeer: (EngineRenderedPeer, Bool) -> Void
init(togglePeer: @escaping (EngineRenderedPeer, Bool) -> Void) {
var selectedTopics: [EnginePeer.Id: (Int64, MessageHistoryThreadData)] = [:]
let togglePeer: (EngineRenderedPeer, Bool) -> Void
let selectTopic: (EnginePeer.Id, Int64, MessageHistoryThreadData) -> Void
init(togglePeer: @escaping (EngineRenderedPeer, Bool) -> Void, selectTopic: @escaping (EnginePeer.Id, Int64, MessageHistoryThreadData) -> Void) {
self.togglePeer = togglePeer
self.selectTopic = selectTopic
}
}
@ -92,17 +97,21 @@ final class ShareControllerPeerGridItem: GridItem {
let strings: PresentationStrings
let peer: EngineRenderedPeer?
let presence: EnginePeer.Presence?
let topicId: Int64?
let threadData: MessageHistoryThreadData?
let controllerInteraction: ShareControllerInteraction
let search: Bool
let section: GridSection?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer?, presence: EnginePeer.Presence?, controllerInteraction: ShareControllerInteraction, sectionTitle: String? = nil, search: Bool = false) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer?, presence: EnginePeer.Presence?, topicId: Int64?, threadData: MessageHistoryThreadData?, controllerInteraction: ShareControllerInteraction, sectionTitle: String? = nil, search: Bool = false) {
self.context = context
self.theme = theme
self.strings = strings
self.peer = peer
self.presence = presence
self.topicId = topicId
self.threadData = threadData
self.controllerInteraction = controllerInteraction
self.search = search
@ -116,7 +125,7 @@ final class ShareControllerPeerGridItem: GridItem {
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
let node = ShareControllerPeerGridItemNode()
node.controllerInteraction = self.controllerInteraction
node.setup(context: self.context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, search: self.search, synchronousLoad: synchronousLoad, force: false)
node.setup(context: self.context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, topicId: self.topicId, threadData: self.threadData, search: self.search, synchronousLoad: synchronousLoad, force: false)
return node
}
@ -126,12 +135,12 @@ final class ShareControllerPeerGridItem: GridItem {
return
}
node.controllerInteraction = self.controllerInteraction
node.setup(context: self.context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, search: self.search, synchronousLoad: false, force: false)
node.setup(context: self.context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, topicId: self.topicId, threadData: self.threadData, search: self.search, synchronousLoad: false, force: false)
}
}
final class ShareControllerPeerGridItemNode: GridItemNode {
private var currentState: (AccountContext, PresentationTheme, PresentationStrings, EngineRenderedPeer?, Bool, EnginePeer.Presence?)?
private var currentState: (AccountContext, PresentationTheme, PresentationStrings, EngineRenderedPeer?, Bool, EnginePeer.Presence?, Int64?, MessageHistoryThreadData?)?
private let peerNode: SelectablePeerNode
private var presenceManager: PeerPresenceStatusManager?
@ -140,6 +149,10 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
private var placeholderNode: ShimmerEffectNode?
private var absoluteLocation: (CGRect, CGSize)?
var peerId: EnginePeer.Id? {
return self.currentState?.3?.peerId
}
override init() {
self.peerNode = SelectablePeerNode()
@ -147,7 +160,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
self.peerNode.toggleSelection = { [weak self] in
if let strongSelf = self {
if let (_, _, _, maybePeer, search, _) = strongSelf.currentState, let peer = maybePeer {
if let (_, _, _, maybePeer, search, _, _, _) = strongSelf.currentState, let peer = maybePeer {
if let _ = peer.peers[peer.peerId] {
strongSelf.controllerInteraction?.togglePeer(peer, search)
}
@ -159,7 +172,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
guard let strongSelf = self, let currentState = strongSelf.currentState else {
return
}
strongSelf.setup(context: currentState.0, theme: currentState.1, strings: currentState.2, peer: currentState.3, presence: currentState.5, search: currentState.4, synchronousLoad: false, force: true)
strongSelf.setup(context: currentState.0, theme: currentState.1, strings: currentState.2, peer: currentState.3, presence: currentState.5, topicId: currentState.6, threadData: currentState.7, search: currentState.4, synchronousLoad: false, force: true)
})
}
@ -171,8 +184,8 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
}
}
func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer?, presence: EnginePeer.Presence?, search: Bool, synchronousLoad: Bool, force: Bool) {
if force || self.currentState == nil || self.currentState!.0 !== context || self.currentState!.3 != peer || self.currentState!.5 != presence {
func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer?, presence: EnginePeer.Presence?, topicId: Int64?, threadData: MessageHistoryThreadData?, search: Bool, synchronousLoad: Bool, force: Bool) {
if force || self.currentState == nil || self.currentState!.0 !== context || self.currentState!.3 != peer || self.currentState!.5 != presence || self.currentState!.6 != topicId {
let itemTheme = SelectablePeerNodeTheme(textColor: theme.actionSheet.primaryTextColor, secretTextColor: theme.chatList.secretTitleColor, selectedTextColor: theme.actionSheet.controlAccentColor, checkBackgroundColor: theme.actionSheet.opaqueItemBackgroundColor, checkFillColor: theme.actionSheet.controlAccentColor, checkColor: theme.actionSheet.checkContentColor, avatarPlaceholderColor: theme.list.mediaPlaceholderColor)
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
@ -186,7 +199,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
self.peerNode.theme = itemTheme
if let peer = peer {
self.peerNode.setup(context: context, theme: theme, strings: strings, peer: peer, online: online, synchronousLoad: synchronousLoad)
self.peerNode.setup(context: context, theme: theme, strings: strings, peer: peer, customTitle: threadData?.info.title, iconId: threadData?.info.icon, iconColor: threadData?.info.iconColor ?? 0, online: online, synchronousLoad: synchronousLoad)
if let shimmerNode = self.placeholderNode {
self.placeholderNode = nil
shimmerNode.removeFromSupernode()
@ -218,7 +231,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
shimmerNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, horizontal: true, size: self.bounds.size)
}
self.currentState = (context, theme, strings, peer, search, presence)
self.currentState = (context, theme, strings, peer, search, presence, topicId, threadData)
self.setNeedsLayout()
if let presence = presence {
self.presenceManager?.reset(presence: presence)
@ -229,7 +242,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
func updateSelection(animated: Bool) {
var selected = false
if let controllerInteraction = self.controllerInteraction, let (_, _, _, maybePeer, _, _) = self.currentState, let peer = maybePeer {
if let controllerInteraction = self.controllerInteraction, let (_, _, _, maybePeer, _, _, _, _) = self.currentState, let peer = maybePeer {
selected = controllerInteraction.selectedPeerIds.contains(peer.peerId)
}
@ -243,7 +256,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
self.peerNode.frame = bounds
self.placeholderNode?.frame = bounds
if let (_, theme, _, _, _, _) = self.currentState, let shimmerNode = self.placeholderNode {
if let (_, theme, _, _, _, _, _, _) = self.currentState, let shimmerNode = self.placeholderNode {
var shapes: [ShimmerEffectNode.Shape] = []
let titleLineWidth: CGFloat = 56.0

View File

@ -91,7 +91,7 @@ public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContain
self.contentOffsetUpdated?(-size.height + 64.0, transition)
}
public func updateSelectedPeers() {
public func updateSelectedPeers(animated: Bool) {
}
}
@ -330,6 +330,6 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte
self.contentOffsetUpdated?(-size.height + nodeHeight * 0.5, transition)
}
public func updateSelectedPeers() {
public func updateSelectedPeers(animated: Bool) {
}
}

View File

@ -17,10 +17,31 @@ import ContextUI
private let subtitleFont = Font.regular(12.0)
private extension CGPoint {
func angle(to other: CGPoint) -> CGFloat {
let originX = other.x - self.x
let originY = other.y - self.y
let bearingRadians = atan2f(Float(originY), Float(originX))
return CGFloat(bearingRadians)
}
func distance(to other: CGPoint) -> CGFloat {
return sqrt((self.x - other.x) * (self.x - other.x) + (self.y - other.y) * (self.y - other.y))
}
func offsetBy(distance: CGFloat, inDirection radians: CGFloat) -> CGPoint {
let vertical = sin(radians) * distance
let horizontal = cos(radians) * distance
return self.offsetBy(dx: horizontal, dy: vertical)
}
}
private struct SharePeerEntry: Comparable, Identifiable {
let index: Int32
let peer: EngineRenderedPeer
let presence: EnginePeer.Presence?
let threadId: Int64?
let threadData: MessageHistoryThreadData?
let theme: PresentationTheme
let strings: PresentationStrings
@ -38,6 +59,12 @@ private struct SharePeerEntry: Comparable, Identifiable {
if lhs.presence != rhs.presence {
return false
}
if lhs.threadId != rhs.threadId {
return false
}
if lhs.threadData != rhs.threadData {
return false
}
return true
}
@ -47,7 +74,7 @@ private struct SharePeerEntry: Comparable, Identifiable {
}
func item(context: AccountContext, interfaceInteraction: ShareControllerInteraction) -> GridItem {
return ShareControllerPeerGridItem(context: context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, controllerInteraction: interfaceInteraction, search: false)
return ShareControllerPeerGridItem(context: context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, topicId: self.threadId, threadData: self.threadData, controllerInteraction: interfaceInteraction, search: false)
}
}
@ -114,6 +141,13 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
let peersValue = Promise<[(EngineRenderedPeer, EnginePeer.Presence?)]>()
private var _tick: Int = 0 {
didSet {
self.tick.set(self._tick)
}
}
private let tick = ValuePromise<Int>(0)
init(sharedContext: SharedAccountContext, context: AccountContext, switchableAccounts: [AccountWithInfo], theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peers: [(EngineRenderedPeer, EnginePeer.Presence?)], accountPeer: EnginePeer, controllerInteraction: ShareControllerInteraction, externalShare: Bool, switchToAnotherAccount: @escaping () -> Void, debugAction: @escaping () -> Void, extendedInitialReveal: Bool, segmentedValues: [ShareControllerSegmentedValue]?) {
self.sharedContext = sharedContext
self.context = context
@ -129,19 +163,19 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.peersValue.set(.single(peers))
let items: Signal<[SharePeerEntry], NoError> = combineLatest(self.peersValue.get(), self.foundPeers.get())
|> map { initialPeers, foundPeers -> [SharePeerEntry] in
let items: Signal<[SharePeerEntry], NoError> = combineLatest(self.peersValue.get(), self.foundPeers.get(), self.tick.get())
|> map { [weak controllerInteraction] initialPeers, foundPeers, _ -> [SharePeerEntry] in
var entries: [SharePeerEntry] = []
var index: Int32 = 0
var existingPeerIds: Set<PeerId> = Set()
entries.append(SharePeerEntry(index: index, peer: EngineRenderedPeer(peer: accountPeer), presence: nil, theme: theme, strings: strings))
entries.append(SharePeerEntry(index: index, peer: EngineRenderedPeer(peer: accountPeer), presence: nil, threadId: nil, threadData: nil, theme: theme, strings: strings))
existingPeerIds.insert(accountPeer.id)
index += 1
for peer in foundPeers.reversed() {
if !existingPeerIds.contains(peer.peerId) {
entries.append(SharePeerEntry(index: index, peer: peer, presence: nil, theme: theme, strings: strings))
entries.append(SharePeerEntry(index: index, peer: peer, presence: nil, threadId: nil, threadData: nil, theme: theme, strings: strings))
existingPeerIds.insert(peer.peerId)
index += 1
}
@ -149,7 +183,8 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
for (peer, presence) in initialPeers {
if !existingPeerIds.contains(peer.peerId) {
entries.append(SharePeerEntry(index: index, peer: peer, presence: presence, theme: theme, strings: strings))
let thread = controllerInteraction?.selectedTopics[peer.peerId]
entries.append(SharePeerEntry(index: index, peer: peer, presence: presence, threadId: thread?.0, threadData: thread?.1, theme: theme, strings: strings))
existingPeerIds.insert(peer.peerId)
index += 1
}
@ -325,6 +360,154 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
func deactivate() {
}
func frameForPeerId(_ peerId: EnginePeer.Id) -> CGRect? {
var node: ASDisplayNode?
self.contentGridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ShareControllerPeerGridItemNode, itemNode.peerId == peerId {
node = itemNode
}
}
return node?.frame
}
func generateMaskImage() -> UIImage? {
return generateImage(CGSize(width: 100.0, height: 100.0), contextGenerator: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
context.setShadow(offset: .zero, blur: 40.0, color: UIColor.white.cgColor)
context.fill(CGRect(origin: .zero, size: size).insetBy(dx: 16.0, dy: 16.0))
})?.stretchableImage(withLeftCapWidth: 49, topCapHeight: 49)
}
func animateIn(peerId: EnginePeer.Id) -> CGRect? {
self.searchButtonNode.alpha = 1.0
self.searchButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.searchButtonNode.layer.animatePosition(from: CGPoint(x: -20.0, y: 0.0), to: .zero, duration: 0.2, additive: true)
self.contentTitleNode.alpha = 1.0
self.contentTitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.contentTitleNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -10.0), to: .zero, duration: 0.2, additive: true)
self.contentTitleNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.2)
self.contentSubtitleNode.alpha = 1.0
self.contentSubtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.contentSubtitleNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -10.0), to: .zero, duration: 0.2, additive: true)
self.contentSubtitleNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.2)
if let targetFrame = self.frameForPeerId(peerId), let (size, bottomInset) = self.validLayout {
let sourceCenter = targetFrame.center
let clippedNode = ASDisplayNode()
clippedNode.clipsToBounds = true
clippedNode.cornerRadius = 16.0
clippedNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.contentTitleNode.frame.minY - 15.0), size: CGSize(width: size.width, height: size.height - bottomInset))
self.contentGridNode.view.superview?.insertSubview(clippedNode.view, aboveSubview: self.contentGridNode.view)
let maskView = UIView()
maskView.frame = clippedNode.bounds
let maskImageView = UIImageView()
maskImageView.image = generateMaskImage()
maskImageView.frame = maskView.bounds.offsetBy(dx: 0.0, dy: 36.0)
maskView.addSubview(maskImageView)
clippedNode.view.mask = maskView
self.contentGridNode.alpha = 1.0
self.contentGridNode.forEachItemNode { itemNode in
if let snapshotView = itemNode.view.snapshotView(afterScreenUpdates: false) {
snapshotView.frame = itemNode.view.convert(itemNode.bounds, to: clippedNode.view)
if let itemNode = itemNode as? ShareControllerPeerGridItemNode, itemNode.peerId == peerId {
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, removeOnCompletion: false)
itemNode.layer.animateScale(from: 1.35, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak clippedNode] _ in
clippedNode?.view.removeFromSuperview()
})
} else {
clippedNode.view.addSubview(snapshotView)
itemNode.alpha = 0.0
let angle = sourceCenter.angle(to: itemNode.position)
let distance = sourceCenter.distance(to: itemNode.position)
let newDistance = distance * 2.8
let newPosition = snapshotView.center.offsetBy(distance: newDistance, inDirection: angle)
snapshotView.layer.animatePosition(from: newPosition, to: snapshotView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
snapshotView.layer.animateScale(from: 1.35, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak itemNode] _ in
itemNode?.alpha = 1.0
})
snapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, removeOnCompletion: false)
}
}
}
return targetFrame
} else {
return nil
}
}
func animateOut(peerId: EnginePeer.Id) -> CGRect? {
self.searchButtonNode.alpha = 0.0
self.searchButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.searchButtonNode.layer.animatePosition(from: .zero, to: CGPoint(x: -20.0, y: 0.0), duration: 0.2, additive: true)
self.contentTitleNode.alpha = 0.0
self.contentTitleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.contentTitleNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -10.0), duration: 0.2, additive: true)
self.contentTitleNode.layer.animateScale(from: 1.0, to: 0.85, duration: 0.3)
self.contentSubtitleNode.alpha = 0.0
self.contentSubtitleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.contentSubtitleNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -10.0), duration: 0.2, additive: true)
self.contentSubtitleNode.layer.animateScale(from: 1.0, to: 0.85, duration: 0.3)
if let sourceFrame = self.frameForPeerId(peerId), let (size, bottomInset) = self.validLayout {
let sourceCenter = sourceFrame.center
let clippedNode = ASDisplayNode()
clippedNode.clipsToBounds = true
clippedNode.cornerRadius = 16.0
clippedNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.contentTitleNode.frame.minY - 15.0), size: CGSize(width: size.width, height: size.height - bottomInset))
self.contentGridNode.view.superview?.insertSubview(clippedNode.view, aboveSubview: self.contentGridNode.view)
let maskView = UIView()
maskView.frame = clippedNode.bounds
let maskImageView = UIImageView()
maskImageView.image = generateMaskImage()
maskImageView.frame = maskView.bounds.offsetBy(dx: 0.0, dy: 36.0)
maskView.addSubview(maskImageView)
clippedNode.view.mask = maskView
self.contentGridNode.forEachItemNode { itemNode in
if let snapshotView = itemNode.view.snapshotView(afterScreenUpdates: false) {
snapshotView.frame = itemNode.view.convert(itemNode.bounds, to: clippedNode.view)
clippedNode.view.addSubview(snapshotView)
if let itemNode = itemNode as? ShareControllerPeerGridItemNode, itemNode.peerId == peerId {
} else {
let angle = sourceCenter.angle(to: itemNode.position)
let distance = sourceCenter.distance(to: itemNode.position)
let newDistance = distance * 2.8
let newPosition = snapshotView.center.offsetBy(distance: newDistance, inDirection: angle)
snapshotView.layer.animatePosition(from: snapshotView.center, to: newPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
}
snapshotView.layer.animateScale(from: 1.0, to: 1.35, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
}
}
clippedNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak clippedNode] _ in
clippedNode?.view.removeFromSuperview()
})
self.contentGridNode.alpha = 0.0
return sourceFrame
} else {
return nil
}
}
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let firstLayout = self.validLayout == nil
self.validLayout = (size, bottomInset)
@ -348,7 +531,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
}
}
let gridSize = CGSize(width: size.width - 12.0, height: size.height)
let gridSize = CGSize(width: size.width - 10.0, height: size.height)
self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 0.0, bottom: bottomInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: gridLayoutTransition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
gridLayoutTransition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((size.width - gridSize.width) / 2.0), y: 0.0), size: gridSize))
@ -424,7 +607,11 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.foundPeers.set(.single(self.controllerInteraction.foundPeers))
}
func updateSelectedPeers() {
func update() {
self._tick += 1
}
func updateSelectedPeers(animated: Bool = true) {
if self.segmentedValues != nil {
self.contentTitleNode.isHidden = true
self.contentSubtitleNode.isHidden = true
@ -453,7 +640,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
}
self.contentGridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ShareControllerPeerGridItemNode {
itemNode.updateSelection(animated: true)
itemNode.updateSelection(animated: animated)
}
}
}

View File

@ -94,7 +94,7 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable {
peers[associatedPeer.id] = associatedPeer
}
let peer = EngineRenderedPeer(RenderedPeer(peerId: peer.id, peers: SimpleDictionary(peers), associatedMedia: [:]))
return ShareControllerPeerGridItem(context: context, theme: theme, strings: strings, peer: peer, presence: presence, controllerInteraction: interfaceInteraction, sectionTitle: strings.DialogList_SearchSectionRecent, search: true)
return ShareControllerPeerGridItem(context: context, theme: theme, strings: strings, peer: peer, presence: presence, topicId: nil, threadData: nil, controllerInteraction: interfaceInteraction, sectionTitle: strings.DialogList_SearchSectionRecent, search: true)
}
}
}
@ -129,7 +129,7 @@ private struct ShareSearchPeerEntry: Comparable, Identifiable {
}
func item(context: AccountContext, interfaceInteraction: ShareControllerInteraction) -> GridItem {
return ShareControllerPeerGridItem(context: context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, controllerInteraction: interfaceInteraction, search: true)
return ShareControllerPeerGridItem(context: context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, topicId: nil, threadData: nil, controllerInteraction: interfaceInteraction, search: true)
}
}
@ -546,7 +546,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
func animateIn() {
}
func updateSelectedPeers() {
func updateSelectedPeers(animated: Bool) {
self.contentGridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ShareControllerPeerGridItemNode {
itemNode.updateSelection(animated: true)

View File

@ -0,0 +1,138 @@
import Foundation
import UIKit
import Display
import TelegramCore
import SwiftSignalKit
import AsyncDisplayKit
import Postbox
import TelegramPresentationData
import TelegramStringFormatting
import SelectablePeerNode
import PeerPresenceStatusManager
import AccountContext
import ShimmerEffect
import ComponentFlow
import EmojiStatusComponent
final class ShareTopicGridItem: GridItem {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let peer: EngineRenderedPeer?
let id: Int64
let threadInfo: MessageHistoryThreadData
let controllerInteraction: ShareControllerInteraction
let section: GridSection?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer?, id: Int64, threadInfo: MessageHistoryThreadData, controllerInteraction: ShareControllerInteraction) {
self.context = context
self.theme = theme
self.strings = strings
self.peer = peer
self.id = id
self.threadInfo = threadInfo
self.controllerInteraction = controllerInteraction
self.section = nil
}
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
return ShareTopicGridItemNode()
}
func update(node: GridItemNode) {
}
}
final class ShareTopicGridItemNode: GridItemNode {
private var currentState: (AccountContext, PresentationTheme, PresentationStrings, EngineRenderedPeer?, MessageHistoryThreadData?)?
private let iconView: ComponentView<Empty>
private let textNode: ImmediateTextNode
private var placeholderNode: ShimmerEffectNode?
private var absoluteLocation: (CGRect, CGSize)?
private var currentItem: ShareTopicGridItem?
var id: Int64? {
return self.currentItem?.id
}
override init() {
self.iconView = ComponentView<Empty>()
self.textNode = ImmediateTextNode()
super.init()
self.addSubnode(self.textNode)
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapped)))
}
@objc private func tapped() {
if let item = self.currentItem, let peerId = item.peer?.peerId {
item.controllerInteraction.selectTopic(peerId, item.id, item.threadInfo)
}
}
override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
let rect = absoluteRect
self.absoluteLocation = (rect, containerSize)
if let shimmerNode = self.placeholderNode {
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
}
}
override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) {
super.updateLayout(item: item, size: size, isVisible: isVisible, synchronousLoads: synchronousLoads)
guard let item = item as? ShareTopicGridItem else {
return
}
self.currentItem = item
self.textNode.attributedText = NSAttributedString(string: item.threadInfo.info.title, font: Font.regular(11.0), textColor: item.theme.actionSheet.primaryTextColor)
let textSize = self.textNode.updateLayout(size)
let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: 4.0 + 60.0 + 4.0), size: textSize)
self.textNode.frame = textFrame
let iconContent: EmojiStatusComponent.Content
if let fileId = item.threadInfo.info.icon {
iconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 54.0, height: 54.0), placeholderColor: item.theme.actionSheet.disabledActionTextColor, themeColor: item.theme.actionSheet.primaryTextColor, loopMode: .count(2))
} else {
iconContent = .topic(title: String(item.threadInfo.info.title.prefix(1)), color: item.threadInfo.info.iconColor, size: CGSize(width: 64.0, height: 64.0))
}
let iconSize = self.iconView.update(
transition: .easeInOut(duration: 0.2),
component: AnyComponent(EmojiStatusComponent(
context: item.context,
animationCache: item.context.animationCache,
animationRenderer: item.context.animationRenderer,
content: iconContent,
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: CGSize(width: 54.0, height: 54.0)
)
if let iconComponentView = self.iconView.view {
if iconComponentView.superview == nil {
self.view.addSubview(iconComponentView)
}
iconComponentView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - iconSize.width) / 2.0), y: 7.0), size: iconSize)
}
}
override func layout() {
super.layout()
}
}

View File

@ -0,0 +1,432 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import Display
import TelegramPresentationData
import TelegramUIPreferences
import MergeLists
import AvatarNode
import AccountContext
import PeerPresenceStatusManager
import AppBundle
import SegmentedControlNode
import ContextUI
private let subtitleFont = Font.regular(12.0)
private struct ShareTopicEntry: Comparable, Identifiable {
let index: Int32
let peer: EngineRenderedPeer
let id: Int64
let threadData: MessageHistoryThreadData
let theme: PresentationTheme
let strings: PresentationStrings
var stableId: Int64 {
return self.id
}
static func ==(lhs: ShareTopicEntry, rhs: ShareTopicEntry) -> Bool {
if lhs.index != rhs.index {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.id != rhs.id {
return false
}
if lhs.threadData != rhs.threadData {
return false
}
return true
}
static func <(lhs: ShareTopicEntry, rhs: ShareTopicEntry) -> Bool {
return lhs.index < rhs.index
}
func item(context: AccountContext, interfaceInteraction: ShareControllerInteraction) -> GridItem {
return ShareTopicGridItem(context: context, theme: self.theme, strings: self.strings, peer: self.peer, id: self.id, threadInfo: self.threadData, controllerInteraction: interfaceInteraction)
}
}
private struct ShareGridTransaction {
let deletions: [Int]
let insertions: [GridNodeInsertItem]
let updates: [GridNodeUpdateItem]
let animated: Bool
}
private func preparedGridEntryTransition(context: AccountContext, from fromEntries: [ShareTopicEntry], to toEntries: [ShareTopicEntry], interfaceInteraction: ShareControllerInteraction) -> ShareGridTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interfaceInteraction: interfaceInteraction), previousIndex: $0.2) }
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interfaceInteraction: interfaceInteraction)) }
return ShareGridTransaction(deletions: deletions, insertions: insertions, updates: updates, animated: false)
}
private class CancelButtonNode: ASDisplayNode {
let buttonNode: HighlightTrackingButtonNode
private let arrowNode: ASImageNode
private let labelNode: ImmediateTextNode
var theme: PresentationTheme {
didSet {
self.updateThemeAndStrings()
}
}
private let strings: PresentationStrings
init(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
self.strings = strings
self.buttonNode = HighlightTrackingButtonNode()
self.arrowNode = ASImageNode()
self.arrowNode.displaysAsynchronously = false
self.arrowNode.isUserInteractionEnabled = false
self.labelNode = ImmediateTextNode()
self.labelNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.arrowNode)
self.buttonNode.addSubnode(self.labelNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
guard let strongSelf = self else {
return
}
if highlighted {
strongSelf.arrowNode.layer.removeAnimation(forKey: "opacity")
strongSelf.arrowNode.alpha = 0.4
strongSelf.labelNode.layer.removeAnimation(forKey: "opacity")
strongSelf.labelNode.alpha = 0.4
} else {
strongSelf.arrowNode.alpha = 1.0
strongSelf.arrowNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.labelNode.alpha = 1.0
strongSelf.labelNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
self.updateThemeAndStrings()
}
func updateThemeAndStrings() {
self.labelNode.attributedText = NSAttributedString(string: self.strings.Common_Back, font: Font.regular(17.0), textColor: self.theme.rootController.navigationBar.accentTextColor)
let labelSize = self.labelNode.updateLayout(CGSize(width: 120.0, height: 56.0))
self.buttonNode.frame = CGRect(origin: .zero, size: CGSize(width: labelSize.width, height: self.buttonNode.frame.height))
self.arrowNode.image = NavigationBarTheme.generateBackArrowImage(color: self.theme.rootController.navigationBar.accentTextColor)
if let image = self.arrowNode.image {
self.arrowNode.frame = CGRect(origin: self.arrowNode.frame.origin, size: image.size)
}
self.labelNode.frame = CGRect(origin: self.labelNode.frame.origin, size: labelSize)
self.buttonNode.subnodeTransform = CATransform3DMakeTranslation(11.0, 0.0, 0.0)
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
self.buttonNode.frame = CGRect(origin: .zero, size: CGSize(width: self.buttonNode.frame.width, height: constrainedSize.height))
self.arrowNode.frame = CGRect(origin: CGPoint(x: -19.0, y: floorToScreenPixels((constrainedSize.height - self.arrowNode.frame.size.height) / 2.0)), size: self.arrowNode.frame.size)
self.labelNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((constrainedSize.height - self.labelNode.frame.size.height) / 2.0)), size: self.labelNode.frame.size)
return CGSize(width: 70.0, height: 56.0)
}
}
final class ShareTopicsContainerNode: ASDisplayNode, ShareContentContainerNode {
func setEnsurePeerVisibleOnLayout(_ peerId: TelegramCore.EnginePeer.Id?) {
}
func updateSelectedPeers(animated: Bool) {
}
private let sharedContext: SharedAccountContext
private let context: AccountContext
private let theme: PresentationTheme
private let strings: PresentationStrings
private let controllerInteraction: ShareControllerInteraction
private let disposable = MetaDisposable()
private var entries: [ShareTopicEntry] = []
private var enqueuedTransitions: [(ShareGridTransaction, Bool)] = []
private let contentGridNode: GridNode
private let contentTitleNode: ASTextNode
private let contentSubtitleNode: ASTextNode
private let backNode: CancelButtonNode
private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
private var validLayout: (CGSize, CGFloat)?
private var overrideGridOffsetTransition: ContainedViewLayoutTransition?
let peersValue = Promise<[EngineChatList.Item]>()
var backPressed: () -> Void = {}
init(sharedContext: SharedAccountContext, context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer, topics: [EngineChatList.Item], controllerInteraction: ShareControllerInteraction) {
self.sharedContext = sharedContext
self.context = context
self.theme = theme
self.strings = strings
self.controllerInteraction = controllerInteraction
self.peersValue.set(.single(topics))
let items: Signal<[ShareTopicEntry], NoError> = self.peersValue.get()
|> map { topics -> [ShareTopicEntry] in
var entries: [ShareTopicEntry] = []
var index: Int32 = 0
for topic in topics {
if case let .forum(_, _, threadId, _, _) = topic.index, let threadData = topic.threadData {
entries.append(ShareTopicEntry(index: index, peer: EngineRenderedPeer(peer: peer), id: threadId, threadData: threadData, theme: theme, strings: strings))
index += 1
}
}
return entries
}
self.contentGridNode = GridNode()
self.contentTitleNode = ASTextNode()
self.contentTitleNode.attributedText = NSAttributedString(string: peer.compactDisplayTitle, font: Font.medium(20.0), textColor: self.theme.actionSheet.primaryTextColor)
self.contentSubtitleNode = ASTextNode()
self.contentSubtitleNode.maximumNumberOfLines = 1
self.contentSubtitleNode.isUserInteractionEnabled = false
self.contentSubtitleNode.displaysAsynchronously = false
self.contentSubtitleNode.truncationMode = .byTruncatingTail
self.contentSubtitleNode.attributedText = NSAttributedString(string: strings.ShareMenu_SelectTopic, font: subtitleFont, textColor: self.theme.actionSheet.secondaryTextColor)
self.backNode = CancelButtonNode(theme: theme, strings: strings)
super.init()
self.addSubnode(self.contentGridNode)
self.addSubnode(self.contentTitleNode)
self.addSubnode(self.contentSubtitleNode)
self.addSubnode(self.backNode)
let previousItems = Atomic<[ShareTopicEntry]?>(value: [])
self.disposable.set((items
|> deliverOnMainQueue).start(next: { [weak self] entries in
if let strongSelf = self {
let previousEntries = previousItems.swap(entries)
strongSelf.entries = entries
let firstTime = previousEntries == nil
let transition = preparedGridEntryTransition(context: context, from: previousEntries ?? [], to: entries, interfaceInteraction: controllerInteraction)
strongSelf.enqueueTransition(transition, firstTime: firstTime)
}
}))
self.contentGridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition)
}
self.backNode.buttonNode.addTarget(self, action: #selector(self.backButtonPressed), forControlEvents: .touchUpInside)
}
deinit {
self.disposable.dispose()
}
@objc private func backButtonPressed() {
self.backPressed()
}
private func enqueueTransition(_ transition: ShareGridTransaction, firstTime: Bool) {
self.enqueuedTransitions.append((transition, firstTime))
if self.validLayout != nil {
while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
}
}
}
private func dequeueTransition() {
if let (transition, _) = self.enqueuedTransitions.first {
self.enqueuedTransitions.remove(at: 0)
var itemTransition: ContainedViewLayoutTransition = .immediate
if transition.animated {
itemTransition = .animated(duration: 0.3, curve: .spring)
}
self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: nil, updateLayout: nil, itemTransition: itemTransition, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
}
}
func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) {
self.contentOffsetUpdated = f
}
private func calculateMetrics(size: CGSize) -> (topInset: CGFloat, itemWidth: CGFloat) {
let itemCount = self.entries.count
let itemInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0)
let minimalItemWidth: CGFloat = size.width > 301.0 ? 70.0 : 60.0
let effectiveWidth = size.width - itemInsets.left - itemInsets.right
let itemsPerRow = Int(effectiveWidth / minimalItemWidth)
let itemWidth = floor(effectiveWidth / CGFloat(itemsPerRow))
var rowCount = itemCount / itemsPerRow + (itemCount % itemsPerRow != 0 ? 1 : 0)
rowCount = max(rowCount, 4)
let minimallyRevealedRowCount: CGFloat = 3.7
let initiallyRevealedRowCount = min(minimallyRevealedRowCount, CGFloat(rowCount))
let gridTopInset = max(0.0, size.height - floor(initiallyRevealedRowCount * itemWidth) - 14.0)
return (gridTopInset, itemWidth)
}
func activate() {
}
func deactivate() {
}
func animateIn(sourceFrame: CGRect) {
self.backNode.alpha = 1.0
self.backNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.backNode.layer.animatePosition(from: CGPoint(x: 20.0, y: 0.0), to: .zero, duration: 0.2, additive: true)
self.contentTitleNode.alpha = 1.0
self.contentTitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.contentTitleNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: .zero, duration: 0.2, additive: true)
self.contentTitleNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.2)
self.contentSubtitleNode.alpha = 1.0
self.contentSubtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.contentSubtitleNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: .zero, duration: 0.2, additive: true)
self.contentSubtitleNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.2)
self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.contentGridNode.forEachItemNode { itemNode in
itemNode.layer.animatePosition(from: sourceFrame.center, to: itemNode.position, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
itemNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
}
}
func animateOut(targetFrame: CGRect, completion: @escaping () -> Void = {}) {
self.backNode.alpha = 0.0
self.backNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.backNode.layer.animatePosition(from: .zero, to: CGPoint(x: 20.0, y: 0.0), duration: 0.2, additive: true)
self.contentTitleNode.alpha = 0.0
self.contentTitleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.contentTitleNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 10.0), duration: 0.2, additive: true)
self.contentTitleNode.layer.animateScale(from: 1.0, to: 0.85, duration: 0.2)
self.contentSubtitleNode.alpha = 0.0
self.contentSubtitleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.contentSubtitleNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 10.0), duration: 0.2, additive: true)
self.contentSubtitleNode.layer.animateScale(from: 1.0, to: 0.85, duration: 0.2)
self.contentGridNode.alpha = 0.0
self.contentGridNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { _ in
completion()
})
self.contentGridNode.forEachItemNode { itemNode in
itemNode.layer.animatePosition(from: itemNode.position, to: targetFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
itemNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
}
}
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let firstLayout = self.validLayout == nil
self.validLayout = (size, bottomInset)
let gridLayoutTransition: ContainedViewLayoutTransition
if firstLayout {
gridLayoutTransition = .immediate
self.overrideGridOffsetTransition = transition
} else {
gridLayoutTransition = transition
self.overrideGridOffsetTransition = nil
}
let (gridTopInset, itemWidth) = self.calculateMetrics(size: size)
let scrollToItem: GridNodeScrollToItem? = nil
let delta = bottomInset
var gridSize = CGSize(width: size.width - 12.0, height: size.height)
gridSize.height -= delta
self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: gridTopInset, left: 0.0, bottom: 0.0, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth + 25.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: gridLayoutTransition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
gridLayoutTransition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((size.width - gridSize.width) / 2.0), y: 0.0), size: gridSize))
if firstLayout {
while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
}
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let nodes: [ASDisplayNode] = [self.backNode]
for node in nodes {
let nodeFrame = node.frame
if node.isHidden {
continue
}
if let result = node.hitTest(point.offsetBy(dx: -nodeFrame.minX, dy: -nodeFrame.minY), with: event) {
return result
}
}
return super.hitTest(point, with: event)
}
private func gridPresentationLayoutUpdated(_ presentationLayout: GridNodeCurrentPresentationLayout, transition: ContainedViewLayoutTransition) {
guard let (size, _) = self.validLayout else {
return
}
let actualTransition = self.overrideGridOffsetTransition ?? transition
self.overrideGridOffsetTransition = nil
let titleAreaHeight: CGFloat = 64.0
let rawTitleOffset = -titleAreaHeight - presentationLayout.contentOffset.y
let titleOffset = max(-titleAreaHeight, rawTitleOffset)
let titleSize = self.contentTitleNode.measure(size)
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: titleOffset + 15.0), size: titleSize)
transition.updateFrame(node: self.contentTitleNode, frame: titleFrame)
let subtitleSize = self.contentSubtitleNode.measure(CGSize(width: size.width - 44.0 * 2.0 - 8.0 * 2.0, height: titleAreaHeight))
let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleOffset + 40.0), size: subtitleSize)
var originalSubtitleFrame = self.contentSubtitleNode.frame
originalSubtitleFrame.origin.x = subtitleFrame.origin.x
originalSubtitleFrame.size = subtitleFrame.size
self.contentSubtitleNode.frame = originalSubtitleFrame
transition.updateFrame(node: self.contentSubtitleNode, frame: subtitleFrame)
let backFrame = CGRect(origin: CGPoint(x: 30.0, y: titleOffset + 6.0), size: CGSize(width: 90.0, height: 56.0))
transition.updateFrame(node: self.backNode, frame: backFrame)
self.contentOffsetUpdated?(presentationLayout.contentOffset.y, actualTransition)
}
}

View File

@ -712,6 +712,6 @@ final class VoiceChatPreviewContentNode: ASDisplayNode, ShareContentContainerNod
self.contentOffsetUpdated?(-size.height + nodeHeight - 64.0, transition)
}
func updateSelectedPeers() {
func updateSelectedPeers(animated: Bool) {
}
}

View File

@ -7,18 +7,40 @@ import TelegramPresentationData
import LottieAnimationComponent
public final class AudioTranscriptionButtonComponent: Component {
public enum Theme: Equatable {
public static func == (lhs: AudioTranscriptionButtonComponent.Theme, rhs: AudioTranscriptionButtonComponent.Theme) -> Bool {
switch lhs {
case let .bubble(lhsTheme):
if case let .bubble(rhsTheme) = lhs {
return lhsTheme === rhsTheme
} else {
return false
}
case let .freeform(lhsFreeform):
if case let .freeform(rhsFreeform) = rhs, lhsFreeform == rhsFreeform {
return true
} else {
return false
}
}
}
case bubble(PresentationThemePartedColors)
case freeform((UIColor, Bool))
}
public enum TranscriptionState {
case inProgress
case expanded
case collapsed
}
public let theme: PresentationThemePartedColors
public let theme: AudioTranscriptionButtonComponent.Theme
public let transcriptionState: TranscriptionState
public let pressed: () -> Void
public init(
theme: PresentationThemePartedColors,
theme: AudioTranscriptionButtonComponent.Theme,
transcriptionState: TranscriptionState,
pressed: @escaping () -> Void
) {
@ -28,7 +50,7 @@ public final class AudioTranscriptionButtonComponent: Component {
}
public static func ==(lhs: AudioTranscriptionButtonComponent, rhs: AudioTranscriptionButtonComponent) -> Bool {
if lhs.theme !== rhs.theme {
if lhs.theme != rhs.theme {
return false
}
if lhs.transcriptionState != rhs.transcriptionState {
@ -40,12 +62,14 @@ public final class AudioTranscriptionButtonComponent: Component {
public final class View: UIButton {
private var component: AudioTranscriptionButtonComponent?
private let blurredBackgroundNode: NavigationBackgroundNode
private let backgroundLayer: SimpleLayer
private let animationView: ComponentHostView<Empty>
private var progressAnimationView: ComponentHostView<Empty>?
override init(frame: CGRect) {
self.blurredBackgroundNode = NavigationBackgroundNode(color: .clear)
self.backgroundLayer = SimpleLayer()
self.animationView = ComponentHostView<Empty>()
@ -73,7 +97,22 @@ public final class AudioTranscriptionButtonComponent: Component {
func update(component: AudioTranscriptionButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
let size = CGSize(width: 30.0, height: 30.0)
let foregroundColor = component.theme.bubble.withWallpaper.reactionActiveBackground
let foregroundColor: UIColor
let backgroundColor: UIColor
switch component.theme {
case let .bubble(theme):
foregroundColor = theme.bubble.withWallpaper.reactionActiveBackground
backgroundColor = theme.bubble.withWallpaper.reactionInactiveBackground
case let .freeform(colorAndBlur):
foregroundColor = UIColor.white
backgroundColor = .clear
if self.blurredBackgroundNode.view.superview == nil {
self.insertSubview(self.blurredBackgroundNode.view, at: 0)
}
self.blurredBackgroundNode.updateColor(color: colorAndBlur.0, enableBlur: colorAndBlur.1, transition: .immediate)
self.blurredBackgroundNode.update(size: size, cornerRadius: 10.0, transition: .immediate)
self.blurredBackgroundNode.frame = CGRect(origin: .zero, size: size)
}
if self.component?.transcriptionState != component.transcriptionState {
switch component.transcriptionState {
@ -130,7 +169,7 @@ public final class AudioTranscriptionButtonComponent: Component {
self.animationView.frame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: floor((size.width - animationSize.height) / 2.0)), size: animationSize)
}
self.backgroundLayer.backgroundColor = component.theme.bubble.withWallpaper.reactionInactiveBackground.cgColor
self.backgroundLayer.backgroundColor = backgroundColor.cgColor
self.component = component

View File

@ -48,7 +48,7 @@ public final class EmojiStatusComponent: Component {
case verified(fillColor: UIColor, foregroundColor: UIColor, sizeType: SizeType)
case text(color: UIColor, string: String)
case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor, themeColor: UIColor?, loopMode: LoopMode)
case topic(title: String, colorIndex: Int, size: CGSize)
case topic(title: String, color: Int32, size: CGSize)
}
public let context: AccountContext
@ -223,7 +223,7 @@ public final class EmojiStatusComponent: Component {
} else {
iconImage = nil
}
case let .topic(title, colorIndex, size):
case let .topic(title, color, size):
func generateTopicIcon(backgroundColors: [UIColor], strokeColors: [UIColor]) -> UIImage? {
return generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
@ -283,18 +283,21 @@ public final class EmojiStatusComponent: Component {
})
}
let topicColors: [([UInt32], [UInt32])] = [
([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
let clippedIndex = colorIndex % topicColors.count
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}
if let image = generateTopicIcon(backgroundColors: topicColors[clippedIndex].0.map(UIColor.init(rgb:)), strokeColors: topicColors[clippedIndex].1.map(UIColor.init(rgb:))) {
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
let colors = topicColors[color] ?? generateTopicColors(color)
if let image = generateTopicIcon(backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:))) {
iconImage = image
} else {
iconImage = nil

View File

@ -2145,7 +2145,7 @@ public final class EmojiPagerContentComponent: Component {
public enum Icon: Equatable, Hashable {
case premiumStar
case topic(String)
case topic(String, Int32)
}
case animation(EntityKeyboardAnimationData)
@ -2943,8 +2943,9 @@ public final class EmojiPagerContentComponent: Component {
let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
}
case let .topic(title):
if let image = generateTopicIcon(backgroundColors: [UIColor(rgb: 0x6FB9F0), UIColor(rgb: 0x0261E4)], strokeColors: [UIColor(rgb: 0x026CB5), UIColor(rgb: 0x064BB7)], title: title) {
case let .topic(title, color):
let colors = self.getTopicColors(color)
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
}
@ -2992,16 +2993,30 @@ public final class EmojiPagerContentComponent: Component {
return nullAction
}
private func getTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
return topicColors[color] ?? ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}
func update(content: ItemContent) {
if self.content != content {
if case let .icon(icon) = content, case let .topic(title) = icon {
if case let .icon(icon) = content, case let .topic(title, color) = icon {
let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
if let image = generateTopicIcon(backgroundColors: [UIColor(rgb: 0x6FB9F0), UIColor(rgb: 0x0261E4)], strokeColors: [UIColor(rgb: 0x026CB5), UIColor(rgb: 0x064BB7)], title: title) {
let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
let colors = self.getTopicColors(color)
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
let imageSize = image.size
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
}
@ -6195,7 +6210,8 @@ public final class EmojiPagerContentComponent: Component {
chatPeerId: EnginePeer.Id?,
selectedItems: Set<MediaId> = Set(),
topStatusTitle: String? = nil,
topicTitle: String? = nil
topicTitle: String? = nil,
topicColor: Int32? = nil
) -> Signal<EmojiPagerContentComponent, NoError> {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
@ -6279,7 +6295,7 @@ public final class EmojiPagerContentComponent: Component {
if isTopicIconSelection {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .icon(.topic(String((topicTitle ?? "").prefix(1)))),
content: .icon(.topic(String((topicTitle ?? "").prefix(1)), topicColor ?? 0)),
itemFile: nil,
subgroupId: nil,
icon: .none,

View File

@ -23,6 +23,7 @@ private final class TitleFieldComponent: Component {
let accentColor: UIColor
let placeholderColor: UIColor
let fileId: Int64
let iconColor: Int32
let text: String
let textUpdated: (String) -> Void
@ -32,6 +33,7 @@ private final class TitleFieldComponent: Component {
accentColor: UIColor,
placeholderColor: UIColor,
fileId: Int64,
iconColor: Int32,
text: String,
textUpdated: @escaping (String) -> Void
) {
@ -40,6 +42,7 @@ private final class TitleFieldComponent: Component {
self.accentColor = accentColor
self.placeholderColor = placeholderColor
self.fileId = fileId
self.iconColor = iconColor
self.text = text
self.textUpdated = textUpdated
}
@ -60,6 +63,9 @@ private final class TitleFieldComponent: Component {
if lhs.fileId != rhs.fileId {
return false
}
if lhs.iconColor != rhs.iconColor {
return false
}
if lhs.text != rhs.text {
return false
}
@ -106,7 +112,7 @@ private final class TitleFieldComponent: Component {
let iconContent: EmojiStatusComponent.Content
if component.fileId == 0 {
iconContent = .topic(title: String(component.text.prefix(1)), colorIndex: 0, size: CGSize(width: 32.0, height: 32.0))
iconContent = .topic(title: String(component.text.prefix(1)), color: component.iconColor, size: CGSize(width: 32.0, height: 32.0))
} else {
iconContent = .animation(content: .customEmoji(fileId: component.fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: component.placeholderColor, themeColor: component.accentColor, loopMode: .count(2))
}
@ -266,7 +272,7 @@ private final class TopicIconSelectionComponent: Component {
deviceMetrics: component.deviceMetrics,
hiddenInputHeight: 0.0,
displayBottomPanel: false,
isExpanded: false
isExpanded: true
)),
environment: {},
containerSize: availableSize
@ -347,6 +353,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
var title: String
var fileId: Int64
var iconColor: Int32
init(context: AccountContext, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void) {
self.context = context
@ -354,12 +361,16 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
self.iconUpdated = iconUpdated
switch mode {
case .create:
self.title = ""
self.fileId = 0
case let .edit(info):
self.title = info.title
self.fileId = info.icon ?? 0
case .create:
self.title = ""
self.fileId = 0
let colors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98,0xFF93B2, 0xFB6F5F]
self.iconColor = colors.randomElement() ?? 0x0
case let .edit(info):
self.title = info.title
self.fileId = info.icon ?? 0
self.iconColor = info.iconColor
}
super.init()
@ -378,7 +389,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
areCustomEmojiEnabled: true,
chatPeerId: self.context.account.peerId,
selectedItems: Set(),
topicTitle: self.title
topicTitle: self.title,
topicColor: self.iconColor
)
|> deliverOnMainQueue).start(next: { [weak self] content in
self?.emojiContent = content
@ -410,7 +422,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
areCustomEmojiEnabled: true,
chatPeerId: self.context.account.peerId,
selectedItems: Set([MediaId(namespace: Namespaces.Media.CloudFile, id: self.fileId)]),
topicTitle: self.title
topicTitle: self.title,
topicColor: self.iconColor
)
|> deliverOnMainQueue).start(next: { [weak self] content in
self?.emojiContent = content
@ -442,7 +455,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
areCustomEmojiEnabled: true,
chatPeerId: self.context.account.peerId,
selectedItems: Set([MediaId(namespace: Namespaces.Media.CloudFile, id: self.fileId)]),
topicTitle: self.title
topicTitle: self.title,
topicColor: self.iconColor
)
|> deliverOnMainQueue).start(next: { [weak self] content in
self?.emojiContent = content
@ -532,6 +546,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
accentColor: environment.theme.list.itemAccentColor,
placeholderColor: environment.theme.list.disclosureArrowColor,
fileId: state.fileId,
iconColor: state.iconColor,
text: state.title,
textUpdated: { [weak state] text in
state?.updateTitle(text)
@ -568,6 +583,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
)
contentHeight += iconHeader.size.height + headerSpacing
let bottomInset = max(environment.safeInsets.bottom, 12.0)
let iconBackground = iconBackground.update(
component: RoundedRectangle(
color: environment.theme.list.itemBlocksBackgroundColor,
@ -575,7 +592,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0,
height: context.availableSize.height - topInset - titleHeader.size.height - titleBackground.size.height
height: context.availableSize.height - contentHeight - bottomInset
),
transition: context.transition
)
@ -584,7 +601,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
)
if let emojiContent = state.emojiContent {
let availableHeight = context.availableSize.height - contentHeight - environment.inputHeight
let availableHeight = context.availableSize.height - contentHeight - max(bottomInset, environment.inputHeight)
let iconSelector = iconSelector.update(
component: TopicIconSelectionComponent(
@ -592,7 +609,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
strings: environment.strings,
deviceMetrics: environment.deviceMetrics,
emojiContent: emojiContent,
backgroundColor: .clear,
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
separatorColor: environment.theme.list.blocksBackgroundColor
),
environment: {},
@ -601,6 +618,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
)
context.add(iconSelector
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconSelector.size.height / 2.0))
.cornerRadius(10.0)
.clipsToBounds(true)
)
let accountContext = context.component.context
@ -736,8 +755,6 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
}
@objc private func createPressed() {
// self.dismiss()
self.completion(self.state.0, self.state.1)
}
}

View File

@ -733,8 +733,28 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
}, forceOrientation: { orientation in
let value = orientation.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
if #available(iOSApplicationExtension 16.0, iOS 16.0, *) {
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
var interfaceOrientations: UIInterfaceOrientationMask = []
switch orientation {
case .portrait:
interfaceOrientations = .portrait
case .landscapeLeft:
interfaceOrientations = .landscapeLeft
case .landscapeRight:
interfaceOrientations = .landscapeRight
case .portraitUpsideDown:
interfaceOrientations = .portraitUpsideDown
case .unknown:
interfaceOrientations = .portrait
@unknown default:
interfaceOrientations = .portrait
}
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: interfaceOrientations))
} else {
UIDevice.current.setValue(value, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
}
})
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true, removeDatabaseOnError: true)

View File

@ -4828,7 +4828,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let fileId = threadInfo.icon {
avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .count(2))
} else {
avatarContent = .topic(title: String(threadInfo.title.prefix(1)), colorIndex: Int(threadInfo.iconColor), size: CGSize(width: 32.0, height: 32.0))
avatarContent = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: 32.0, height: 32.0))
}
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setStatus(context: strongSelf.context, content: avatarContent)
} else {

View File

@ -838,11 +838,11 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent,
if let fileId = self.fileId {
iconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 54.0, height: 54.0), placeholderColor: .clear, themeColor: serviceColor.primaryText, loopMode: .count(2))
} else {
var colorIndex: Int = 0
if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation {
colorIndex = Int(clamping: abs(replyThreadMessage.effectiveTopId.id))
}
iconContent = .topic(title: String(title.prefix(1)), colorIndex: colorIndex, size: CGSize(width: 64.0, height: 64.0))
let colorIndex: Int32 = 0
// if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation {
// colorIndex = Int(clamping: abs(replyThreadMessage.effectiveTopId.id))
// }
iconContent = .topic(title: String(title.prefix(1)), color: colorIndex, size: CGSize(width: 64.0, height: 64.0))
}
let insets = UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)

View File

@ -173,14 +173,14 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode {
var lineWidth = self.progressLayer.lineWidth
self.progressLayer.lineWidth = 1.0
self.progressLayer.animate(from: lineWidth as NSNumber, to: 1.0 as NSNumber, keyPath: "lineWidth", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.25, completion: { [weak self] _ in
self?.progressLayer.animate(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "lineWidth", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.15, removeOnCompletion: false)
self.progressLayer.animate(from: lineWidth as NSNumber, to: 1.0 as NSNumber, keyPath: "lineWidth", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2, completion: { [weak self] _ in
self?.progressLayer.animate(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "lineWidth", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.1, removeOnCompletion: false)
})
var path = self.progressLayer.path
var targetPath = UIBezierPath(arcCenter: CGPoint(x: self.progressLayer.frame.width / 2.0, y: self.progressLayer.frame.height / 2.0), radius: 35.0, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(-0.5 * .pi + 2.0 * .pi), clockwise: true).cgPath
self.progressLayer.path = targetPath
self.progressLayer.animate(from: path, to: targetPath, keyPath: "path", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
self.progressLayer.animate(from: path, to: targetPath, keyPath: "path", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.2)
self.fillLayer.isHidden = false
self.fillLayer.path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)).cgPath

View File

@ -425,7 +425,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if let iconFileId = threadInfo.icon {
content = .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: avatarSize, height: avatarSize), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever)
} else {
content = .topic(title: String(threadInfo.title.prefix(1)), colorIndex: Int(threadInfo.iconColor), size: CGSize(width: avatarSize, height: avatarSize))
content = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: avatarSize, height: avatarSize))
}
let _ = iconView.update(
transition: .immediate,