mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-20 05:09:23 +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.MessageChangedGroupUsernames" = "%@ changed group links:";
|
||||||
"Channel.AdminLog.MessageChangedChannelUsernames" = "%@ changed channel links:";
|
"Channel.AdminLog.MessageChangedChannelUsernames" = "%@ changed channel links:";
|
||||||
"Channel.AdminLog.MessagePreviousLinks" = "Previous 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 {
|
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)
|
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 {
|
} 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(
|
let avatarIconComponent = EmojiStatusComponent(
|
||||||
|
|||||||
@ -1053,7 +1053,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
if let fileId = icon, fileId != 0 {
|
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)
|
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 {
|
} 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(
|
let avatarIconComponent = EmojiStatusComponent(
|
||||||
|
|||||||
@ -21,7 +21,9 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
case iPhone13Pro
|
case iPhone13Pro
|
||||||
case iPhone13ProMax
|
case iPhone13ProMax
|
||||||
case iPhone14Pro
|
case iPhone14Pro
|
||||||
|
case iPhone14ProZoomed
|
||||||
case iPhone14ProMax
|
case iPhone14ProMax
|
||||||
|
case iPhone14ProMaxZoomed
|
||||||
case iPad
|
case iPad
|
||||||
case iPadMini
|
case iPadMini
|
||||||
case iPad102Inch
|
case iPad102Inch
|
||||||
@ -49,7 +51,9 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
.iPhone13Pro,
|
.iPhone13Pro,
|
||||||
.iPhone13ProMax,
|
.iPhone13ProMax,
|
||||||
.iPhone14Pro,
|
.iPhone14Pro,
|
||||||
|
.iPhone14ProZoomed,
|
||||||
.iPhone14ProMax,
|
.iPhone14ProMax,
|
||||||
|
.iPhone14ProMaxZoomed,
|
||||||
.iPad,
|
.iPad,
|
||||||
.iPadMini,
|
.iPadMini,
|
||||||
.iPad102Inch,
|
.iPad102Inch,
|
||||||
@ -83,7 +87,9 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
let width = device.screenSize.width
|
let width = device.screenSize.width
|
||||||
let height = device.screenSize.height
|
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 ((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
|
self = .iPhoneXr
|
||||||
} else {
|
} else {
|
||||||
self = device
|
self = device
|
||||||
@ -135,8 +141,12 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return CGSize(width: 428.0, height: 926.0)
|
return CGSize(width: 428.0, height: 926.0)
|
||||||
case .iPhone14Pro:
|
case .iPhone14Pro:
|
||||||
return CGSize(width: 393.0, height: 852.0)
|
return CGSize(width: 393.0, height: 852.0)
|
||||||
|
case .iPhone14ProZoomed:
|
||||||
|
return CGSize(width: 320.0, height: 693.0)
|
||||||
case .iPhone14ProMax:
|
case .iPhone14ProMax:
|
||||||
return CGSize(width: 430.0, height: 932.0)
|
return CGSize(width: 430.0, height: 932.0)
|
||||||
|
case .iPhone14ProMaxZoomed:
|
||||||
|
return CGSize(width: 375.0, height: 812.0)
|
||||||
case .iPad:
|
case .iPad:
|
||||||
return CGSize(width: 768.0, height: 1024.0)
|
return CGSize(width: 768.0, height: 1024.0)
|
||||||
case .iPadMini:
|
case .iPadMini:
|
||||||
@ -164,9 +174,9 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return 41.0 + UIScreenPixel
|
return 41.0 + UIScreenPixel
|
||||||
case .iPhone12Mini:
|
case .iPhone12Mini:
|
||||||
return 44.0
|
return 44.0
|
||||||
case .iPhone12, .iPhone13, .iPhone13Pro, .iPhone14Pro:
|
case .iPhone12, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed:
|
||||||
return 47.0 + UIScreenPixel
|
return 47.0 + UIScreenPixel
|
||||||
case .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax:
|
case .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax, .iPhone14ProMaxZoomed:
|
||||||
return 53.0 + UIScreenPixel
|
return 53.0 + UIScreenPixel
|
||||||
case let .unknown(_, _, onScreenNavigationHeight):
|
case let .unknown(_, _, onScreenNavigationHeight):
|
||||||
if let _ = onScreenNavigationHeight {
|
if let _ = onScreenNavigationHeight {
|
||||||
@ -181,7 +191,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
|
|
||||||
func safeInsets(inLandscape: Bool) -> UIEdgeInsets {
|
func safeInsets(inLandscape: Bool) -> UIEdgeInsets {
|
||||||
switch self {
|
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)
|
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:
|
default:
|
||||||
return UIEdgeInsets.zero
|
return UIEdgeInsets.zero
|
||||||
@ -192,6 +202,10 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
switch self {
|
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, .iPhone14ProMax:
|
||||||
return inLandscape ? 21.0 : 34.0
|
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:
|
case .iPadPro3rdGen, .iPadPro11Inch:
|
||||||
return 21.0
|
return 21.0
|
||||||
case .iPad, .iPadPro, .iPadPro10Inch, .iPadMini, .iPadMini6thGen:
|
case .iPad, .iPadPro, .iPadPro10Inch, .iPadMini, .iPadMini6thGen:
|
||||||
@ -224,6 +238,8 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
switch self {
|
switch self {
|
||||||
case .iPhone14Pro, .iPhone14ProMax:
|
case .iPhone14Pro, .iPhone14ProMax:
|
||||||
return 54.0
|
return 54.0
|
||||||
|
case .iPhone14ProMaxZoomed:
|
||||||
|
return 47.0
|
||||||
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax:
|
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax:
|
||||||
return 44.0
|
return 44.0
|
||||||
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
||||||
@ -242,7 +258,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return 162.0
|
return 162.0
|
||||||
case .iPhone6, .iPhone6Plus:
|
case .iPhone6, .iPhone6Plus:
|
||||||
return 163.0
|
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
|
return 172.0
|
||||||
case .iPad, .iPad102Inch, .iPadPro10Inch:
|
case .iPad, .iPad102Inch, .iPadPro10Inch:
|
||||||
return 348.0
|
return 348.0
|
||||||
@ -261,7 +277,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return 216.0
|
return 216.0
|
||||||
case .iPhone6Plus:
|
case .iPhone6Plus:
|
||||||
return 226.0
|
return 226.0
|
||||||
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro:
|
case .iPhoneX, .iPhone12Mini, .iPhone12, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMaxZoomed:
|
||||||
return 292.0
|
return 292.0
|
||||||
case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax:
|
case .iPhoneXSMax, .iPhoneXr, .iPhone12ProMax, .iPhone13ProMax, .iPhone14ProMax:
|
||||||
return 302.0
|
return 302.0
|
||||||
@ -282,7 +298,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
func predictiveInputHeight(inLandscape: Bool) -> CGFloat {
|
func predictiveInputHeight(inLandscape: Bool) -> CGFloat {
|
||||||
if inLandscape {
|
if inLandscape {
|
||||||
switch self {
|
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
|
return 37.0
|
||||||
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
||||||
return 50.0
|
return 50.0
|
||||||
@ -293,7 +309,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
switch self {
|
switch self {
|
||||||
case .iPhone4, .iPhone5:
|
case .iPhone4, .iPhone5:
|
||||||
return 37.0
|
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
|
return 44.0
|
||||||
case .iPhone6Plus:
|
case .iPhone6Plus:
|
||||||
return 45.0
|
return 45.0
|
||||||
@ -316,7 +332,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
|
|
||||||
public var hasDynamicIsland: Bool {
|
public var hasDynamicIsland: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .iPhone14Pro, .iPhone14ProMax:
|
case .iPhone14Pro, .iPhone14ProZoomed, .iPhone14ProMax, .iPhone14ProMaxZoomed:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -85,16 +85,24 @@ private final class WindowRootViewController: UIViewController, UIViewController
|
|||||||
didSet {
|
didSet {
|
||||||
if oldValue != self.orientations {
|
if oldValue != self.orientations {
|
||||||
if self.orientations == .portrait {
|
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
|
let value = UIInterfaceOrientation.portrait.rawValue
|
||||||
UIDevice.current.setValue(value, forKey: "orientation")
|
UIDevice.current.setValue(value, forKey: "orientation")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if #available(iOSApplicationExtension 16.0, iOS 16.0, *) {
|
||||||
|
self.setNeedsUpdateOfSupportedInterfaceOrientations()
|
||||||
} else {
|
} else {
|
||||||
UIViewController.attemptRotationToDeviceOrientation()
|
UIViewController.attemptRotationToDeviceOrientation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var gestureEdges: UIRectEdge = [] {
|
var gestureEdges: UIRectEdge = [] {
|
||||||
didSet {
|
didSet {
|
||||||
|
|||||||
@ -160,7 +160,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
|
|
||||||
public private(set) var validLayout: ContainerViewLayout?
|
public private(set) var validLayout: ContainerViewLayout?
|
||||||
private var validStatusBarStyle: NavigationStatusBarStyle?
|
private var validStatusBarStyle: NavigationStatusBarStyle?
|
||||||
private var validStatusBarHidden: Bool = false
|
private var validStatusBarHidden: Bool?
|
||||||
|
|
||||||
private var ignoreInputHeight: Bool = false
|
private var ignoreInputHeight: Bool = false
|
||||||
private var currentStatusBarExternalHidden: Bool = false
|
private var currentStatusBarExternalHidden: Bool = false
|
||||||
|
|||||||
@ -270,7 +270,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
|
|||||||
self.contentOffsetUpdated?(-size.height + nodeHeight, transition)
|
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)
|
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)
|
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.topOffset = 226.0
|
||||||
self.biometricsOffset = 30.0
|
self.biometricsOffset = 30.0
|
||||||
self.deleteOffset = 20.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.buttonSize = 75.0
|
||||||
self.horizontalSecond = 103.0
|
self.horizontalSecond = 103.0
|
||||||
self.horizontalThird = 206.0
|
self.horizontalThird = 206.0
|
||||||
@ -151,7 +151,7 @@ public struct PasscodeLayout {
|
|||||||
self.titleOffset = 112.0
|
self.titleOffset = 112.0
|
||||||
self.subtitleOffset = -6.0
|
self.subtitleOffset = -6.0
|
||||||
self.inputFieldOffset = 156.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.titleOffset = 162.0
|
||||||
self.subtitleOffset = 0.0
|
self.subtitleOffset = 0.0
|
||||||
self.inputFieldOffset = 206.0
|
self.inputFieldOffset = 206.0
|
||||||
|
|||||||
@ -22,6 +22,8 @@ swift_library(
|
|||||||
"//submodules/LocalizedPeerData:LocalizedPeerData",
|
"//submodules/LocalizedPeerData:LocalizedPeerData",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
"//submodules/CheckNode:CheckNode",
|
"//submodules/CheckNode:CheckNode",
|
||||||
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import ContextUI
|
|||||||
import LocalizedPeerData
|
import LocalizedPeerData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import CheckNode
|
import CheckNode
|
||||||
|
import ComponentFlow
|
||||||
|
import EmojiStatusComponent
|
||||||
|
|
||||||
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
||||||
private let textFont = Font.regular(11.0)
|
private let textFont = Font.regular(11.0)
|
||||||
@ -73,6 +75,8 @@ public final class SelectablePeerNode: ASDisplayNode {
|
|||||||
private var checkNode: CheckNode?
|
private var checkNode: CheckNode?
|
||||||
private let textNode: ASTextNode
|
private let textNode: ASTextNode
|
||||||
|
|
||||||
|
private let iconView: ComponentView<Empty>
|
||||||
|
|
||||||
public var toggleSelection: (() -> Void)?
|
public var toggleSelection: (() -> Void)?
|
||||||
public var contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? {
|
public var contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? {
|
||||||
didSet {
|
didSet {
|
||||||
@ -119,6 +123,8 @@ public final class SelectablePeerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.onlineNode = PeerOnlineMarkerNode()
|
self.onlineNode = PeerOnlineMarkerNode()
|
||||||
|
|
||||||
|
self.iconView = ComponentView<Empty>()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.contextContainer)
|
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
|
let isFirstTime = self.peer == nil
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
guard let mainPeer = peer.chatMainPeer else {
|
guard let mainPeer = peer.chatMainPeer else {
|
||||||
@ -166,7 +172,7 @@ public final class SelectablePeerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.textNode.maximumNumberOfLines = numberOfLines
|
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)
|
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()
|
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.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(theme, state: .panel), color: nil, transition: .immediate)
|
||||||
self.onlineNode.frame = CGRect(origin: CGPoint(), size: onlineSize)
|
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()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +307,18 @@ public final class SelectablePeerNode: ASDisplayNode {
|
|||||||
self.contextContainer.frame = bounds
|
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.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 avatarFrame = self.avatarNode.frame
|
||||||
let avatarContainerFrame = self.avatarNodeContainer.frame
|
let avatarContainerFrame = self.avatarNodeContainer.frame
|
||||||
|
|||||||
@ -35,6 +35,8 @@ swift_library(
|
|||||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
|
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
|
||||||
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -10,5 +10,5 @@ public protocol ShareContentContainerNode: AnyObject {
|
|||||||
func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?)
|
func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?)
|
||||||
func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?)
|
func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?)
|
||||||
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition)
|
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)
|
return (resultPeers, accountPeer)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
var animatedIn = false
|
||||||
self.peersDisposable.set((self.peers.get()
|
self.peersDisposable.set((self.peers.get()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] next in
|
|> deliverOnMainQueue).start(next: { [weak self] next in
|
||||||
if let strongSelf = self {
|
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)
|
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()
|
strongSelf.readyDisposable.set((strongSelf.controllerNode.ready.get()
|
||||||
|> filter({ $0 })
|
|> filter({ $0 })
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|
|||||||
@ -73,6 +73,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
private var controllerInteraction: ShareControllerInteraction?
|
private var controllerInteraction: ShareControllerInteraction?
|
||||||
|
|
||||||
private var peersContentNode: SharePeersContainerNode?
|
private var peersContentNode: SharePeersContainerNode?
|
||||||
|
private var topicsContentNode: ShareTopicsContainerNode?
|
||||||
|
|
||||||
private var scheduledLayoutTransitionRequestId: Int = 0
|
private var scheduledLayoutTransitionRequestId: Int = 0
|
||||||
private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)?
|
private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)?
|
||||||
@ -244,15 +245,28 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer, search in
|
self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer, search in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var added = false
|
var added = false
|
||||||
|
var openedTopic = false
|
||||||
if strongSelf.controllerInteraction!.selectedPeerIds.contains(peer.peerId) {
|
if strongSelf.controllerInteraction!.selectedPeerIds.contains(peer.peerId) {
|
||||||
|
strongSelf.controllerInteraction!.selectedTopics[peer.peerId] = nil
|
||||||
|
strongSelf.peersContentNode?.update()
|
||||||
strongSelf.controllerInteraction!.selectedPeerIds.remove(peer.peerId)
|
strongSelf.controllerInteraction!.selectedPeerIds.remove(peer.peerId)
|
||||||
strongSelf.controllerInteraction!.selectedPeers = strongSelf.controllerInteraction!.selectedPeers.filter({ $0.peerId != peer.peerId })
|
strongSelf.controllerInteraction!.selectedPeers = strongSelf.controllerInteraction!.selectedPeers.filter({ $0.peerId != peer.peerId })
|
||||||
|
} else {
|
||||||
|
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 {
|
} else {
|
||||||
strongSelf.controllerInteraction!.selectedPeerIds.insert(peer.peerId)
|
strongSelf.controllerInteraction!.selectedPeerIds.insert(peer.peerId)
|
||||||
strongSelf.controllerInteraction!.selectedPeers.append(peer)
|
strongSelf.controllerInteraction!.selectedPeers.append(peer)
|
||||||
|
added = true
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.contentNode?.setEnsurePeerVisibleOnLayout(peer.peerId)
|
strongSelf.contentNode?.setEnsurePeerVisibleOnLayout(peer.peerId)
|
||||||
added = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if search && added {
|
if search && added {
|
||||||
@ -263,12 +277,14 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
strongSelf.peersContentNode?.updateFoundPeers()
|
strongSelf.peersContentNode?.updateFoundPeers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !openedTopic {
|
||||||
strongSelf.setActionNodesHidden(strongSelf.controllerInteraction!.selectedPeers.isEmpty && strongSelf.presetText == nil, inputField: true, actions: strongSelf.defaultAction == nil)
|
strongSelf.setActionNodesHidden(strongSelf.controllerInteraction!.selectedPeers.isEmpty && strongSelf.presetText == nil, inputField: true, actions: strongSelf.defaultAction == nil)
|
||||||
|
|
||||||
strongSelf.updateButton()
|
strongSelf.updateButton()
|
||||||
|
|
||||||
strongSelf.peersContentNode?.updateSelectedPeers()
|
strongSelf.peersContentNode?.updateSelectedPeers(animated: true)
|
||||||
strongSelf.contentNode?.updateSelectedPeers()
|
strongSelf.contentNode?.updateSelectedPeers(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
if let (layout, navigationBarHeight, _) = strongSelf.containerLayout {
|
if let (layout, navigationBarHeight, _) = strongSelf.containerLayout {
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
|
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
|
self.backgroundColor = nil
|
||||||
@ -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) {
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
guard self.presentationData !== presentationData else {
|
guard self.presentationData !== presentationData else {
|
||||||
return
|
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))
|
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)
|
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) {
|
private func contentNodeOffsetUpdated(_ contentOffset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
@ -967,7 +1063,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateButton() {
|
private func updateButton() {
|
||||||
if self.controllerInteraction!.selectedPeers.isEmpty {
|
let count = self.controllerInteraction!.selectedPeerIds.count
|
||||||
|
if count == 0 {
|
||||||
if self.presetText != nil {
|
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.setTitle(self.presentationData.strings.ShareMenu_Send, with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.disabledActionTextColor, for: .normal)
|
||||||
self.actionButtonNode.isEnabled = false
|
self.actionButtonNode.isEnabled = false
|
||||||
@ -990,13 +1087,13 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
let text: String
|
let text: String
|
||||||
if let segmentedValues = self.segmentedValues {
|
if let segmentedValues = self.segmentedValues {
|
||||||
let value = segmentedValues[self.selectedSegmentedIndex]
|
let value = segmentedValues[self.selectedSegmentedIndex]
|
||||||
text = value.formatSendTitle(self.controllerInteraction!.selectedPeers.count)
|
text = value.formatSendTitle(count)
|
||||||
} else {
|
} else {
|
||||||
text = self.presentationData.strings.ShareMenu_Send
|
text = self.presentationData.strings.ShareMenu_Send
|
||||||
}
|
}
|
||||||
self.actionButtonNode.isEnabled = true
|
self.actionButtonNode.isEnabled = true
|
||||||
self.actionButtonNode.setTitle(text, with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal)
|
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)
|
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 foundPeers: [EngineRenderedPeer] = []
|
||||||
var selectedPeerIds = Set<PeerId>()
|
var selectedPeerIds = Set<PeerId>()
|
||||||
var selectedPeers: [EngineRenderedPeer] = []
|
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.togglePeer = togglePeer
|
||||||
|
self.selectTopic = selectTopic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,17 +97,21 @@ final class ShareControllerPeerGridItem: GridItem {
|
|||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let peer: EngineRenderedPeer?
|
let peer: EngineRenderedPeer?
|
||||||
let presence: EnginePeer.Presence?
|
let presence: EnginePeer.Presence?
|
||||||
|
let topicId: Int64?
|
||||||
|
let threadData: MessageHistoryThreadData?
|
||||||
let controllerInteraction: ShareControllerInteraction
|
let controllerInteraction: ShareControllerInteraction
|
||||||
let search: Bool
|
let search: Bool
|
||||||
|
|
||||||
let section: GridSection?
|
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.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.presence = presence
|
self.presence = presence
|
||||||
|
self.topicId = topicId
|
||||||
|
self.threadData = threadData
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.search = search
|
self.search = search
|
||||||
|
|
||||||
@ -116,7 +125,7 @@ final class ShareControllerPeerGridItem: GridItem {
|
|||||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||||
let node = ShareControllerPeerGridItemNode()
|
let node = ShareControllerPeerGridItemNode()
|
||||||
node.controllerInteraction = self.controllerInteraction
|
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
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,12 +135,12 @@ final class ShareControllerPeerGridItem: GridItem {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
node.controllerInteraction = self.controllerInteraction
|
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 {
|
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 let peerNode: SelectablePeerNode
|
||||||
private var presenceManager: PeerPresenceStatusManager?
|
private var presenceManager: PeerPresenceStatusManager?
|
||||||
|
|
||||||
@ -140,6 +149,10 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
|||||||
private var placeholderNode: ShimmerEffectNode?
|
private var placeholderNode: ShimmerEffectNode?
|
||||||
private var absoluteLocation: (CGRect, CGSize)?
|
private var absoluteLocation: (CGRect, CGSize)?
|
||||||
|
|
||||||
|
var peerId: EnginePeer.Id? {
|
||||||
|
return self.currentState?.3?.peerId
|
||||||
|
}
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.peerNode = SelectablePeerNode()
|
self.peerNode = SelectablePeerNode()
|
||||||
|
|
||||||
@ -147,7 +160,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
|||||||
|
|
||||||
self.peerNode.toggleSelection = { [weak self] in
|
self.peerNode.toggleSelection = { [weak self] in
|
||||||
if let strongSelf = self {
|
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] {
|
if let _ = peer.peers[peer.peerId] {
|
||||||
strongSelf.controllerInteraction?.togglePeer(peer, search)
|
strongSelf.controllerInteraction?.togglePeer(peer, search)
|
||||||
}
|
}
|
||||||
@ -159,7 +172,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
|||||||
guard let strongSelf = self, let currentState = strongSelf.currentState else {
|
guard let strongSelf = self, let currentState = strongSelf.currentState else {
|
||||||
return
|
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) {
|
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 {
|
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 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)
|
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||||
@ -186,7 +199,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
|||||||
|
|
||||||
self.peerNode.theme = itemTheme
|
self.peerNode.theme = itemTheme
|
||||||
if let peer = peer {
|
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 {
|
if let shimmerNode = self.placeholderNode {
|
||||||
self.placeholderNode = nil
|
self.placeholderNode = nil
|
||||||
shimmerNode.removeFromSupernode()
|
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)
|
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()
|
self.setNeedsLayout()
|
||||||
if let presence = presence {
|
if let presence = presence {
|
||||||
self.presenceManager?.reset(presence: presence)
|
self.presenceManager?.reset(presence: presence)
|
||||||
@ -229,7 +242,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
|||||||
|
|
||||||
func updateSelection(animated: Bool) {
|
func updateSelection(animated: Bool) {
|
||||||
var selected = false
|
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)
|
selected = controllerInteraction.selectedPeerIds.contains(peer.peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +256,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
|
|||||||
self.peerNode.frame = bounds
|
self.peerNode.frame = bounds
|
||||||
self.placeholderNode?.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] = []
|
var shapes: [ShimmerEffectNode.Shape] = []
|
||||||
|
|
||||||
let titleLineWidth: CGFloat = 56.0
|
let titleLineWidth: CGFloat = 56.0
|
||||||
|
|||||||
@ -91,7 +91,7 @@ public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContain
|
|||||||
self.contentOffsetUpdated?(-size.height + 64.0, transition)
|
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)
|
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 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 {
|
private struct SharePeerEntry: Comparable, Identifiable {
|
||||||
let index: Int32
|
let index: Int32
|
||||||
let peer: EngineRenderedPeer
|
let peer: EngineRenderedPeer
|
||||||
let presence: EnginePeer.Presence?
|
let presence: EnginePeer.Presence?
|
||||||
|
let threadId: Int64?
|
||||||
|
let threadData: MessageHistoryThreadData?
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
|
|
||||||
@ -38,6 +59,12 @@ private struct SharePeerEntry: Comparable, Identifiable {
|
|||||||
if lhs.presence != rhs.presence {
|
if lhs.presence != rhs.presence {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.threadId != rhs.threadId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.threadData != rhs.threadData {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -47,7 +74,7 @@ private struct SharePeerEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func item(context: AccountContext, interfaceInteraction: ShareControllerInteraction) -> GridItem {
|
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?)]>()
|
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]?) {
|
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.sharedContext = sharedContext
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -129,19 +163,19 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
|
|||||||
|
|
||||||
self.peersValue.set(.single(peers))
|
self.peersValue.set(.single(peers))
|
||||||
|
|
||||||
let items: Signal<[SharePeerEntry], NoError> = combineLatest(self.peersValue.get(), self.foundPeers.get())
|
let items: Signal<[SharePeerEntry], NoError> = combineLatest(self.peersValue.get(), self.foundPeers.get(), self.tick.get())
|
||||||
|> map { initialPeers, foundPeers -> [SharePeerEntry] in
|
|> map { [weak controllerInteraction] initialPeers, foundPeers, _ -> [SharePeerEntry] in
|
||||||
var entries: [SharePeerEntry] = []
|
var entries: [SharePeerEntry] = []
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
|
|
||||||
var existingPeerIds: Set<PeerId> = Set()
|
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)
|
existingPeerIds.insert(accountPeer.id)
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
for peer in foundPeers.reversed() {
|
for peer in foundPeers.reversed() {
|
||||||
if !existingPeerIds.contains(peer.peerId) {
|
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)
|
existingPeerIds.insert(peer.peerId)
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
@ -149,7 +183,8 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
|
|||||||
|
|
||||||
for (peer, presence) in initialPeers {
|
for (peer, presence) in initialPeers {
|
||||||
if !existingPeerIds.contains(peer.peerId) {
|
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)
|
existingPeerIds.insert(peer.peerId)
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
@ -325,6 +360,154 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
|
|||||||
func deactivate() {
|
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) {
|
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
let firstLayout = self.validLayout == nil
|
let firstLayout = self.validLayout == nil
|
||||||
self.validLayout = (size, bottomInset)
|
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 })
|
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))
|
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))
|
self.foundPeers.set(.single(self.controllerInteraction.foundPeers))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSelectedPeers() {
|
func update() {
|
||||||
|
self._tick += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSelectedPeers(animated: Bool = true) {
|
||||||
if self.segmentedValues != nil {
|
if self.segmentedValues != nil {
|
||||||
self.contentTitleNode.isHidden = true
|
self.contentTitleNode.isHidden = true
|
||||||
self.contentSubtitleNode.isHidden = true
|
self.contentSubtitleNode.isHidden = true
|
||||||
@ -453,7 +640,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
|
|||||||
}
|
}
|
||||||
self.contentGridNode.forEachItemNode { itemNode in
|
self.contentGridNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? ShareControllerPeerGridItemNode {
|
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
|
peers[associatedPeer.id] = associatedPeer
|
||||||
}
|
}
|
||||||
let peer = EngineRenderedPeer(RenderedPeer(peerId: peer.id, peers: SimpleDictionary(peers), associatedMedia: [:]))
|
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 {
|
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 animateIn() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSelectedPeers() {
|
func updateSelectedPeers(animated: Bool) {
|
||||||
self.contentGridNode.forEachItemNode { itemNode in
|
self.contentGridNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? ShareControllerPeerGridItemNode {
|
if let itemNode = itemNode as? ShareControllerPeerGridItemNode {
|
||||||
itemNode.updateSelection(animated: true)
|
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)
|
self.contentOffsetUpdated?(-size.height + nodeHeight - 64.0, transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSelectedPeers() {
|
func updateSelectedPeers(animated: Bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,18 +7,40 @@ import TelegramPresentationData
|
|||||||
import LottieAnimationComponent
|
import LottieAnimationComponent
|
||||||
|
|
||||||
public final class AudioTranscriptionButtonComponent: Component {
|
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 {
|
public enum TranscriptionState {
|
||||||
case inProgress
|
case inProgress
|
||||||
case expanded
|
case expanded
|
||||||
case collapsed
|
case collapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
public let theme: PresentationThemePartedColors
|
public let theme: AudioTranscriptionButtonComponent.Theme
|
||||||
public let transcriptionState: TranscriptionState
|
public let transcriptionState: TranscriptionState
|
||||||
public let pressed: () -> Void
|
public let pressed: () -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
theme: PresentationThemePartedColors,
|
theme: AudioTranscriptionButtonComponent.Theme,
|
||||||
transcriptionState: TranscriptionState,
|
transcriptionState: TranscriptionState,
|
||||||
pressed: @escaping () -> Void
|
pressed: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
@ -28,7 +50,7 @@ public final class AudioTranscriptionButtonComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: AudioTranscriptionButtonComponent, rhs: AudioTranscriptionButtonComponent) -> Bool {
|
public static func ==(lhs: AudioTranscriptionButtonComponent, rhs: AudioTranscriptionButtonComponent) -> Bool {
|
||||||
if lhs.theme !== rhs.theme {
|
if lhs.theme != rhs.theme {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.transcriptionState != rhs.transcriptionState {
|
if lhs.transcriptionState != rhs.transcriptionState {
|
||||||
@ -40,12 +62,14 @@ public final class AudioTranscriptionButtonComponent: Component {
|
|||||||
public final class View: UIButton {
|
public final class View: UIButton {
|
||||||
private var component: AudioTranscriptionButtonComponent?
|
private var component: AudioTranscriptionButtonComponent?
|
||||||
|
|
||||||
|
private let blurredBackgroundNode: NavigationBackgroundNode
|
||||||
private let backgroundLayer: SimpleLayer
|
private let backgroundLayer: SimpleLayer
|
||||||
private let animationView: ComponentHostView<Empty>
|
private let animationView: ComponentHostView<Empty>
|
||||||
|
|
||||||
private var progressAnimationView: ComponentHostView<Empty>?
|
private var progressAnimationView: ComponentHostView<Empty>?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
|
self.blurredBackgroundNode = NavigationBackgroundNode(color: .clear)
|
||||||
self.backgroundLayer = SimpleLayer()
|
self.backgroundLayer = SimpleLayer()
|
||||||
self.animationView = ComponentHostView<Empty>()
|
self.animationView = ComponentHostView<Empty>()
|
||||||
|
|
||||||
@ -73,7 +97,22 @@ public final class AudioTranscriptionButtonComponent: Component {
|
|||||||
func update(component: AudioTranscriptionButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
func update(component: AudioTranscriptionButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||||
let size = CGSize(width: 30.0, height: 30.0)
|
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 {
|
if self.component?.transcriptionState != component.transcriptionState {
|
||||||
switch 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.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
|
self.component = component
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ public final class EmojiStatusComponent: Component {
|
|||||||
case verified(fillColor: UIColor, foregroundColor: UIColor, sizeType: SizeType)
|
case verified(fillColor: UIColor, foregroundColor: UIColor, sizeType: SizeType)
|
||||||
case text(color: UIColor, string: String)
|
case text(color: UIColor, string: String)
|
||||||
case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor, themeColor: UIColor?, loopMode: LoopMode)
|
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
|
public let context: AccountContext
|
||||||
@ -223,7 +223,7 @@ public final class EmojiStatusComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
iconImage = nil
|
iconImage = nil
|
||||||
}
|
}
|
||||||
case let .topic(title, colorIndex, size):
|
case let .topic(title, color, size):
|
||||||
func generateTopicIcon(backgroundColors: [UIColor], strokeColors: [UIColor]) -> UIImage? {
|
func generateTopicIcon(backgroundColors: [UIColor], strokeColors: [UIColor]) -> UIImage? {
|
||||||
return generateImage(size, rotatedContext: { size, context in
|
return generateImage(size, rotatedContext: { size, context in
|
||||||
context.clear(CGRect(origin: .zero, size: size))
|
context.clear(CGRect(origin: .zero, size: size))
|
||||||
@ -283,18 +283,21 @@ public final class EmojiStatusComponent: Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let topicColors: [([UInt32], [UInt32])] = [
|
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
|
||||||
([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
|
return ([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
|
|
||||||
|
|
||||||
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
|
iconImage = image
|
||||||
} else {
|
} else {
|
||||||
iconImage = nil
|
iconImage = nil
|
||||||
|
|||||||
@ -2145,7 +2145,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
public enum Icon: Equatable, Hashable {
|
public enum Icon: Equatable, Hashable {
|
||||||
case premiumStar
|
case premiumStar
|
||||||
case topic(String)
|
case topic(String, Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
case animation(EntityKeyboardAnimationData)
|
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))
|
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))
|
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):
|
case let .topic(title, color):
|
||||||
if let image = generateTopicIcon(backgroundColors: [UIColor(rgb: 0x6FB9F0), UIColor(rgb: 0x0261E4)], strokeColors: [UIColor(rgb: 0x026CB5), UIColor(rgb: 0x064BB7)], title: title) {
|
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))
|
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))
|
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
|
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) {
|
func update(content: ItemContent) {
|
||||||
if self.content != content {
|
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
|
let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
UIGraphicsPushContext(context)
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
if let image = generateTopicIcon(backgroundColors: [UIColor(rgb: 0x6FB9F0), UIColor(rgb: 0x0261E4)], strokeColors: [UIColor(rgb: 0x026CB5), UIColor(rgb: 0x064BB7)], title: title) {
|
let colors = self.getTopicColors(color)
|
||||||
let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
|
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))
|
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?,
|
chatPeerId: EnginePeer.Id?,
|
||||||
selectedItems: Set<MediaId> = Set(),
|
selectedItems: Set<MediaId> = Set(),
|
||||||
topStatusTitle: String? = nil,
|
topStatusTitle: String? = nil,
|
||||||
topicTitle: String? = nil
|
topicTitle: String? = nil,
|
||||||
|
topicColor: Int32? = nil
|
||||||
) -> Signal<EmojiPagerContentComponent, NoError> {
|
) -> Signal<EmojiPagerContentComponent, NoError> {
|
||||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||||
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||||
@ -6279,7 +6295,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
if isTopicIconSelection {
|
if isTopicIconSelection {
|
||||||
let resultItem = EmojiPagerContentComponent.Item(
|
let resultItem = EmojiPagerContentComponent.Item(
|
||||||
animationData: nil,
|
animationData: nil,
|
||||||
content: .icon(.topic(String((topicTitle ?? "").prefix(1)))),
|
content: .icon(.topic(String((topicTitle ?? "").prefix(1)), topicColor ?? 0)),
|
||||||
itemFile: nil,
|
itemFile: nil,
|
||||||
subgroupId: nil,
|
subgroupId: nil,
|
||||||
icon: .none,
|
icon: .none,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ private final class TitleFieldComponent: Component {
|
|||||||
let accentColor: UIColor
|
let accentColor: UIColor
|
||||||
let placeholderColor: UIColor
|
let placeholderColor: UIColor
|
||||||
let fileId: Int64
|
let fileId: Int64
|
||||||
|
let iconColor: Int32
|
||||||
let text: String
|
let text: String
|
||||||
let textUpdated: (String) -> Void
|
let textUpdated: (String) -> Void
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ private final class TitleFieldComponent: Component {
|
|||||||
accentColor: UIColor,
|
accentColor: UIColor,
|
||||||
placeholderColor: UIColor,
|
placeholderColor: UIColor,
|
||||||
fileId: Int64,
|
fileId: Int64,
|
||||||
|
iconColor: Int32,
|
||||||
text: String,
|
text: String,
|
||||||
textUpdated: @escaping (String) -> Void
|
textUpdated: @escaping (String) -> Void
|
||||||
) {
|
) {
|
||||||
@ -40,6 +42,7 @@ private final class TitleFieldComponent: Component {
|
|||||||
self.accentColor = accentColor
|
self.accentColor = accentColor
|
||||||
self.placeholderColor = placeholderColor
|
self.placeholderColor = placeholderColor
|
||||||
self.fileId = fileId
|
self.fileId = fileId
|
||||||
|
self.iconColor = iconColor
|
||||||
self.text = text
|
self.text = text
|
||||||
self.textUpdated = textUpdated
|
self.textUpdated = textUpdated
|
||||||
}
|
}
|
||||||
@ -60,6 +63,9 @@ private final class TitleFieldComponent: Component {
|
|||||||
if lhs.fileId != rhs.fileId {
|
if lhs.fileId != rhs.fileId {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.iconColor != rhs.iconColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -106,7 +112,7 @@ private final class TitleFieldComponent: Component {
|
|||||||
|
|
||||||
let iconContent: EmojiStatusComponent.Content
|
let iconContent: EmojiStatusComponent.Content
|
||||||
if component.fileId == 0 {
|
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 {
|
} 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))
|
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,
|
deviceMetrics: component.deviceMetrics,
|
||||||
hiddenInputHeight: 0.0,
|
hiddenInputHeight: 0.0,
|
||||||
displayBottomPanel: false,
|
displayBottomPanel: false,
|
||||||
isExpanded: false
|
isExpanded: true
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
@ -347,6 +353,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
var title: String
|
var title: String
|
||||||
var fileId: Int64
|
var fileId: Int64
|
||||||
|
var iconColor: Int32
|
||||||
|
|
||||||
init(context: AccountContext, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void) {
|
init(context: AccountContext, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -357,9 +364,13 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
case .create:
|
case .create:
|
||||||
self.title = ""
|
self.title = ""
|
||||||
self.fileId = 0
|
self.fileId = 0
|
||||||
|
|
||||||
|
let colors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98,0xFF93B2, 0xFB6F5F]
|
||||||
|
self.iconColor = colors.randomElement() ?? 0x0
|
||||||
case let .edit(info):
|
case let .edit(info):
|
||||||
self.title = info.title
|
self.title = info.title
|
||||||
self.fileId = info.icon ?? 0
|
self.fileId = info.icon ?? 0
|
||||||
|
self.iconColor = info.iconColor
|
||||||
}
|
}
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
@ -378,7 +389,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
areCustomEmojiEnabled: true,
|
areCustomEmojiEnabled: true,
|
||||||
chatPeerId: self.context.account.peerId,
|
chatPeerId: self.context.account.peerId,
|
||||||
selectedItems: Set(),
|
selectedItems: Set(),
|
||||||
topicTitle: self.title
|
topicTitle: self.title,
|
||||||
|
topicColor: self.iconColor
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] content in
|
|> deliverOnMainQueue).start(next: { [weak self] content in
|
||||||
self?.emojiContent = content
|
self?.emojiContent = content
|
||||||
@ -410,7 +422,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
areCustomEmojiEnabled: true,
|
areCustomEmojiEnabled: true,
|
||||||
chatPeerId: self.context.account.peerId,
|
chatPeerId: self.context.account.peerId,
|
||||||
selectedItems: Set([MediaId(namespace: Namespaces.Media.CloudFile, id: self.fileId)]),
|
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
|
|> deliverOnMainQueue).start(next: { [weak self] content in
|
||||||
self?.emojiContent = content
|
self?.emojiContent = content
|
||||||
@ -442,7 +455,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
areCustomEmojiEnabled: true,
|
areCustomEmojiEnabled: true,
|
||||||
chatPeerId: self.context.account.peerId,
|
chatPeerId: self.context.account.peerId,
|
||||||
selectedItems: Set([MediaId(namespace: Namespaces.Media.CloudFile, id: self.fileId)]),
|
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
|
|> deliverOnMainQueue).start(next: { [weak self] content in
|
||||||
self?.emojiContent = content
|
self?.emojiContent = content
|
||||||
@ -532,6 +546,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
accentColor: environment.theme.list.itemAccentColor,
|
accentColor: environment.theme.list.itemAccentColor,
|
||||||
placeholderColor: environment.theme.list.disclosureArrowColor,
|
placeholderColor: environment.theme.list.disclosureArrowColor,
|
||||||
fileId: state.fileId,
|
fileId: state.fileId,
|
||||||
|
iconColor: state.iconColor,
|
||||||
text: state.title,
|
text: state.title,
|
||||||
textUpdated: { [weak state] text in
|
textUpdated: { [weak state] text in
|
||||||
state?.updateTitle(text)
|
state?.updateTitle(text)
|
||||||
@ -568,6 +583,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
contentHeight += iconHeader.size.height + headerSpacing
|
contentHeight += iconHeader.size.height + headerSpacing
|
||||||
|
|
||||||
|
let bottomInset = max(environment.safeInsets.bottom, 12.0)
|
||||||
|
|
||||||
let iconBackground = iconBackground.update(
|
let iconBackground = iconBackground.update(
|
||||||
component: RoundedRectangle(
|
component: RoundedRectangle(
|
||||||
color: environment.theme.list.itemBlocksBackgroundColor,
|
color: environment.theme.list.itemBlocksBackgroundColor,
|
||||||
@ -575,7 +592,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
),
|
),
|
||||||
availableSize: CGSize(
|
availableSize: CGSize(
|
||||||
width: context.availableSize.width - sideInset * 2.0,
|
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
|
transition: context.transition
|
||||||
)
|
)
|
||||||
@ -584,7 +601,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if let emojiContent = state.emojiContent {
|
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(
|
let iconSelector = iconSelector.update(
|
||||||
component: TopicIconSelectionComponent(
|
component: TopicIconSelectionComponent(
|
||||||
@ -592,7 +609,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
deviceMetrics: environment.deviceMetrics,
|
deviceMetrics: environment.deviceMetrics,
|
||||||
emojiContent: emojiContent,
|
emojiContent: emojiContent,
|
||||||
backgroundColor: .clear,
|
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
||||||
separatorColor: environment.theme.list.blocksBackgroundColor
|
separatorColor: environment.theme.list.blocksBackgroundColor
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -601,6 +618,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
context.add(iconSelector
|
context.add(iconSelector
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + iconSelector.size.height / 2.0))
|
.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
|
let accountContext = context.component.context
|
||||||
@ -736,8 +755,6 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func createPressed() {
|
@objc private func createPressed() {
|
||||||
// self.dismiss()
|
|
||||||
|
|
||||||
self.completion(self.state.0, self.state.1)
|
self.completion(self.state.0, self.state.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -733,8 +733,28 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
}
|
}
|
||||||
}, forceOrientation: { orientation in
|
}, forceOrientation: { orientation in
|
||||||
let value = orientation.rawValue
|
let value = orientation.rawValue
|
||||||
|
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")
|
UIDevice.current.setValue(value, forKey: "orientation")
|
||||||
UINavigationController.attemptRotationToDeviceOrientation()
|
UINavigationController.attemptRotationToDeviceOrientation()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let accountManager = AccountManager<TelegramAccountManagerTypes>(basePath: rootPath + "/accounts-metadata", isTemporary: false, isReadOnly: false, useCaches: true, removeDatabaseOnError: true)
|
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 {
|
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))
|
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 {
|
} 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)
|
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setStatus(context: strongSelf.context, content: avatarContent)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -838,11 +838,11 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent,
|
|||||||
if let fileId = self.fileId {
|
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))
|
iconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 54.0, height: 54.0), placeholderColor: .clear, themeColor: serviceColor.primaryText, loopMode: .count(2))
|
||||||
} else {
|
} else {
|
||||||
var colorIndex: Int = 0
|
let colorIndex: Int32 = 0
|
||||||
if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation {
|
// if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation {
|
||||||
colorIndex = Int(clamping: abs(replyThreadMessage.effectiveTopId.id))
|
// colorIndex = Int(clamping: abs(replyThreadMessage.effectiveTopId.id))
|
||||||
}
|
// }
|
||||||
iconContent = .topic(title: String(title.prefix(1)), colorIndex: colorIndex, size: CGSize(width: 64.0, height: 64.0))
|
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)
|
let insets = UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
|
||||||
|
|||||||
@ -173,14 +173,14 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode {
|
|||||||
|
|
||||||
var lineWidth = self.progressLayer.lineWidth
|
var lineWidth = self.progressLayer.lineWidth
|
||||||
self.progressLayer.lineWidth = 1.0
|
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: 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.15, removeOnCompletion: false)
|
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 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
|
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.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.isHidden = false
|
||||||
self.fillLayer.path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)).cgPath
|
self.fillLayer.path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)).cgPath
|
||||||
|
|||||||
@ -425,7 +425,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
if let iconFileId = threadInfo.icon {
|
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)
|
content = .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: avatarSize, height: avatarSize), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever)
|
||||||
} else {
|
} 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(
|
let _ = iconView.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user