mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
85ac817816
commit
3d5218b3f6
@ -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";
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,6 @@ final class LanguageLinkPreviewContentNode: ASDisplayNode, ShareContentContainer
|
||||
self.contentOffsetUpdated?(-size.height + nodeHeight - 64.0, transition)
|
||||
}
|
||||
|
||||
func updateSelectedPeers() {
|
||||
func updateSelectedPeers(animated: Bool) {
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
138
submodules/ShareController/Sources/ShareTopicGridItem.swift
Normal file
138
submodules/ShareController/Sources/ShareTopicGridItem.swift
Normal 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()
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -712,6 +712,6 @@ final class VoiceChatPreviewContentNode: ASDisplayNode, ShareContentContainerNod
|
||||
self.contentOffsetUpdated?(-size.height + nodeHeight - 64.0, transition)
|
||||
}
|
||||
|
||||
func updateSelectedPeers() {
|
||||
func updateSelectedPeers(animated: Bool) {
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user