Various improvements

This commit is contained in:
Ilya Laktyushin 2025-10-16 05:30:06 +04:00
parent babf5cc9d7
commit 0915a42e64
289 changed files with 9723 additions and 2071 deletions

View file

@ -1416,6 +1416,8 @@ public protocol SharedAccountContext: AnyObject {
func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController) func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController)
func makeNewContactScreen(context: AccountContext, firstName: String?, lastName: String?, phoneNumber: String?) -> ViewController
func navigateToCurrentCall() func navigateToCurrentCall()
var hasOngoingCall: ValuePromise<Bool> { get } var hasOngoingCall: ValuePromise<Bool> { get }
var immediateHasOngoingCall: Bool { get } var immediateHasOngoingCall: Bool { get }
@ -1486,7 +1488,6 @@ public protocol AccountContext: AnyObject {
var engine: TelegramEngine { get } var engine: TelegramEngine { get }
var liveLocationManager: LiveLocationManager? { get } var liveLocationManager: LiveLocationManager? { get }
var peersNearbyManager: PeersNearbyManager? { get }
var fetchManager: FetchManager { get } var fetchManager: FetchManager { get }
var prefetchManager: PrefetchManager? { get } var prefetchManager: PrefetchManager? { get }
var downloadedMediaStoreManager: DownloadedMediaStoreManager { get } var downloadedMediaStoreManager: DownloadedMediaStoreManager { get }

View file

@ -9,7 +9,7 @@ public protocol ContactSelectionController: ViewController {
var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> { get } var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> { get }
var displayProgress: Bool { get set } var displayProgress: Bool { get set }
var dismissed: (() -> Void)? { get set } var dismissed: (() -> Void)? { get set }
var presentScheduleTimePicker: (@escaping (Int32) -> Void) -> Void { get set } var presentScheduleTimePicker: (@escaping (Int32, Int32?) -> Void) -> Void { get set }
func dismissSearch() func dismissSearch()
} }
@ -105,7 +105,13 @@ public final class ContactSelectionControllerParams {
case always case always
} }
public enum Style {
case glass
case legacy
}
public let context: AccountContext public let context: AccountContext
public let style: Style
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
public let mode: ContactSelectionControllerMode public let mode: ContactSelectionControllerMode
public let autoDismiss: Bool public let autoDismiss: Bool
@ -123,6 +129,7 @@ public final class ContactSelectionControllerParams {
public init( public init(
context: AccountContext, context: AccountContext,
style: Style = .legacy,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
mode: ContactSelectionControllerMode = .generic, mode: ContactSelectionControllerMode = .generic,
autoDismiss: Bool = true, autoDismiss: Bool = true,
@ -139,6 +146,7 @@ public final class ContactSelectionControllerParams {
sendMessage: ((EnginePeer) -> Void)? = nil sendMessage: ((EnginePeer) -> Void)? = nil
) { ) {
self.context = context self.context = context
self.style = style
self.updatedPresentationData = updatedPresentationData self.updatedPresentationData = updatedPresentationData
self.mode = mode self.mode = mode
self.autoDismiss = autoDismiss self.autoDismiss = autoDismiss

View file

@ -1,8 +0,0 @@
import Foundation
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
public protocol PeersNearbyManager {
}

View file

@ -36,6 +36,7 @@ swift_library(
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities", "//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
"//submodules/TelegramUI/Components/Chat/ChatInputTextNode", "//submodules/TelegramUI/Components/Chat/ChatInputTextNode",
"//submodules/AnimatedCountLabelNode", "//submodules/AnimatedCountLabelNode",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -9,12 +9,15 @@ import ChatPresentationInterfaceState
import ComponentFlow import ComponentFlow
import AccountContext import AccountContext
import AnimatedCountLabelNode import AnimatedCountLabelNode
import GlassBackgroundComponent
final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode { final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode {
private let strings: PresentationStrings private let strings: PresentationStrings
private let glass: Bool
let sendContainerNode: ASDisplayNode let sendContainerNode: ASDisplayNode
let backgroundNode: ASDisplayNode let backgroundView: GlassBackgroundView?
let backgroundNode: ASDisplayNode?
let sendButton: HighlightTrackingButtonNode let sendButton: HighlightTrackingButtonNode
var sendButtonHasApplyIcon = false var sendButtonHasApplyIcon = false
var animatingSendButton = false var animatingSendButton = false
@ -35,16 +38,24 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
private var validLayout: CGSize? private var validLayout: CGSize?
init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) { init(presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool, presentController: @escaping (ViewController) -> Void) {
self.theme = presentationInterfaceState.theme self.theme = presentationInterfaceState.theme
self.strings = presentationInterfaceState.strings self.strings = presentationInterfaceState.strings
self.glass = glass
self.sendContainerNode = ASDisplayNode() self.sendContainerNode = ASDisplayNode()
self.sendContainerNode.layer.allowsGroupOpacity = true self.sendContainerNode.layer.allowsGroupOpacity = true
self.backgroundNode = ASDisplayNode() if glass {
self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor self.backgroundView = GlassBackgroundView()
self.backgroundNode.clipsToBounds = true self.backgroundNode = nil
} else {
self.backgroundNode = ASDisplayNode()
self.backgroundNode?.backgroundColor = theme.chat.inputPanel.actionControlFillColor
self.backgroundNode?.clipsToBounds = true
self.backgroundView = nil
}
self.sendButton = HighlightTrackingButtonNode(pointerStyle: nil) self.sendButton = HighlightTrackingButtonNode(pointerStyle: nil)
self.textNode = ImmediateAnimatedCountLabelNode() self.textNode = ImmediateAnimatedCountLabelNode()
@ -78,7 +89,11 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
} }
self.addSubnode(self.sendContainerNode) self.addSubnode(self.sendContainerNode)
self.sendContainerNode.addSubnode(self.backgroundNode) if let backgroundView = self.backgroundView {
self.sendContainerNode.view.addSubview(backgroundView)
} else if let backgroundNode = self.backgroundNode {
self.sendContainerNode.addSubnode(backgroundNode)
}
self.sendContainerNode.addSubnode(self.sendButton) self.sendContainerNode.addSubnode(self.sendButton)
self.sendContainerNode.addSubnode(self.textNode) self.sendContainerNode.addSubnode(self.textNode)
} }
@ -99,11 +114,13 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
} }
} }
self.sendButtonPointerInteraction = PointerInteraction(view: self.sendButton.view, customInteractionView: self.backgroundNode.view, style: .lift) if let backgroundNode = self.backgroundNode {
self.sendButtonPointerInteraction = PointerInteraction(view: self.sendButton.view, customInteractionView: backgroundNode.view, style: .lift)
}
} }
func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) { func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) {
self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor self.backgroundNode?.backgroundColor = theme.chat.inputPanel.actionControlFillColor
} }
private var absoluteRect: (CGRect, CGSize)? private var absoluteRect: (CGRect, CGSize)?
@ -141,7 +158,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
let textSize = self.textNode.updateLayout(size: CGSize(width: 100.0, height: 100.0), animated: transition.isAnimated) let textSize = self.textNode.updateLayout(size: CGSize(width: 100.0, height: 100.0), animated: transition.isAnimated)
if minimized { if minimized {
width = 44.0 width = 53.0
} else { } else {
width = textSize.width + buttonInset * 2.0 width = textSize.width + buttonInset * 2.0
} }
@ -155,9 +172,16 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: buttonSize)) transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: buttonSize))
transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: buttonSize)) transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: buttonSize))
let backgroundSize = CGSize(width: width - 11.0, height: 33.0) if let backgroundView = self.backgroundView {
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize)) let backgroundSize = CGSize(width: width - 13.0, height: 40.0)
self.backgroundNode.cornerRadius = backgroundSize.height / 2.0 let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize)
transition.updateFrame(view: backgroundView, frame: backgroundFrame)
backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: false, tintColor: .init(kind: .custom, color: self.theme.chat.inputPanel.actionControlFillColor), transition: ComponentTransition(transition))
} else if let backgroundNode {
let backgroundSize = CGSize(width: width - 11.0, height: 33.0)
transition.updateFrame(node: backgroundNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize))
backgroundNode.cornerRadius = backgroundSize.height / 2.0
}
return buttonSize return buttonSize
} }

View file

@ -28,7 +28,7 @@ import ChatInputTextNode
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
private let minInputFontSize: CGFloat = 5.0 private let minInputFontSize: CGFloat = 5.0
private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool = false, metrics: LayoutMetrics) -> CGFloat {
let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
var result: CGFloat var result: CGFloat
if baseFontSize.isEqual(to: 26.0) { if baseFontSize.isEqual(to: 26.0) {
@ -49,25 +49,34 @@ private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPres
result = max(33.0, result) result = max(33.0, result)
} }
if glass {
result = max(38.0, result)
}
return result return result
} }
private func calculateTextFieldRealInsets(_ presentationInterfaceState: ChatPresentationInterfaceState) -> UIEdgeInsets { private func calculateTextFieldRealInsets(_ presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool = false) -> UIEdgeInsets {
let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
let top: CGFloat let top: CGFloat
let bottom: CGFloat let bottom: CGFloat
if baseFontSize.isEqual(to: 14.0) { if glass {
top = 2.0 top = 4.0
bottom = 1.0 bottom = 1.0
} else if baseFontSize.isEqual(to: 15.0) {
top = 1.0
bottom = 1.0
} else if baseFontSize.isEqual(to: 16.0) {
top = 0.5
bottom = 0.0
} else { } else {
top = 0.0 if baseFontSize.isEqual(to: 14.0) {
bottom = 0.0 top = 2.0
bottom = 1.0
} else if baseFontSize.isEqual(to: 15.0) {
top = 1.0
bottom = 1.0
} else if baseFontSize.isEqual(to: 16.0) {
top = 0.5
bottom = 0.0
} else {
top = 0.0
bottom = 0.0
}
} }
return UIEdgeInsets(top: 4.5 + top, left: 0.0, bottom: 5.5 + bottom, right: 32.0) return UIEdgeInsets(top: 4.5 + top, left: 0.0, bottom: 5.5 + bottom, right: 32.0)
} }
@ -243,15 +252,16 @@ private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPre
public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate { public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate {
private let context: AccountContext private let context: AccountContext
private let glass: Bool
private let isCaption: Bool private let isCaption: Bool
private let isAttachment: Bool private let isAttachment: Bool
private let presentController: (ViewController) -> Void private let presentController: (ViewController) -> Void
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
private var textPlaceholderNode: ImmediateTextNode public var textPlaceholderNode: ImmediateTextNode
private let textInputContainerBackgroundNode: ASImageNode private let textInputContainerBackgroundNode: ASImageNode
private let textInputContainer: ASDisplayNode public let textInputContainer: ASDisplayNode
public var textInputNode: ChatInputTextNode? public var textInputNode: ChatInputTextNode?
private var dustNode: InvisibleInkDustNode? private var dustNode: InvisibleInkDustNode?
private var customEmojiContainerView: CustomEmojiContainerView? private var customEmojiContainerView: CustomEmojiContainerView?
@ -265,7 +275,11 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private let actionButtons: AttachmentTextInputActionButtonsNode private let actionButtons: AttachmentTextInputActionButtonsNode
private let counterTextNode: ImmediateTextNode private let counterTextNode: ImmediateTextNode
private let inputModeView: ComponentHostView<Empty> public var opaqueActionButtons: ASDisplayNode {
return self.actionButtons
}
public let inputModeView: ComponentHostView<Empty>
private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)? private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)?
@ -379,9 +393,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private var maxCaptionLength: Int32? private var maxCaptionLength: Int32?
public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, isCaption: Bool = false, isAttachment: Bool = false, isScheduledMessages: Bool = false, presentController: @escaping (ViewController) -> Void, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool = false, isCaption: Bool = false, isAttachment: Bool = false, isScheduledMessages: Bool = false, presentController: @escaping (ViewController) -> Void, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
self.context = context self.context = context
self.presentationInterfaceState = presentationInterfaceState self.presentationInterfaceState = presentationInterfaceState
self.glass = glass
self.isCaption = isCaption self.isCaption = isCaption
self.isAttachment = isAttachment self.isAttachment = isAttachment
self.presentController = presentController self.presentController = presentController
@ -401,7 +416,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.textInputContainerBackgroundNode.displaysAsynchronously = false self.textInputContainerBackgroundNode.displaysAsynchronously = false
self.textInputContainer = ASDisplayNode() self.textInputContainer = ASDisplayNode()
if !isCaption { if !isCaption && !glass {
self.textInputContainer.addSubnode(self.textInputContainerBackgroundNode) self.textInputContainer.addSubnode(self.textInputContainerBackgroundNode)
} }
@ -420,7 +435,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.oneLineNode = TextNodeWithEntities() self.oneLineNode = TextNodeWithEntities()
self.oneLineNode.textNode.isUserInteractionEnabled = false self.oneLineNode.textNode.isUserInteractionEnabled = false
self.actionButtons = AttachmentTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, presentController: presentController) self.actionButtons = AttachmentTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, glass: glass, presentController: presentController)
self.counterTextNode = ImmediateTextNode() self.counterTextNode = ImmediateTextNode()
self.counterTextNode.textAlignment = .center self.counterTextNode.textAlignment = .center
@ -441,7 +456,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.addSubnode(self.textInputContainer) self.addSubnode(self.textInputContainer)
self.addSubnode(self.textInputBackgroundNode) self.addSubnode(self.textInputBackgroundNode)
self.textInputBackgroundNode.addSubnode(self.textInputBackgroundImageNode)
if !glass {
self.textInputBackgroundNode.addSubnode(self.textInputBackgroundImageNode)
}
self.addSubnode(self.textPlaceholderNode) self.addSubnode(self.textPlaceholderNode)
@ -589,7 +607,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
if let presentationInterfaceState = self.presentationInterfaceState { if let presentationInterfaceState = self.presentationInterfaceState {
refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize)
textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState) textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass)
} }
if !self.textInputContainer.bounds.size.width.isZero { if !self.textInputContainer.bounds.size.width.isZero {
@ -628,7 +646,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat { private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat {
let textFieldInsets = self.textFieldInsets(metrics: metrics) let textFieldInsets = self.textFieldInsets(metrics: metrics)
return max(33.0, maxHeight - (textFieldInsets.top + textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom)) return max(self.glass ? 40.0 : 33.0, maxHeight - (textFieldInsets.top + textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom))
} }
private func calculateTextFieldMetrics(width: CGFloat, maxHeight: CGFloat, metrics: LayoutMetrics) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat) { private func calculateTextFieldMetrics(width: CGFloat, maxHeight: CGFloat, metrics: LayoutMetrics) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat) {
@ -641,8 +659,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
var textFieldMinHeight: CGFloat = 35.0 var textFieldMinHeight: CGFloat = 35.0
var textFieldRealInsets = UIEdgeInsets() var textFieldRealInsets = UIEdgeInsets()
if let presentationInterfaceState = self.presentationInterfaceState { if let presentationInterfaceState = self.presentationInterfaceState {
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics)
textFieldRealInsets = calculateTextFieldRealInsets(presentationInterfaceState) textFieldRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass)
} }
let textFieldHeight: CGFloat let textFieldHeight: CGFloat
@ -666,7 +684,12 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private func textFieldInsets(metrics: LayoutMetrics) -> UIEdgeInsets { private func textFieldInsets(metrics: LayoutMetrics) -> UIEdgeInsets {
var insets = UIEdgeInsets(top: 6.0, left: 6.0, bottom: 6.0, right: 42.0) var insets = UIEdgeInsets(top: 6.0, left: 6.0, bottom: 6.0, right: 42.0)
if case .regular = metrics.widthClass, case .regular = metrics.heightClass { if self.glass {
insets.left = 8.0
insets.top = 0.0
insets.bottom = 0.0
insets.right += 3.0
} else if case .regular = metrics.widthClass, case .regular = metrics.heightClass {
insets.top += 1.0 insets.top += 1.0
insets.bottom += 1.0 insets.bottom += 1.0
} }
@ -680,7 +703,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
} }
func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics) let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, glass: self.glass, metrics: metrics)
var minimalHeight: CGFloat = 14.0 + textFieldMinHeight var minimalHeight: CGFloat = 14.0 + textFieldMinHeight
if case .regular = metrics.widthClass, case .regular = metrics.heightClass { if case .regular = metrics.widthClass, case .regular = metrics.heightClass {
minimalHeight += 2.0 minimalHeight += 2.0
@ -689,6 +712,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
} }
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard self.isUserInteractionEnabled else {
return nil
}
if !self.inputModeView.isHidden, let result = self.inputModeView.hitTest(self.view.convert(point, to: self.inputModeView), with: event) { if !self.inputModeView.isHidden, let result = self.inputModeView.hitTest(self.view.convert(point, to: self.inputModeView), with: event) {
return result return result
} }
@ -703,7 +729,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let leftInset = leftInset + 8.0 let leftInset = leftInset + 8.0
let rightInset = rightInset + 8.0 let rightInset = rightInset + 8.0
var transition = transition var transition = transition
if let previousAdditionalSideInsets = previousAdditionalSideInsets, previousAdditionalSideInsets.right != additionalSideInsets.right { if let previousAdditionalSideInsets = previousAdditionalSideInsets, previousAdditionalSideInsets.right != additionalSideInsets.right {
if case .animated = transition { if case .animated = transition {
@ -758,7 +784,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.actionButtons.updateTheme(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) self.actionButtons.updateTheme(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics) let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, glass: self.glass, metrics: metrics)
let minimalInputHeight: CGFloat = 2.0 + textFieldMinHeight let minimalInputHeight: CGFloat = 2.0 + textFieldMinHeight
let backgroundColor: UIColor let backgroundColor: UIColor
@ -828,9 +854,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
} }
} }
var textFieldMinHeight: CGFloat = 33.0 var textFieldMinHeight: CGFloat = self.glass ? 40.0 : 33.0
if let presentationInterfaceState = self.presentationInterfaceState { if let presentationInterfaceState = self.presentationInterfaceState {
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics)
} }
let minimalHeight: CGFloat = 14.0 + textFieldMinHeight let minimalHeight: CGFloat = 14.0 + textFieldMinHeight
@ -852,7 +878,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
var textInputViewRealInsets = UIEdgeInsets() var textInputViewRealInsets = UIEdgeInsets()
if let presentationInterfaceState = self.presentationInterfaceState { if let presentationInterfaceState = self.presentationInterfaceState {
textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState) textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass)
} }
if self.isCaption { if self.isCaption {
@ -922,7 +948,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom)) let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom))
let shouldUpdateLayout = textFieldFrame.size != textInputNode.frame.size let shouldUpdateLayout = textFieldFrame.size != textInputNode.frame.size
if let presentationInterfaceState = self.presentationInterfaceState { if let presentationInterfaceState = self.presentationInterfaceState {
textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState) textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass)
} }
transition.updateFrame(node: textInputNode, frame: textFieldFrame) transition.updateFrame(node: textInputNode, frame: textFieldFrame)
if shouldUpdateLayout { if shouldUpdateLayout {
@ -932,6 +958,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.actionButtons.updateAccessibility() self.actionButtons.updateAccessibility()
if self.glass {
panelHeight += 11.0
}
return panelHeight return panelHeight
} }
@ -943,11 +973,14 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let leftInset = leftInsetValue + 8.0 let leftInset = leftInsetValue + 8.0
let rightInset = rightInsetValue + 8.0 let rightInset = rightInsetValue + 8.0
var textFieldMinHeight: CGFloat = 33.0 var textFieldMinHeight: CGFloat = self.glass ? 40.0 : 33.0
if let presentationInterfaceState = self.presentationInterfaceState { if let presentationInterfaceState = self.presentationInterfaceState {
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics)
}
var minimalHeight: CGFloat = textFieldMinHeight
if !self.glass {
minimalHeight += 14.0
} }
let minimalHeight: CGFloat = 14.0 + textFieldMinHeight
var panelHeight = panelHeight var panelHeight = panelHeight
var composeButtonsOffset: CGFloat = 0.0 var composeButtonsOffset: CGFloat = 0.0
@ -983,16 +1016,18 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
isMinimized = !self.isAttachment || inputHasText isMinimized = !self.isAttachment || inputHasText
text = presentationInterfaceState.strings.MediaPicker_Send text = presentationInterfaceState.strings.MediaPicker_Send
} }
actionButtonsSize = self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, minimized: isMinimized, text: text, interfaceState: presentationInterfaceState) actionButtonsSize = self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, minimized: isMinimized && !self.glass, text: text, interfaceState: presentationInterfaceState)
textBackgroundInset = actionButtonsSize.width - 44.0 textBackgroundInset = actionButtonsSize.width - 44.0
} else { } else {
actionButtonsSize = CGSize(width: 44.0, height: minimalHeight) actionButtonsSize = CGSize(width: 44.0, height: minimalHeight)
} }
let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - actionButtonsSize.width + 1.0 - UIScreenPixel + composeButtonsOffset, y: panelHeight - minimalHeight), size: actionButtonsSize) let actionButtonsOriginOffset: CGFloat = self.glass ? -3.0 : 0.0
let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - actionButtonsSize.width + 1.0 - UIScreenPixel + composeButtonsOffset + actionButtonsOriginOffset, y: panelHeight - minimalHeight), size: actionButtonsSize)
transition.updateFrame(node: self.actionButtons, frame: actionButtonsFrame) transition.updateFrame(node: self.actionButtons, frame: actionButtonsFrame)
let textInputBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: textInputFrame.size.height)) let textInputHeight = panelHeight - textFieldInsets.top - textFieldInsets.bottom
let textInputBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: textInputHeight))
transition.updateFrame(node: self.textInputContainerBackgroundNode, frame: textInputBackgroundFrame) transition.updateFrame(node: self.textInputContainerBackgroundNode, frame: textInputBackgroundFrame)
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)) transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom))
@ -1000,13 +1035,13 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
var textInputViewRealInsets = UIEdgeInsets() var textInputViewRealInsets = UIEdgeInsets()
if let presentationInterfaceState = self.presentationInterfaceState { if let presentationInterfaceState = self.presentationInterfaceState {
textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState) textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass)
var colors: [String: UIColor] = [:] var colors: [String: UIColor] = [:]
let colorKeys: [String] = [ let colorKeys: [String] = [
"__allcolors__" "__allcolors__"
] ]
let color = defaultDarkPresentationTheme.chat.inputPanel.inputControlColor let color = self.theme?.chat.inputPanel.inputControlColor ?? defaultDarkPresentationTheme.chat.inputPanel.inputControlColor
for colorKey in colorKeys { for colorKey in colorKeys {
colors[colorKey] = color colors[colorKey] = color
} }
@ -1028,7 +1063,11 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
environment: {}, environment: {},
containerSize: CGSize(width: 32.0, height: 32.0) containerSize: CGSize(width: 32.0, height: 32.0)
) )
transition.updateFrame(view: self.inputModeView, frame: CGRect(origin: CGPoint(x: textInputBackgroundFrame.maxX - inputNodeSize.width - 1.0, y: textInputBackgroundFrame.maxY - inputNodeSize.height - 1.0), size: inputNodeSize)) var inputNodeOffset: CGPoint = CGPoint(x: -1.0, y: -1.0)
if self.glass {
inputNodeOffset = CGPoint(x: -4.0, y: -4.0)
}
transition.updateFrame(view: self.inputModeView, frame: CGRect(origin: CGPoint(x: textInputBackgroundFrame.maxX - inputNodeSize.width + inputNodeOffset.x, y: textInputBackgroundFrame.maxY - inputNodeSize.height + inputNodeOffset.y), size: inputNodeSize))
} }
let placeholderFrame: CGRect let placeholderFrame: CGRect
@ -1294,7 +1333,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics)
var textFieldMinHeight: CGFloat = 33.0 var textFieldMinHeight: CGFloat = 33.0
if let presentationInterfaceState = self.presentationInterfaceState { if let presentationInterfaceState = self.presentationInterfaceState {
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics)
} }
let minimalHeight: CGFloat = 14.0 + textFieldMinHeight let minimalHeight: CGFloat = 14.0 + textFieldMinHeight

View file

@ -42,6 +42,8 @@ swift_library(
"//submodules/ReactionSelectionNode", "//submodules/ReactionSelectionNode",
"//submodules/TelegramUI/Components/Chat/TopMessageReactions", "//submodules/TelegramUI/Components/Chat/TopMessageReactions",
"//submodules/TelegramUI/Components/MinimizedContainer", "//submodules/TelegramUI/Components/MinimizedContainer",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -8,6 +8,8 @@ import DirectionalPanGesture
import TelegramPresentationData import TelegramPresentationData
import MapKit import MapKit
import WebKit import WebKit
import ComponentFlow
import EdgeEffect
private let overflowInset: CGFloat = 0.0 private let overflowInset: CGFloat = 0.0
@ -27,10 +29,13 @@ public func attachmentDefaultTopInset(layout: ContainerViewLayout?) -> CGFloat {
} }
final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
private let glass: Bool
let wrappingNode: ASDisplayNode let wrappingNode: ASDisplayNode
let clipNode: ASDisplayNode let clipNode: ASDisplayNode
let bottomClipNode: ASDisplayNode
let container: NavigationContainer let container: NavigationContainer
private let pillView: UIImageView
private(set) var isReady: Bool = false private(set) var isReady: Bool = false
private(set) var dismissProgress: CGFloat = 0.0 private(set) var dismissProgress: CGFloat = 0.0
var isReadyUpdated: (() -> Void)? var isReadyUpdated: (() -> Void)?
@ -66,6 +71,12 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
} }
} }
var presentationData: PresentationData {
didSet {
self.pillView.tintColor = self.presentationData.theme.rootController.navigationBar.primaryTextColor
}
}
private var panGestureRecognizer: UIPanGestureRecognizer? private var panGestureRecognizer: UIPanGestureRecognizer?
var isPanningUpdated: (Bool) -> Void = { _ in } var isPanningUpdated: (Bool) -> Void = { _ in }
@ -74,14 +85,19 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
var isInnerPanGestureEnabled: (() -> Bool)? var isInnerPanGestureEnabled: (() -> Bool)?
var onExpandAnimationCompleted: () -> Void = {} var onExpandAnimationCompleted: () -> Void = {}
init(isFullSize: Bool) { init(presentationData: PresentationData, isFullSize: Bool, glass: Bool) {
self.presentationData = presentationData
self.isFullSize = isFullSize self.isFullSize = isFullSize
if isFullSize { if isFullSize {
self.isExpanded = true self.isExpanded = true
} }
self.glass = glass
self.wrappingNode = ASDisplayNode() self.wrappingNode = ASDisplayNode()
self.clipNode = ASDisplayNode() self.clipNode = ASDisplayNode()
self.bottomClipNode = ASDisplayNode()
self.bottomClipNode.clipsToBounds = true
self.bottomClipNode.cornerRadius = 62.0
var controllerRemovedImpl: ((ViewController) -> Void)? var controllerRemovedImpl: ((ViewController) -> Void)?
self.container = NavigationContainer(isFlat: false, controllerRemoved: { c in self.container = NavigationContainer(isFlat: false, controllerRemoved: { c in
@ -90,12 +106,18 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
self.container.clipsToBounds = true self.container.clipsToBounds = true
self.container.overflowInset = overflowInset self.container.overflowInset = overflowInset
self.container.shouldAnimateDisappearance = true self.container.shouldAnimateDisappearance = true
self.pillView = UIImageView()
self.pillView.alpha = 0.2
self.pillView.image = generateFilledRoundedRectImage(size: CGSize(width: 36.0, height: 5.0), cornerRadius: 2.5, color: .black)?.withRenderingMode(.alwaysTemplate)
self.pillView.tintColor = self.presentationData.theme.rootController.navigationBar.primaryTextColor
super.init() super.init()
self.addSubnode(self.wrappingNode) self.addSubnode(self.wrappingNode)
self.wrappingNode.addSubnode(self.clipNode) self.wrappingNode.addSubnode(self.clipNode)
self.clipNode.addSubnode(self.container) self.clipNode.addSubnode(self.bottomClipNode)
self.bottomClipNode.addSubnode(self.container)
self.isReady = self.container.isReady self.isReady = self.container.isReady
self.container.isReadyUpdated = { [weak self] in self.container.isReadyUpdated = { [weak self] in
@ -126,6 +148,8 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
panRecognizer.cancelsTouchesInView = true panRecognizer.cancelsTouchesInView = true
self.panGestureRecognizer = panRecognizer self.panGestureRecognizer = panRecognizer
self.wrappingNode.view.addGestureRecognizer(panRecognizer) self.wrappingNode.view.addGestureRecognizer(panRecognizer)
self.clipNode.view.addSubview(self.pillView)
} }
func cancelPanGesture() { func cancelPanGesture() {
@ -480,13 +504,17 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
}) })
let modalProgress: CGFloat let modalProgress: CGFloat
let scaleProgress: CGFloat
if isLandscape { if isLandscape {
modalProgress = 0.0 modalProgress = 0.0
scaleProgress = 1.0
} else { } else {
if self.isFullSize, self.panGestureArguments != nil { if self.isFullSize, self.panGestureArguments != nil {
modalProgress = 1.0 - min(1.0, max(0.0, -1.0 * self.bounds.minY / defaultTopInset)) modalProgress = 1.0 - min(1.0, max(0.0, -1.0 * self.bounds.minY / defaultTopInset))
scaleProgress = min(1.0, max(0.0, -1.0 * self.bounds.minY / defaultTopInset))
} else { } else {
modalProgress = 1.0 - topInset / defaultTopInset modalProgress = 1.0 - topInset / defaultTopInset
scaleProgress = topInset / defaultTopInset
} }
} }
@ -514,7 +542,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
if isLandscape { if isLandscape {
self.clipNode.cornerRadius = 0.0 self.clipNode.cornerRadius = 0.0
} else { } else {
self.clipNode.cornerRadius = 10.0 self.clipNode.cornerRadius = self.glass ? 38.0 : 10.0
} }
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
@ -562,7 +590,10 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition
containerFrame = unscaledFrame.offsetBy(dx: -overflowInset, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) containerFrame = unscaledFrame.offsetBy(dx: -overflowInset, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0))
clipFrame = CGRect(x: containerFrame.minX + overflowInset, y: containerFrame.minY, width: containerFrame.width - overflowInset * 2.0, height: containerFrame.height) let topPortion = self.wrappingNode.frame.minY
let clipTopOffset: CGFloat = 2.0 * scaleProgress + UIScreenPixel
clipFrame = CGRect(x: containerFrame.minX + overflowInset, y: containerFrame.minY + clipTopOffset, width: containerFrame.width - overflowInset * 2.0, height: containerFrame.height - topPortion)
} }
} else { } else {
containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: .zero, additionalInsets: .zero, statusBarHeight: isFullscreen ? layout.statusBarHeight : nil, inputHeight: isFullscreen ? layout.inputHeight : nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver) containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: .zero, additionalInsets: .zero, statusBarHeight: isFullscreen ? layout.statusBarHeight : nil, inputHeight: isFullscreen ? layout.inputHeight : nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver)
@ -573,10 +604,21 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
clipFrame = unscaledFrame clipFrame = unscaledFrame
} }
transition.updateFrameAsPositionAndBounds(node: self.clipNode, frame: clipFrame) transition.updateFrameAsPositionAndBounds(node: self.clipNode, frame: clipFrame)
transition.updateFrameAsPositionAndBounds(node: self.container, frame: CGRect(origin: CGPoint(x: containerFrame.minX, y: 0.0), size: containerFrame.size))
let clipDeltaScale: CGFloat = 1.0 - (clipFrame.width - 10.0) / clipFrame.width
let clipScale = 1.0 - clipDeltaScale + clipDeltaScale * (1.0 - scaleProgress)
transition.updateTransformScale(node: self.clipNode, scale: CGPoint(x: clipScale, y: clipScale))
transition.updateFrameAsPositionAndBounds(node: self.bottomClipNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -100.0), size: CGSize(width: clipFrame.size.width, height: self.clipNode.bounds.height + 100.0)))
transition.updateFrameAsPositionAndBounds(node: self.container, frame: CGRect(origin: CGPoint(x: containerFrame.minX, y: 100.0), size: containerFrame.size))
transition.updateTransformScale(node: self.container, scale: containerScale) transition.updateTransformScale(node: self.container, scale: containerScale)
self.container.update(layout: containerLayout, canBeClosed: true, controllers: controllers, transition: transition) self.container.update(layout: containerLayout, canBeClosed: true, controllers: controllers, transition: transition)
if let image = self.pillView.image {
transition.updateFrame(view: self.pillView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((clipFrame.width - image.size.width) / 2.0), y: 5.0), size: image.size))
self.pillView.isHidden = layout.metrics.isTablet
}
self.isUpdatingState = false self.isUpdatingState = false
} }
@ -647,7 +689,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
} }
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let convertedPoint = self.view.convert(point, to: self.container.view) let convertedPoint = self.view.convert(point, to: self.bottomClipNode.view)
if !self.container.frame.contains(convertedPoint) { if !self.container.frame.contains(convertedPoint) {
return false return false
} }

View file

@ -4,6 +4,7 @@ import Display
import AsyncDisplayKit import AsyncDisplayKit
import SwiftSignalKit import SwiftSignalKit
import Postbox import Postbox
import ComponentFlow
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences import TelegramUIPreferences
@ -16,6 +17,8 @@ import LegacyMessageInputPanelInputView
import AttachmentTextInputPanelNode import AttachmentTextInputPanelNode
import ChatSendMessageActionUI import ChatSendMessageActionUI
import MinimizedContainer import MinimizedContainer
import ComponentFlow
import GlassBackgroundComponent
public enum AttachmentButtonType: Equatable { public enum AttachmentButtonType: Equatable {
case gallery case gallery
@ -308,42 +311,74 @@ public extension AttachmentMediaPickerContext {
} }
} }
private func generateShadowImage() -> UIImage? { private func generateShadowImage(glass: Bool) -> UIImage? {
return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in let shadowInset: CGFloat = 60.0
let middle: CGFloat = 10.0
let innerSide = glass ? 62.0 * 2.0 : 10.0 * 2.0
let side = innerSide + middle + shadowInset * 2.0
return generateImage(CGSize(width: side, height: side), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState() context.saveGState()
context.setShadow(offset: CGSize(), blur: 60.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor) context.setShadow(offset: CGSize(), blur: glass ? 80.0 : 60.0, color: UIColor(white: 0.0, alpha: glass ? 0.3 : 0.4).cgColor)
let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 20.0, height: 20.0), cornerRadius: 10.0).cgPath if glass {
context.addPath(path) let firstPath = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide + middle, height: innerSide + middle), cornerRadius: 62.0).cgPath
context.fillPath() let secondPath = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide + middle, height: innerSide * 0.65), cornerRadius: 38.0).cgPath
context.addPath(firstPath)
context.restoreGState() context.addPath(secondPath)
context.fillPath()
context.setBlendMode(.clear)
context.addPath(path) context.restoreGState()
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 70, topCapHeight: 70) context.setBlendMode(.clear)
context.addPath(firstPath)
context.addPath(secondPath)
context.fillPath()
} else {
let path = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide, height: innerSide), cornerRadius: 10.0).cgPath
context.addPath(path)
context.fillPath()
context.restoreGState()
context.setBlendMode(.clear)
context.addPath(path)
context.fillPath()
}
})?.stretchableImage(withLeftCapWidth: Int(side / 2.0), topCapHeight: Int(side / 2.0))
} }
private func generateMaskImage() -> UIImage? { private func generateMaskImage(glass: Bool) -> UIImage? {
return generateImage(CGSize(width: 390.0, height: 220.0), rotatedContext: { size, context in return generateImage(CGSize(width: 390.0, height: 220.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor) context.setFillColor(UIColor.white.cgColor)
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 10.0).cgPath if glass {
context.addPath(path) let firstPath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 62.0).cgPath
context.fillPath() let secondPath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 130.0), cornerRadius: 38.0).cgPath
context.addPath(firstPath)
context.addPath(secondPath)
context.fillPath()
} else {
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 10.0).cgPath
context.addPath(path)
context.fillPath()
}
try? drawSvgPath(context, path: "M183.219,208.89 H206.781 C205.648,208.89 204.567,209.371 203.808,210.214 L197.23,217.523 C196.038,218.848 193.962,218.848 192.77,217.523 L186.192,210.214 C185.433,209.371 184.352,208.89 183.219,208.89 Z ") try? drawSvgPath(context, path: "M183.219,208.89 H206.781 C205.648,208.89 204.567,209.371 203.808,210.214 L197.23,217.523 C196.038,218.848 193.962,218.848 192.77,217.523 L186.192,210.214 C185.433,209.371 184.352,208.89 183.219,208.89 Z ")
})?.stretchableImage(withLeftCapWidth: 195, topCapHeight: 110) })?.stretchableImage(withLeftCapWidth: 195, topCapHeight: 110)
} }
public class AttachmentController: ViewController, MinimizableController { public class AttachmentController: ViewController, MinimizableController {
public enum Style {
case glass
case legacy
}
private let context: AccountContext private let context: AccountContext
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private let style: Style
private let chatLocation: ChatLocation? private let chatLocation: ChatLocation?
private let isScheduledMessages: Bool private let isScheduledMessages: Bool
private var buttons: [AttachmentButtonType] private var buttons: [AttachmentButtonType]
@ -380,6 +415,8 @@ public class AttachmentController: ViewController, MinimizableController {
public var isFullscreen: Bool { public var isFullscreen: Bool {
return self.mainController.isFullscreen return self.mainController.isFullscreen
} }
public weak var attachmentButton: UIView?
private final class Node: ASDisplayNode { private final class Node: ASDisplayNode {
private weak var controller: AttachmentController? private weak var controller: AttachmentController?
@ -426,7 +463,7 @@ public class AttachmentController: ViewController, MinimizableController {
if let strongSelf = self { if let strongSelf = self {
strongSelf.panel.updateLoadingProgress(progress) strongSelf.panel.updateLoadingProgress(progress)
if let layout = strongSelf.validLayout { if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
} }
} }
})) }))
@ -441,7 +478,7 @@ public class AttachmentController: ViewController, MinimizableController {
if let strongSelf = self { if let strongSelf = self {
strongSelf.panel.updateMainButtonState(mainButtonState) strongSelf.panel.updateMainButtonState(mainButtonState)
if let layout = strongSelf.validLayout { if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
} }
} }
}) })
@ -458,7 +495,7 @@ public class AttachmentController: ViewController, MinimizableController {
if let strongSelf = self { if let strongSelf = self {
strongSelf.panel.updateSecondaryButtonState(mainButtonState) strongSelf.panel.updateSecondaryButtonState(mainButtonState)
if let layout = strongSelf.validLayout { if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
} }
} }
}) })
@ -491,12 +528,21 @@ public class AttachmentController: ViewController, MinimizableController {
private let wrapperNode: ASDisplayNode private let wrapperNode: ASDisplayNode
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private var isMinimizing = false private var isMinimizing = false
init(controller: AttachmentController, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { init(controller: AttachmentController, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
self.controller = controller self.controller = controller
self.makeEntityInputView = makeEntityInputView self.makeEntityInputView = makeEntityInputView
if let updatedPresentationData = controller.updatedPresentationData {
self.presentationData = updatedPresentationData.initial
} else {
self.presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
}
self.dim = ASDisplayNode() self.dim = ASDisplayNode()
self.dim.alpha = 0.0 self.dim.alpha = 0.0
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
@ -507,9 +553,18 @@ public class AttachmentController: ViewController, MinimizableController {
self.wrapperNode = ASDisplayNode() self.wrapperNode = ASDisplayNode()
self.wrapperNode.clipsToBounds = true self.wrapperNode.clipsToBounds = true
self.container = AttachmentContainer(isFullSize: controller.isFullSize) self.container = AttachmentContainer(presentationData: self.presentationData, isFullSize: controller.isFullSize, glass: controller.style == .glass)
self.container.canHaveKeyboardFocus = true self.container.canHaveKeyboardFocus = true
self.panel = AttachmentPanel(controller: controller, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView)
let panelStyle: AttachmentPanel.Style
switch controller.style {
case .glass:
panelStyle = .glass
case .legacy:
panelStyle = .legacy
}
self.panel = AttachmentPanel(controller: controller, style: panelStyle, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView)
self.panel.fromMenu = controller.fromMenu self.panel.fromMenu = controller.fromMenu
self.panel.isStandalone = controller.isStandalone self.panel.isStandalone = controller.isStandalone
@ -529,10 +584,10 @@ public class AttachmentController: ViewController, MinimizableController {
} }
self.container.updateModalProgress = { [weak self] progress, topInset, bounds, transition in self.container.updateModalProgress = { [weak self] progress, topInset, bounds, transition in
if let strongSelf = self, let layout = strongSelf.validLayout, !strongSelf.isDismissing { if let strongSelf = self, let controller = strongSelf.controller, let layout = strongSelf.validLayout, !strongSelf.isDismissing {
var transition = transition var transition = transition
if strongSelf.container.supernode == nil { if strongSelf.container.supernode == nil {
transition = .animated(duration: 0.4, curve: .spring) transition = .animated(duration: 0.5, curve: .spring)
} }
strongSelf.modalProgress = progress strongSelf.modalProgress = progress
@ -540,33 +595,37 @@ public class AttachmentController: ViewController, MinimizableController {
strongSelf.controller?.minimizedBounds = bounds strongSelf.controller?.minimizedBounds = bounds
if !strongSelf.isMinimizing { if !strongSelf.isMinimizing {
strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition) if controller.style != .glass {
strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition)
}
strongSelf.containerLayoutUpdated(layout, transition: transition) strongSelf.containerLayoutUpdated(layout, transition: transition)
} }
} }
} }
self.container.isReadyUpdated = { [weak self] in self.container.isReadyUpdated = { [weak self] in
if let strongSelf = self, let layout = strongSelf.validLayout { if let strongSelf = self, let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
} }
} }
self.container.interactivelyDismissed = { [weak self] velocity in self.container.interactivelyDismissed = { [weak self] velocity in
if let strongSelf = self, let layout = strongSelf.validLayout { guard let self, let controller = self.controller, let layout = self.validLayout else {
if let controller = strongSelf.controller, controller.shouldMinimizeOnSwipe?(strongSelf.currentType) == true { return true
var delta = layout.size.height }
if let minimizedTopEdgeOffset = controller.minimizedTopEdgeOffset { let damping: CGFloat = 180.0
delta -= minimizedTopEdgeOffset var delta = layout.size.height
} if let minimizedTopEdgeOffset = controller.minimizedTopEdgeOffset {
let damping: CGFloat = 180.0 delta -= minimizedTopEdgeOffset
let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0 }
let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0
strongSelf.minimize(damping: damping, initialVelocity: initialVelocity)
if controller.shouldMinimizeOnSwipe?(self.currentType) == true {
return false self.minimize(damping: damping, initialVelocity: initialVelocity)
} else { return false
strongSelf.controller?.dismiss(animated: true) } else {
} self.animateOut(damping: damping, initialVelocity: initialVelocity, completion: {
self.controller?.dismiss(animated: false)
})
} }
return true return true
} }
@ -637,7 +696,7 @@ public class AttachmentController: ViewController, MinimizableController {
self.panel.beganTextEditing = { [weak self] in self.panel.beganTextEditing = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.5, curve: .spring))
} }
} }
@ -699,6 +758,17 @@ public class AttachmentController: ViewController, MinimizableController {
return currentController.getCurrentSendMessageContextMediaPreview?() return currentController.getCurrentSendMessageContextMediaPreview?()
} }
if let updatedPresentationData = controller.updatedPresentationData {
self.presentationDataDisposable = (updatedPresentationData.signal
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
guard let self else {
return
}
self.presentationData = presentationData
self.container.presentationData = presentationData
})
}
} }
deinit { deinit {
@ -708,6 +778,7 @@ public class AttachmentController: ViewController, MinimizableController {
self.mainButtonStateDisposable.dispose() self.mainButtonStateDisposable.dispose()
self.secondaryButtonStateDisposable.dispose() self.secondaryButtonStateDisposable.dispose()
self.bottomPanelBackgroundColorDisposable.dispose() self.bottomPanelBackgroundColorDisposable.dispose()
self.presentationDataDisposable?.dispose()
} }
private var inputContainerHeight: CGFloat? private var inputContainerHeight: CGFloat?
@ -783,7 +854,7 @@ public class AttachmentController: ViewController, MinimizableController {
fileprivate func updateSelectionCount(_ count: Int, animated: Bool = true) { fileprivate func updateSelectionCount(_ count: Int, animated: Bool = true) {
self.selectionCount = count self.selectionCount = count
if let layout = self.validLayout { if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate) self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate)
} }
} }
@ -808,7 +879,7 @@ public class AttachmentController: ViewController, MinimizableController {
func switchToController(_ type: AttachmentButtonType, animated: Bool = true) -> Bool { func switchToController(_ type: AttachmentButtonType, animated: Bool = true) -> Bool {
guard self.currentType != type else { guard self.currentType != type else {
if self.animating { if self.isAnimating {
return false return false
} }
if let controller = self.currentControllers.last { if let controller = self.currentControllers.last {
@ -822,12 +893,15 @@ public class AttachmentController: ViewController, MinimizableController {
self.controller?.requestController(type, { [weak self] controller, mediaPickerContext in self.controller?.requestController(type, { [weak self] controller, mediaPickerContext in
if let strongSelf = self { if let strongSelf = self {
if let controller = controller { if let controller = controller {
if case .glass = strongSelf.controller?.style {
controller._hasGlassStyle = true
}
strongSelf.controller?._ready.set(controller.ready.get()) strongSelf.controller?._ready.set(controller.ready.get())
controller._presentedInModal = true controller._presentedInModal = true
controller.navigation_setPresenting(strongSelf.controller) controller.navigation_setPresenting(strongSelf.controller)
controller.requestAttachmentMenuExpansion = { [weak self] in controller.requestAttachmentMenuExpansion = { [weak self] in
if let strongSelf = self, !strongSelf.container.isTracking { if let strongSelf = self, !strongSelf.container.isTracking {
strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.5, curve: .spring))
} }
} }
controller.updateNavigationStack = { [weak self] f in controller.updateNavigationStack = { [weak self] f in
@ -836,7 +910,7 @@ public class AttachmentController: ViewController, MinimizableController {
strongSelf.currentControllers = controllers strongSelf.currentControllers = controllers
strongSelf.mediaPickerContext = mediaPickerContext strongSelf.mediaPickerContext = mediaPickerContext
if let layout = strongSelf.validLayout { if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
} }
} }
} }
@ -902,9 +976,9 @@ public class AttachmentController: ViewController, MinimizableController {
guard let snapshotView = self.container.container.view.snapshotView(afterScreenUpdates: false) else { guard let snapshotView = self.container.container.view.snapshotView(afterScreenUpdates: false) else {
return return
} }
snapshotView.alpha = 0.0
snapshotView.frame = self.container.container.frame snapshotView.frame = self.container.container.frame
self.container.clipNode.view.addSubview(snapshotView) self.container.bottomClipNode.view.insertSubview(snapshotView, at: self.container.bottomClipNode.view.subviews.count)
let _ = (controller.ready.get() let _ = (controller.ready.get()
|> filter { |> filter {
@ -915,22 +989,22 @@ public class AttachmentController: ViewController, MinimizableController {
guard let strongSelf = self, let layout = strongSelf.validLayout else { guard let strongSelf = self, let layout = strongSelf.validLayout else {
return return
} }
let _ = layout
if case .compact = layout.metrics.widthClass { if case .compact = layout.metrics.widthClass {
let offset = 25.0 let offset = 10.0
let initialPosition = strongSelf.container.clipNode.layer.position let initialPosition = strongSelf.container.wrappingNode.layer.position
let targetPosition = initialPosition.offsetBy(dx: 0.0, dy: offset) let targetPosition = initialPosition.offsetBy(dx: 0.0, dy: offset)
var startPosition = initialPosition var startPosition = initialPosition
if let presentation = strongSelf.container.clipNode.layer.presentation() { if let presentation = strongSelf.container.wrappingNode.layer.presentation() {
startPosition = presentation.position startPosition = presentation.position
} }
strongSelf.container.clipNode.layer.animatePosition(from: startPosition, to: targetPosition, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in strongSelf.container.wrappingNode.layer.animatePosition(from: startPosition, to: targetPosition, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in
if let strongSelf = self, finished { if let strongSelf = self, finished {
strongSelf.container.clipNode.layer.animateSpring(from: NSValue(cgPoint: targetPosition), to: NSValue(cgPoint: initialPosition), keyPath: "position", duration: 0.4, delay: 0.0, initialVelocity: 0.0, damping: 70.0, removeOnCompletion: false, completion: { [weak self] finished in strongSelf.container.wrappingNode.layer.animateSpring(from: NSValue(cgPoint: targetPosition), to: NSValue(cgPoint: initialPosition), keyPath: "position", duration: 0.3, delay: 0.0, initialVelocity: 0.0, damping: 70.0, removeOnCompletion: false, completion: { [weak self] finished in
if finished { if finished {
self?.container.clipNode.layer.removeAllAnimations() self?.container.wrappingNode.layer.removeAllAnimations()
} }
}) })
} }
@ -944,13 +1018,13 @@ public class AttachmentController: ViewController, MinimizableController {
}) })
} }
private var animating = false private var isAnimating = false
func animateIn() { func animateIn() {
guard let layout = self.validLayout, let controller = self.controller else { guard let layout = self.validLayout, let controller = self.controller else {
return return
} }
self.animating = true self.isAnimating = true
if case .regular = layout.metrics.widthClass { if case .regular = layout.metrics.widthClass {
if controller.animateAppearance { if controller.animateAppearance {
let targetPosition = self.position let targetPosition = self.position
@ -960,30 +1034,103 @@ public class AttachmentController: ViewController, MinimizableController {
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
transition.animateView(allowUserInteraction: true, { transition.animateView(allowUserInteraction: true, {
self.position = targetPosition self.position = targetPosition
}, completion: { _ in }, completion: { _ in
self.animating = false self.isAnimating = false
}) })
} else { } else {
self.animating = false self.isAnimating = false
} }
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 0.1) ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 0.1)
} else { } else {
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
let targetPosition = self.container.position if case .glass = controller.style, let attachmentButton = controller.attachmentButton {
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .customSpring(damping: 115.0, initialVelocity: 0.0))
let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut)
self.container.position = startPosition
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) let targetFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view)
transition.animateView(allowUserInteraction: true, {
self.container.position = targetPosition let sourceButtonFrame = attachmentButton.convert(attachmentButton.bounds, to: self.view)
}, completion: { _ in let sourceButtonScale = sourceButtonFrame.width / targetFrame.width
self.animating = false
}) if let sourceGlassView = findParentGlassBackgroundView(attachmentButton), let glassParams = sourceGlassView.params {
let containerView = UIView()
containerView.clipsToBounds = true
containerView.frame = targetFrame
if #available(iOS 26.0, *) {
containerView.cornerConfiguration = .uniformCorners(radius: .fixed(containerView.bounds.width * 0.5))
} else {
containerView.layer.cornerRadius = containerView.bounds.width * 0.5
}
self.view.addSubview(containerView)
let localGlassView = GlassBackgroundView()
localGlassView.update(
size: targetFrame.size,
cornerRadius: 0.0,
isDark: glassParams.isDark,
tintColor: glassParams.tintColor,
transition: .immediate
)
localGlassView.frame = CGRect(origin: .zero, size: targetFrame.size)
containerView.addSubview(localGlassView)
let initialContainerBounds = self.container.bounds
let initialContainerFrame = self.container.frame
let clipInnerFrame = self.container.container.view.convert(self.container.container.view.bounds, to: self.container.view)
self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size)
self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels ((targetFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size)
self.container.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
containerView.addSubnode(self.container)
let buttonIcon = GlassBackgroundView.ContentImageView()
let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
buttonIcon.image = PresentationResourcesChat.chatInputPanelAttachmentButtonImage(presentationData.theme)
buttonIcon.tintColor = presentationData.theme.chat.inputPanel.panelControlColor
if let image = buttonIcon.image {
buttonIcon.bounds = CGRect(origin: .zero, size: image.size)
buttonIcon.center = CGPoint(x: targetFrame.width * 0.5, y: targetFrame.height * 0.5)
buttonIcon.transform = CGAffineTransformMakeScale(1.0 / sourceButtonScale, 1.0 / sourceButtonScale)
}
localGlassView.contentView.addSubview(buttonIcon)
ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 0.0, toRadius: 10.0)
transition.animateBounds(layer: containerView.layer, from: CGRect(origin: CGPoint(x: 0.0, y: (targetFrame.height - targetFrame.width) * 0.5), size: CGSize(width: targetFrame.width, height: targetFrame.width)))
ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).animateView {
if #available(iOS 26.0, *) {
containerView.cornerConfiguration = .corners(topLeftRadius: 38.0, topRightRadius: 38.0, bottomLeftRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0), bottomRightRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0))
} else {
containerView.layer.cornerRadius = layout.deviceMetrics.screenCornerRadius - 2.0
}
}
transition.animateTransformScale(view: containerView, from: sourceButtonScale)
transition.animatePosition(layer: containerView.layer, from: sourceButtonFrame.center, to: containerView.center, completion: { _ in
self.container.bounds = initialContainerBounds
self.container.frame = initialContainerFrame
self.wrapperNode.addSubnode(self.container)
containerView.removeFromSuperview()
sourceGlassView.isHidden = false
self.isAnimating = false
})
sourceGlassView.isHidden = true
}
} else {
let targetPosition = self.container.position
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height)
self.container.position = startPosition
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
transition.animateView(allowUserInteraction: true, {
self.container.position = targetPosition
}, completion: { _ in
self.isAnimating = false
})
}
} }
} }
func animateOut(completion: @escaping () -> Void = {}) { func animateOut(damping: CGFloat? = nil, initialVelocity: CGFloat? = nil, completion: @escaping () -> Void = {}) {
guard let controller = self.controller else { guard let controller = self.controller else {
return return
} }
@ -993,28 +1140,102 @@ public class AttachmentController: ViewController, MinimizableController {
return return
} }
self.animating = true self.isAnimating = true
if case .regular = layout.metrics.widthClass {
switch layout.metrics.widthClass {
case .regular:
self.layer.allowsGroupOpacity = true self.layer.allowsGroupOpacity = true
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
let _ = self?.container.dismiss(transition: .immediate, completion: completion) let _ = self?.container.dismiss(transition: .immediate, completion: completion)
self?.animating = false self?.isAnimating = false
self?.layer.removeAllAnimations() self?.layer.removeAllAnimations()
}) })
} else { case .compact:
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0), completion: { [weak self] _ in
let _ = self?.container.dismiss(transition: .immediate, completion: completion)
self?.animating = false
})
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) alphaTransition.updateAlpha(node: self.dim, alpha: 0.0)
self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) if case .glass = controller.style, let attachmentButton = controller.attachmentButton {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .customSpring(damping: damping ?? 124.0, initialVelocity: initialVelocity ?? 0.0))
if controller.fromMenu && self.hasButton, let (_, _, getTransition) = controller.getInputContainerNode(), let inputTransition = getTransition() { let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
self.panel.animateTransitionOut(inputTransition: inputTransition, dismissed: true, transition: positionTransition)
self.containerLayoutUpdated(layout, transition: positionTransition) let initialFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view)
let targetButtonFrame = attachmentButton.convert(attachmentButton.bounds, to: self.view)
let targetButtonScale = targetButtonFrame.width / initialFrame.width
if let sourceGlassView = findParentGlassBackgroundView(attachmentButton), let glassParams = sourceGlassView.params {
let containerView = UIView()
containerView.clipsToBounds = true
containerView.frame = initialFrame
if #available(iOS 26.0, *) {
containerView.cornerConfiguration = .corners(topLeftRadius: 38.0, topRightRadius: 38.0, bottomLeftRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0), bottomRightRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0))
} else {
containerView.layer.cornerRadius = layout.deviceMetrics.screenCornerRadius - 2.0
}
self.view.addSubview(containerView)
let localGlassView = GlassBackgroundView()
localGlassView.update(
size: initialFrame.size,
cornerRadius: 0.0,
isDark: glassParams.isDark,
tintColor: glassParams.tintColor,
transition: .immediate
)
localGlassView.frame = CGRect(origin: .zero, size: initialFrame.size)
containerView.addSubview(localGlassView)
let clipInnerFrame = self.container.container.view.convert(self.container.container.view.bounds, to: self.container.view)
self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size)
self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((initialFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size)
self.container.isUserInteractionEnabled = false
self.container.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
containerView.addSubnode(self.container)
let buttonIcon = GlassBackgroundView.ContentImageView()
let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
buttonIcon.image = PresentationResourcesChat.chatInputPanelAttachmentButtonImage(presentationData.theme)
buttonIcon.tintColor = presentationData.theme.chat.inputPanel.panelControlColor
if let image = buttonIcon.image {
buttonIcon.bounds = CGRect(origin: .zero, size: image.size)
buttonIcon.center = CGPoint(x: initialFrame.width * 0.5, y: initialFrame.height * 0.5)
buttonIcon.transform = CGAffineTransformMakeScale(1.0 / targetButtonScale, 1.0 / targetButtonScale)
}
localGlassView.contentView.addSubview(buttonIcon)
ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 10.0, toRadius: 0.0)
transition.updateBounds(layer: containerView.layer, bounds: CGRect(origin: CGPoint(x: 0.0, y: (initialFrame.height - initialFrame.width) * 0.5), size: CGSize(width: initialFrame.width, height: initialFrame.width)))
transition.animateView {
if #available(iOS 26.0, *) {
containerView.cornerConfiguration = .uniformCorners(radius: .fixed(containerView.bounds.width * 0.5))
} else {
containerView.layer.cornerRadius = containerView.bounds.width * 0.5
}
}
transition.updateTransformScale(layer: containerView.layer, scale: targetButtonScale)
transition.updatePosition(layer: containerView.layer, position: targetButtonFrame.center, completion: { [weak self] _ in
sourceGlassView.isHidden = false
let _ = self?.container.dismiss(transition: .immediate, completion: completion)
self?.isAnimating = false
})
sourceGlassView.isHidden = true
}
} else {
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0), completion: { [weak self] _ in
let _ = self?.container.dismiss(transition: .immediate, completion: completion)
self?.isAnimating = false
})
if controller.style != .glass {
self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition)
}
if controller.fromMenu && self.hasButton, let (_, _, getTransition) = controller.getInputContainerNode(), let inputTransition = getTransition() {
self.panel.animateTransitionOut(inputTransition: inputTransition, dismissed: true, transition: positionTransition)
self.containerLayoutUpdated(layout, transition: positionTransition)
}
} }
} }
} }
@ -1080,9 +1301,9 @@ public class AttachmentController: ViewController, MinimizableController {
let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
let position: CGPoint let position: CGPoint
let positionY = layout.size.height - size.height - insets.bottom - 40.0 let positionY = layout.size.height - size.height - insets.bottom - 54.0
if let sourceRect = controller.getSourceRect?() { if let sourceRect = controller.getSourceRect?() {
position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0)), y: min(positionY, sourceRect.minY - size.height)) position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0) - 2.0), y: min(positionY, sourceRect.minY - size.height))
} else { } else {
position = CGPoint(x: masterWidth - 174.0, y: positionY) position = CGPoint(x: masterWidth - 174.0, y: positionY)
} }
@ -1104,7 +1325,7 @@ public class AttachmentController: ViewController, MinimizableController {
self.wrapperNode.cornerRadius = 10.0 self.wrapperNode.cornerRadius = 10.0
} else if self.wrapperNode.view.mask == nil { } else if self.wrapperNode.view.mask == nil {
let maskView = UIImageView() let maskView = UIImageView()
maskView.image = generateMaskImage() maskView.image = generateMaskImage(glass: controller.style == .glass)
maskView.contentMode = .scaleToFill maskView.contentMode = .scaleToFill
self.wrapperNode.view.mask = maskView self.wrapperNode.view.mask = maskView
} }
@ -1115,7 +1336,7 @@ public class AttachmentController: ViewController, MinimizableController {
self.shadowNode.alpha = 1.0 self.shadowNode.alpha = 1.0
if self.shadowNode.image == nil { if self.shadowNode.image == nil {
self.shadowNode.image = generateShadowImage() self.shadowNode.image = generateShadowImage(glass: controller.style == .glass)
} }
} }
} else { } else {
@ -1148,22 +1369,36 @@ public class AttachmentController: ViewController, MinimizableController {
if !self.isPanelVisible { if !self.isPanelVisible {
hasPanel = false hasPanel = false
} }
var panelOffset: CGFloat = 0.0
if case .glass = controller.style {
if layout.metrics.isTablet {
panelOffset = 18.0
} else {
panelOffset = 8.0
}
}
let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting) let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting)
let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition) let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition)
if hasPanel || hasButton { if hasPanel || hasButton {
containerInsets.bottom = panelHeight containerInsets.bottom = panelHeight + panelOffset
} }
var panelTransition = transition var panelTransition = transition
if isEffecitvelyCollapsedUpdated { if isEffecitvelyCollapsedUpdated {
panelTransition = .animated(duration: 0.25, curve: .easeInOut) if case .glass = controller.style {
} else {
panelTransition = .animated(duration: 0.25, curve: .easeInOut)
}
} }
var panelY = containerRect.height - panelHeight var panelY = containerRect.height - panelHeight
if case .glass = controller.style {
panelY -= panelOffset
}
if !hasPanel && !hasButton { if !hasPanel && !hasButton {
panelY = containerRect.height panelY = containerRect.height
} }
panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: containerRect.width, height: panelHeight))) panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: containerRect.width, height: panelHeight)))
var shadowFrame = containerRect.insetBy(dx: -60.0, dy: -60.0) var shadowFrame = containerRect.insetBy(dx: -60.0, dy: -60.0)
@ -1182,7 +1417,7 @@ public class AttachmentController: ViewController, MinimizableController {
} }
let controllers = self.currentControllers let controllers = self.currentControllers
if !self.animating { if !self.isAnimating {
containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size)) containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size))
} }
@ -1190,9 +1425,9 @@ public class AttachmentController: ViewController, MinimizableController {
self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition) self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition)
if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady && !self.isDismissing { if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady && !self.isDismissing && !self.isAnimating {
self.wrapperNode.addSubnode(self.container) self.wrapperNode.addSubnode(self.container)
if fromMenu, let _ = controller.getInputContainerNode() { if fromMenu, let _ = controller.getInputContainerNode() {
self.addSubnode(self.panel) self.addSubnode(self.panel)
} else { } else {
@ -1220,6 +1455,7 @@ public class AttachmentController: ViewController, MinimizableController {
public init( public init(
context: AccountContext, context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
style: Style = .legacy,
chatLocation: ChatLocation?, chatLocation: ChatLocation?,
isScheduledMessages: Bool = false, isScheduledMessages: Bool = false,
buttons: [AttachmentButtonType], buttons: [AttachmentButtonType],
@ -1231,6 +1467,7 @@ public class AttachmentController: ViewController, MinimizableController {
{ {
self.context = context self.context = context
self.updatedPresentationData = updatedPresentationData self.updatedPresentationData = updatedPresentationData
self.style = style
self.chatLocation = chatLocation self.chatLocation = chatLocation
self.isScheduledMessages = isScheduledMessages self.isScheduledMessages = isScheduledMessages
self.buttons = buttons self.buttons = buttons
@ -1242,6 +1479,8 @@ public class AttachmentController: ViewController, MinimizableController {
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
self._hasGlassStyle = style == .glass
self.statusBar.statusBarStyle = .Ignore self.statusBar.statusBarStyle = .Ignore
self.blocksBackgroundWhenInOverlay = true self.blocksBackgroundWhenInOverlay = true
self.acceptsFocusWhenInOverlay = true self.acceptsFocusWhenInOverlay = true
@ -1446,3 +1685,12 @@ public class AttachmentController: ViewController, MinimizableController {
return snapshotView return snapshotView
} }
} }
private func findParentGlassBackgroundView(_ view: UIView) -> GlassBackgroundView? {
if let view = view as? GlassBackgroundView {
return view
} else if let superview = view.superview {
return findParentGlassBackgroundView(superview)
}
return nil
}

View file

@ -23,8 +23,11 @@ import LegacyMessageInputPanel
import LegacyMessageInputPanelInputView import LegacyMessageInputPanelInputView
import ReactionSelectionNode import ReactionSelectionNode
import TopMessageReactions import TopMessageReactions
import GlassBackgroundComponent
private let buttonSize = CGSize(width: 88.0, height: 49.0) private let legacyButtonSize = CGSize(width: 88.0, height: 49.0)
private let glassButtonSize = CGSize(width: 72.0, height: 62.0)
private let smallGlassButtonSize = CGSize(width: 70.0, height: 62.0)
private let smallButtonWidth: CGFloat = 69.0 private let smallButtonWidth: CGFloat = 69.0
private let iconSize = CGSize(width: 30.0, height: 30.0) private let iconSize = CGSize(width: 30.0, height: 30.0)
private let sideInset: CGFloat = 3.0 private let sideInset: CGFloat = 3.0
@ -124,7 +127,12 @@ private final class IconComponent: Component {
private final class AttachButtonComponent: CombinedComponent { private final class AttachButtonComponent: CombinedComponent {
enum Style {
case glass
case legacy
}
let context: AccountContext let context: AccountContext
let style: Style
let type: AttachmentButtonType let type: AttachmentButtonType
let isSelected: Bool let isSelected: Bool
let strings: PresentationStrings let strings: PresentationStrings
@ -134,6 +142,7 @@ private final class AttachButtonComponent: CombinedComponent {
init( init(
context: AccountContext, context: AccountContext,
style: Style,
type: AttachmentButtonType, type: AttachmentButtonType,
isSelected: Bool, isSelected: Bool,
strings: PresentationStrings, strings: PresentationStrings,
@ -142,6 +151,7 @@ private final class AttachButtonComponent: CombinedComponent {
longPressAction: @escaping () -> Void longPressAction: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.style = style
self.type = type self.type = type
self.isSelected = isSelected self.isSelected = isSelected
self.strings = strings self.strings = strings
@ -154,6 +164,9 @@ private final class AttachButtonComponent: CombinedComponent {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.style != rhs.style {
return false
}
if lhs.type != rhs.type { if lhs.type != rhs.type {
return false return false
} }
@ -227,11 +240,21 @@ private final class AttachButtonComponent: CombinedComponent {
imageName = "Chat/Attach Menu/Reply" imageName = "Chat/Attach Menu/Reply"
} }
let tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedIconColor : component.theme.rootController.tabBar.iconColor let tintColor: UIColor
switch component.style {
case .glass:
tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor
case .legacy:
tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedIconColor : component.theme.rootController.tabBar.iconColor
}
let iconSize = CGSize(width: 30.0, height: 30.0) let iconSize = CGSize(width: 30.0, height: 30.0)
let topInset: CGFloat = 4.0 + UIScreenPixel var topInset: CGFloat = 4.0 + UIScreenPixel
let spacing: CGFloat = 15.0 + UIScreenPixel var spacing: CGFloat = 15.0 + UIScreenPixel
if case .glass = component.style {
topInset += 5.0
spacing += UIScreenPixel
}
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - iconSize.width) / 2.0), y: topInset), size: iconSize) let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - iconSize.width) / 2.0), y: topInset), size: iconSize)
if let animationFile = animationFile { if let animationFile = animationFile {
@ -275,11 +298,19 @@ private final class AttachButtonComponent: CombinedComponent {
) )
} }
let titleFont: UIFont
switch component.style {
case .glass:
titleFont = Font.medium(10.0)
case .legacy:
titleFont = Font.regular(10.0)
}
let title = title.update( let title = title.update(
component: MultilineTextComponent( component: MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: name, string: name,
font: Font.regular(10.0), font: titleFont,
textColor: context.component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor, textColor: context.component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor,
paragraphAlignment: .center)), paragraphAlignment: .center)),
horizontalAlignment: .center, horizontalAlignment: .center,
@ -476,6 +507,7 @@ private final class BadgeNode: ASDisplayNode {
private final class MainButtonNode: HighlightTrackingButtonNode { private final class MainButtonNode: HighlightTrackingButtonNode {
private var state: AttachmentMainButtonState private var state: AttachmentMainButtonState
private var panelStyle: AttachmentPanel.Style?
private var size: CGSize? private var size: CGSize?
private let backgroundAnimationNode: ASImageNode private let backgroundAnimationNode: ASImageNode
@ -512,13 +544,13 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
self.addSubnode(self.statusNode) self.addSubnode(self.statusNode)
self.highligthedChanged = { [weak self] highlighted in self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self, strongSelf.state.isEnabled { if let self, self.state.isEnabled {
if highlighted { if highlighted {
strongSelf.layer.removeAnimation(forKey: "opacity") self.layer.removeAnimation(forKey: "opacity")
strongSelf.alpha = 0.65 self.alpha = 0.65
} else { } else {
strongSelf.alpha = 1.0 self.alpha = 1.0
strongSelf.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2) self.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2)
} }
} }
} }
@ -527,7 +559,6 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
self.cornerRadius = 12.0
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
self.layer.cornerCurve = .continuous self.layer.cornerCurve = .continuous
} }
@ -624,8 +655,8 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
self.updateShimmerParameters() self.updateShimmerParameters()
if let size = self.size { if let size = self.size, let style = self.panelStyle {
self.updateLayout(size: size, state: state, transition: .immediate) self.updateLayout(size: size, style: style, state: state, transition: .immediate)
} }
} }
} else if self.shimmerView != nil { } else if self.shimmerView != nil {
@ -695,11 +726,19 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
} }
} }
func updateLayout(size: CGSize, state: AttachmentMainButtonState, animateBackground: Bool = false, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, style: AttachmentPanel.Style, state: AttachmentMainButtonState, animateBackground: Bool = false, transition: ContainedViewLayoutTransition) {
let previousState = self.state let previousState = self.state
self.state = state self.state = state
self.panelStyle = style
self.size = size self.size = size
switch style {
case .glass:
self.cornerRadius = size.height * 0.5
case .legacy:
self.cornerRadius = 12.0
}
self.isUserInteractionEnabled = state.isVisible self.isUserInteractionEnabled = state.isVisible
self.setupShimmering() self.setupShimmering()
@ -842,6 +881,13 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
} }
final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
enum Style {
case glass
case legacy
}
private let panelStyle: Style
private weak var controller: AttachmentController? private weak var controller: AttachmentController?
private let context: AccountContext private let context: AccountContext
private let isScheduledMessages: Bool private let isScheduledMessages: Bool
@ -858,9 +904,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
private let containerNode: ASDisplayNode private let containerNode: ASDisplayNode
private var backgroundView: GlassBackgroundView?
private let backgroundNode: NavigationBackgroundNode private let backgroundNode: NavigationBackgroundNode
private let scrollNode: ASScrollNode private let scrollNode: ASScrollNode
private let separatorNode: ASDisplayNode private let separatorNode: ASDisplayNode
private let selectionNode: ASImageNode
private var buttonViews: [AnyHashable: ComponentHostView<Empty>] = [:] private var buttonViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var textInputPanelNode: AttachmentTextInputPanelNode? private var textInputPanelNode: AttachmentTextInputPanelNode?
@ -904,10 +953,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
var onMainButtonPressed: () -> Void = { } var onMainButtonPressed: () -> Void = { }
var onSecondaryButtonPressed: () -> Void = { } var onSecondaryButtonPressed: () -> Void = { }
init(controller: AttachmentController, context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { init(controller: AttachmentController, style: Style, context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
self.controller = controller self.controller = controller
self.context = context self.context = context
self.panelStyle = style
self.updatedPresentationData = updatedPresentationData self.updatedPresentationData = updatedPresentationData
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
self.isScheduledMessages = isScheduledMessages self.isScheduledMessages = isScheduledMessages
@ -917,9 +967,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.containerNode = ASDisplayNode() self.containerNode = ASDisplayNode()
self.containerNode.clipsToBounds = true self.containerNode.clipsToBounds = false
self.scrollNode = ASScrollNode() self.scrollNode = ASScrollNode()
self.scrollNode.clipsToBounds = true
self.selectionNode = ASImageNode()
self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor) self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor)
self.separatorNode = ASDisplayNode() self.separatorNode = ASDisplayNode()
@ -931,9 +984,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
super.init() super.init()
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.separatorNode) switch style {
self.containerNode.addSubnode(self.scrollNode) case .glass:
self.scrollNode.cornerRadius = glassButtonSize.height * 0.5
self.scrollNode.addSubnode(self.selectionNode)
case .legacy:
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.separatorNode)
self.containerNode.addSubnode(self.scrollNode)
}
self.addSubnode(self.secondaryButtonNode) self.addSubnode(self.secondaryButtonNode)
self.addSubnode(self.mainButtonNode) self.addSubnode(self.mainButtonNode)
@ -1294,6 +1354,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
strongSelf.backgroundNode.updateColor(color: strongSelf.customBottomPanelBackgroundColor ?? presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) strongSelf.backgroundNode.updateColor(color: strongSelf.customBottomPanelBackgroundColor ?? presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate)
strongSelf.separatorNode.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor strongSelf.separatorNode.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor
strongSelf.selectionNode.backgroundColor = presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05)
strongSelf.updateChatPresentationInterfaceState({ $0.updatedTheme(presentationData.theme) }) strongSelf.updateChatPresentationInterfaceState({ $0.updatedTheme(presentationData.theme) })
@ -1396,29 +1457,61 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
} }
var buttonSize: CGSize {
switch self.panelStyle {
case .glass:
return glassButtonSize
case .legacy:
return legacyButtonSize
}
}
func updateViews(transition: ComponentTransition) { func updateViews(transition: ComponentTransition) {
guard let layout = self.validLayout else { guard let layout = self.validLayout else {
return return
} }
let width: CGFloat
switch self.panelStyle {
case .glass:
width = layout.size.width - 44.0
case .legacy:
width = layout.size.width
}
let visibleRect = self.scrollNode.bounds.insetBy(dx: -180.0, dy: 0.0) let visibleRect = self.scrollNode.bounds.insetBy(dx: -180.0, dy: 0.0)
var distanceBetweenNodes = layout.size.width / CGFloat(self.buttons.count) var distanceBetweenNodes = (width - sideInset * 2.0) / CGFloat(self.buttons.count)
let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1) let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1)
var leftNodeOriginX = (layout.size.width - internalWidth) / 2.0 var buttonWidth = self.buttonSize.width
var leftNodeOriginX: CGFloat
var buttonWidth = buttonSize.width switch self.panelStyle {
if self.buttons.count > 6 && layout.size.width < layout.size.height { case .glass:
buttonWidth = smallButtonWidth leftNodeOriginX = layout.safeInsets.left + sideInset + buttonWidth / 2.0
distanceBetweenNodes = buttonWidth case .legacy:
leftNodeOriginX = (width - internalWidth) / 2.0
}
if self.buttons.count > 5 && layout.size.width < layout.size.height {
switch self.panelStyle {
case .glass:
buttonWidth = smallGlassButtonSize.width
distanceBetweenNodes = 60.0
case .legacy:
buttonWidth = smallButtonWidth
distanceBetweenNodes = 60.0
}
leftNodeOriginX = layout.safeInsets.left + sideInset + buttonWidth / 2.0 leftNodeOriginX = layout.safeInsets.left + sideInset + buttonWidth / 2.0
} }
var validIds = Set<AnyHashable>() var validIds = Set<AnyHashable>()
var selectionFrame = CGRect()
var mostRightX = 0.0
for i in 0 ..< self.buttons.count { for i in 0 ..< self.buttons.count {
let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0) let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0)
let buttonFrame = CGRect(origin: CGPoint(x: originX, y: 0.0), size: CGSize(width: buttonWidth, height: buttonSize.height)) let buttonFrame = CGRect(origin: CGPoint(x: originX, y: 0.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height))
mostRightX = buttonFrame.maxX
if !visibleRect.intersects(buttonFrame) { if !visibleRect.intersects(buttonFrame) {
continue continue
} }
@ -1470,6 +1563,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
transition: buttonTransition, transition: buttonTransition,
component: AnyComponent(AttachButtonComponent( component: AnyComponent(AttachButtonComponent(
context: self.context, context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy,
type: type, type: type,
isSelected: i == self.selectedIndex, isSelected: i == self.selectedIndex,
strings: self.presentationData.strings, strings: self.presentationData.strings,
@ -1480,7 +1574,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
strongSelf.selectedIndex = i strongSelf.selectedIndex = i
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
if strongSelf.buttons.count > 6, let button = strongSelf.buttonViews[i] { if strongSelf.buttons.count > 5, let button = strongSelf.buttonViews[i] {
strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true) strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true)
} }
} }
@ -1492,8 +1586,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}) })
), ),
environment: {}, environment: {},
containerSize: CGSize(width: buttonWidth, height: buttonSize.height) containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height)
) )
if i == self.selectedIndex {
selectionFrame = buttonFrame
}
buttonTransition.setFrame(view: buttonView, frame: buttonFrame) buttonTransition.setFrame(view: buttonView, frame: buttonFrame)
var accessibilityTitle = "" var accessibilityTitle = ""
switch type { switch type {
@ -1532,34 +1630,24 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
for id in removeIds { for id in removeIds {
self.buttonViews.removeValue(forKey: id) self.buttonViews.removeValue(forKey: id)
} }
}
private func updateScrollLayoutIfNeeded(force: Bool, transition: ContainedViewLayoutTransition) -> Bool {
guard let layout = self.validLayout else {
return false
}
if self.scrollLayout?.width == layout.size.width && !force {
return false
}
var contentSize = CGSize(width: layout.size.width, height: buttonSize.height) selectionFrame = selectionFrame.insetBy(dx: 1.0, dy: 4.0)
var buttonWidth = buttonSize.width self.selectionNode.cornerRadius = selectionFrame.height * 0.5
if self.buttons.count > 6 && layout.size.width < layout.size.height { transition.setFrame(view: self.selectionNode.view, frame: selectionFrame)
buttonWidth = smallButtonWidth
contentSize.width = layout.safeInsets.left + layout.safeInsets.right + sideInset * 2.0 + CGFloat(self.buttons.count) * buttonWidth mostRightX += layout.safeInsets.right + sideInset
let contentSize = CGSize(width: mostRightX, height: self.buttonSize.height)
if contentSize != self.scrollNode.view.contentSize {
self.scrollNode.view.contentSize = contentSize
self.scrollNode.view.isScrollEnabled = abs(contentSize.width - self.scrollNode.view.bounds.width) > 1.0
} }
self.scrollLayout = (layout.size.width, contentSize)
transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSelecting || self._isButtonVisible ? -buttonSize.height : 0.0), size: CGSize(width: layout.size.width, height: buttonSize.height)))
self.scrollNode.view.contentSize = contentSize
return true
} }
private func loadTextNodeIfNeeded() { private func loadTextNodeIfNeeded() {
if let _ = self.textInputPanelNode { if let _ = self.textInputPanelNode {
} else { } else {
let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, isAttachment: true, isScheduledMessages: self.isScheduledMessages, presentController: { [weak self] c in let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, glass: self.panelStyle == .glass, isAttachment: true, isScheduledMessages: self.isScheduledMessages, presentController: { [weak self] c in
if let strongSelf = self { if let strongSelf = self {
strongSelf.present(c) strongSelf.present(c)
} }
@ -1788,7 +1876,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
} else { } else {
self.textInputPanelNode?.ensureUnfocused() self.textInputPanelNode?.ensureUnfocused()
} }
let panelSideInset: CGFloat = isSelecting ? 8.0 : 22.0
var textPanelHeight: CGFloat = 0.0 var textPanelHeight: CGFloat = 0.0
var textPanelWidth: CGFloat = 0.0
if let textInputPanelNode = self.textInputPanelNode { if let textInputPanelNode = self.textInputPanelNode {
textInputPanelNode.isUserInteractionEnabled = isSelecting textInputPanelNode.isUserInteractionEnabled = isSelecting
@ -1805,16 +1896,52 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
if panelFrame.height > 0.0 { if panelFrame.height > 0.0 {
textPanelHeight = panelFrame.height textPanelHeight = panelFrame.height
} else { } else {
textPanelHeight = 45.0 textPanelHeight = self.panelStyle == .glass ? 40.0 : 45.0
} }
textPanelWidth = layout.size.width - panelSideInset - 88.0 - 6.0
} }
let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: buttonSize.height + insets.bottom)) let glassPanelHeight: CGFloat = 62.0
var containerTransition: ContainedViewLayoutTransition let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom))
let containerFrame: CGRect if case .glass = self.panelStyle {
let backgroundView: GlassBackgroundView
if let current = self.backgroundView {
backgroundView = current
} else {
backgroundView = GlassBackgroundView()
self.containerNode.view.addSubview(backgroundView)
self.containerNode.view.addSubview(self.scrollNode.view)
self.backgroundView = backgroundView
}
let panelSize = CGSize(width: isSelecting ? textPanelWidth : layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0, height: isSelecting ? textPanelHeight - 11.0 : glassPanelHeight)
let backgroundViewColor: UIColor
if self.presentationData.theme.overallDarkAppearance {
backgroundViewColor = self.presentationData.theme.list.modalBlocksBackgroundColor.withAlphaComponent(0.55)
} else {
backgroundViewColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.75)
}
let backgroundOriginX: CGFloat = isSelecting ? panelSideInset + 8.0 : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0)
backgroundView.update(size: panelSize, cornerRadius: isSelecting ? 20.0 : glassPanelHeight * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: backgroundViewColor), transition: ComponentTransition(transition))
transition.updatePosition(layer: backgroundView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5))
transition.updateBounds(layer: backgroundView.layer, bounds: CGRect(origin: .zero, size: panelSize))
}
let sideInset: CGFloat = 16.0 var containerTransition: ContainedViewLayoutTransition
let buttonHeight: CGFloat = 50.0 var containerFrame: CGRect
let buttonSideInset: CGFloat
let buttonSpacing: CGFloat = 16.0
let buttonHeight: CGFloat
switch self.panelStyle {
case .glass:
buttonHeight = 52.0
buttonSideInset = 22.0
case .legacy:
buttonHeight = 50.0
buttonSideInset = 16.0
}
if isAnyButtonVisible { if isAnyButtonVisible {
var height: CGFloat var height: CGFloat
@ -1833,10 +1960,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
if isTwoVerticalButtons && self.secondaryButtonState.smallSpacing { if isTwoVerticalButtons && self.secondaryButtonState.smallSpacing {
} else if !isNarrowButton { } else if !isNarrowButton {
height += 9.0 if case .glass = self.panelStyle {
} else {
height += 9.0
}
} }
if isTwoVerticalButtons { if isTwoVerticalButtons {
height += buttonHeight + sideInset height += buttonHeight + buttonSpacing
} }
containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: height)) containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: height))
} else if isSelecting { } else if isSelecting {
@ -1846,26 +1976,74 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
} }
let containerBounds = CGRect(origin: CGPoint(), size: containerFrame.size) let containerBounds = CGRect(origin: CGPoint(), size: containerFrame.size)
if isSelectingUpdated || isButtonVisibleUpdated { if isSelectingUpdated || isButtonVisibleUpdated {
containerTransition = .animated(duration: 0.25, curve: .easeInOut) if case .glass = self.panelStyle {
containerTransition = transition
} else {
containerTransition = .animated(duration: 0.25, curve: .easeInOut)
}
} else { } else {
containerTransition = transition containerTransition = transition
} }
containerTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0) let alphaTransition = ContainedViewLayoutTransition.animated(duration: isSelecting ? 0.1 : 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0)
containerTransition.updateTransformScale(node: self.scrollNode, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0) containerTransition.updateTransformScale(node: self.scrollNode, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0)
if let backgroundView = self.backgroundView {
containerTransition.updateTransformScale(layer: backgroundView.layer, scale: isAnyButtonVisible ? 0.85 : 1.0)
}
if isSelectingUpdated { if isSelectingUpdated {
if isSelecting { if isSelecting {
self.loadTextNodeIfNeeded() self.loadTextNodeIfNeeded()
if let textInputPanelNode = self.textInputPanelNode { if let textInputPanelNode = self.textInputPanelNode {
textInputPanelNode.alpha = 1.0 textInputPanelNode.isUserInteractionEnabled = true
textInputPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) if case .glass = self.panelStyle {
textInputPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: CGPoint(), duration: 0.25, additive: true) var textInputPanelHeight = textInputPanelNode.frame.height
if textInputPanelHeight < 1.0 {
textInputPanelHeight = 51.0
}
let heightDelta = glassPanelHeight - textInputPanelHeight
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 1.0)
ComponentTransition.easeInOut(duration: 0.25).animateBlur(layer: self.scrollNode.layer, fromRadius: 0.0, toRadius: 10.0)
transition.animatePosition(node: textInputPanelNode.opaqueActionButtons, from: CGPoint(x: textInputPanelNode.opaqueActionButtons.position.x + 62.0, y: textInputPanelNode.opaqueActionButtons.position.y + heightDelta))
transition.animateTransformScale(node: textInputPanelNode.opaqueActionButtons, from: 0.01)
transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: CGPoint(x: 6.0, y: heightDelta))
transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: CGPoint(x: 64.0, y: heightDelta))
} else {
textInputPanelNode.alpha = 1.0
textInputPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
textInputPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: CGPoint(), duration: 0.25, additive: true)
}
} }
} else { } else {
if let textInputPanelNode = self.textInputPanelNode { if let textInputPanelNode = self.textInputPanelNode {
textInputPanelNode.alpha = 0.0 textInputPanelNode.isUserInteractionEnabled = false
textInputPanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) if case .glass = self.panelStyle {
textInputPanelNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 44.0), duration: 0.25, additive: true) var textInputPanelHeight = textInputPanelNode.frame.height
if textInputPanelHeight < 1.0 {
textInputPanelHeight = 51.0
}
let heightDelta = glassPanelHeight - textInputPanelHeight
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 0.0)
ComponentTransition.easeInOut(duration: 0.25).animateBlur(layer: self.scrollNode.layer, fromRadius: 10.0, toRadius: 0.0)
transition.animatePosition(node: textInputPanelNode.opaqueActionButtons, to: CGPoint(x: textInputPanelNode.opaqueActionButtons.position.x + 62.0, y: textInputPanelNode.opaqueActionButtons.position.y + heightDelta))
transition.animateTransformScale(layer: textInputPanelNode.opaqueActionButtons.layer, from: CGPoint(x: 1.0, y: 1.0), to: CGPoint(x: 0.01, y: 0.01))
transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: .zero, to: CGPoint(x: 6.0, y: heightDelta))
transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: .zero, to: CGPoint(x: 64.0, y: heightDelta))
} else {
textInputPanelNode.alpha = 0.0
textInputPanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
textInputPanelNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 44.0), duration: 0.25, additive: true)
}
} }
} }
} }
@ -1879,7 +2057,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
self.backgroundNode.update(size: containerBounds.size, transition: transition) self.backgroundNode.update(size: containerBounds.size, transition: transition)
containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel))) containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel)))
let _ = self.updateScrollLayoutIfNeeded(force: isSelectingUpdated || isButtonVisibleUpdated, transition: containerTransition) if case .glass = self.panelStyle {
transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - 22.0 : panelSideInset, y: self.isSelecting ? -11.0 : 0.0), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height)))
}
self.updateViews(transition: .immediate) self.updateViews(transition: .immediate)
@ -1904,14 +2084,21 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}) })
} }
var buttonSize = CGSize(width: layout.size.width - (sideInset + layout.safeInsets.left) * 2.0, height: buttonHeight) var buttonSize = CGSize(width: layout.size.width - (buttonSideInset + layout.safeInsets.left) * 2.0, height: buttonHeight)
if isTwoHorizontalButtons { if isTwoHorizontalButtons {
buttonSize = CGSize(width: (buttonSize.width - sideInset) / 2.0, height: buttonSize.height) buttonSize = CGSize(width: (buttonSize.width - buttonSideInset) / 2.0, height: buttonSize.height)
} }
let buttonTopInset: CGFloat = isNarrowButton ? 2.0 : 8.0 let buttonTopInset: CGFloat
switch self.panelStyle {
case .glass:
buttonTopInset = 5.0
case .legacy:
buttonTopInset = isNarrowButton ? 2.0 : 8.0
}
if !self.animatingTransition { if !self.animatingTransition {
let buttonOriginX = layout.safeInsets.left + sideInset let buttonOriginX = layout.safeInsets.left + buttonSideInset
let buttonOriginY = isAnyButtonVisible || self.fromMenu ? buttonTopInset : containerFrame.height let buttonOriginY = isAnyButtonVisible || self.fromMenu ? buttonTopInset : containerFrame.height
var mainButtonFrame: CGRect? var mainButtonFrame: CGRect?
var secondaryButtonFrame: CGRect? var secondaryButtonFrame: CGRect?
@ -1919,17 +2106,17 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
switch position { switch position {
case .top: case .top:
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize) secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + sideInset + buttonSize.height), size: buttonSize) mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + buttonSpacing + buttonSize.height), size: buttonSize)
case .bottom: case .bottom:
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize) mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
let buttonSpacing = self.secondaryButtonState.smallSpacing ? 8.0 : sideInset let effectiveButtonSpacing = self.secondaryButtonState.smallSpacing ? 8.0 : buttonSpacing
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + buttonSpacing + buttonSize.height), size: buttonSize) secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + effectiveButtonSpacing + buttonSize.height), size: buttonSize)
case .left: case .left:
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize) secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + sideInset, y: buttonOriginY), size: buttonSize) mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + buttonSideInset, y: buttonOriginY), size: buttonSize)
case .right: case .right:
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize) mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + sideInset, y: buttonOriginY), size: buttonSize) secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + buttonSideInset, y: buttonOriginY), size: buttonSize)
} }
} else { } else {
if self.mainButtonState.isVisible { if self.mainButtonState.isVisible {
@ -1942,7 +2129,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
if let mainButtonFrame { if let mainButtonFrame {
if !self.dismissed { if !self.dismissed {
self.mainButtonNode.updateLayout(size: buttonSize, state: self.mainButtonState, animateBackground: self.mainButtonState.background.colorValue == self.backgroundNode.color && transition.isAnimated, transition: transition) self.mainButtonNode.updateLayout(size: buttonSize, style: self.panelStyle, state: self.mainButtonState, animateBackground: self.mainButtonState.background.colorValue == self.backgroundNode.color && transition.isAnimated, transition: transition)
} }
if self.mainButtonNode.frame.width.isZero { if self.mainButtonNode.frame.width.isZero {
self.mainButtonNode.frame = mainButtonFrame self.mainButtonNode.frame = mainButtonFrame
@ -1955,7 +2142,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
} }
if let secondaryButtonFrame { if let secondaryButtonFrame {
if !self.dismissed { if !self.dismissed {
self.secondaryButtonNode.updateLayout(size: buttonSize, state: self.secondaryButtonState, animateBackground: self.secondaryButtonState.background.colorValue == self.backgroundNode.color && transition.isAnimated, transition: transition) self.secondaryButtonNode.updateLayout(size: buttonSize, style: self.panelStyle, state: self.secondaryButtonState, animateBackground: self.secondaryButtonState.background.colorValue == self.backgroundNode.color && transition.isAnimated, transition: transition)
} }
if self.secondaryButtonNode.frame.width.isZero { if self.secondaryButtonNode.frame.width.isZero {
self.secondaryButtonNode.frame = secondaryButtonFrame self.secondaryButtonNode.frame = secondaryButtonFrame

View file

@ -149,7 +149,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
let masterDatacenterId = strongSelf.account.masterDatacenterId let masterDatacenterId = strongSelf.account.masterDatacenterId
let isTestingEnvironment = strongSelf.account.testingEnvironment let isTestingEnvironment = strongSelf.account.testingEnvironment
let countryCode = AuthorizationSequenceController.defaultCountryCode() let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: isTestingEnvironment, masterDatacenterId: masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: isTestingEnvironment, masterDatacenterId: masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone()
} }
@ -338,7 +338,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let countryCode = AuthorizationSequenceController.defaultCountryCode() let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone()
}) })
@ -705,7 +705,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let countryCode = AuthorizationSequenceController.defaultCountryCode() let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone()
}) })
@ -772,7 +772,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let self else { guard let self else {
return return
} }
let countryCode = AuthorizationSequenceController.defaultCountryCode() let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: self.account.testingEnvironment, masterDatacenterId: self.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: self.account.testingEnvironment, masterDatacenterId: self.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone()
}) })
return controller return controller
@ -889,7 +889,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let countryCode = AuthorizationSequenceController.defaultCountryCode() let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone()
}) })
@ -1034,7 +1034,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let countryCode = AuthorizationSequenceController.defaultCountryCode() let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone()
}) })
@ -1094,7 +1094,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let countryCode = AuthorizationSequenceController.defaultCountryCode() let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone()
}, displayCancel: displayCancel) }, displayCancel: displayCancel)
@ -1213,7 +1213,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
if self.otherAccountPhoneNumbers.1.isEmpty { if self.otherAccountPhoneNumbers.1.isEmpty {
controllers.append(self.splashController()) controllers.append(self.splashController())
} else { } else {
controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceController.defaultCountryCode(), number: "", splashController: nil)) controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceCountrySelectionController.defaultCountryCode(), number: "", splashController: nil))
} }
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
} }
@ -1241,7 +1241,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
if !self.otherAccountPhoneNumbers.1.isEmpty { if !self.otherAccountPhoneNumbers.1.isEmpty {
controllers.append(self.splashController()) controllers.append(self.splashController())
} }
controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceController.defaultCountryCode(), number: "", splashController: nil)) controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceCountrySelectionController.defaultCountryCode(), number: "", splashController: nil))
var isGoingBack = false var isGoingBack = false
if case let .emailSetupRequired(appleSignInAllowed) = type { if case let .emailSetupRequired(appleSignInAllowed) = type {

View file

@ -65,6 +65,7 @@ private func callListNeighbors(item: ListViewItem, topItem: ListViewItem?, botto
class CallListCallItem: ListViewItem { class CallListCallItem: ListViewItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let context: AccountContext let context: AccountContext
let style: ItemListStyle let style: ItemListStyle
@ -78,8 +79,9 @@ class CallListCallItem: ListViewItem {
let headerAccessoryItem: ListViewAccessoryItem? let headerAccessoryItem: ListViewAccessoryItem?
let header: ListViewItemHeader? let header: ListViewItemHeader?
init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, style: ItemListStyle, topMessage: EngineMessage, messages: [EngineMessage], editing: Bool, revealed: Bool, displayHeader: Bool, interaction: CallListNodeInteraction) { init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, style: ItemListStyle, topMessage: EngineMessage, messages: [EngineMessage], editing: Bool, revealed: Bool, displayHeader: Bool, interaction: CallListNodeInteraction) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.context = context self.context = context
self.style = style self.style = style
@ -345,6 +347,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
let insets: UIEdgeInsets let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: UIColor let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor let itemSeparatorColor: UIColor
@ -550,7 +554,13 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - dateRightInset - dateLayout.size.width - (item.editing ? -30.0 : 10.0)), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - dateRightInset - dateLayout.size.width - (item.editing ? -30.0 : 10.0)), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let titleSpacing: CGFloat = -1.0 let titleSpacing: CGFloat = -1.0
let verticalInset: CGFloat = 6.0 let verticalInset: CGFloat
switch item.systemStyle {
case .glass:
verticalInset = 10.0
case .legacy:
verticalInset = 6.0
}
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: titleLayout.size.height + titleSpacing + statusLayout.size.height + verticalInset * 2.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)) let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: titleLayout.size.height + titleSpacing + statusLayout.size.height + verticalInset * 2.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
@ -672,12 +682,12 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset, height: separatorHeight))) transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)))
} }

View file

@ -120,20 +120,20 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
return entries.map { entry -> ListViewInsertItem in return entries.map { entry -> ListViewInsertItem in
switch entry.entry { switch entry.entry {
case let .displayTab(_, text, value): case let .displayTab(_, text, value):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
nodeInteraction.updateShowCallsTab(value) nodeInteraction.updateShowCallsTab(value)
}), directionHint: entry.directionHint) }), directionHint: entry.directionHint)
case let .displayTabInfo(_, text): case let .displayTabInfo(_, text):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
case .openNewCall: case .openNewCall:
let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: !showSettings, editing: false, action: { let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, systemStyle: .glass, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: !showSettings, editing: false, action: {
nodeInteraction.openNewCall() nodeInteraction.openNewCall()
}) })
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .groupCall(peer, _, isActive): case let .groupCall(peer, _, isActive):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, systemStyle: .glass, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .messageEntry(topMessage, messages, _, _, dateTimeFormat, editing, hasActiveRevealControls, displayHeader, _): case let .messageEntry(topMessage, messages, _, _, dateTimeFormat, editing, hasActiveRevealControls, displayHeader, _):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListCallItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, context: context, style: showSettings ? .blocks : .plain, topMessage: topMessage, messages: messages, editing: editing, revealed: hasActiveRevealControls, displayHeader: displayHeader, interaction: nodeInteraction), directionHint: entry.directionHint) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListCallItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, context: context, style: showSettings ? .blocks : .plain, topMessage: topMessage, messages: messages, editing: editing, revealed: hasActiveRevealControls, displayHeader: displayHeader, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .holeEntry(_, theme): case let .holeEntry(_, theme):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListHoleItem(theme: theme), directionHint: entry.directionHint) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListHoleItem(theme: theme), directionHint: entry.directionHint)
} }
@ -144,20 +144,20 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
return entries.map { entry -> ListViewUpdateItem in return entries.map { entry -> ListViewUpdateItem in
switch entry.entry { switch entry.entry {
case let .displayTab(_, text, value): case let .displayTab(_, text, value):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in
nodeInteraction.updateShowCallsTab(value) nodeInteraction.updateShowCallsTab(value)
}), directionHint: entry.directionHint) }), directionHint: entry.directionHint)
case let .displayTabInfo(_, text): case let .displayTabInfo(_, text):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
case .openNewCall: case .openNewCall:
let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: !showSettings, editing: false, action: { let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, systemStyle: .glass, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: !showSettings, editing: false, action: {
nodeInteraction.openNewCall() nodeInteraction.openNewCall()
}) })
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .groupCall(peer, _, isActive): case let .groupCall(peer, _, isActive):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, systemStyle: .glass, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .messageEntry(topMessage, messages, _, _, dateTimeFormat, editing, hasActiveRevealControls, displayHeader, _): case let .messageEntry(topMessage, messages, _, _, dateTimeFormat, editing, hasActiveRevealControls, displayHeader, _):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListCallItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, context: context, style: showSettings ? .blocks : .plain, topMessage: topMessage, messages: messages, editing: editing, revealed: hasActiveRevealControls, displayHeader: displayHeader, interaction: nodeInteraction), directionHint: entry.directionHint) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListCallItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, context: context, style: showSettings ? .blocks : .plain, topMessage: topMessage, messages: messages, editing: editing, revealed: hasActiveRevealControls, displayHeader: displayHeader, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .holeEntry(_, theme): case let .holeEntry(_, theme):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListHoleItem(theme: theme), directionHint: entry.directionHint) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListHoleItem(theme: theme), directionHint: entry.directionHint)
} }

View file

@ -57,6 +57,7 @@ private func callListNeighbors(item: ListViewItem, topItem: ListViewItem?, botto
class CallListGroupCallItem: ListViewItem { class CallListGroupCallItem: ListViewItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let context: AccountContext let context: AccountContext
let style: ItemListStyle let style: ItemListStyle
let peer: EnginePeer let peer: EnginePeer
@ -68,8 +69,9 @@ class CallListGroupCallItem: ListViewItem {
let headerAccessoryItem: ListViewAccessoryItem? let headerAccessoryItem: ListViewAccessoryItem?
let header: ListViewItemHeader? let header: ListViewItemHeader?
init(presentationData: ItemListPresentationData, context: AccountContext, style: ItemListStyle, peer: EnginePeer, isActive: Bool, editing: Bool, interaction: CallListNodeInteraction) { init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, context: AccountContext, style: ItemListStyle, peer: EnginePeer, isActive: Bool, editing: Bool, interaction: CallListNodeInteraction) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.context = context self.context = context
self.style = style self.style = style
self.peer = peer self.peer = peer

View file

@ -23,6 +23,7 @@ enum ChatListFilterCategoryIcon {
final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem { final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String let title: String
let icon: ChatListFilterCategoryIcon let icon: ChatListFilterCategoryIcon
let isRevealed: Bool let isRevealed: Bool
@ -33,6 +34,7 @@ final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem {
init( init(
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
title: String, title: String,
icon: ChatListFilterCategoryIcon, icon: ChatListFilterCategoryIcon,
isRevealed: Bool, isRevealed: Bool,
@ -41,6 +43,7 @@ final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem {
remove: @escaping () -> Void remove: @escaping () -> Void
) { ) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title self.title = title
self.icon = icon self.icon = icon
self.isRevealed = isRevealed self.isRevealed = isRevealed
@ -185,11 +188,14 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: titleColor) titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: titleColor)
let leftInset: CGFloat let leftInset: CGFloat
let verticalInset: CGFloat var verticalInset: CGFloat = 14.0
let verticalOffset: CGFloat let verticalOffset: CGFloat
let avatarSize: CGFloat let avatarSize: CGFloat
verticalInset = 14.0 if case .glass = item.systemStyle {
verticalInset += 4.0
}
verticalOffset = 0.0 verticalOffset = 0.0
avatarSize = 40.0 avatarSize = 40.0
leftInset = 65.0 + params.leftInset leftInset = 65.0 + params.leftInset
@ -208,6 +214,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight))
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size let layoutSize = layout.size
@ -340,12 +347,12 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size)) transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size))

View file

@ -389,6 +389,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
return ItemListFilterTitleInputItem( return ItemListFilterTitleInputItem(
context: arguments.context, context: arguments.context,
presentationData: presentationData, presentationData: presentationData,
systemStyle: .glass,
text: value, text: value,
enableAnimations: enableAnimations, enableAnimations: enableAnimations,
placeholder: placeholder, placeholder: placeholder,
@ -407,12 +408,13 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case .includePeerInfo(let text), .excludePeerInfo(let text): case .includePeerInfo(let text), .excludePeerInfo(let text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .addIncludePeer(title): case let .addIncludePeer(title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: {
arguments.openAddIncludePeer() arguments.openAddIncludePeer()
}) })
case let .includeCategory(_, category, title, isRevealed): case let .includeCategory(_, category, title, isRevealed):
return ChatListFilterPresetCategoryItem( return ChatListFilterPresetCategoryItem(
presentationData: presentationData, presentationData: presentationData,
systemStyle: .glass,
title: title, title: title,
icon: ChatListFilterCategoryIcon(category: category), icon: ChatListFilterCategoryIcon(category: category),
isRevealed: isRevealed, isRevealed: isRevealed,
@ -429,12 +431,13 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
} }
) )
case let .addExcludePeer(title): case let .addExcludePeer(title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: {
arguments.openAddExcludePeer() arguments.openAddExcludePeer()
}) })
case let .excludeCategory(_, category, title, isRevealed): case let .excludeCategory(_, category, title, isRevealed):
return ChatListFilterPresetCategoryItem( return ChatListFilterPresetCategoryItem(
presentationData: presentationData, presentationData: presentationData,
systemStyle: .glass,
title: title, title: title,
icon: ChatListFilterCategoryIcon(category: category), icon: ChatListFilterCategoryIcon(category: category),
isRevealed: isRevealed, isRevealed: isRevealed,
@ -451,7 +454,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
} }
) )
case let .includePeer(_, peer, isRevealed): case let .includePeer(_, peer, isRevealed):
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
arguments.deleteIncludePeer(peer.peerId) arguments.deleteIncludePeer(peer.peerId)
})]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in })]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) }) arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) })
@ -465,7 +468,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
arguments.peerContextAction(peer, sourceNode, gesture, nil) arguments.peerContextAction(peer, sourceNode, gesture, nil)
}) })
case let .excludePeer(_, peer, isRevealed): case let .excludePeer(_, peer, isRevealed):
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
arguments.deleteExcludePeer(peer.peerId) arguments.deleteExcludePeer(peer.peerId)
})]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in })]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) }) arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) })
@ -479,11 +482,11 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
arguments.peerContextAction(peer, sourceNode, gesture, nil) arguments.peerContextAction(peer, sourceNode, gesture, nil)
}) })
case let .includeExpand(text): case let .includeExpand(text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: {
arguments.expandSection(.include) arguments.expandSection(.include)
}) })
case let .excludeExpand(text): case let .excludeExpand(text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: {
arguments.expandSection(.exclude) arguments.expandSection(.exclude)
}) })
case let .tagColorHeader(name, color, isPremium): case let .tagColorHeader(name, color, isPremium):
@ -507,6 +510,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case let .tagColor(colors, color, isPremium): case let .tagColor(colors, color, isPremium):
return PeerNameColorItem( return PeerNameColorItem(
theme: presentationData.theme, theme: presentationData.theme,
systemStyle: .glass,
colors: colors, colors: colors,
mode: .folderTag, mode: .folderTag,
displayEmptyColor: true, displayEmptyColor: true,
@ -526,11 +530,11 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case .inviteLinkHeader: case .inviteLinkHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ChatListFilter_SectionShare, badge: nil, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ChatListFilter_SectionShare, badge: nil, sectionId: self.section)
case let .inviteLinkCreate(hasLinks): case let .inviteLinkCreate(hasLinks):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? presentationData.strings.ChatListFilter_CreateLink : presentationData.strings.ChatListFilter_CreateLinkNew, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? presentationData.strings.ChatListFilter_CreateLink : presentationData.strings.ChatListFilter_CreateLinkNew, sectionId: self.section, editing: false, action: {
arguments.createLink() arguments.createLink()
}) })
case let .inviteLink(_, link): case let .inviteLink(_, link):
return ItemListFolderInviteLinkListItem(presentationData: presentationData, invite: link, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in return ItemListFolderInviteLinkListItem(presentationData: presentationData, systemStyle: .glass, invite: link, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
arguments.openLink(invite) arguments.openLink(invite)
}, removeAction: { invite in }, removeAction: { invite in
arguments.removeLink(invite) arguments.removeLink(invite)

View file

@ -176,11 +176,11 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
case let .suggestedListHeader(text): case let .suggestedListHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
case let .suggestedPreset(_, title, label, preset): case let .suggestedPreset(_, title, label, preset):
return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, title: title.text, label: label, sectionId: self.section, style: .blocks, installAction: { return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, systemStyle: .glass, title: title.text, label: label, sectionId: self.section, style: .blocks, installAction: {
arguments.addSuggestedPressed(title, preset) arguments.addSuggestedPressed(title, preset)
}, tag: nil) }, tag: nil)
case let .suggestedAddCustom(text): case let .suggestedAddCustom(text):
return ItemListPeerActionItem(presentationData: presentationData, icon: nil, title: text, sectionId: self.section, height: .generic, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, sectionId: self.section, height: .generic, editing: false, action: {
arguments.addNew() arguments.addNew()
}) })
case let .listHeader(text): case let .listHeader(text):
@ -194,7 +194,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
} }
} }
return ChatListFilterPresetListItem(context: arguments.context, presentationData: presentationData, preset: preset, title: title, label: label, tagColor: resolvedColor, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, isDisabled: isDisabled, sectionId: self.section, action: { return ChatListFilterPresetListItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, preset: preset, title: title, label: label, tagColor: resolvedColor, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, isDisabled: isDisabled, sectionId: self.section, action: {
if isDisabled { if isDisabled {
arguments.addNew() arguments.addNew()
} else { } else {
@ -206,13 +206,13 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
arguments.removePreset(preset.id) arguments.removePreset(preset.id)
}) })
case let .addItem(text, isEditing): case let .addItem(text, isEditing):
return ItemListPeerActionItem(presentationData: presentationData, icon: nil, title: text, sectionId: self.section, height: .generic, editing: isEditing, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, sectionId: self.section, height: .generic, editing: isEditing, action: {
arguments.addNew() arguments.addNew()
}) })
case let .listFooter(text): case let .listFooter(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .displayTags(value): case let .displayTags(value):
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ChatListFilterList_ShowTags, value: value == true, enableInteractiveChanges: value != nil, enabled: true, displayLocked: value == nil, sectionId: self.section, style: .blocks, updated: { updatedValue in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.ChatListFilterList_ShowTags, value: value == true, enableInteractiveChanges: value != nil, enabled: true, displayLocked: value == nil, sectionId: self.section, style: .blocks, updated: { updatedValue in
if value != nil { if value != nil {
arguments.updateDisplayTags(updatedValue) arguments.updateDisplayTags(updatedValue)
} else { } else {

View file

@ -19,6 +19,7 @@ struct ChatListFilterPresetListItemEditing: Equatable {
final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
let context: AccountContext let context: AccountContext
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let preset: ChatListFilter let preset: ChatListFilter
let title: ChatFolderTitle let title: ChatFolderTitle
let label: String let label: String
@ -36,6 +37,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
init( init(
context: AccountContext, context: AccountContext,
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
preset: ChatListFilter, preset: ChatListFilter,
title: ChatFolderTitle, title: ChatFolderTitle,
label: String, label: String,
@ -52,6 +54,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
) { ) {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.preset = preset self.preset = preset
self.title = title self.title = title
self.label = label self.label = label
@ -278,9 +281,18 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
let labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0 let labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: titleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: titleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let verticalInset: CGFloat
switch item.systemStyle {
case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 11.0
}
let insets = itemListNeighborsGroupedInsets(neighbors, params) let insets = itemListNeighborsGroupedInsets(neighbors, params)
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + 11.0 * 2.0) let contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0)
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size let layoutSize = layout.size
@ -405,17 +417,17 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size)
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 11.0), size: titleLayout.size)) transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset), size: titleLayout.size))
let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: 11.0), size: labelLayout.size) let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: verticalInset), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame strongSelf.labelNode.frame = labelFrame
transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0) transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)

View file

@ -8,6 +8,7 @@ import ItemListUI
public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem { public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String let title: String
let label: String let label: String
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
@ -17,6 +18,7 @@ public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem {
public init( public init(
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
title: String, title: String,
label: String, label: String,
sectionId: ItemListSectionId, sectionId: ItemListSectionId,
@ -25,6 +27,7 @@ public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem {
tag: ItemListItemTag? = nil tag: ItemListItemTag? = nil
) { ) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title self.title = title
self.label = label self.label = label
self.sectionId = sectionId self.sectionId = sectionId
@ -180,6 +183,8 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi
let contentSize: CGSize let contentSize: CGSize
let insets: UIEdgeInsets let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: UIColor let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor let itemSeparatorColor: UIColor
@ -210,7 +215,13 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor:labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor:labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let verticalInset: CGFloat = 11.0 let verticalInset: CGFloat
switch item.systemStyle {
case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 11.0
}
let titleSpacing: CGFloat = 3.0 let titleSpacing: CGFloat = 3.0
let height: CGFloat let height: CGFloat
@ -301,15 +312,15 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))
} }
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame strongSelf.titleNode.frame = titleFrame
let labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size) let labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size)

View file

@ -14,6 +14,7 @@ import TextFieldComponent
public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem { public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem {
let context: AccountContext let context: AccountContext
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let text: NSAttributedString let text: NSAttributedString
let enableAnimations: Bool let enableAnimations: Bool
let placeholder: String let placeholder: String
@ -29,6 +30,7 @@ public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem {
public init( public init(
context: AccountContext, context: AccountContext,
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
text: NSAttributedString, text: NSAttributedString,
enableAnimations: Bool, enableAnimations: Bool,
placeholder: String, placeholder: String,
@ -43,6 +45,7 @@ public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem {
) { ) {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.text = text self.text = text
self.enableAnimations = enableAnimations self.enableAnimations = enableAnimations
self.placeholder = placeholder self.placeholder = placeholder
@ -144,8 +147,9 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
let _ = rightInset let _ = rightInset
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let contentSize = CGSize(width: params.width, height: 44.0)
let contentSize = CGSize(width: params.width, height: item.systemStyle == .glass ? 52.0 : 44.0)
let insets = itemListNeighborsGroupedInsets(neighbors, params) let insets = itemListNeighborsGroupedInsets(neighbors, params)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -200,12 +204,12 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
self.bottomStripeNode.isHidden = hasCorners self.bottomStripeNode.isHidden = hasCorners
} }
self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
self.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) self.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))
self.textField.parentState = self.componentState self.textField.parentState = self.componentState
self.componentState._updated = { [weak self] transition, _ in self.componentState._updated = { [weak self] transition, _ in
@ -249,7 +253,7 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
environment: {}, environment: {},
containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: layout.size.height) containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: layout.size.height)
) )
let textFieldFrame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: textFieldSize) let textFieldFrame = CGRect(origin: CGPoint(x: params.leftInset, y: floorToScreenPixels((layoutSize.height - textFieldSize.height) / 2.0)), size: textFieldSize)
if let textFieldView = self.textField.view { if let textFieldView = self.textField.view {
if textFieldView.superview == nil { if textFieldView.superview == nil {
self.view.addSubview(textFieldView) self.view.addSubview(textFieldView)

View file

@ -221,7 +221,13 @@ public class InteractiveCheckNode: CheckNode {
} }
} }
private var lastPressTime: Double?
@objc private func buttonPressed() { @objc private func buttonPressed() {
let currentTime = CACurrentMediaTime()
if let lastPressTime = self.lastPressTime, currentTime - lastPressTime < 0.5 {
return
}
self.lastPressTime = currentTime
self.setSelected(!self.selected, animated: true) self.setSelected(!self.selected, animated: true)
self.valueChanged?(self.selected) self.valueChanged?(self.selected)
} }

View file

@ -14,6 +14,7 @@ swift_library(
"//submodules/ComponentFlow:ComponentFlow", "//submodules/ComponentFlow:ComponentFlow",
"//submodules/Components/ViewControllerComponent:ViewControllerComponent", "//submodules/Components/ViewControllerComponent:ViewControllerComponent",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -4,6 +4,7 @@ import Display
import ComponentFlow import ComponentFlow
import ViewControllerComponent import ViewControllerComponent
import SwiftSignalKit import SwiftSignalKit
import DynamicCornerRadiusView
public final class SheetComponentEnvironment: Equatable { public final class SheetComponentEnvironment: Equatable {
public let isDisplaying: Bool public let isDisplaying: Bool
@ -59,8 +60,14 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
case blur(BlurStyle) case blur(BlurStyle)
} }
public enum Style: Equatable {
case glass
case legacy
}
public let content: AnyComponent<ChildEnvironmentType> public let content: AnyComponent<ChildEnvironmentType>
public let headerContent: AnyComponent<Empty>? public let headerContent: AnyComponent<Empty>?
public let style: Style
public let backgroundColor: BackgroundColor public let backgroundColor: BackgroundColor
public let followContentSizeChanges: Bool public let followContentSizeChanges: Bool
public let clipsContent: Bool public let clipsContent: Bool
@ -75,6 +82,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
public init( public init(
content: AnyComponent<ChildEnvironmentType>, content: AnyComponent<ChildEnvironmentType>,
headerContent: AnyComponent<Empty>? = nil, headerContent: AnyComponent<Empty>? = nil,
style: Style = .legacy,
backgroundColor: BackgroundColor, backgroundColor: BackgroundColor,
followContentSizeChanges: Bool = false, followContentSizeChanges: Bool = false,
clipsContent: Bool = false, clipsContent: Bool = false,
@ -88,6 +96,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
) { ) {
self.content = content self.content = content
self.headerContent = headerContent self.headerContent = headerContent
self.style = style
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
self.followContentSizeChanges = followContentSizeChanges self.followContentSizeChanges = followContentSizeChanges
self.clipsContent = clipsContent self.clipsContent = clipsContent
@ -107,6 +116,9 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
if lhs.headerContent != rhs.headerContent { if lhs.headerContent != rhs.headerContent {
return false return false
} }
if lhs.style != rhs.style {
return false
}
if lhs.backgroundColor != rhs.backgroundColor { if lhs.backgroundColor != rhs.backgroundColor {
return false return false
} }
@ -162,7 +174,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
private let dimView: UIView private let dimView: UIView
private let scrollView: ScrollView private let scrollView: ScrollView
private let backgroundView: UIView private let backgroundView: DynamicCornerRadiusView
private var effectView: UIVisualEffectView? private var effectView: UIVisualEffectView?
private let contentView: ComponentView<ChildEnvironmentType> private let contentView: ComponentView<ChildEnvironmentType>
private var headerView: ComponentView<Empty>? private var headerView: ComponentView<Empty>?
@ -186,9 +198,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceVertical = true self.scrollView.alwaysBounceVertical = true
self.backgroundView = UIView() self.backgroundView = DynamicCornerRadiusView()
self.backgroundView.layer.cornerRadius = 12.0
self.backgroundView.layer.masksToBounds = true
self.contentView = ComponentView<ChildEnvironmentType>() self.contentView = ComponentView<ChildEnvironmentType>()
@ -354,6 +364,19 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
self.component = component self.component = component
self.currentHasInputHeight = sheetEnvironment.hasInputHeight self.currentHasInputHeight = sheetEnvironment.hasInputHeight
let glassInset: CGFloat = 6.0
var topCornerRadius: CGFloat
var bottomCornerRadius: CGFloat
switch component.style {
case .glass:
topCornerRadius = 38.0
bottomCornerRadius = 56.0
case .legacy:
topCornerRadius = 12.0
bottomCornerRadius = 12.0
}
switch component.backgroundColor { switch component.backgroundColor {
case let .blur(style): case let .blur(style):
self.backgroundView.isHidden = true self.backgroundView.isHidden = true
@ -365,7 +388,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
self.effectView = effectView self.effectView = effectView
} }
case let .color(color): case let .color(color):
self.backgroundView.backgroundColor = color self.backgroundView.updateColor(color: color, transition: .immediate)
self.backgroundView.isHidden = false self.backgroundView.isHidden = false
self.effectView?.removeFromSuperview() self.effectView?.removeFromSuperview()
self.effectView = nil self.effectView = nil
@ -383,7 +406,12 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
containerSize = regularMetricsSize containerSize = regularMetricsSize
} }
} else { } else {
containerSize = CGSize(width: availableSize.width, height: .greatestFiniteMagnitude) switch component.style {
case .glass:
containerSize = CGSize(width: availableSize.width - glassInset * 2.0, height: .greatestFiniteMagnitude)
case .legacy:
containerSize = CGSize(width: availableSize.width, height: .greatestFiniteMagnitude)
}
} }
self.contentView.parentState = state self.contentView.parentState = state
@ -403,7 +431,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
self.scrollView.addSubview(contentView) self.scrollView.addSubview(contentView)
} }
contentView.clipsToBounds = component.clipsContent contentView.clipsToBounds = component.clipsContent
contentView.layer.cornerRadius = self.backgroundView.layer.cornerRadius contentView.layer.cornerRadius = topCornerRadius
if sheetEnvironment.isCentered { if sheetEnvironment.isCentered {
let y: CGFloat = floorToScreenPixels((availableSize.height - contentSize.height) / 2.0) let y: CGFloat = floorToScreenPixels((availableSize.height - contentSize.height) / 2.0)
transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil)
@ -411,12 +439,20 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
if let effectView = self.effectView { if let effectView = self.effectView {
transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil)
} }
self.backgroundView.update(size: contentSize, corners: .init(minXMinY: topCornerRadius, maxXMinY: topCornerRadius, minXMaxY: bottomCornerRadius, maxXMaxY: bottomCornerRadius), transition: transition)
} else { } else {
transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 100.0)), completion: nil) switch component.style {
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) case .glass:
if let effectView = self.effectView { transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil)
transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil)
case .legacy:
transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 100.0)), completion: nil)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil)
if let effectView = self.effectView {
transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil)
}
} }
self.backgroundView.update(size: contentSize, corners: .init(minXMinY: topCornerRadius, maxXMinY: topCornerRadius, minXMaxY: bottomCornerRadius, maxXMaxY: bottomCornerRadius), transition: transition)
} }
} }
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)

View file

@ -42,6 +42,11 @@ open class ViewControllerComponentContainer: ViewController {
case custom(PresentationTheme) case custom(PresentationTheme)
} }
public enum Style {
case glass
case legacy
}
public final class Environment: Equatable { public final class Environment: Equatable {
public let statusBarHeight: CGFloat public let statusBarHeight: CGFloat
public let navigationHeight: CGFloat public let navigationHeight: CGFloat

View file

@ -42,6 +42,8 @@ swift_library(
"//submodules/ChatPresentationInterfaceState", "//submodules/ChatPresentationInterfaceState",
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent", "//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
"//submodules/TelegramUI/Components/ListComposePollOptionComponent", "//submodules/TelegramUI/Components/ListComposePollOptionComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -27,6 +27,8 @@ import EmojiSuggestionsComponent
import TextFormat import TextFormat
import TextFieldComponent import TextFieldComponent
import ListComposePollOptionComponent import ListComposePollOptionComponent
import EdgeEffect
import GlassBarButtonComponent
public final class ComposedPoll { public final class ComposedPoll {
public struct Text { public struct Text {
@ -110,6 +112,8 @@ final class ComposePollScreenComponent: Component {
final class View: UIView, UIScrollViewDelegate { final class View: UIView, UIScrollViewDelegate {
private let scrollView: UIScrollView private let scrollView: UIScrollView
private let edgeEffectView: EdgeEffectView
private var reactionInput: ComponentView<Empty>? private var reactionInput: ComponentView<Empty>?
private let pollTextSection = ComponentView<Empty>() private let pollTextSection = ComponentView<Empty>()
private let quizAnswerSection = ComponentView<Empty>() private let quizAnswerSection = ComponentView<Empty>()
@ -120,7 +124,10 @@ final class ComposePollScreenComponent: Component {
private var pollOptionsSectionContainer: ListSectionContentView private var pollOptionsSectionContainer: ListSectionContentView
private let pollSettingsSection = ComponentView<Empty>() private let pollSettingsSection = ComponentView<Empty>()
private let actionButton = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private let cancelButton = ComponentView<Empty>()
private let doneButton = ComponentView<Empty>()
private var reactionSelectionControl: ComponentView<Empty>? private var reactionSelectionControl: ComponentView<Empty>?
@ -178,6 +185,8 @@ final class ComposePollScreenComponent: Component {
self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.contentInsetAdjustmentBehavior = .never
self.scrollView.alwaysBounceVertical = true self.scrollView.alwaysBounceVertical = true
self.edgeEffectView = EdgeEffectView()
self.pollOptionsSectionContainer = ListSectionContentView(frame: CGRect()) self.pollOptionsSectionContainer = ListSectionContentView(frame: CGRect())
super.init(frame: frame) super.init(frame: frame)
@ -185,6 +194,8 @@ final class ComposePollScreenComponent: Component {
self.scrollView.delegate = self self.scrollView.delegate = self
self.addSubview(self.scrollView) self.addSubview(self.scrollView)
self.addSubview(self.edgeEffectView)
let reorderRecognizer = ReorderGestureRecognizer( let reorderRecognizer = ReorderGestureRecognizer(
shouldBegin: { [weak self] point in shouldBegin: { [weak self] point in
guard let self, let (id, item) = self.item(at: point) else { guard let self, let (id, item) = self.item(at: point) else {
@ -851,6 +862,7 @@ final class ComposePollScreenComponent: Component {
pollTextSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent( pollTextSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent(
externalState: self.pollTextInputState, externalState: self.pollTextInputState,
context: component.context, context: component.context,
style: .glass,
theme: theme, theme: theme,
strings: environment.strings, strings: environment.strings,
resetText: self.resetPollText.flatMap { resetText in resetText: self.resetPollText.flatMap { resetText in
@ -894,6 +906,7 @@ final class ComposePollScreenComponent: Component {
transition: transition, transition: transition,
component: AnyComponent(ListSectionComponent( component: AnyComponent(ListSectionComponent(
theme: theme, theme: theme,
style: .glass,
header: AnyComponent(MultilineTextComponent( header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: environment.strings.CreatePoll_TextHeader, string: environment.strings.CreatePoll_TextHeader,
@ -951,6 +964,7 @@ final class ComposePollScreenComponent: Component {
pollOptionsSectionItems.append(AnyComponentWithIdentity(id: pollOption.id, component: AnyComponent(ListComposePollOptionComponent( pollOptionsSectionItems.append(AnyComponentWithIdentity(id: pollOption.id, component: AnyComponent(ListComposePollOptionComponent(
externalState: pollOption.textInputState, externalState: pollOption.textInputState,
context: component.context, context: component.context,
style: .glass,
theme: theme, theme: theme,
strings: environment.strings, strings: environment.strings,
resetText: pollOption.resetText.flatMap { resetText in resetText: pollOption.resetText.flatMap { resetText in
@ -1092,6 +1106,7 @@ final class ComposePollScreenComponent: Component {
let pollOptionsSectionUpdateResult = self.pollOptionsSectionContainer.update( let pollOptionsSectionUpdateResult = self.pollOptionsSectionContainer.update(
configuration: ListSectionContentView.Configuration( configuration: ListSectionContentView.Configuration(
theme: theme, theme: theme,
style: .glass,
displaySeparators: true, displaySeparators: true,
extendsItemHighlightToSection: false, extendsItemHighlightToSection: false,
background: .all background: .all
@ -1231,6 +1246,7 @@ final class ComposePollScreenComponent: Component {
if canBePublic { if canBePublic {
pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "anonymous", component: AnyComponent(ListActionItemComponent( pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "anonymous", component: AnyComponent(ListActionItemComponent(
theme: theme, theme: theme,
style: .glass,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
@ -1253,6 +1269,7 @@ final class ComposePollScreenComponent: Component {
} }
pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "multiAnswer", component: AnyComponent(ListActionItemComponent( pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "multiAnswer", component: AnyComponent(ListActionItemComponent(
theme: theme, theme: theme,
style: .glass,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
@ -1277,6 +1294,7 @@ final class ComposePollScreenComponent: Component {
)))) ))))
pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "quiz", component: AnyComponent(ListActionItemComponent( pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "quiz", component: AnyComponent(ListActionItemComponent(
theme: theme, theme: theme,
style: .glass,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
@ -1304,6 +1322,7 @@ final class ComposePollScreenComponent: Component {
transition: transition, transition: transition,
component: AnyComponent(ListSectionComponent( component: AnyComponent(ListSectionComponent(
theme: theme, theme: theme,
style: .glass,
header: nil, header: nil,
footer: AnyComponent(MultilineTextComponent( footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
@ -1334,6 +1353,7 @@ final class ComposePollScreenComponent: Component {
transition: transition, transition: transition,
component: AnyComponent(ListSectionComponent( component: AnyComponent(ListSectionComponent(
theme: theme, theme: theme,
style: .glass,
header: AnyComponent(MultilineTextComponent( header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: environment.strings.CreatePoll_ExplanationHeader, string: environment.strings.CreatePoll_ExplanationHeader,
@ -1354,6 +1374,7 @@ final class ComposePollScreenComponent: Component {
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent( AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent(
externalState: self.quizAnswerTextInputState, externalState: self.quizAnswerTextInputState,
context: component.context, context: component.context,
style: .glass,
theme: theme, theme: theme,
strings: environment.strings, strings: environment.strings,
resetText: self.resetQuizAnswerText.flatMap { resetText in resetText: self.resetQuizAnswerText.flatMap { resetText in
@ -1620,6 +1641,104 @@ final class ComposePollScreenComponent: Component {
self.scrollView.verticalScrollIndicatorInsets = scrollInsets self.scrollView.verticalScrollIndicatorInsets = scrollInsets
} }
let edgeEffectHeight: CGFloat = 66.0
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
let title = self.isQuiz ? environment.strings.CreatePoll_QuizTitle : environment.strings.CreatePoll_Title
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(
NSAttributedString(
string: title,
font: Font.semibold(17.0),
textColor: environment.theme.rootController.navigationBar.primaryTextColor
)
)
)
),
environment: {},
containerSize: CGSize(width: 200.0, height: 40.0)
)
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((environment.navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
transition.setFrame(view: titleView, frame: titleFrame)
}
let barButtonSize = CGSize(width: 40.0, height: 40.0)
let cancelButtonSize = self.cancelButton.update(
transition: transition,
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
isDark: environment.theme.overallDarkAppearance,
state: .generic,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor
)
)),
action: { [weak self] _ in
guard let self, let controller = self.environment?.controller() as? ComposePollScreen else {
return
}
controller.dismiss()
}
)),
environment: {},
containerSize: barButtonSize
)
let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize)
if let cancelButtonView = self.cancelButton.view {
if cancelButtonView.superview == nil {
self.addSubview(cancelButtonView)
}
transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame)
}
let isValid = self.validatedInput() != nil
let doneButtonSize = self.doneButton.update(
transition: transition,
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: isValid ? environment.theme.list.itemCheckColors.fillColor : environment.theme.list.itemCheckColors.fillColor.desaturated().withMultipliedAlpha(0.5),
isDark: environment.theme.overallDarkAppearance,
state: .tintedGlass,
isEnabled: isValid,
component: AnyComponentWithIdentity(id: "done", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Done",
tintColor: environment.theme.list.itemCheckColors.foregroundColor
)
)),
action: { [weak self] _ in
guard let self, let controller = self.environment?.controller() as? ComposePollScreen else {
return
}
if let input = self.validatedInput() {
controller.completion(input)
}
controller.dismiss()
}
)),
environment: {},
containerSize: barButtonSize
)
let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize)
if let doneButtonView = self.doneButton.view {
if doneButtonView.superview == nil {
self.addSubview(doneButtonView)
}
transition.setFrame(view: doneButtonView, frame: doneButtonFrame)
}
if let recenterOnTag { if let recenterOnTag {
if let targetView = self.collectTextInputStates().first(where: { $0.view.currentTag === recenterOnTag })?.view { if let targetView = self.collectTextInputStates().first(where: { $0.view.currentTag === recenterOnTag })?.view {
let caretRect = targetView.convert(targetView.bounds, to: self.scrollView) let caretRect = targetView.convert(targetView.bounds, to: self.scrollView)
@ -1655,18 +1774,6 @@ final class ComposePollScreenComponent: Component {
} }
} }
let isValid = self.validatedInput() != nil
if let controller = environment.controller() as? ComposePollScreen, let sendButtonItem = controller.sendButtonItem {
if sendButtonItem.isEnabled != isValid {
sendButtonItem.isEnabled = isValid
}
let controllerTitle = self.isQuiz ? presentationData.strings.CreatePoll_QuizTitle : presentationData.strings.CreatePoll_Title
if controller.title != controllerTitle {
controller.title = controllerTitle
}
}
if let currentEditingTag = self.currentEditingTag, previousEditingTag !== currentEditingTag, self.currentInputMode != .keyboard { if let currentEditingTag = self.currentEditingTag, previousEditingTag !== currentEditingTag, self.currentInputMode != .keyboard {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self else { guard let self else {
@ -1708,7 +1815,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
} }
private let context: AccountContext private let context: AccountContext
private let completion: (ComposedPoll) -> Void fileprivate let completion: (ComposedPoll) -> Void
private var isDismissed: Bool = false private var isDismissed: Bool = false
fileprivate private(set) var sendButtonItem: UIBarButtonItem? fileprivate private(set) var sendButtonItem: UIBarButtonItem?
@ -1761,17 +1868,25 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
isQuiz: isQuiz, isQuiz: isQuiz,
initialData: initialData, initialData: initialData,
completion: completion completion: completion
), navigationBarAppearance: .default, theme: .default) ), navigationBarAppearance: .transparent, theme: .default)
self._hasGlassStyle = true
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.title = isQuiz == true ? presentationData.strings.CreatePoll_QuizTitle : presentationData.strings.CreatePoll_Title if self._hasGlassStyle {
self.navigationItem.setLeftBarButton(UIBarButtonItem(customView: UIView()), animated: false)
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false) } else {
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false)
}
let sendButtonItem = UIBarButtonItem(title: presentationData.strings.CreatePoll_Create, style: .done, target: self, action: #selector(self.sendPressed)) let sendButtonItem = UIBarButtonItem(title: presentationData.strings.CreatePoll_Create, style: .done, target: self, action: #selector(self.sendPressed))
self.sendButtonItem = sendButtonItem self.sendButtonItem = sendButtonItem
self.navigationItem.setRightBarButton(sendButtonItem, animated: false) if self._hasGlassStyle {
} else {
self.navigationItem.setRightBarButton(sendButtonItem, animated: false)
}
sendButtonItem.isEnabled = false sendButtonItem.isEnabled = false
self.scrollToTop = { [weak self] in self.scrollToTop = { [weak self] in

View file

@ -47,6 +47,7 @@ swift_library(
"//submodules/TelegramIntents", "//submodules/TelegramIntents",
"//submodules/ContextUI", "//submodules/ContextUI",
"//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -50,6 +50,7 @@ private enum ContactListNodeEntryId: Hashable {
case sort case sort
case permission(index: Int) case permission(index: Int)
case option(index: Int) case option(index: Int)
case header(index: Int64)
case peerId(peerId: Int64, section: ContactListNodeEntrySection) case peerId(peerId: Int64, section: ContactListNodeEntrySection)
case deviceContact(DeviceContactStableId) case deviceContact(DeviceContactStableId)
} }
@ -101,7 +102,8 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
case permissionEnable(PresentationTheme, String) case permissionEnable(PresentationTheme, String)
case permissionLimited(PresentationTheme, PresentationStrings) case permissionLimited(PresentationTheme, PresentationStrings)
case option(Int, ContactListAdditionalOption, ListViewItemHeader?, PresentationTheme, PresentationStrings) case option(Int, ContactListAdditionalOption, ListViewItemHeader?, PresentationTheme, PresentationStrings)
case peer(Int, ContactListPeer, EnginePeer.Presence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool, Bool, Bool, StoryData?, Bool, String?) case header(Int64, ListViewItemHeader?)
case peer(Int64, ContactListPeer, EnginePeer.Presence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool, Bool, Bool, StoryData?, Bool, String?)
var stableId: ContactListNodeEntryId { var stableId: ContactListNodeEntryId {
switch self { switch self {
@ -117,6 +119,8 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
return .permission(index: 2) return .permission(index: 2)
case let .option(index, _, _, _, _): case let .option(index, _, _, _, _):
return .option(index: index) return .option(index: index)
case let .header(index, _):
return .header(index: index)
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, storyData, _, _): case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, storyData, _, _):
switch peer { switch peer {
case let .peer(peer, _, _): case let .peer(peer, _, _):
@ -127,7 +131,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
} }
} }
func item(context: AccountContext, presentationData: PresentationData, interaction: ContactListNodeInteraction, isSearch: Bool) -> ListViewItem { func item(context: AccountContext, presentationData: PresentationData, interaction: ContactListNodeInteraction, isSearch: Bool, listStyle: ItemListStyle) -> ListViewItem {
switch self { switch self {
case let .search(theme, strings): case let .search(theme, strings):
return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: { return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: {
@ -164,6 +168,16 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
height = .tall height = .tall
} }
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, subtitle: option.subtitle, icon: option.icon, style: style, height: height, clearHighlightAutomatically: option.clearHighlightAutomatically, header: header, action: option.action) return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, subtitle: option.subtitle, icon: option.icon, style: style, height: height, clearHighlightAutomatically: option.clearHighlightAutomatically, header: header, action: option.action)
case let .header(_, header):
var sectionId: Int32 = 0
var text = ""
if let header = header as? ContactListNameIndexHeader, let id = header.id.id.base as? Int64 {
if let scalar = UnicodeScalar(header.letter) {
text = String(Character(scalar) )
}
sectionId = Int32(clamping: id)
}
return ItemListSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), text: text, sectionId: sectionId)
case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, hasMoreButton, enabled, storyData, requiresPremiumForMessaging, customSubtitle): case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, hasMoreButton, enabled, storyData, requiresPremiumForMessaging, customSubtitle):
var status: ContactsPeerItemStatus var status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer let itemPeer: ContactsPeerItemPeer
@ -236,8 +250,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
if let customSubtitle { if let customSubtitle {
status = .custom(string: NSAttributedString(string: customSubtitle), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: customSubtitle), multiline: false, isActive: false, icon: nil)
} }
var sectionId: Int32 = 0
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in if case .blocks = listStyle, let id = header?.id.id.base as? Int64 {
sectionId = Int32(clamping: id)
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), style: listStyle, systemStyle: listStyle == .blocks ? .glass : .legacy, sectionId: listStyle == .blocks ? sectionId : 0, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: listStyle == .blocks ? nil : header, action: { _ in
interaction.openPeer(peer, .generic, nil, nil) interaction.openPeer(peer, .generic, nil, nil)
}, disabledAction: { _ in }, disabledAction: { _ in
if case let .peer(peer, _, _) = peer { if case let .peer(peer, _, _) = peer {
@ -289,6 +306,12 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .header(lhsIndex, lhsHeader):
if case let .header(rhsIndex, rhsHeader) = rhs, lhsIndex == rhsIndex, lhsHeader?.id == rhsHeader?.id {
return true
} else {
return false
}
case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder, lhsDisplayCallIcons, lhsHasMoreButton, lhsEnabled, lhsStoryData, lhsRequiresPremiumForMessaging, lhsCustomSubtitle): case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder, lhsDisplayCallIcons, lhsHasMoreButton, lhsEnabled, lhsStoryData, lhsRequiresPremiumForMessaging, lhsCustomSubtitle):
switch rhs { switch rhs {
case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder, rhsDisplayCallIcons, rhsHasMoreButton, rhsEnabled, rhsStoryData, rhsRequiresPremiumForMessaging, rhsCustomSubtitle): case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder, rhsDisplayCallIcons, rhsHasMoreButton, rhsEnabled, rhsStoryData, rhsRequiresPremiumForMessaging, rhsCustomSubtitle):
@ -386,31 +409,62 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
case let .option(lhsIndex, _, _, _, _): case let .option(lhsIndex, _, _, _, _):
switch rhs { switch rhs {
case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited: case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited:
return false return false
case let .option(rhsIndex, _, _, _, _): case let .option(rhsIndex, _, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
case .peer: case .header, .peer:
return true return true
} }
case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, lhsStoryData, _, _): case let .header(lhsIndex, _):
switch rhs {
case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option:
return false
case let .header(rhsIndex, _), let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex
}
case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
switch rhs { switch rhs {
case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option: case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option:
return false return false
case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, rhsStoryData, _, _): case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _), let .header(rhsIndex, _):
if (lhsStoryData == nil) != (rhsStoryData == nil) { // if (lhsStoryData == nil) != (rhsStoryData == nil) {
if lhsStoryData != nil { // if lhsStoryData != nil {
return true // return true
} else { // } else {
return false // return false
} // }
} // }
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
} }
} }
} }
} }
private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactListPeer], presences: [EnginePeer.Id: EnginePeer.Presence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set<EnginePeer.Id>, peerRequiresPremiumForMessaging: [EnginePeer.Id: Bool], peersWithStories: [EnginePeer.Id: PeerStoryStats], authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool, storySubscriptions: EngineStorySubscriptions?, topPeers: [EnginePeer], topPeersPresentation: ContactListPresentation.TopPeers, isPeerEnabled: ((EnginePeer) -> Bool)?, interaction: ContactListNodeInteraction) -> [ContactListNodeEntry] { private func contactListNodeEntries(
listStyle: ItemListStyle = .plain,
accountPeer: EnginePeer?,
peers: [ContactListPeer],
presences: [EnginePeer.Id: EnginePeer.Presence],
presentation: ContactListPresentation,
selectionState: ContactListNodeGroupSelectionState?,
theme: PresentationTheme,
strings: PresentationStrings,
dateTimeFormat: PresentationDateTimeFormat,
sortOrder: PresentationPersonNameOrder,
displayOrder: PresentationPersonNameOrder,
disabledPeerIds: Set<EnginePeer.Id>,
peerRequiresPremiumForMessaging: [EnginePeer.Id: Bool],
peersWithStories: [EnginePeer.Id: PeerStoryStats],
authorizationStatus: AccessType,
warningSuppressed: (Bool, Bool),
displaySortOptions: Bool,
displayCallIcons: Bool,
storySubscriptions: EngineStorySubscriptions?,
topPeers: [EnginePeer],
topPeersPresentation: ContactListPresentation.TopPeers,
isPeerEnabled: ((EnginePeer) -> Bool)?,
interaction: ContactListNodeInteraction
) -> [ContactListNodeEntry] {
var entries: [ContactListNodeEntry] = [] var entries: [ContactListNodeEntry] = []
var commonHeader: ListViewItemHeader? var commonHeader: ListViewItemHeader?
@ -579,7 +633,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
interaction.deselectAll() interaction.deselectAll()
}) })
var index: Int = 0 var index: Int64 = 0
for peer in topPeers.prefix(15) { for peer in topPeers.prefix(15) {
if peer.isDeleted { if peer.isDeleted {
continue continue
@ -601,7 +655,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
} }
case let .custom(showSelf, selfSubtitle, sections): case let .custom(showSelf, selfSubtitle, sections):
if !topPeers.isEmpty { if !topPeers.isEmpty {
var index: Int = 0 var index: Int64 = 0
var sectionId: Int = 2 var sectionId: Int = 2
for (title, peerIds, hasActions) in sections { for (title, peerIds, hasActions) in sections {
@ -732,7 +786,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
}*/ }*/
} }
var index: Int = 0 var index: Int64 = 0
if let selectionState = selectionState { if let selectionState = selectionState {
for peer in selectionState.foundPeers { for peer in selectionState.foundPeers {
@ -767,6 +821,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
} }
} }
var existingHeaders = Set<Int64>()
for i in 0 ..< orderedPeers.count { for i in 0 ..< orderedPeers.count {
let peer = orderedPeers[i] let peer = orderedPeers[i]
if existingPeerIds.contains(peer.id) { if existingPeerIds.contains(peer.id) {
@ -819,18 +874,24 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
} }
} }
if case .natural = presentation, case .blocks = listStyle, let header, let headerId = header.id.id.base as? Int64, !existingHeaders.contains(headerId) {
entries.append(.header(index, header))
index += 1
existingHeaders.insert(headerId)
}
entries.append(.peer(index, peer, presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, false, enabled, storyData, requiresPremiumForMessaging, nil)) entries.append(.peer(index, peer, presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, false, enabled, storyData, requiresPremiumForMessaging, nil))
index += 1 index += 1
} }
return entries return entries
} }
private func preparedContactListNodeTransition(context: AccountContext, presentationData: PresentationData, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, hasOptions: Bool, generateIndexSections: Bool, animation: ContactListAnimation, isSearch: Bool) -> ContactsListNodeTransition { private func preparedContactListNodeTransition(context: AccountContext, presentationData: PresentationData, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, hasOptions: Bool, generateIndexSections: Bool, animation: ContactListAnimation, isSearch: Bool, listStyle: ItemListStyle) -> ContactsListNodeTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch), directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch, listStyle: listStyle), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch, listStyle: listStyle), directionHint: nil) }
var shouldFixScroll = false var shouldFixScroll = false
var indexSections: [String] = [] var indexSections: [String] = []
@ -974,6 +1035,7 @@ public struct ContactListNodeGroupSelectionState: Equatable {
public final class ContactListNode: ASDisplayNode { public final class ContactListNode: ASDisplayNode {
private let context: AccountContext private let context: AccountContext
private let listStyle: ItemListStyle
private var presentation: ContactListPresentation? private var presentation: ContactListPresentation?
private let filters: [ContactListFilter] private let filters: [ContactListFilter]
private let onlyWriteable: Bool private let onlyWriteable: Bool
@ -1161,8 +1223,25 @@ public final class ContactListNode: ASDisplayNode {
private let isPeerEnabled: ((EnginePeer) -> Bool)? private let isPeerEnabled: ((EnginePeer) -> Bool)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, presentation: Signal<ContactListPresentation, NoError>, filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool, isGroupInvitation: Bool, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) { public init(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
listStyle: ItemListStyle = .plain,
presentation: Signal<ContactListPresentation, NoError>,
filters: [ContactListFilter] = [.excludeSelf],
onlyWriteable: Bool,
isGroupInvitation: Bool,
isPeerEnabled: ((EnginePeer) -> Bool)? = nil,
selectionState: ContactListNodeGroupSelectionState? = nil,
displayPermissionPlaceholder: Bool = true,
displaySortOptions: Bool = false,
displayCallIcons: Bool = false,
contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)? = nil,
isSearch: Bool = false,
multipleSelection: Bool = false
) {
self.context = context self.context = context
self.listStyle = listStyle
self.filters = filters self.filters = filters
self.displayPermissionPlaceholder = displayPermissionPlaceholder self.displayPermissionPlaceholder = displayPermissionPlaceholder
self.contextAction = contextAction self.contextAction = contextAction
@ -1217,8 +1296,13 @@ public final class ContactListNode: ASDisplayNode {
super.init() super.init()
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.backgroundColor = listStyle == .blocks ? self.presentationData.theme.list.blocksBackgroundColor : self.presentationData.theme.chatList.backgroundColor
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
if listStyle == .blocks {
self.listNode.verticalScrollIndicatorColor = .clear
} else {
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
}
self.selectionStateValue = selectionState self.selectionStateValue = selectionState
self.selectionStatePromise.set(.single(selectionState)) self.selectionStatePromise.set(.single(selectionState))
@ -1524,7 +1608,14 @@ public final class ContactListNode: ASDisplayNode {
} }
return combineLatest(accountPeer, foundPeers.get(), peerRequiresPremiumForMessaging, foundDeviceContacts, selectionStateSignal, pendingRemovalPeerIdsSignal, presentationDataPromise.get()) return combineLatest(accountPeer, foundPeers.get(), peerRequiresPremiumForMessaging, foundDeviceContacts, selectionStateSignal, pendingRemovalPeerIdsSignal, presentationDataPromise.get())
|> mapToQueue { accountPeer, foundPeers, peerRequiresPremiumForMessaging, deviceContacts, selectionState, pendingRemovalPeerIds, presentationData -> Signal<ContactsListNodeTransition, NoError> in |> mapToQueue {
accountPeer,
foundPeers,
peerRequiresPremiumForMessaging,
deviceContacts,
selectionState,
pendingRemovalPeerIds,
presentationData -> Signal<ContactsListNodeTransition, NoError> in
let localPeersAndStatuses = foundPeers.foundLocalContacts let localPeersAndStatuses = foundPeers.foundLocalContacts
let remotePeers = foundPeers.foundRemoteContacts let remotePeers = foundPeers.foundRemoteContacts
@ -1535,7 +1626,7 @@ public final class ContactListNode: ASDisplayNode {
var existingPeerIds = Set<EnginePeer.Id>() var existingPeerIds = Set<EnginePeer.Id>()
var disabledPeerIds = Set<EnginePeer.Id>() var disabledPeerIds = Set<EnginePeer.Id>()
var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>() var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>()
var excludeSelf = false var excludeSelf = false
var requirePhoneNumbers = false var requirePhoneNumbers = false
@ -1580,7 +1671,9 @@ public final class ContactListNode: ASDisplayNode {
} }
existingPeerIds.insert(peer.peer.id) existingPeerIds.insert(peer.peer.id)
peers.append(.peer(peer: peer.peer, isGlobal: false, participantCount: peer.subscribers)) peers.append(.peer(peer: peer.peer, isGlobal: false, participantCount: peer.subscribers))
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { if searchDeviceContacts,
let user = peer.peer as? TelegramUser,
let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
} }
} }
@ -1615,7 +1708,9 @@ public final class ContactListNode: ASDisplayNode {
} }
existingPeerIds.insert(peer.peer.id) existingPeerIds.insert(peer.peer.id)
peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers))
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { if searchDeviceContacts,
let user = peer.peer as? TelegramUser,
let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
} }
} }
@ -1651,7 +1746,9 @@ public final class ContactListNode: ASDisplayNode {
} }
existingPeerIds.insert(peer.peer.id) existingPeerIds.insert(peer.peer.id)
peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers))
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { if searchDeviceContacts,
let user = peer.peer as? TelegramUser,
let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
} }
} }
@ -1672,9 +1769,33 @@ public final class ContactListNode: ASDisplayNode {
peers.append(.deviceContact(stableId, contact.0)) peers.append(.deviceContact(stableId, contact.0))
} }
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: peerRequiresPremiumForMessaging, peersWithStories: [:], authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false, displayCallIcons: displayCallIcons, storySubscriptions: nil, topPeers: [], topPeersPresentation: .none, isPeerEnabled: isPeerEnabled, interaction: interaction) let entries = contactListNodeEntries(
listStyle: listStyle,
accountPeer: nil,
peers: peers,
presences: localPeersAndStatuses.1,
presentation: presentation,
selectionState: selectionState,
theme: presentationData.theme,
strings: presentationData.strings,
dateTimeFormat: presentationData.dateTimeFormat,
sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder,
disabledPeerIds: disabledPeerIds,
peerRequiresPremiumForMessaging: peerRequiresPremiumForMessaging,
peersWithStories: [:],
authorizationStatus: .allowed,
warningSuppressed: (true, true),
displaySortOptions: false,
displayCallIcons: displayCallIcons,
storySubscriptions: nil,
topPeers: [],
topPeersPresentation: .none,
isPeerEnabled: isPeerEnabled,
interaction: interaction
)
let previous = previousEntries.swap(entries) let previous = previousEntries.swap(entries)
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, hasOptions: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch)) return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, hasOptions: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch, listStyle: listStyle))
} }
if OSAtomicCompareAndSwap32(1, 0, &firstTime) { if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
@ -1808,115 +1929,151 @@ public final class ContactListNode: ASDisplayNode {
topPeers = .single([]) topPeers = .single([])
} }
return (combineLatest( return (
self.contactPeersViewPromise.get(), combineLatest(
chatListSignal, self.contactPeersViewPromise.get(),
selectionStateSignal, chatListSignal,
pendingRemovalPeerIdsSignal, selectionStateSignal,
presentationDataPromise.get(), pendingRemovalPeerIdsSignal,
contactsAuthorization.get(), presentationDataPromise.get(),
contactsWarningSuppressed.get(), contactsAuthorization.get(),
self.storySubscriptions.get(), contactsWarningSuppressed.get(),
topPeers self.storySubscriptions.get(),
) topPeers
|> mapToQueue { view, chatListPeers, selectionState, pendingRemovalPeerIds, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, topPeers -> Signal<ContactsListNodeTransition, NoError> in )
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in |> mapToQueue {
if !view.2.isEmpty { view,
context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys)) chatListPeers,
} selectionState,
pendingRemovalPeerIds,
var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) }) presentationData,
for (peer, memberCount) in chatListPeers { authorizationStatus,
peers.append(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: memberCount)) warningSuppressed,
} storySubscriptions,
var existingPeerIds = Set<EnginePeer.Id>() topPeers -> Signal<ContactsListNodeTransition, NoError> in
var disabledPeerIds = Set<EnginePeer.Id>() let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
var requirePhoneNumbers = false if !view.2.isEmpty {
for filter in filters { context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys))
switch filter {
case .excludeSelf:
existingPeerIds.insert(context.account.peerId)
case let .exclude(peerIds):
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
case .excludeWithoutPhoneNumbers:
requirePhoneNumbers = true
case .excludeBots:
break
} }
}
var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) })
peers = peers.filter { contact in for (peer, memberCount) in chatListPeers {
switch contact { peers.append(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: memberCount))
case let .peer(peer, _, _): }
if requirePhoneNumbers, let user = peer as? TelegramUser { var existingPeerIds = Set<EnginePeer.Id>()
let phone = user.phone ?? "" var disabledPeerIds = Set<EnginePeer.Id>()
if phone.isEmpty { var requirePhoneNumbers = false
return false for filter in filters {
switch filter {
case .excludeSelf:
existingPeerIds.insert(context.account.peerId)
case let .exclude(peerIds):
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
case .excludeWithoutPhoneNumbers:
requirePhoneNumbers = true
case .excludeBots:
break
}
}
peers = peers.filter { contact in
switch contact {
case let .peer(peer, _, _):
if requirePhoneNumbers,
let user = peer as? TelegramUser {
let phone = user.phone ?? ""
if phone.isEmpty {
return false
}
}
return !existingPeerIds.contains(peer.id) && !pendingRemovalPeerIds.contains(peer.id)
default:
return true
}
}
var presences = view.0.presences
for peer in topPeers {
if let presence = peer.presence {
presences[peer.peer.id] = presence
}
}
var isEmpty = false
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty && topPeers.isEmpty {
isEmpty = true
}
let entries = contactListNodeEntries(
listStyle: listStyle,
accountPeer: view.1,
peers: peers,
presences: presences,
presentation: presentation,
selectionState: selectionState,
theme: presentationData.theme,
strings: presentationData.strings,
dateTimeFormat: presentationData.dateTimeFormat,
sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder,
disabledPeerIds: disabledPeerIds,
peerRequiresPremiumForMessaging: view.2,
peersWithStories: view.3,
authorizationStatus: authorizationStatus,
warningSuppressed: warningSuppressed,
displaySortOptions: displaySortOptions,
displayCallIcons: displayCallIcons,
storySubscriptions: storySubscriptions,
topPeers: topPeers.map { $0.peer
},
topPeersPresentation: displayTopPeers,
isPeerEnabled: isPeerEnabled,
interaction: interaction
)
let previous = previousEntries.swap(entries)
let previousSelection = previousSelectionState.swap(selectionState)
let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds)
var hadPermissionInfo = false
var previousOptionsCount = 0
if let previous = previous {
for entry in previous {
if case .permissionInfo = entry {
hadPermissionInfo = true
}
if case .option = entry {
previousOptionsCount += 1
} }
} }
return !existingPeerIds.contains(peer.id) && !pendingRemovalPeerIds.contains(peer.id)
default:
return true
} }
} var hasPermissionInfo = false
var optionsCount = 0
var presences = view.0.presences for entry in entries {
for peer in topPeers {
if let presence = peer.presence {
presences[peer.peer.id] = presence
}
}
var isEmpty = false
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty && topPeers.isEmpty {
isEmpty = true
}
let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, peersWithStories: view.3, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers.map { $0.peer }, topPeersPresentation: displayTopPeers, isPeerEnabled: isPeerEnabled, interaction: interaction)
let previous = previousEntries.swap(entries)
let previousSelection = previousSelectionState.swap(selectionState)
let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds)
var hadPermissionInfo = false
var previousOptionsCount = 0
if let previous = previous {
for entry in previous {
if case .permissionInfo = entry { if case .permissionInfo = entry {
hadPermissionInfo = true hasPermissionInfo = true
} }
if case .option = entry { if case .option = entry {
previousOptionsCount += 1 optionsCount += 1
} }
} }
}
var hasPermissionInfo = false let animation: ContactListAnimation
var optionsCount = 0 if (previousSelection == nil) != (selectionState == nil) {
for entry in entries { animation = .insertion
if case .permissionInfo = entry { } else if previousPendingRemovalPeerIds != pendingRemovalPeerIds {
hasPermissionInfo = true animation = .insertion
} } else if hadPermissionInfo != hasPermissionInfo {
if case .option = entry { animation = .insertion
optionsCount += 1 } else if optionsCount < previousOptionsCount {
animation = .insertion
} else {
animation = .none
} }
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, hasOptions: optionsCount != 0, generateIndexSections: generateSections, animation: animation, isSearch: isSearch, listStyle: listStyle))
} }
let animation: ContactListAnimation
if (previousSelection == nil) != (selectionState == nil) {
animation = .insertion
} else if previousPendingRemovalPeerIds != pendingRemovalPeerIds {
animation = .insertion
} else if hadPermissionInfo != hasPermissionInfo {
animation = .insertion
} else if optionsCount < previousOptionsCount {
animation = .insertion
} else {
animation = .none
}
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, hasOptions: optionsCount != 0, generateIndexSections: generateSections, animation: animation, isSearch: isSearch))
}
if OSAtomicCompareAndSwap32(1, 0, &firstTime) { if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
return signal |> runOn(Queue.mainQueue()) return signal |> runOn(Queue.mainQueue())
@ -2082,7 +2239,10 @@ public final class ContactListNode: ASDisplayNode {
insets.left = layout.safeInsets.left insets.left = layout.safeInsets.left
insets.right = layout.safeInsets.right insets.right = layout.safeInsets.right
let indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom)) var indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom))
if case .blocks = self.listStyle {
indexNodeFrame.origin.x += 18.0
}
transition.updateFrame(node: indexNode, frame: indexNodeFrame) transition.updateFrame(node: indexNode, frame: indexNodeFrame)
self.indexNode.update(size: indexNodeFrame.size, color: self.presentationData.theme.list.itemAccentColor, sections: indexSections, transition: transition) self.indexNode.update(size: indexNodeFrame.size, color: self.presentationData.theme.list.itemAccentColor, sections: indexSections, transition: transition)
} }
@ -2139,7 +2299,10 @@ public final class ContactListNode: ASDisplayNode {
insets.bottom -= inputHeight insets.bottom -= inputHeight
} }
let indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom)) var indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom))
if case .blocks = self.listStyle {
indexNodeFrame.origin.x += 18.0
}
self.indexNode.frame = indexNodeFrame self.indexNode.frame = indexNodeFrame
self.indexNode.update(size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom), color: self.presentationData.theme.list.itemAccentColor, sections: transition.indexSections, transition: .animated(duration: 0.2, curve: .easeInOut)) self.indexNode.update(size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom), color: self.presentationData.theme.list.itemAccentColor, sections: transition.indexSections, transition: .animated(duration: 0.2, curve: .easeInOut))

View file

@ -740,26 +740,34 @@ public class ContactsController: ViewController {
switch status { switch status {
case .allowed: case .allowed:
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") //let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in let controller = strongSelf.context.sharedContext.makeNewContactScreen(
guard let strongSelf = self else { context: strongSelf.context,
return firstName: nil,
} lastName: nil,
if let peer = peer { phoneNumber: nil
DispatchQueue.main.async { )
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { navigationController.pushViewController(controller)
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
navigationController.pushViewController(infoController) // navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in
} // guard let strongSelf = self else {
} // return
} // }
} else { // if let peer = peer {
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { // DispatchQueue.main.async {
navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) // if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
} // if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
} // navigationController.pushViewController(infoController)
}), completed: nil, cancelled: nil)) // }
// }
// }
// } else {
// if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
// navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil))
// }
// }
// }), completed: nil, cancelled: nil))
} }
case .notDetermined: case .notDetermined:
DeviceAccess.authorizeAccess(to: .contacts) DeviceAccess.authorizeAccess(to: .contacts)

View file

@ -16,6 +16,8 @@ import PhoneNumberFormat
import ItemListUI import ItemListUI
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import ComponentFlow
import SearchInputPanelComponent
private enum ContactListSearchGroup { private enum ContactListSearchGroup {
case contacts case contacts
@ -229,6 +231,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
} }
private let context: AccountContext private let context: AccountContext
private let glass: Bool
private let isPeerEnabled: (ContactListPeer) -> Bool private let isPeerEnabled: (ContactListPeer) -> Bool
private let addContact: ((String) -> Void)? private let addContact: ((String) -> Void)?
private let openPeer: (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void private let openPeer: (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void
@ -252,12 +255,28 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
private var containerViewLayout: (ContainerViewLayout, CGFloat)? private var containerViewLayout: (ContainerViewLayout, CGFloat)?
private var enqueuedTransitions: [ContactListSearchContainerTransition] = [] private var enqueuedTransitions: [ContactListSearchContainerTransition] = []
private let searchInput = ComponentView<Empty>()
public override var hasDim: Bool { public override var hasDim: Bool {
return true return true
} }
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], displayCallIcons: Bool = false, isPeerEnabled: @escaping (ContactListPeer) -> Bool = { _ in true }, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) { public init(
context: AccountContext,
glass: Bool = false,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
onlyWriteable: Bool,
categories: ContactsSearchCategories,
filters: [ContactListFilter] = [.excludeSelf],
displayCallIcons: Bool = false,
isPeerEnabled: @escaping (ContactListPeer) -> Bool = { _ in true },
addContact: ((String) -> Void)?,
openPeer: @escaping (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void,
openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void,
contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
) {
self.context = context self.context = context
self.glass = glass
self.isPeerEnabled = isPeerEnabled self.isPeerEnabled = isPeerEnabled
self.addContact = addContact self.addContact = addContact
self.openPeer = openPeer self.openPeer = openPeer
@ -270,10 +289,11 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings))
self.dimNode = ASDisplayNode() self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) self.dimNode.backgroundColor = glass ? .clear : UIColor.black.withAlphaComponent(0.5)
self.listNode = ListView() self.listNode = ListView()
self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.listNode.isHidden = true self.listNode.alpha = 0.0
self.listNode.accessibilityPageScrolledString = { row, count in self.listNode.accessibilityPageScrolledString = { row, count in
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
} }
@ -307,9 +327,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
self.addSubnode(self.emptyResultsAnimationNode) self.addSubnode(self.emptyResultsAnimationNode)
self.addSubnode(self.emptyResultsTitleNode) self.addSubnode(self.emptyResultsTitleNode)
self.addSubnode(self.emptyResultsTextNode) self.addSubnode(self.emptyResultsTextNode)
self.listNode.isHidden = true
let themeAndStringsPromise = self.themeAndStringsPromise let themeAndStringsPromise = self.themeAndStringsPromise
let previousFoundRemoteContacts = Atomic<([FoundPeer], [FoundPeer])?>(value: nil) let previousFoundRemoteContacts = Atomic<([FoundPeer], [FoundPeer])?>(value: nil)
@ -615,7 +633,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
} }
override public func scrollToTop() { override public func scrollToTop() {
if !self.listNode.isHidden { if self.listNode.alpha > 0.0 {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
} }
} }
@ -642,6 +660,13 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
} }
} }
private func deactivateInput() {
if let (layout, _) = self.containerViewLayout, let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View {
let transition = ComponentTransition.spring(duration: 0.4)
transition.setFrame(view: searchInputView, frame: CGRect(origin: CGPoint(x: searchInputView.frame.minX, y: layout.size.height), size: searchInputView.frame.size))
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
@ -675,6 +700,47 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize)) textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
self.emptyResultsAnimationNode.updateLayout(size: self.emptyResultsAnimationSize) self.emptyResultsAnimationNode.updateLayout(size: self.emptyResultsAnimationSize)
if self.glass {
let searchInputSize = self.searchInput.update(
transition: .immediate,
component: AnyComponent(
SearchInputPanelComponent(
theme: self.presentationData.theme,
strings: self.presentationData.strings,
placeholder: nil,
resetText: nil,
updated: { [weak self] query in
guard let self else {
return
}
self.searchTextUpdated(text: query)
},
cancel: { [weak self] in
guard let self else {
return
}
self.cancel?()
self.deactivateInput()
}
)
),
environment: {},
containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)
)
let bottomInset: CGFloat = layout.insets(options: .input).bottom
let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize)
if let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View {
if searchInputView.superview == nil {
self.view.addSubview(searchInputView)
searchInputView.frame = CGRect(origin: CGPoint(x: searchInputFrame.minX, y: layout.size.height), size: searchInputFrame.size)
searchInputView.activateInput()
}
transition.updateFrame(view: searchInputView, frame: searchInputFrame)
}
}
if !hadValidLayout { if !hadValidLayout {
while !self.enqueuedTransitions.isEmpty { while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition() self.dequeueTransition()
@ -713,7 +779,9 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
if let (layout, navigationBarHeight) = strongSelf.containerViewLayout { if let (layout, navigationBarHeight) = strongSelf.containerViewLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
} }
strongSelf.listNode.isHidden = !isSearching
let containerTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
containerTransition.updateAlpha(node: strongSelf.listNode, alpha: isSearching ? 1.0 : 0.0)
strongSelf.dimNode.isHidden = isSearching strongSelf.dimNode.isHidden = isSearching
strongSelf.emptyResultsAnimationNode.isHidden = !emptyResults strongSelf.emptyResultsAnimationNode.isHidden = !emptyResults

View file

@ -176,6 +176,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let style: ItemListStyle let style: ItemListStyle
let systemStyle: ItemListSystemStyle
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
let sortOrder: PresentationPersonNameOrder let sortOrder: PresentationPersonNameOrder
let displayOrder: PresentationPersonNameOrder let displayOrder: PresentationPersonNameOrder
@ -221,6 +222,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
public init( public init(
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
style: ItemListStyle = .plain, style: ItemListStyle = .plain,
systemStyle: ItemListSystemStyle = .legacy,
sectionId: ItemListSectionId = 0, sectionId: ItemListSectionId = 0,
sortOrder: PresentationPersonNameOrder, sortOrder: PresentationPersonNameOrder,
displayOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder,
@ -260,6 +262,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
) { ) {
self.presentationData = presentationData self.presentationData = presentationData
self.style = style self.style = style
self.systemStyle = systemStyle
self.sectionId = sectionId self.sectionId = sectionId
self.sortOrder = sortOrder self.sortOrder = sortOrder
self.displayOrder = displayOrder self.displayOrder = displayOrder
@ -1847,16 +1850,17 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
} }
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset))
strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(nodeLayout.insets.top, separatorHeight)), size: CGSize(width: nodeLayout.contentSize.width, height: separatorHeight)) strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(nodeLayout.insets.top, separatorHeight)), size: CGSize(width: nodeLayout.contentSize.width, height: separatorHeight))
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - leftInset), height: separatorHeight)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - leftInset - separatorRightInset), height: separatorHeight))
if !item.alwaysShowLastSeparator { if !item.alwaysShowLastSeparator && item.style != .blocks {
strongSelf.separatorNode.isHidden = last strongSelf.separatorNode.isHidden = last
} }

View file

@ -18,6 +18,11 @@ swift_library(
"//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/SearchBarNode:SearchBarNode", "//submodules/SearchBarNode:SearchBarNode",
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",
"//submodules/ComponentFlow",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -8,6 +8,9 @@ import TelegramStringFormatting
import SearchBarNode import SearchBarNode
import AppBundle import AppBundle
import TelegramCore import TelegramCore
import ComponentFlow
import BundleIconComponent
import GlassBarButtonComponent
private func loadCountryCodes() -> [Country] { private func loadCountryCodes() -> [Country] {
guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else { guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else {
@ -289,10 +292,31 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
return nil return nil
} }
public static func defaultCountryCode() -> Int32 {
let countryId = (Locale.current as NSLocale).object(forKey: .countryCode) as? String
var countryCode: Int32 = 1
if let countryId = countryId {
let normalizedId = countryId.uppercased()
for (code, idAndName) in countryCodeToIdAndName {
if idAndName.0 == normalizedId {
countryCode = Int32(code)
break
}
}
}
return countryCode
}
private let theme: PresentationTheme private let theme: PresentationTheme
private let strings: PresentationStrings private let strings: PresentationStrings
private let displayCodes: Bool private let displayCodes: Bool
private let glass: Bool
private var closeButtonNode: BarComponentHostNode?
private var searchButtonNode: BarComponentHostNode?
private var navigationContentNode: AuthorizationSequenceCountrySelectionNavigationContentNode? private var navigationContentNode: AuthorizationSequenceCountrySelectionNavigationContentNode?
private var controllerNode: AuthorizationSequenceCountrySelectionControllerNode { private var controllerNode: AuthorizationSequenceCountrySelectionControllerNode {
@ -302,29 +326,39 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
public var completeWithCountryCode: ((Int, String) -> Void)? public var completeWithCountryCode: ((Int, String) -> Void)?
public var dismissed: (() -> Void)? public var dismissed: (() -> Void)?
public init(strings: PresentationStrings, theme: PresentationTheme, displayCodes: Bool = true) { public init(strings: PresentationStrings, theme: PresentationTheme, displayCodes: Bool = true, glass: Bool = false) {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.displayCodes = displayCodes self.displayCodes = displayCodes
self.glass = glass
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings))) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme, hideBackground: glass, hideSeparator: glass), strings: NavigationBarStrings(presentationStrings: strings)))
self._hasGlassStyle = glass
self.navigationPresentation = .modal self.navigationPresentation = .modal
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in //TODO:localize
self?.dismissed?() self.title = "Select Country"
self?.dismiss()
}) if glass {
self.navigationContentNode = navigationContentNode
navigationContentNode.setQueryUpdated { [weak self] query in } else {
guard let strongSelf = self, strongSelf.isNodeLoaded else { let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in
return self?.dismissed?()
self?.dismiss()
})
self.navigationContentNode = navigationContentNode
navigationContentNode.setQueryUpdated { [weak self] query in
guard let strongSelf = self, strongSelf.isNodeLoaded else {
return
}
strongSelf.controllerNode.updateSearchQuery(query)
} }
strongSelf.controllerNode.updateSearchQuery(query) self.navigationBar?.setContentNode(navigationContentNode, animated: false)
} }
self.navigationBar?.setContentNode(navigationContentNode, animated: false)
} }
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
@ -332,11 +366,15 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = AuthorizationSequenceCountrySelectionControllerNode(theme: self.theme, strings: self.strings, displayCodes: self.displayCodes, itemSelected: { [weak self] args in self.displayNode = AuthorizationSequenceCountrySelectionControllerNode(theme: self.theme, strings: self.strings, displayCodes: self.displayCodes, glass: self.glass, itemSelected: { [weak self] args in
let (_, countryId, code) = args let (_, countryId, code) = args
self?.completeWithCountryCode?(code, countryId) self?.completeWithCountryCode?(code, countryId)
self?.dismiss() self?.dismiss()
}) })
self.controllerNode.deactivateSearch = { [weak self] in
self?.controllerNode.isSearching = false
self?.requestLayout(transition: .animated(duration: 0.5, curve: .spring))
}
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }
@ -348,9 +386,80 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
} }
} }
private func updateNavigationButtons() {
guard self.glass else {
return
}
let barButtonSize = CGSize(width: 40.0, height: 40.0)
let closeComponent: AnyComponentWithIdentity<Empty> = AnyComponentWithIdentity(
id: "close",
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: self.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
isDark: self.theme.overallDarkAppearance,
state: .generic,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: self.theme.rootController.navigationBar.glassBarButtonForegroundColor
)
)),
action: { [weak self] _ in
self?.cancelPressed()
}
))
)
let searchComponent: AnyComponentWithIdentity<Empty>?
if !self.controllerNode.isSearching {
searchComponent = AnyComponentWithIdentity(
id: "search",
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: self.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
isDark: self.theme.overallDarkAppearance,
state: .generic,
component: AnyComponentWithIdentity(id: "search", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Search",
tintColor: self.theme.rootController.navigationBar.glassBarButtonForegroundColor
)
)),
action: { [weak self] _ in
self?.controllerNode.isSearching = true
self?.requestLayout(transition: .animated(duration: 0.5, curve: .spring))
}
))
)
} else {
searchComponent = nil
}
let closeButtonNode: BarComponentHostNode
if let current = self.closeButtonNode {
closeButtonNode = current
closeButtonNode.component = closeComponent
} else {
closeButtonNode = BarComponentHostNode(component: closeComponent, size: barButtonSize)
self.closeButtonNode = closeButtonNode
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: closeButtonNode)
}
let searchButtonNode: BarComponentHostNode
if let current = self.searchButtonNode {
searchButtonNode = current
searchButtonNode.component = searchComponent
} else {
searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize)
self.searchButtonNode = searchButtonNode
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode)
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)
self.updateNavigationButtons()
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
} }

View file

@ -6,6 +6,9 @@ import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import TelegramStringFormatting import TelegramStringFormatting
import AppBundle import AppBundle
import ComponentFlow
import EdgeEffect
import SearchInputPanelComponent
private func loadCountryCodes() -> [(String, Int)] { private func loadCountryCodes() -> [(String, Int)] {
guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else { guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else {
@ -186,10 +189,12 @@ public func searchCountries(items: [((String, String), String, [Int])], query: S
final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, UITableViewDelegate, UITableViewDataSource { final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, UITableViewDelegate, UITableViewDataSource {
let itemSelected: (((String, String), String, Int)) -> Void let itemSelected: (((String, String), String, Int)) -> Void
var deactivateSearch: () -> Void = {}
private let theme: PresentationTheme private let theme: PresentationTheme
private let strings: PresentationStrings private let strings: PresentationStrings
private let displayCodes: Bool private let displayCodes: Bool
private let glass: Bool
private let needsSubtitle: Bool private let needsSubtitle: Bool
private let tableView: UITableView private let tableView: UITableView
@ -201,19 +206,28 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode,
private var searchResults: [((String, String), String, Int)] = [] private var searchResults: [((String, String), String, Int)] = []
private let countryNamesAndCodes: [((String, String), String, [Int])] private let countryNamesAndCodes: [((String, String), String, [Int])]
init(theme: PresentationTheme, strings: PresentationStrings, displayCodes: Bool, itemSelected: @escaping (((String, String), String, Int)) -> Void) { private let topEdgeEffectView: EdgeEffectView
private var searchInput: ComponentView<Empty>?
var isSearching = true
private var validLayout: ContainerViewLayout?
init(theme: PresentationTheme, strings: PresentationStrings, displayCodes: Bool, glass: Bool, itemSelected: @escaping (((String, String), String, Int)) -> Void) {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.displayCodes = displayCodes self.displayCodes = displayCodes
self.glass = glass
self.itemSelected = itemSelected self.itemSelected = itemSelected
self.needsSubtitle = strings.baseLanguageCode != "en" self.needsSubtitle = strings.baseLanguageCode != "en"
self.tableView = UITableView(frame: CGRect(), style: .plain) self.tableView = UITableView(frame: CGRect(), style: glass ? .insetGrouped : .plain)
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
self.tableView.sectionHeaderTopPadding = 0.0 self.tableView.sectionHeaderTopPadding = 0.0
} }
self.searchTableView = UITableView(frame: CGRect(), style: .plain) self.searchTableView = UITableView(frame: CGRect(), style: glass ? .insetGrouped : .plain)
self.searchTableView.isHidden = true self.searchTableView.isHidden = true
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
@ -239,28 +253,32 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode,
self.sections = sections self.sections = sections
self.sectionTitles = sections.map { $0.0 } self.sectionTitles = sections.map { $0.0 }
self.topEdgeEffectView = EdgeEffectView()
super.init() super.init()
self.setViewBlock({ self.setViewBlock({
return UITracingLayerView() return UITracingLayerView()
}) })
self.backgroundColor = theme.list.plainBackgroundColor if glass {
self.backgroundColor = theme.list.blocksBackgroundColor
self.tableView.backgroundColor = theme.list.plainBackgroundColor self.tableView.backgroundColor = theme.list.blocksBackgroundColor
self.searchTableView.backgroundColor = theme.list.blocksBackgroundColor
self.tableView.backgroundColor = self.theme.list.plainBackgroundColor } else {
self.tableView.separatorColor = self.theme.list.itemPlainSeparatorColor self.backgroundColor = theme.list.plainBackgroundColor
self.tableView.backgroundView = UIView()
self.tableView.sectionIndexColor = self.theme.list.itemAccentColor self.tableView.backgroundColor = self.theme.list.plainBackgroundColor
self.tableView.separatorColor = self.theme.list.itemPlainSeparatorColor
self.searchTableView.backgroundColor = self.theme.list.plainBackgroundColor self.tableView.backgroundView = UIView()
self.tableView.sectionIndexColor = self.theme.list.itemAccentColor
self.searchTableView.backgroundColor = self.theme.list.plainBackgroundColor
self.searchTableView.separatorColor = self.theme.list.itemPlainSeparatorColor self.searchTableView.backgroundColor = self.theme.list.plainBackgroundColor
self.searchTableView.backgroundView = UIView() self.searchTableView.separatorColor = self.theme.list.itemPlainSeparatorColor
self.searchTableView.sectionIndexColor = self.theme.list.itemAccentColor self.searchTableView.backgroundView = UIView()
self.searchTableView.sectionIndexColor = self.theme.list.itemAccentColor
}
self.tableView.delegate = self self.tableView.delegate = self
self.tableView.dataSource = self self.tableView.dataSource = self
@ -269,13 +287,76 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode,
self.view.addSubview(self.tableView) self.view.addSubview(self.tableView)
self.view.addSubview(self.searchTableView) self.view.addSubview(self.searchTableView)
if glass {
self.view.addSubview(self.topEdgeEffectView)
}
} }
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.tableView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0) self.validLayout = layout
self.searchTableView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0) self.tableView.contentInset = UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0)
transition.updateFrame(view: self.tableView, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight))) self.searchTableView.contentInset = UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0)
transition.updateFrame(view: self.searchTableView, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight))) transition.updateFrame(view: self.tableView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
transition.updateFrame(view: self.searchTableView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
let edgeEffectHeight: CGFloat = 88.0
let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame)
self.topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
if self.isSearching {
let searchInput: ComponentView<Empty>
if let current = self.searchInput {
searchInput = current
} else {
searchInput = ComponentView()
self.searchInput = searchInput
}
let searchInputSize = searchInput.update(
transition: .immediate,
component: AnyComponent(
SearchInputPanelComponent(
theme: self.theme,
strings: self.strings,
updated: { [weak self] query in
guard let self else {
return
}
self.updateSearchQuery(query)
},
cancel: { [weak self] in
guard let self else {
return
}
self.deactivateSearch()
self.updateSearchQuery("")
}
)
),
environment: {},
containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)
)
let bottomInset: CGFloat = layout.insets(options: .input).bottom
let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize)
if let searchInputView = searchInput.view as? SearchInputPanelComponent.View {
if searchInputView.superview == nil {
self.view.addSubview(searchInputView)
searchInputView.frame = CGRect(origin: CGPoint(x: searchInputFrame.minX, y: layout.size.height), size: searchInputFrame.size)
searchInputView.activateInput()
}
transition.updateFrame(view: searchInputView, frame: searchInputFrame)
}
} else if let searchInput = self.searchInput {
self.searchInput = nil
if let searchInputView = searchInput.view {
transition.updateFrame(view: searchInputView, frame: CGRect(origin: CGPoint(x: searchInputView.frame.minX, y: layout.size.height), size: searchInputView.frame.size), completion: { _ in
searchInputView.removeFromSuperview()
})
}
}
} }
func animateIn() { func animateIn() {

View file

@ -63,17 +63,17 @@ public final class DatePickerTheme: Equatable {
public extension DatePickerTheme { public extension DatePickerTheme {
convenience init(theme: PresentationTheme) { convenience init(theme: PresentationTheme) {
self.init(backgroundColor: theme.list.itemBlocksBackgroundColor, textColor: theme.list.itemPrimaryTextColor, secondaryTextColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, disabledColor: theme.list.itemDisabledTextColor, selectionColor: theme.list.itemCheckColors.fillColor, selectionTextColor: theme.list.itemCheckColors.foregroundColor, separatorColor: theme.list.itemBlocksSeparatorColor, segmentedControlTheme: SegmentedControlTheme(theme: theme), overallDarkAppearance: theme.overallDarkAppearance) self.init(backgroundColor: theme.list.itemBlocksBackgroundColor, textColor: theme.list.itemPrimaryTextColor, secondaryTextColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, disabledColor: theme.list.itemSecondaryTextColor, selectionColor: theme.list.itemCheckColors.fillColor, selectionTextColor: theme.list.itemCheckColors.foregroundColor, separatorColor: theme.list.itemBlocksSeparatorColor, segmentedControlTheme: SegmentedControlTheme(theme: theme), overallDarkAppearance: theme.overallDarkAppearance)
} }
} }
private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0) private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0)
private let upperLimitDate = Date(timeIntervalSince1970: Double(Int32.max - 1)) private let upperLimitDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
private let controlFont = Font.regular(17.0) private let controlFont = Font.semibold(17.0)
private let dayFont = Font.regular(13.0) private let dayFont = Font.medium(13.0)
private let dateFont = Font.with(size: 17.0, design: .regular, traits: .monospacedNumbers) private let dateFont = Font.with(size: 19.0, design: .regular, traits: .monospacedNumbers)
private let selectedDateFont = Font.with(size: 17.0, design: .regular, weight: .bold, traits: .monospacedNumbers) private let selectedDateFont = Font.with(size: 19.0, design: .regular, weight: .bold, traits: .monospacedNumbers)
private var calendar: Calendar = { private var calendar: Calendar = {
var calendar = Calendar(identifier: .gregorian) var calendar = Calendar(identifier: .gregorian)
@ -295,6 +295,7 @@ public final class DatePickerNode: ASDisplayNode {
private let strings: PresentationStrings private let strings: PresentationStrings
private let dateTimeFormat: PresentationDateTimeFormat private let dateTimeFormat: PresentationDateTimeFormat
private let title: String private let title: String
private let hasValueRow: Bool
private let timeTitleNode: ImmediateTextNode private let timeTitleNode: ImmediateTextNode
@ -387,10 +388,11 @@ public final class DatePickerNode: ASDisplayNode {
} }
} }
public init(theme: DatePickerTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, title: String) { public init(theme: DatePickerTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, title: String = "", hasValueRow: Bool = true) {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.hasValueRow = hasValueRow
self.state = State(minDate: telegramReleaseDate, maxDate: upperLimitDate, date: nil, displayingMonthSelection: false, displayingDateSelection: false, displayingTimeSelection: false, selectedMonth: monthForDate(Date())) self.state = State(minDate: telegramReleaseDate, maxDate: upperLimitDate, date: nil, displayingMonthSelection: false, displayingDateSelection: false, displayingTimeSelection: false, selectedMonth: monthForDate(Date()))
self.title = title self.title = title
@ -483,10 +485,11 @@ public final class DatePickerNode: ASDisplayNode {
self.addSubnode(self.timePickerBackgroundNode) self.addSubnode(self.timePickerBackgroundNode)
self.timePickerBackgroundNode.addSubnode(self.timePickerNode) self.timePickerBackgroundNode.addSubnode(self.timePickerNode)
self.addSubnode(self.timeTitleNode) if hasValueRow {
self.addSubnode(self.timeTitleNode)
self.addSubnode(self.dateButtonNode) self.addSubnode(self.dateButtonNode)
self.addSubnode(self.timeButtonNode) self.addSubnode(self.timeButtonNode)
}
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor) self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal) self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal)
@ -497,21 +500,6 @@ public final class DatePickerNode: ASDisplayNode {
self.setupItems() self.setupItems()
self.monthButtonNode.addTarget(self, action: #selector(self.monthButtonPressed), forControlEvents: .touchUpInside) self.monthButtonNode.addTarget(self, action: #selector(self.monthButtonPressed), forControlEvents: .touchUpInside)
self.monthButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.monthTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.monthTextNode.alpha = 0.4
strongSelf.monthArrowNode.layer.removeAnimation(forKey: "opacity")
strongSelf.monthArrowNode.alpha = 0.4
} else {
strongSelf.monthTextNode.alpha = 1.0
strongSelf.monthTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.monthArrowNode.alpha = 1.0
strongSelf.monthArrowNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.previousButtonNode.addTarget(self, action: #selector(self.previousButtonPressed), forControlEvents: .touchUpInside) self.previousButtonNode.addTarget(self, action: #selector(self.previousButtonPressed), forControlEvents: .touchUpInside)
self.nextButtonNode.addTarget(self, action: #selector(self.nextButtonPressed), forControlEvents: .touchUpInside) self.nextButtonNode.addTarget(self, action: #selector(self.nextButtonPressed), forControlEvents: .touchUpInside)
@ -697,7 +685,7 @@ public final class DatePickerNode: ASDisplayNode {
} }
self.transitionFraction = transitionFraction self.transitionFraction = transitionFraction
if let size = self.validLayout { if let size = self.validLayout {
let topInset: CGFloat = 78.0 + 44.0 let topInset: CGFloat = self.hasValueRow ? 78.0 + 44.0 : 65.0
let containerSize = CGSize(width: size.width, height: size.height - topInset) let containerSize = CGSize(width: size.width, height: size.height - topInset)
self.updateItems(size: containerSize, transition: .animated(duration: 0.3, curve: .spring)) self.updateItems(size: containerSize, transition: .animated(duration: 0.3, curve: .spring))
} }
@ -783,7 +771,7 @@ public final class DatePickerNode: ASDisplayNode {
let constrainedSize = CGSize(width: min(390.0, size.width), height: size.height) let constrainedSize = CGSize(width: min(390.0, size.width), height: size.height)
let timeHeight: CGFloat = 50.0 let timeHeight: CGFloat = 50.0
let topInset: CGFloat = 78.0 + timeHeight let topInset: CGFloat = self.hasValueRow ? 78.0 + timeHeight : 65.0
let sideInset: CGFloat = 16.0 let sideInset: CGFloat = 16.0
let month = monthForDate(self.state.selectedMonth) let month = monthForDate(self.state.selectedMonth)
@ -798,7 +786,7 @@ public final class DatePickerNode: ASDisplayNode {
self.monthTextNode.attributedText = NSAttributedString(string: stringForMonth(strings: self.strings, month: components.month.flatMap { Int32($0) - 1 } ?? 0, ofYear: components.year.flatMap { Int32($0) - 1900 } ?? 100), font: controlFont, textColor: self.state.displayingMonthSelection ? self.theme.accentColor : self.theme.textColor) self.monthTextNode.attributedText = NSAttributedString(string: stringForMonth(strings: self.strings, month: components.month.flatMap { Int32($0) - 1 } ?? 0, ofYear: components.year.flatMap { Int32($0) - 1900 } ?? 100), font: controlFont, textColor: self.state.displayingMonthSelection ? self.theme.accentColor : self.theme.textColor)
let monthSize = self.monthTextNode.updateLayout(size) let monthSize = self.monthTextNode.updateLayout(size)
let monthTextFrame = CGRect(x: sideInset, y: 11.0 + timeHeight, width: monthSize.width, height: monthSize.height) let monthTextFrame = CGRect(x: sideInset, y: self.hasValueRow ? 11.0 + timeHeight : 0.0, width: monthSize.width, height: monthSize.height)
self.monthTextNode.frame = monthTextFrame self.monthTextNode.frame = monthTextFrame
let monthArrowFrame = CGRect(x: monthTextFrame.maxX + 10.0, y: monthTextFrame.minY + 4.0, width: 7.0, height: 12.0) let monthArrowFrame = CGRect(x: monthTextFrame.maxX + 10.0, y: monthTextFrame.minY + 4.0, width: 7.0, height: 12.0)
@ -856,7 +844,7 @@ public final class DatePickerNode: ASDisplayNode {
self.updateItems(size: containerSize, transition: transition) self.updateItems(size: containerSize, transition: transition)
let monthInset: CGFloat = timeHeight + 30.0 let monthInset: CGFloat = self.hasValueRow ? timeHeight + 30.0 : 30.0
self.monthPickerBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: monthInset), size: size) self.monthPickerBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: monthInset), size: size)
self.monthPickerBackgroundNode.isUserInteractionEnabled = self.state.displayingMonthSelection self.monthPickerBackgroundNode.isUserInteractionEnabled = self.state.displayingMonthSelection
transition.updateAlpha(node: self.monthPickerBackgroundNode, alpha: self.state.displayingMonthSelection ? 1.0 : 0.0) transition.updateAlpha(node: self.monthPickerBackgroundNode, alpha: self.state.displayingMonthSelection ? 1.0 : 0.0)
@ -872,7 +860,7 @@ public final class DatePickerNode: ASDisplayNode {
self.datePickerBackgroundNode.isUserInteractionEnabled = self.state.displayingDateSelection self.datePickerBackgroundNode.isUserInteractionEnabled = self.state.displayingDateSelection
transition.updateAlpha(node: self.datePickerBackgroundNode, alpha: self.state.displayingDateSelection ? 1.0 : 0.0) transition.updateAlpha(node: self.datePickerBackgroundNode, alpha: self.state.displayingDateSelection ? 1.0 : 0.0)
self.monthPickerNode.frame = CGRect(x: sideInset, y: topInset - monthInset, width: size.width - sideInset * 2.0, height: 180.0) self.monthPickerNode.frame = CGRect(x: sideInset, y: topInset - monthInset, width: size.width - sideInset * 2.0, height: 260.0)
} }
public var toggleDateSelection: () -> Void = {} public var toggleDateSelection: () -> Void = {}

View file

@ -76,11 +76,11 @@ private enum DebugAccountsControllerEntry: ItemListNodeEntry {
let arguments = arguments as! DebugAccountsControllerArguments let arguments = arguments as! DebugAccountsControllerArguments
switch self { switch self {
case let .record(_, record, current): case let .record(_, record, current):
return ItemListCheckboxItem(presentationData: presentationData, title: "\(UInt64(bitPattern: record.id.int64))", style: .left, checked: current, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: "\(UInt64(bitPattern: record.id.int64))", style: .left, checked: current, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.switchAccount(record.id) arguments.switchAccount(record.id)
}) })
case .loginNewAccount: case .loginNewAccount:
return ItemListActionItem(presentationData: presentationData, title: "Login to another account", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Login to another account", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.loginNewAccount() arguments.loginNewAccount()
}) })
} }

View file

@ -287,7 +287,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
let arguments = arguments as! DebugControllerArguments let arguments = arguments as! DebugControllerArguments
switch self { switch self {
case .testStickerImport: case .testStickerImport:
return ItemListActionItem(presentationData: presentationData, title: "Simulate Stickers Import", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Simulate Stickers Import", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -302,7 +302,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
} }
}) })
case .sendLogs: case .sendLogs:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
let _ = (Logger.shared.collectLogs() let _ = (Logger.shared.collectLogs()
|> deliverOnMainQueue).start(next: { logs in |> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -386,7 +386,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .sendOneLog: case .sendOneLog:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Latest Logs (Up to 4 MB)", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Latest Logs (Up to 4 MB)", label: "", sectionId: self.section, style: .blocks, action: {
let _ = (Logger.shared.collectLogs() let _ = (Logger.shared.collectLogs()
|> deliverOnMainQueue).start(next: { logs in |> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -468,7 +468,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .sendShareLogs: case .sendShareLogs:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Share Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Share Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
let _ = (Logger.shared.collectLogs(prefix: "/logs/share-logs") let _ = (Logger.shared.collectLogs(prefix: "/logs/share-logs")
|> deliverOnMainQueue).start(next: { logs in |> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -552,7 +552,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .sendGroupCallLogs: case .sendGroupCallLogs:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Group Call Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Group Call Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
let _ = (Logger.shared.collectLogs(basePath: arguments.context!.account.basePath + "/group-calls") let _ = (Logger.shared.collectLogs(basePath: arguments.context!.account.basePath + "/group-calls")
|> deliverOnMainQueue).start(next: { logs in |> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -636,7 +636,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .sendNotificationLogs: case .sendNotificationLogs:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Notification Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Notification Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
let logsPath = arguments.sharedContext.basePath + "/logs/notification-logs" let logsPath = arguments.sharedContext.basePath + "/logs/notification-logs"
let _ = (Logger(rootPath: logsPath, basePath: logsPath).collectLogs() let _ = (Logger(rootPath: logsPath, basePath: logsPath).collectLogs()
|> deliverOnMainQueue).start(next: { logs in |> deliverOnMainQueue).start(next: { logs in
@ -721,7 +721,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .sendCriticalLogs: case .sendCriticalLogs:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Critical Logs", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Critical Logs", label: "", sectionId: self.section, style: .blocks, action: {
let _ = (Logger.shared.collectShortLogFiles() let _ = (Logger.shared.collectShortLogFiles()
|> deliverOnMainQueue).start(next: { logs in |> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -774,7 +774,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .sendAllLogs: case .sendAllLogs:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send All Logs", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send All Logs", label: "", sectionId: self.section, style: .blocks, action: {
let logTypes: [String] = [ let logTypes: [String] = [
"app-logs", "app-logs",
"broadcast-logs", "broadcast-logs",
@ -870,7 +870,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .sendStorageStats: case .sendStorageStats:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Storage Stats", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Storage Stats", label: "", sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context, context.sharedContext.applicationBindings.isMainApp else { guard let context = arguments.context, context.sharedContext.applicationBindings.isMainApp else {
return return
} }
@ -925,32 +925,32 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .accounts: case .accounts:
return ItemListDisclosureItem(presentationData: presentationData, title: "Accounts", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Accounts", label: "", sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
arguments.pushController(debugAccountsController(context: context, accountManager: arguments.sharedContext.accountManager)) arguments.pushController(debugAccountsController(context: context, accountManager: arguments.sharedContext.accountManager))
}) })
case let .logToFile(_, value): case let .logToFile(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Log to File", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Log to File", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, { let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, {
$0.withUpdatedLogToFile(value) $0.withUpdatedLogToFile(value)
}).start() }).start()
}) })
case let .logToConsole(_, value): case let .logToConsole(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Log to Console", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Log to Console", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, { let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, {
$0.withUpdatedLogToConsole(value) $0.withUpdatedLogToConsole(value)
}).start() }).start()
}) })
case let .redactSensitiveData(_, value): case let .redactSensitiveData(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Remove Sensitive Data", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Remove Sensitive Data", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, { let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, {
$0.withUpdatedRedactSensitiveData(value) $0.withUpdatedRedactSensitiveData(value)
}).start() }).start()
}) })
case let .keepChatNavigationStack(_, value): case let .keepChatNavigationStack(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Keep Chat Stack", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Keep Chat Stack", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings var settings = settings
settings.keepChatNavigationStack = value settings.keepChatNavigationStack = value
@ -958,7 +958,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .skipReadHistory(_, value): case let .skipReadHistory(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Skip read history", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Skip read history", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings var settings = settings
settings.skipReadHistory = value settings.skipReadHistory = value
@ -966,7 +966,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .alwaysDisplayTyping(value): case let .alwaysDisplayTyping(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Show Typing", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Show Typing", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings var settings = settings
settings.alwaysDisplayTyping = value settings.alwaysDisplayTyping = value
@ -974,7 +974,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .debugRatingLayout(value): case let .debugRatingLayout(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Rating Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Rating Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings var settings = settings
settings.debugRatingLayout = value settings.debugRatingLayout = value
@ -982,7 +982,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .crashOnSlowQueries(_, value): case let .crashOnSlowQueries(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings var settings = settings
settings.crashOnLongQueries = value settings.crashOnLongQueries = value
@ -990,7 +990,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .crashOnMemoryPressure(_, value): case let .crashOnMemoryPressure(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Crash on memory pressure", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Crash on memory pressure", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings var settings = settings
settings.crashOnMemoryPressure = value settings.crashOnMemoryPressure = value
@ -998,7 +998,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case .clearTips: case .clearTips:
return ItemListActionItem(presentationData: presentationData, title: "Clear Tips", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Clear Tips", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in
transaction.clearNotices() transaction.clearNotices()
}).start() }).start()
@ -1012,7 +1012,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
} }
}) })
case let .logTranslationRecognition(value): case let .logTranslationRecognition(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Log Language Recognition", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Log Language Recognition", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings var settings = settings
settings.logLanguageRecognition = value settings.logLanguageRecognition = value
@ -1020,7 +1020,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case .resetTranslationStates: case .resetTranslationStates:
return ItemListActionItem(presentationData: presentationData, title: "Reset Translation States", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Translation States", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
if let context = arguments.context { if let context = arguments.context {
let _ = context.engine.itemCache.clear(collectionIds: [ let _ = context.engine.itemCache.clear(collectionIds: [
ApplicationSpecificItemCacheCollectionId.translationState ApplicationSpecificItemCacheCollectionId.translationState
@ -1028,7 +1028,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
} }
}) })
case .resetNotifications: case .resetNotifications:
return ItemListActionItem(presentationData: presentationData, title: "Reset Notifications", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Notifications", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
UIApplication.shared.unregisterForRemoteNotifications() UIApplication.shared.unregisterForRemoteNotifications()
if let context = arguments.context { if let context = arguments.context {
@ -1037,11 +1037,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
} }
}) })
case .crash: case .crash:
return ItemListActionItem(presentationData: presentationData, title: "Crash", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Crash", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
preconditionFailure() preconditionFailure()
}) })
case .fillLocalSavedMessageCache: case .fillLocalSavedMessageCache:
return ItemListActionItem(presentationData: presentationData, title: "Reload Saved Messages", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reload Saved Messages", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -1054,7 +1054,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .resetDatabase: case .resetDatabase:
return ItemListActionItem(presentationData: presentationData, title: "Clear Database", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Clear Database", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -1076,7 +1076,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
arguments.presentController(actionSheet, nil) arguments.presentController(actionSheet, nil)
}) })
case .resetDatabaseAndCache: case .resetDatabaseAndCache:
return ItemListActionItem(presentationData: presentationData, title: "Clear Database and Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Clear Database and Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -1098,7 +1098,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
arguments.presentController(actionSheet, nil) arguments.presentController(actionSheet, nil)
}) })
case .resetHoles: case .resetHoles:
return ItemListActionItem(presentationData: presentationData, title: "Reset Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -1111,7 +1111,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .resetTagHoles: case .resetTagHoles:
return ItemListActionItem(presentationData: presentationData, title: "Reset Tag Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Tag Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -1124,7 +1124,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .reindexUnread: case .reindexUnread:
return ItemListActionItem(presentationData: presentationData, title: "Reindex Unread Counters", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reindex Unread Counters", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -1137,7 +1137,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case .resetCacheIndex: case .resetCacheIndex:
return ItemListActionItem(presentationData: presentationData, title: "Reset Cache Index [!]", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Cache Index [!]", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -1145,7 +1145,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
context.account.postbox.mediaBox.storageBox.reset() context.account.postbox.mediaBox.storageBox.reset()
}) })
case .reindexCache: case .reindexCache:
return ItemListActionItem(presentationData: presentationData, title: "Reindex Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reindex Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -1185,13 +1185,13 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})) }))
}) })
case .resetBiometricsData: case .resetBiometricsData:
return ItemListActionItem(presentationData: presentationData, title: "Reset Biometrics Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Biometrics Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
let _ = updatePresentationPasscodeSettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updatePresentationPasscodeSettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
return settings.withUpdatedBiometricsDomainState(nil).withUpdatedShareBiometricsDomainState(nil) return settings.withUpdatedBiometricsDomainState(nil).withUpdatedShareBiometricsDomainState(nil)
}).start() }).start()
}) })
case let .webViewInspection(value): case let .webViewInspection(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Allow Web View Inspection", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Allow Web View Inspection", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings var settings = settings
settings.allowWebViewInspection = value settings.allowWebViewInspection = value
@ -1199,11 +1199,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case .resetWebViewCache: case .resetWebViewCache:
return ItemListActionItem(presentationData: presentationData, title: "Clear Web View Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Clear Web View Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{ }) WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{ })
}) })
case .optimizeDatabase: case .optimizeDatabase:
return ItemListActionItem(presentationData: presentationData, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else { guard let context = arguments.context else {
return return
} }
@ -1211,15 +1211,15 @@ private enum DebugControllerEntry: ItemListNodeEntry {
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
arguments.presentController(controller, nil) arguments.presentController(controller, nil)
let _ = (context.account.postbox.optimizeStorage() let _ = (context.account.postbox.optimizeStorage()
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
controller.dismiss() controller.dismiss()
let controller = OverlayStatusController(theme: presentationData.theme, type: .success) let controller = OverlayStatusController(theme: presentationData.theme, type: .success)
arguments.presentController(controller, nil) arguments.presentController(controller, nil)
}) })
}) })
case let .photoPreview(_, value): case let .photoPreview(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Media Preview (Updated)", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Media Preview (Updated)", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1229,7 +1229,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .knockoutWallpaper(_, value): case let .knockoutWallpaper(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Knockout Wallpaper", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Knockout Wallpaper", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1239,7 +1239,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .experimentalCompatibility(value): case let .experimentalCompatibility(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Experimental Compatibility", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Experimental Compatibility", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1249,7 +1249,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .enableDebugDataDisplay(value): case let .enableDebugDataDisplay(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Debug Data Display", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Debug Data Display", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1259,7 +1259,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .fakeGlass(value): case let .fakeGlass(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Fake glass", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Fake glass", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1269,7 +1269,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .browserExperiment(value): case let .browserExperiment(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Inline UI", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Inline UI", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1279,7 +1279,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .allForumsHaveTabs(value): case let .allForumsHaveTabs(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Forum Tabs Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Forum Tabs Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1289,7 +1289,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .enableReactionOverrides(value): case let .enableReactionOverrides(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Effect Overrides", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Effect Overrides", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1303,7 +1303,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .compressedEmojiCache(value): case let .compressedEmojiCache(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Compressed Emoji Cache", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Compressed Emoji Cache", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1313,7 +1313,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .storiesJpegExperiment(value): case let .storiesJpegExperiment(value):
return ItemListSwitchItem(presentationData: presentationData, title: "JPEG X", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "JPEG X", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1323,7 +1323,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .checkSerializedData(value): case let .checkSerializedData(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Check Serialized Data", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Check Serialized Data", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1333,7 +1333,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .enableQuickReactionSwitch(value): case let .enableQuickReactionSwitch(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Enable Quick Reaction", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Enable Quick Reaction", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1343,7 +1343,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .liveStreamV2(value): case let .liveStreamV2(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Live Stream V2", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Live Stream V2", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1353,7 +1353,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .experimentalCallMute(value): case let .experimentalCallMute(value):
return ItemListSwitchItem(presentationData: presentationData, title: "[WIP] OS mic mute", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "[WIP] OS mic mute", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1363,7 +1363,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .playerV2(value): case let .playerV2(value):
return ItemListSwitchItem(presentationData: presentationData, title: "PlayerV2", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "PlayerV2", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1373,7 +1373,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .devRequests(value): case let .devRequests(value):
return ItemListSwitchItem(presentationData: presentationData, title: "DevRequests", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "DevRequests", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1383,7 +1383,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .enableUpdates(value): case let .enableUpdates(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Enable Updates", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Enable Updates", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1393,7 +1393,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .fakeAds(value): case let .fakeAds(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Fake Ads", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Fake Ads", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1403,7 +1403,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .enableLocalTranslation(value): case let .enableLocalTranslation(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Local Translation", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Local Translation", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1413,7 +1413,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .preferredVideoCodec(_, title, value, isSelected): case let .preferredVideoCodec(_, title, value, isSelected):
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1423,7 +1423,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .disableVideoAspectScaling(value): case let .disableVideoAspectScaling(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Video Cropping Optimization", value: !value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Video Cropping Optimization", value: !value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1433,7 +1433,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start() }).start()
}) })
case let .enableNetworkFramework(value): case let .enableNetworkFramework(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Network X [Restart App]", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Network X [Restart App]", value: value, sectionId: self.section, style: .blocks, updated: { value in
if let context = arguments.context { if let context = arguments.context {
let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in
var settings = settings var settings = settings
@ -1443,7 +1443,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
} }
}) })
case let .enableNetworkExperiments(value): case let .enableNetworkExperiments(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Download X [Restart App]", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Download X [Restart App]", value: value, sectionId: self.section, style: .blocks, updated: { value in
if let context = arguments.context { if let context = arguments.context {
let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in
var settings = settings var settings = settings
@ -1453,7 +1453,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
} }
}) })
case .restorePurchases: case .restorePurchases:
return ItemListActionItem(presentationData: presentationData, title: "Restore Purchases", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Restore Purchases", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.context?.inAppPurchaseManager?.restorePurchases(completion: { state in arguments.context?.inAppPurchaseManager?.restorePurchases(completion: { state in
let text: String let text: String
switch state { switch state {
@ -1469,7 +1469,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}) })
case let .disableReloginTokens(value): case let .disableReloginTokens(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Disable Relogin Tokens", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Disable Relogin Tokens", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings var settings = settings
settings.disableReloginTokens = value settings.disableReloginTokens = value

View file

@ -250,7 +250,7 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega
bottomController.viewWillAppear(true) bottomController.viewWillAppear(true)
let bottomNode = bottomController.displayNode let bottomNode = bottomController.displayNode
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, isInteractive: true, isFlat: self.isFlat, container: self, topNode: topNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.transitionNavigationBar, didUpdateProgress: { [weak self, weak bottomController] progress, transition, topFrame, bottomFrame in let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, isInteractive: true, isFlat: self.isFlat, container: self, topNode: topNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: layout.deviceMetrics.screenCornerRadius, didUpdateProgress: { [weak self, weak bottomController] progress, transition, topFrame, bottomFrame in
if let strongSelf = self { if let strongSelf = self {
if let top = strongSelf.state.top { if let top = strongSelf.state.top {
strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition) strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition)
@ -490,7 +490,7 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega
} }
toValue.value.setIgnoreAppearanceMethodInvocations(false) toValue.value.setIgnoreAppearanceMethodInvocations(false)
let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, isInteractive: false, isFlat: self.isFlat, container: self, topNode: topController.displayNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.transitionNavigationBar, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, isInteractive: false, isFlat: self.isFlat, container: self, topNode: topController.displayNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: layout.deviceMetrics.screenCornerRadius, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View file

@ -388,7 +388,11 @@ final class NavigationModalContainer: ASDisplayNode, ASScrollViewDelegate, ASGes
if isStandaloneModal || isLandscape || (self.isFlat && !flatReceivesModalTransition) { if isStandaloneModal || isLandscape || (self.isFlat && !flatReceivesModalTransition) {
self.container.cornerRadius = 0.0 self.container.cornerRadius = 0.0
} else { } else {
self.container.cornerRadius = 10.0 var cornerRadius: CGFloat = 10.0
if let controller = controllers.first, controller._hasGlassStyle {
cornerRadius = 38.0
}
self.container.cornerRadius = cornerRadius
} }
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {

View file

@ -109,7 +109,7 @@ public final class NavigationModalFrame: ASDisplayNode {
let contentScale = (layout.size.width - sideInset * 2.0) / layout.size.width let contentScale = (layout.size.width - sideInset * 2.0) / layout.size.width
let bottomInset: CGFloat = layout.size.height - contentScale * layout.size.height - topInset let bottomInset: CGFloat = layout.size.height - contentScale * layout.size.height - topInset
let cornerRadius: CGFloat = 9.0 let cornerRadius: CGFloat = 28.0
let initialCornerRadius: CGFloat let initialCornerRadius: CGFloat
if !layout.safeInsets.top.isZero { if !layout.safeInsets.top.isZero {
initialCornerRadius = layout.deviceMetrics.screenCornerRadius initialCornerRadius = layout.deviceMetrics.screenCornerRadius

View file

@ -1503,7 +1503,12 @@ open class NavigationBar: ASDisplayNode {
transition = .immediate transition = .immediate
} }
self.titleNode.alpha = 1.0 self.titleNode.alpha = 1.0
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
var titleOffset: CGFloat = 0.0
if self.presentationData.theme.backgroundColor == .clear && self.presentationData.theme.separatorColor == .clear {
titleOffset += 3.0
}
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + titleOffset + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
} }
} }

View file

@ -46,6 +46,8 @@ final class NavigationTransitionCoordinator {
private let shadowNode: ASImageNode private let shadowNode: ASImageNode
private let customTransitionNode: CustomNavigationTransitionNode? private let customTransitionNode: CustomNavigationTransitionNode?
private var topNodeInitialParameters: (clipsToBounds: Bool, cornerRadius: CGFloat)?
private let inlineNavigationBarTransition: Bool private let inlineNavigationBarTransition: Bool
private(set) var animatingCompletion = false private(set) var animatingCompletion = false
@ -54,7 +56,7 @@ final class NavigationTransitionCoordinator {
private var frameRateLink: SharedDisplayLinkDriver.Link? private var frameRateLink: SharedDisplayLinkDriver.Link?
init(transition: NavigationTransition, isInteractive: Bool, isFlat: Bool, container: NavigationContainer, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) { init(transition: NavigationTransition, isInteractive: Bool, isFlat: Bool, container: NavigationContainer, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, screenCornerRadius: CGFloat, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) {
self.transition = transition self.transition = transition
self.isInteractive = isInteractive self.isInteractive = isInteractive
self.isFlat = isFlat self.isFlat = isFlat
@ -109,6 +111,15 @@ final class NavigationTransitionCoordinator {
if !self.isFlat { if !self.isFlat {
self.container.insertSubnode(self.dimNode, belowSubnode: topNode) self.container.insertSubnode(self.dimNode, belowSubnode: topNode)
self.container.insertSubnode(self.shadowNode, belowSubnode: self.dimNode) self.container.insertSubnode(self.shadowNode, belowSubnode: self.dimNode)
if screenCornerRadius > 0.0 {
self.topNodeInitialParameters = (topNode.clipsToBounds, topNode.cornerRadius)
if #available(iOS 13.0, *) {
topNode.layer.cornerCurve = .continuous
}
topNode.clipsToBounds = true
topNode.cornerRadius = screenCornerRadius
}
} }
if let customTransitionNode = self.customTransitionNode { if let customTransitionNode = self.customTransitionNode {
self.container.addSubnode(customTransitionNode) self.container.addSubnode(customTransitionNode)
@ -159,7 +170,7 @@ final class NavigationTransitionCoordinator {
} }
}) })
canInvokeCompletion = true canInvokeCompletion = true
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, topFrame.minX + self.container.overflowInset), height: self.container.bounds.size.height - dimInset))) transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, topFrame.minX + self.container.overflowInset + 62.0), height: self.container.bounds.size.height - dimInset)))
transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: self.dimNode.frame.maxX - shadowWidth, y: dimInset), size: CGSize(width: shadowWidth, height: containerSize.height - dimInset))) transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: self.dimNode.frame.maxX - shadowWidth, y: dimInset), size: CGSize(width: shadowWidth, height: containerSize.height - dimInset)))
transition.updateAlpha(node: self.dimNode, alpha: (1.0 - position) * 0.15) transition.updateAlpha(node: self.dimNode, alpha: (1.0 - position) * 0.15)
transition.updateAlpha(node: self.shadowNode, alpha: (1.0 - position) * 0.9) transition.updateAlpha(node: self.shadowNode, alpha: (1.0 - position) * 0.9)
@ -241,6 +252,8 @@ final class NavigationTransitionCoordinator {
strongSelf.endNavigationBarTransition() strongSelf.endNavigationBarTransition()
strongSelf.restoreTopNodeCorners()
if let currentCompletion = strongSelf.currentCompletion { if let currentCompletion = strongSelf.currentCompletion {
strongSelf.currentCompletion = nil strongSelf.currentCompletion = nil
currentCompletion() currentCompletion()
@ -261,6 +274,8 @@ final class NavigationTransitionCoordinator {
self.endNavigationBarTransition() self.endNavigationBarTransition()
self.restoreTopNodeCorners()
if let currentCompletion = self.currentCompletion { if let currentCompletion = self.currentCompletion {
self.currentCompletion = nil self.currentCompletion = nil
currentCompletion() currentCompletion()
@ -278,6 +293,8 @@ final class NavigationTransitionCoordinator {
strongSelf.endNavigationBarTransition() strongSelf.endNavigationBarTransition()
strongSelf.restoreTopNodeCorners()
if let currentCompletion = strongSelf.currentCompletion { if let currentCompletion = strongSelf.currentCompletion {
strongSelf.currentCompletion = nil strongSelf.currentCompletion = nil
currentCompletion() currentCompletion()
@ -300,6 +317,8 @@ final class NavigationTransitionCoordinator {
self.endNavigationBarTransition() self.endNavigationBarTransition()
self.restoreTopNodeCorners()
if let currentCompletion = self.currentCompletion { if let currentCompletion = self.currentCompletion {
self.currentCompletion = nil self.currentCompletion = nil
currentCompletion() currentCompletion()
@ -316,4 +335,15 @@ final class NavigationTransitionCoordinator {
}) })
} }
} }
private func restoreTopNodeCorners() {
guard let (clipsToBounds, cornerRadius) = self.topNodeInitialParameters else {
return
}
if #available(iOS 13.0, *) {
self.topNode.layer.cornerCurve = .circular
}
self.topNode.clipsToBounds = clipsToBounds
self.topNode.cornerRadius = cornerRadius
}
} }

View file

@ -243,6 +243,58 @@ public extension UIColor {
return UIColor(hue: max(0.0, min(1.0, hueValue * hue)), saturation: max(0.0, min(1.0, saturationValue * saturation)), brightness: max(0.0, min(1.0, brightnessValue * brightness)), alpha: alphaValue) return UIColor(hue: max(0.0, min(1.0, hueValue * hue)), saturation: max(0.0, min(1.0, saturationValue * saturation)), brightness: max(0.0, min(1.0, brightnessValue * brightness)), alpha: alphaValue)
} }
func desaturatedHSL(by amount: CGFloat) -> UIColor {
let amount = max(0, min(1, amount))
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
guard self.getRed(&r, green: &g, blue: &b, alpha: &a) else { return self }
let maxC = max(r, g, b)
let minC = min(r, g, b)
let delta = maxC - minC
var h: CGFloat = 0
let l: CGFloat = (maxC + minC) / 2
var s: CGFloat = 0
if delta != 0 {
s = delta / (1 - abs(2 * l - 1))
if maxC == r {
h = ((g - b) / delta).truncatingRemainder(dividingBy: 6)
} else if maxC == g {
h = ((b - r) / delta) + 2
} else {
h = ((r - g) / delta) + 4
}
h /= 6
if h < 0 { h += 1 }
}
let s2 = s * (1 - amount)
func hue2rgb(_ p: CGFloat, _ q: CGFloat, _ t: CGFloat) -> CGFloat {
var t = t
if t < 0 { t += 1 }
if t > 1 { t -= 1 }
if t < 1/6 { return p + (q - p) * 6 * t }
if t < 1/2 { return q }
if t < 2/3 { return p + (q - p) * (2/3 - t) * 6 }
return p
}
let q: CGFloat = l < 0.5 ? l * (1 + s2) : l + s2 - l * s2
let p: CGFloat = 2 * l - q
let r2 = hue2rgb(p, q, h + 1/3)
let g2 = hue2rgb(p, q, h)
let b2 = hue2rgb(p, q, h - 1/3)
return UIColor(red: r2, green: g2, blue: b2, alpha: a)
}
func desaturated() -> UIColor {
return desaturatedHSL(by: 1.0)
}
func mixedWith(_ other: UIColor, alpha: CGFloat) -> UIColor { func mixedWith(_ other: UIColor, alpha: CGFloat) -> UIColor {
let alpha = min(1.0, max(0.0, alpha)) let alpha = min(1.0, max(0.0, alpha))
let oneMinusAlpha = 1.0 - alpha let oneMinusAlpha = 1.0 - alpha

View file

@ -163,6 +163,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
open var navigationPresentation: ViewControllerNavigationPresentation = .default open var navigationPresentation: ViewControllerNavigationPresentation = .default
open var _presentedInModal: Bool = false open var _presentedInModal: Bool = false
open var _hasGlassStyle: Bool = false
open var flatReceivesModalTransition: Bool = false open var flatReceivesModalTransition: Bool = false
public var presentedOverCoveringView: Bool = false public var presentedOverCoveringView: Bool = false
@ -241,7 +242,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
var defaultNavigationBarHeight: CGFloat var defaultNavigationBarHeight: CGFloat
if self._presentedInModal && layout.orientation == .portrait { if self._presentedInModal && layout.orientation == .portrait {
defaultNavigationBarHeight = 56.0 defaultNavigationBarHeight = self._hasGlassStyle ? 66.0 : 56.0
} else { } else {
defaultNavigationBarHeight = 44.0 defaultNavigationBarHeight = 44.0
} }

View file

@ -150,7 +150,7 @@ final class InviteRequestsSearchItem: ItemListControllerSearch {
} }
} }
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
if let current = current as? SearchNavigationContentNode { if let current = current as? SearchNavigationContentNode {
current.updateTheme(presentationData.theme) current.updateTheme(presentationData.theme)

View file

@ -33,6 +33,7 @@ private enum ItemBackgroundColor: Equatable {
public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem { public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let invite: ExportedChatFolderLink? let invite: ExportedChatFolderLink?
let share: Bool let share: Bool
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
@ -44,6 +45,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
public init( public init(
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
invite: ExportedChatFolderLink?, invite: ExportedChatFolderLink?,
share: Bool, share: Bool,
sectionId: ItemListSectionId, sectionId: ItemListSectionId,
@ -54,6 +56,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
tag: ItemListItemTag? = nil tag: ItemListItemTag? = nil
) { ) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.invite = invite self.invite = invite
self.share = share self.share = share
self.sectionId = sectionId self.sectionId = sectionId
@ -319,7 +322,10 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode
let leftInset: CGFloat = 65.0 + params.leftInset let leftInset: CGFloat = 65.0 + params.leftInset
let rightInset: CGFloat = 16.0 + params.rightInset let rightInset: CGFloat = 16.0 + params.rightInset
let verticalInset: CGFloat = subtitleAttributedString.string.isEmpty ? 14.0 : 8.0 var verticalInset: CGFloat = subtitleAttributedString.string.isEmpty ? 14.0 : 8.0
if case .glass = item.systemStyle {
verticalInset += 4.0
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -347,6 +353,7 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode
let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight))
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -453,12 +460,12 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))
} }
let iconSize: CGSize = CGSize(width: 40.0, height: 40.0) let iconSize: CGSize = CGSize(width: 40.0, height: 40.0)

View file

@ -145,6 +145,7 @@ public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem {
} }
let itemContext: ItemContext let itemContext: ItemContext
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let mode: ItemListAvatarAndNameInfoItemMode let mode: ItemListAvatarAndNameInfoItemMode
let peer: EnginePeer? let peer: EnginePeer?
@ -166,9 +167,10 @@ public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem {
public let selectable: Bool public let selectable: Bool
public init(itemContext: ItemContext, presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, mode: ItemListAvatarAndNameInfoItemMode, peer: EnginePeer?, presence: EnginePeer.Presence?, label: String? = nil, memberCount: Int?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListAvatarAndNameInfoItemStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, editingNameCompleted: @escaping () -> Void = {}, avatarTapped: @escaping () -> Void, context: ItemListAvatarAndNameInfoItemContext? = nil, updatingImage: ItemListAvatarAndNameInfoItemUpdatingAvatar? = nil, call: (() -> Void)? = nil, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, tag: ItemListItemTag? = nil) { public init(itemContext: ItemContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, dateTimeFormat: PresentationDateTimeFormat, mode: ItemListAvatarAndNameInfoItemMode, peer: EnginePeer?, presence: EnginePeer.Presence?, label: String? = nil, memberCount: Int?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListAvatarAndNameInfoItemStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, editingNameCompleted: @escaping () -> Void = {}, avatarTapped: @escaping () -> Void, context: ItemListAvatarAndNameInfoItemContext? = nil, updatingImage: ItemListAvatarAndNameInfoItemUpdatingAvatar? = nil, call: (() -> Void)? = nil, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, tag: ItemListItemTag? = nil) {
self.itemContext = itemContext self.itemContext = itemContext
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.mode = mode self.mode = mode
self.peer = peer self.peer = peer
@ -511,6 +513,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
let (statusNodeLayout, statusNodeApply) = layoutStatusNode(TextNodeLayoutArguments(attributedString: NSAttributedString(string: statusText, font: statusFont, textColor: statusColor), backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: availableStatusWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (statusNodeLayout, statusNodeApply) = layoutStatusNode(TextNodeLayoutArguments(attributedString: NSAttributedString(string: statusText, font: statusFont, textColor: statusColor), backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: availableStatusWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let nameSpacing: CGFloat = 3.0 let nameSpacing: CGFloat = 3.0
@ -528,7 +531,13 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
contentSize = CGSize(width: params.width, height: max(baseHeight, verticalInset * 2.0 + 66.0)) contentSize = CGSize(width: params.width, height: max(baseHeight, verticalInset * 2.0 + 66.0))
insets = itemListNeighborsPlainInsets(neighbors) insets = itemListNeighborsPlainInsets(neighbors)
case let .blocks(withTopInset, withExtendedBottomInset): case let .blocks(withTopInset, withExtendedBottomInset):
let verticalInset: CGFloat = 13.0 let verticalInset: CGFloat
switch item.systemStyle {
case .glass:
verticalInset = 17.0
case .legacy:
verticalInset = 13.0
}
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
let baseHeight = nameNodeLayout.size.height + nameSpacing + statusNodeLayout.size.height + 30.0 let baseHeight = nameNodeLayout.size.height + nameSpacing + statusNodeLayout.size.height + 30.0
@ -667,13 +676,13 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height)) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel))
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layoutSize.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: layoutSize.height - insets.top - insets.bottom), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: layoutSize.height - insets.top - insets.bottom), size: CGSize(width: layoutSize.width - bottomStripeInset - separatorRightInset, height: separatorHeight))
} }
let _ = nameNodeApply() let _ = nameNodeApply()
@ -820,7 +829,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
strongSelf.addSubnode(strongSelf.inputSecondClearButton!) strongSelf.addSubnode(strongSelf.inputSecondClearButton!)
} }
strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 46.0), size: CGSize(width: params.width - params.leftInset - 100.0, height: separatorHeight)) strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 46.0), size: CGSize(width: params.width - params.leftInset - 100.0 - separatorRightInset, height: separatorHeight))
strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 12.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 30.0)) strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 12.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 30.0))
strongSelf.inputSecondField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 52.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 30.0)) strongSelf.inputSecondField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 52.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 30.0))
@ -882,7 +891,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
strongSelf.addSubnode(strongSelf.inputFirstClearButton!) strongSelf.addSubnode(strongSelf.inputFirstClearButton!)
} }
strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 64.0), size: CGSize(width: params.width - params.leftInset - 100.0, height: separatorHeight)) strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 64.0), size: CGSize(width: params.width - params.leftInset - 100.0 - separatorRightInset, height: separatorHeight))
strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 28.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 35.0)) strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 28.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 35.0))
if let image = strongSelf.inputFirstClearButton?.image(for: []), let inputFieldFrame = strongSelf.inputFirstField?.frame { if let image = strongSelf.inputFirstClearButton?.image(for: []), let inputFieldFrame = strongSelf.inputFirstField?.frame {

View file

@ -22,6 +22,7 @@ public enum ItemListPeerActionItemColor {
public class ItemListPeerActionItem: ListViewItem, ItemListItem { public class ItemListPeerActionItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let style: ItemListStyle let style: ItemListStyle
let systemStyle: ItemListSystemStyle
let icon: UIImage? let icon: UIImage?
let iconSignal: Signal<UIImage?, NoError>? let iconSignal: Signal<UIImage?, NoError>?
let title: String let title: String
@ -35,9 +36,10 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
public let action: (() -> Void)? public let action: (() -> Void)?
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .blocks, icon: UIImage?, iconSignal: Signal<UIImage?, NoError>? = nil, title: String, additionalBadgeIcon: UIImage? = nil, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, noInsets: Bool = false, editing: Bool = false, action: (() -> Void)?) { public init(presentationData: ItemListPresentationData, style: ItemListStyle = .blocks, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage?, iconSignal: Signal<UIImage?, NoError>? = nil, title: String, additionalBadgeIcon: UIImage? = nil, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, noInsets: Bool = false, editing: Bool = false, action: (() -> Void)?) {
self.presentationData = presentationData self.presentationData = presentationData
self.style = style self.style = style
self.systemStyle = systemStyle
self.icon = icon self.icon = icon
self.iconSignal = iconSignal self.iconSignal = iconSignal
self.title = title self.title = title
@ -186,22 +188,33 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
let leftInset: CGFloat let leftInset: CGFloat
let iconOffset: CGFloat let iconOffset: CGFloat
let verticalInset: CGFloat let verticalInset: CGFloat
let verticalOffset: CGFloat
switch item.height { switch item.height {
case .generic: case .generic:
iconOffset = 1.0 iconOffset = 1.0
verticalInset = 12.0 switch item.systemStyle {
verticalOffset = 0.0 case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 12.0
}
leftInset = (item.icon == nil && item.iconSignal == nil ? 16.0 : 59.0) + params.leftInset leftInset = (item.icon == nil && item.iconSignal == nil ? 16.0 : 59.0) + params.leftInset
case .peerList: case .peerList:
iconOffset = 3.0 iconOffset = 3.0
verticalInset = 14.0 switch item.systemStyle {
verticalOffset = 0.0 case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 14.0
}
leftInset = 65.0 + params.leftInset leftInset = 65.0 + params.leftInset
case .compactPeerList: case .compactPeerList:
iconOffset = 3.0 iconOffset = 3.0
verticalInset = 11.0 switch item.systemStyle {
verticalOffset = 0.0 case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 11.0
}
leftInset = 65.0 + params.leftInset leftInset = 65.0 + params.leftInset
} }
@ -220,6 +233,7 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - editingOffset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - editingOffset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
var insets = itemListNeighborsGroupedInsets(neighbors, params) var insets = itemListNeighborsGroupedInsets(neighbors, params)
if item.noInsets { if item.noInsets {
@ -264,7 +278,7 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
strongSelf.iconNode.image = item.icon strongSelf.iconNode.image = item.icon
if let image = item.icon { if let image = item.icon {
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + iconOffset, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)) transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + iconOffset, y: floorToScreenPixels((contentSize.height - image.size.height) / 2.0)), size: image.size))
} else if let iconSignal = item.iconSignal { } else if let iconSignal = item.iconSignal {
let imageSize = CGSize(width: 28.0, height: 28.0) let imageSize = CGSize(width: 28.0, height: 28.0)
strongSelf.iconDisposable.set((iconSignal strongSelf.iconDisposable.set((iconSignal
@ -330,15 +344,15 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners || !item.hasSeparator strongSelf.bottomStripeNode.isHidden = hasCorners || !item.hasSeparator
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - separatorRightInset - params.rightInset, height: separatorHeight)))
} }
let titleFrame = CGRect(origin: CGPoint(x: leftInset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size) let titleFrame = CGRect(origin: CGPoint(x: leftInset + editingOffset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size)
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame) transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))

View file

@ -440,6 +440,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
} }
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder let nameDisplayOrder: PresentationPersonNameOrder
let context: Context let context: Context
@ -482,6 +483,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
public init( public init(
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle = .legacy,
dateTimeFormat: PresentationDateTimeFormat, dateTimeFormat: PresentationDateTimeFormat,
nameDisplayOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder,
context: AccountContext, context: AccountContext,
@ -523,6 +525,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
openStories: ((UIView) -> Void)? = nil openStories: ((UIView) -> Void)? = nil
) { ) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
self.context = .account(context) self.context = .account(context)
@ -566,6 +569,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
public init( public init(
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle = .legacy,
dateTimeFormat: PresentationDateTimeFormat, dateTimeFormat: PresentationDateTimeFormat,
nameDisplayOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder,
context: Context, context: Context,
@ -607,6 +611,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
openStories: ((UIView) -> Void)? = nil openStories: ((UIView) -> Void)? = nil
) { ) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
self.context = context self.context = context
@ -1153,7 +1158,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
switch item.height { switch item.height {
case .generic: case .generic:
if case .none = item.text { if case .none = item.text {
verticalInset = 11.0 if case .glass = item.systemStyle {
verticalInset = 15.0
} else {
verticalInset = 11.0
}
} else { } else {
verticalInset = 6.0 verticalInset = 6.0
} }
@ -1163,9 +1172,17 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
avatarFontSize = floor(31.0 * 16.0 / 37.0) avatarFontSize = floor(31.0 * 16.0 / 37.0)
case .peerList: case .peerList:
if case .none = item.text { if case .none = item.text {
verticalInset = 14.0 if case .glass = item.systemStyle {
verticalInset = 15.0
} else {
verticalInset = 14.0
}
} else { } else {
verticalInset = 8.0 if case .glass = item.systemStyle {
verticalInset = 10.0
} else {
verticalInset = 8.0
}
} }
verticalOffset = 0.0 verticalOffset = 0.0
avatarSize = 40.0 avatarSize = 40.0
@ -1266,6 +1283,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight))
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size let layoutSize = layout.size
@ -1438,12 +1456,12 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.bottomStripeNode.isHidden = hasCorners || !item.displayDecorations strongSelf.bottomStripeNode.isHidden = hasCorners || !item.displayDecorations
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)))
var titleFrame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size) var titleFrame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size)

View file

@ -40,6 +40,7 @@ public enum ItemListStickerPackItemControl: Equatable {
public final class ItemListStickerPackItem: ListViewItem, ItemListItem { public final class ItemListStickerPackItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let context: AccountContext let context: AccountContext
let systemStyle: ItemListSystemStyle
let packInfo: StickerPackCollectionInfo.Accessor let packInfo: StickerPackCollectionInfo.Accessor
let itemCount: String let itemCount: String
let topItem: StickerPackItem? let topItem: StickerPackItem?
@ -56,9 +57,10 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem {
let removePack: () -> Void let removePack: () -> Void
let toggleSelected: () -> Void let toggleSelected: () -> Void
public init(presentationData: ItemListPresentationData, context: AccountContext, packInfo: StickerPackCollectionInfo.Accessor, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, style: ItemListStyle = .blocks, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) { public init(presentationData: ItemListPresentationData, context: AccountContext, systemStyle: ItemListSystemStyle = .legacy, packInfo: StickerPackCollectionInfo.Accessor, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, style: ItemListStyle = .blocks, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) {
self.presentationData = presentationData self.presentationData = presentationData
self.context = context self.context = context
self.systemStyle = systemStyle
self.packInfo = packInfo self.packInfo = packInfo
self.itemCount = itemCount self.itemCount = itemCount
self.topItem = topItem self.topItem = topItem
@ -409,10 +411,19 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
let leftInset: CGFloat = 65.0 + params.leftInset let leftInset: CGFloat = 65.0 + params.leftInset
let verticalInset: CGFloat = 11.0 let verticalInset: CGFloat
switch item.systemStyle {
case .glass:
verticalInset = 13.0
case .legacy:
verticalInset = 11.0
}
let titleSpacing: CGFloat = 2.0 let titleSpacing: CGFloat = 2.0
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let insets: UIEdgeInsets let insets: UIEdgeInsets
let itemBackgroundColor: UIColor let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor let itemSeparatorColor: UIColor
@ -775,13 +786,13 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size)
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)))
if let unreadImage = unreadImage { if let unreadImage = unreadImage {
strongSelf.unreadNode.image = unreadImage strongSelf.unreadNode.image = unreadImage

View file

@ -121,6 +121,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
private var tabsNavigationContentNode: ItemListControllerTabsContentNode? private var tabsNavigationContentNode: ItemListControllerTabsContentNode?
private var presentationData: ItemListPresentationData private var presentationData: ItemListPresentationData
private let hideNavigationBarBackground: Bool
private var validLayout: ContainerViewLayout? private var validLayout: ContainerViewLayout?
@ -265,15 +266,22 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
} }
} }
public init<ItemGenerationArguments>(presentationData: ItemListPresentationData, updatedPresentationData: Signal<ItemListPresentationData, NoError>, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>?) { public init<ItemGenerationArguments>(
presentationData: ItemListPresentationData,
updatedPresentationData: Signal<ItemListPresentationData, NoError>,
state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>,
tabBarItem: Signal<ItemListControllerTabBarItem, NoError>?,
hideNavigationBarBackground: Bool = false
) {
self.state = state self.state = state
|> map { controllerState, nodeStateAndArgument -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { controllerState, nodeStateAndArgument -> (ItemListControllerState, (ItemListNodeState, Any)) in
return (controllerState, (nodeStateAndArgument.0, nodeStateAndArgument.1)) return (controllerState, (nodeStateAndArgument.0, nodeStateAndArgument.1))
} }
self.presentationData = presentationData self.presentationData = presentationData
self.hideNavigationBarBackground = hideNavigationBarBackground
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideNavigationBarBackground, hideSeparator: hideNavigationBarBackground), strings: NavigationBarStrings(presentationStrings: presentationData.strings)))
self.isOpaqueWhenInOverlay = true self.isOpaqueWhenInOverlay = true
self.blocksBackgroundWhenInOverlay = true self.blocksBackgroundWhenInOverlay = true
@ -392,7 +400,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
} }
} }
strongSelf.navigationButtonActions = (left: controllerState.leftNavigationButton?.action, right: controllerState.rightNavigationButton?.action, secondaryRight: controllerState.secondaryRightNavigationButton?.action) strongSelf.navigationButtonActions = (left: controllerState.leftNavigationButton?.action, right: controllerState.rightNavigationButton?.action, secondaryRight: controllerState.secondaryRightNavigationButton?.action)
let themeUpdated = strongSelf.presentationData != controllerState.presentationData let themeUpdated = strongSelf.presentationData != controllerState.presentationData
if strongSelf.leftNavigationButtonTitleAndStyle?.0 != controllerState.leftNavigationButton?.content || strongSelf.leftNavigationButtonTitleAndStyle?.1 != controllerState.leftNavigationButton?.style || themeUpdated { if strongSelf.leftNavigationButtonTitleAndStyle?.0 != controllerState.leftNavigationButton?.content || strongSelf.leftNavigationButtonTitleAndStyle?.1 != controllerState.leftNavigationButton?.style || themeUpdated {
if let leftNavigationButton = controllerState.leftNavigationButton { if let leftNavigationButton = controllerState.leftNavigationButton {
@ -510,7 +518,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
if strongSelf.presentationData != controllerState.presentationData { if strongSelf.presentationData != controllerState.presentationData {
strongSelf.presentationData = controllerState.presentationData strongSelf.presentationData = controllerState.presentationData
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme, hideBackground: strongSelf.hideNavigationBarBackground, hideSeparator: strongSelf.hideNavigationBarBackground), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)))
strongSelf.statusBar.updateStatusBarStyle(strongSelf.presentationData.theme.rootController.statusBarStyle.style, animated: true) strongSelf.statusBar.updateStatusBarStyle(strongSelf.presentationData.theme.rootController.statusBarStyle.style, animated: true)
strongSelf.segmentedTitleView?.theme = controllerState.presentationData.theme strongSelf.segmentedTitleView?.theme = controllerState.presentationData.theme

View file

@ -70,6 +70,11 @@ public enum ItemListStyle {
case blocks case blocks
} }
public enum ItemListSystemStyle {
case glass
case legacy
}
open class ItemListToolbarItem { open class ItemListToolbarItem {
public struct Action { public struct Action {
public let title: String public let title: String
@ -887,13 +892,13 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
if let titleContentNode = self.navigationBar.contentNode as? ItemListControllerSearchNavigationContentNode { if let titleContentNode = self.navigationBar.contentNode as? ItemListControllerSearchNavigationContentNode {
titleContentNode.deactivate() titleContentNode.deactivate()
} }
updatedTitleContentNode.setQueryUpdated { [weak self] query in updatedTitleContentNode?.setQueryUpdated { [weak self] query in
if let strongSelf = self { if let strongSelf = self {
strongSelf.searchNode?.queryUpdated(query) strongSelf.searchNode?.queryUpdated(query)
} }
} }
self.navigationBar.setContentNode(updatedTitleContentNode, animated: true) self.navigationBar.setContentNode(updatedTitleContentNode, animated: true)
updatedTitleContentNode.activate() updatedTitleContentNode?.activate()
} }
let updatedNode = searchItem.node(current: self.searchNode, titleContentNode: updatedTitleContentNode) let updatedNode = searchItem.node(current: self.searchNode, titleContentNode: updatedTitleContentNode)

View file

@ -12,7 +12,7 @@ public protocol ItemListControllerSearchNavigationContentNode {
public protocol ItemListControllerSearch { public protocol ItemListControllerSearch {
func isEqual(to: ItemListControllerSearch) -> Bool func isEqual(to: ItemListControllerSearch) -> Bool
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?
func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode
} }

View file

@ -157,7 +157,7 @@ public func itemListNeighborsGroupedInsets(_ neighbors: ItemListNeighbors, _ par
} }
public func itemListHasRoundedBlockLayout(_ params: ListViewItemLayoutParams) -> Bool { public func itemListHasRoundedBlockLayout(_ params: ListViewItemLayoutParams) -> Bool {
return params.width >= 375.0 return params.width >= 350.0
} }
public final class ItemListPresentationData: Equatable { public final class ItemListPresentationData: Equatable {

View file

@ -19,6 +19,7 @@ public enum ItemListActionAlignment {
public class ItemListActionItem: ListViewItem, ItemListItem { public class ItemListActionItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String let title: String
let kind: ItemListActionKind let kind: ItemListActionKind
let alignment: ItemListActionAlignment let alignment: ItemListActionAlignment
@ -29,8 +30,9 @@ public class ItemListActionItem: ListViewItem, ItemListItem {
let clearHighlightAutomatically: Bool let clearHighlightAutomatically: Bool
public let tag: Any? public let tag: Any?
public init(presentationData: ItemListPresentationData, title: String, kind: ItemListActionKind, alignment: ItemListActionAlignment, sectionId: ItemListSectionId, style: ItemListStyle, action: @escaping () -> Void, longTapAction: (() -> Void)? = nil, clearHighlightAutomatically: Bool = true, tag: Any? = nil) { public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String, kind: ItemListActionKind, alignment: ItemListActionAlignment, sectionId: ItemListSectionId, style: ItemListStyle, action: @escaping () -> Void, longTapAction: (() -> Void)? = nil, clearHighlightAutomatically: Bool = true, tag: Any? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title self.title = title
self.kind = kind self.kind = kind
self.alignment = alignment self.alignment = alignment
@ -161,6 +163,15 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
let contentSize: CGSize let contentSize: CGSize
let insets: UIEdgeInsets let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let verticalInset: CGFloat
switch item.systemStyle {
case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 11.0
}
let itemBackgroundColor: UIColor let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor let itemSeparatorColor: UIColor
@ -168,12 +179,12 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
case .plain: case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
contentSize = CGSize(width: params.width, height: titleLayout.size.height + 22.0) contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0)
insets = itemListNeighborsPlainInsets(neighbors) insets = itemListNeighborsPlainInsets(neighbors)
case .blocks: case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
contentSize = CGSize(width: params.width, height: titleLayout.size.height + 22.0) contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0)
insets = itemListNeighborsGroupedInsets(neighbors, params) insets = itemListNeighborsGroupedInsets(neighbors, params)
} }
@ -259,19 +270,19 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight))
} }
switch item.alignment { switch item.alignment {
case .natural: case .natural:
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: titleLayout.size) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size)
case .center: case .center:
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((params.width - params.leftInset - params.rightInset - titleLayout.size.width) / 2.0), y: 12.0), size: titleLayout.size) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((params.width - params.leftInset - params.rightInset - titleLayout.size.width) / 2.0), y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size)
} }
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))

View file

@ -27,6 +27,7 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
} }
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let icon: UIImage? let icon: UIImage?
let iconSize: CGSize? let iconSize: CGSize?
let iconPlacement: IconPlacement let iconPlacement: IconPlacement
@ -42,8 +43,9 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
let action: () -> Void let action: () -> Void
let deleteAction: (() -> Void)? let deleteAction: (() -> Void)?
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, subtitle: String? = nil, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, textColor: TextColor = .primary, checked: Bool, enabled: Bool = true, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil) { public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, subtitle: String? = nil, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, textColor: TextColor = .primary, checked: Bool, enabled: Bool = true, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.icon = icon self.icon = icon
self.iconSize = iconSize self.iconSize = iconSize
self.iconPlacement = iconPlacement self.iconPlacement = iconPlacement
@ -230,9 +232,18 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.subtitle ?? "", font: subtitleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.subtitle ?? "", font: subtitleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let verticalInset: CGFloat
switch item.systemStyle {
case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 11.0
}
let insets = itemListNeighborsGroupedInsets(neighbors, params) let insets = itemListNeighborsGroupedInsets(neighbors, params)
var contentSize = CGSize(width: params.width, height: titleLayout.size.height + 22.0) var contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0)
if item.subtitle != nil { if item.subtitle != nil {
contentSize.height += subtitleLayout.size.height contentSize.height += subtitleLayout.size.height
} }
@ -341,14 +352,14 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
} }
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight))
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + 1.0), size: titleLayout.size)
strongSelf.subtitleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY), size: subtitleLayout.size) strongSelf.subtitleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY), size: subtitleLayout.size)
if let icon = item.icon { if let icon = item.icon {

View file

@ -49,6 +49,7 @@ public enum ItemListDisclosureItemDetailLabelColor {
public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator { public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let icon: UIImage? let icon: UIImage?
let context: AccountContext? let context: AccountContext?
let iconPeer: EnginePeer? let iconPeer: EnginePeer?
@ -73,8 +74,9 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemCompone
public let tag: ItemListItemTag? public let tag: ItemListItemTag?
public let shimmeringIndex: Int? public let shimmeringIndex: Int?
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, titleBadge: String? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, noInsets: Bool = false, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) { public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, titleBadge: String? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, noInsets: Bool = false, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.icon = icon self.icon = icon
self.context = context self.context = context
self.iconPeer = iconPeer self.iconPeer = iconPeer
@ -364,6 +366,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
let contentSize: CGSize let contentSize: CGSize
var insets: UIEdgeInsets var insets: UIEdgeInsets
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: UIColor let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor let itemSeparatorColor: UIColor
@ -472,16 +476,34 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
if item.iconPeer != nil { if item.iconPeer != nil {
verticalInset = 6.0 verticalInset = 6.0
} else { } else {
verticalInset = 11.0 switch item.systemStyle {
case .glass:
if !item.label.isEmpty {
switch item.labelStyle {
case .detailText, .multilineDetailText:
verticalInset = 13.0
default:
if let additionalDetailLabel = item.additionalDetailLabel, !additionalDetailLabel.isEmpty {
verticalInset = 13.0
} else {
verticalInset = 15.0
}
}
} else if let additionalDetailLabel = item.additionalDetailLabel, !additionalDetailLabel.isEmpty {
verticalInset = 13.0
} else {
verticalInset = 15.0
}
case .legacy:
verticalInset = 11.0
}
} }
let titleSpacing: CGFloat = 1.0 let titleSpacing: CGFloat = 1.0
var height: CGFloat var height: CGFloat
switch item.labelStyle { switch item.labelStyle {
case .detailText: case .detailText, .multilineDetailText:
height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height
case .multilineDetailText:
height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height
default: default:
height = verticalInset * 2.0 + titleLayout.size.height height = verticalInset * 2.0 + titleLayout.size.height
@ -646,12 +668,12 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight))
} }
var centralContentHeight: CGFloat = titleLayout.size.height var centralContentHeight: CGFloat = titleLayout.size.height

View file

@ -20,6 +20,7 @@ public class InfoListItem: ListViewItem {
public let selectable: Bool = false public let selectable: Bool = false
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String let title: String
let text: InfoListItemText let text: InfoListItemText
let style: ItemListStyle let style: ItemListStyle
@ -28,8 +29,9 @@ public class InfoListItem: ListViewItem {
let linkAction: ((InfoListItemLinkAction) -> Void)? let linkAction: ((InfoListItemLinkAction) -> Void)?
let closeAction: (() -> Void)? let closeAction: (() -> Void)?
public init(presentationData: ItemListPresentationData, title: String, text: InfoListItemText, style: ItemListStyle, hasDecorations: Bool = true, isWarning: Bool = false, linkAction: ((InfoListItemLinkAction) -> Void)? = nil, closeAction: (() -> Void)?) { public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String, text: InfoListItemText, style: ItemListStyle, hasDecorations: Bool = true, isWarning: Bool = false, linkAction: ((InfoListItemLinkAction) -> Void)? = nil, closeAction: (() -> Void)?) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title self.title = title
self.text = text self.text = text
self.style = style self.style = style
@ -76,9 +78,18 @@ public class InfoListItem: ListViewItem {
public class ItemListInfoItem: InfoListItem, ItemListItem { public class ItemListInfoItem: InfoListItem, ItemListItem {
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
public init(presentationData: ItemListPresentationData, title: String, text: InfoListItemText, style: ItemListStyle, sectionId: ItemListSectionId, linkAction: ((InfoListItemLinkAction) -> Void)? = nil, closeAction: (() -> Void)?) { public init(
presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle = .legacy,
title: String,
text: InfoListItemText,
style: ItemListStyle,
sectionId: ItemListSectionId,
linkAction: ((InfoListItemLinkAction) -> Void)? = nil,
closeAction: (() -> Void)?
) {
self.sectionId = sectionId self.sectionId = sectionId
super.init(presentationData: presentationData, title: title, text: text, style: style, linkAction: linkAction, closeAction: closeAction) super.init(presentationData: presentationData, systemStyle: systemStyle, title: title, text: text, style: style, linkAction: linkAction, closeAction: closeAction)
} }
override public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) { override public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
@ -237,6 +248,7 @@ public class InfoItemNode: ListViewItemNode {
insets = UIEdgeInsets() insets = UIEdgeInsets()
} }
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: UIColor let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor let itemSeparatorColor: UIColor
@ -264,7 +276,12 @@ public class InfoItemNode: ListViewItemNode {
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - badgeDiameter - 8.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - badgeDiameter - 8.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 38.0) var verticalInset: CGFloat = 0.0
if case .glass = item.systemStyle {
verticalInset = 4.0
}
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 38.0 + verticalInset * 2.0)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (layout, { [weak self] in return (layout, { [weak self] in
@ -331,12 +348,12 @@ public class InfoItemNode: ListViewItemNode {
strongSelf.badgeNode.isHidden = item.isWarning strongSelf.badgeNode.isHidden = item.isWarning
strongSelf.closeButton.isHidden = item.closeAction == nil strongSelf.closeButton.isHidden = item.closeAction == nil
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))
if let updateBadgeImage = updatedBadgeImage { if let updateBadgeImage = updatedBadgeImage {
if strongSelf.badgeNode.supernode == nil { if strongSelf.badgeNode.supernode == nil {
@ -349,15 +366,15 @@ public class InfoItemNode: ListViewItemNode {
strongSelf.closeButton.setImage(updatedCloseIcon, for: []) strongSelf.closeButton.setImage(updatedCloseIcon, for: [])
} }
strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 15.0 + (item.isWarning ? 4.0 : 0.0)), size: CGSize(width: badgeDiameter, height: badgeDiameter)) strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + 15.0 + (item.isWarning ? 4.0 : 0.0)), size: CGSize(width: badgeDiameter, height: badgeDiameter))
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.midX - labelLayout.size.width / 2.0, y: strongSelf.badgeNode.frame.minY + 2.0 + UIScreenPixel), size: labelLayout.size) strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.midX - labelLayout.size.width / 2.0, y: strongSelf.badgeNode.frame.minY + 2.0 + UIScreenPixel), size: labelLayout.size)
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: 16.0), size: titleLayout.size) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: verticalInset + 16.0), size: titleLayout.size)
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 9.0), size: textLayout.size) strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 9.0), size: textLayout.size)
strongSelf.closeButton.frame = CGRect(x: params.width - rightInset - 26.0, y: 10.0, width: 32.0, height: 32.0) strongSelf.closeButton.frame = CGRect(x: params.width - rightInset - 26.0, y: verticalInset + 10.0, width: 32.0, height: 32.0)
} }
}) })
} }

View file

@ -34,6 +34,7 @@ public struct ItemListMultilineInputInlineAction {
public class ItemListMultilineInputItem: ListViewItem, ItemListItem { public class ItemListMultilineInputItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let text: String let text: String
let placeholder: String let placeholder: String
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
@ -52,8 +53,9 @@ public class ItemListMultilineInputItem: ListViewItem, ItemListItem {
let noInsets: Bool let noInsets: Bool
public let tag: ItemListItemTag? public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, text: String, placeholder: String, maxLength: ItemListMultilineInputItemTextLimit?, sectionId: ItemListSectionId, style: ItemListStyle, capitalization: Bool = true, autocorrection: Bool = true, returnKeyType: UIReturnKeyType = .default, minimalHeight: CGFloat? = nil, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> Void)? = nil, updatedFocus: ((Bool) -> Void)? = nil, tag: ItemListItemTag? = nil, action: (() -> Void)? = nil, inlineAction: ItemListMultilineInputInlineAction? = nil, noInsets: Bool = false) { public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, text: String, placeholder: String, maxLength: ItemListMultilineInputItemTextLimit?, sectionId: ItemListSectionId, style: ItemListStyle, capitalization: Bool = true, autocorrection: Bool = true, returnKeyType: UIReturnKeyType = .default, minimalHeight: CGFloat? = nil, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> Void)? = nil, updatedFocus: ((Bool) -> Void)? = nil, tag: ItemListItemTag? = nil, action: (() -> Void)? = nil, inlineAction: ItemListMultilineInputInlineAction? = nil, noInsets: Bool = false) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.text = text self.text = text
self.placeholder = placeholder self.placeholder = placeholder
self.maxLength = maxLength self.maxLength = maxLength
@ -238,9 +240,18 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedMeasureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 16.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedMeasureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 16.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let textTopInset: CGFloat = 11.0 let textTopInset: CGFloat
let textBottomInset: CGFloat = 11.0 let textBottomInset: CGFloat
switch item.systemStyle {
case .glass:
textTopInset = 15.0
textBottomInset = 15.0
case .legacy:
textTopInset = 11.0
textBottomInset = 11.0
}
var contentHeight: CGFloat = textLayout.size.height + textTopInset + textBottomInset var contentHeight: CGFloat = textLayout.size.height + textTopInset + textBottomInset
if let minimalHeight = item.minimalHeight { if let minimalHeight = item.minimalHeight {
@ -332,11 +343,11 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
if !item.noInsets { if !item.noInsets {
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))
} }
if strongSelf.textNode.attributedPlaceholderText == nil || !strongSelf.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) { if strongSelf.textNode.attributedPlaceholderText == nil || !strongSelf.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) {

View file

@ -46,6 +46,7 @@ public enum ItemListSingleLineInputAlignment {
public class ItemListSingleLineInputItem: ListViewItem, ItemListItem { public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let context: AccountContext? let context: AccountContext?
let systemStyle: ItemListSystemStyle
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let title: NSAttributedString let title: NSAttributedString
let text: String let text: String
@ -69,9 +70,10 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let cleared: (() -> Void)? let cleared: (() -> Void)?
public let tag: ItemListItemTag? public let tag: ItemListItemTag?
public init(context: AccountContext? = nil, presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, label: String? = nil, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) { public init(context: AccountContext? = nil, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: NSAttributedString, text: String, placeholder: String, label: String? = nil, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title self.title = title
self.text = text self.text = text
self.placeholder = placeholder self.placeholder = placeholder
@ -272,8 +274,17 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label ?? "", font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label ?? "", font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let contentSize = CGSize(width: params.width, height: max(titleLayout.size.height, measureTitleLayout.size.height) + 22.0)
let verticalInset: CGFloat
switch item.systemStyle {
case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 11.0
}
let contentSize = CGSize(width: params.width, height: max(titleLayout.size.height, measureTitleLayout.size.height) + verticalInset * 2.0)
let insets = itemListNeighborsGroupedInsets(neighbors, params) let insets = itemListNeighborsGroupedInsets(neighbors, params)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -444,12 +455,12 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))
if strongSelf.textNode.textField.attributedPlaceholder == nil || !strongSelf.textNode.textField.attributedPlaceholder!.isEqual(to: attributedPlaceholderText) { if strongSelf.textNode.textField.attributedPlaceholder == nil || !strongSelf.textNode.textField.attributedPlaceholder!.isEqual(to: attributedPlaceholderText) {
strongSelf.textNode.textField.attributedPlaceholder = attributedPlaceholderText strongSelf.textNode.textField.attributedPlaceholder = attributedPlaceholderText

View file

@ -20,6 +20,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
} }
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let icon: UIImage? let icon: UIImage?
let title: String let title: String
let text: String? let text: String?
@ -40,8 +41,9 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
let action: (() -> Void)? let action: (() -> Void)?
public let tag: ItemListItemTag? public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, text: String? = nil, textColor: TextColor = .primary, titleBadgeComponent: AnyComponent<Empty>? = nil, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, action: (() -> Void)? = nil, tag: ItemListItemTag? = nil) { public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, title: String, text: String? = nil, textColor: TextColor = .primary, titleBadgeComponent: AnyComponent<Empty>? = nil, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, action: (() -> Void)? = nil, tag: ItemListItemTag? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.icon = icon self.icon = icon
self.title = title self.title = title
self.text = text self.text = text
@ -243,6 +245,8 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
var contentSize: CGSize var contentSize: CGSize
var insets: UIEdgeInsets var insets: UIEdgeInsets
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: UIColor let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor let itemSeparatorColor: UIColor
@ -277,12 +281,18 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
insets = itemListNeighborsGroupedInsets(neighbors, params) insets = itemListNeighborsGroupedInsets(neighbors, params)
} }
let topInset: CGFloat
var topInset: CGFloat
if item.text != nil { if item.text != nil {
topInset = 9.0 topInset = 9.0
} else { } else {
topInset = 11.0 topInset = 11.0
} }
if case .glass = item.systemStyle {
contentSize.height = 52.0
topInset += 4.0
}
var leftInset = 16.0 + params.leftInset var leftInset = 16.0 + params.leftInset
if let _ = item.icon { if let _ = item.icon {
@ -452,12 +462,12 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))) transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))))
transition.updateFrame(node: strongSelf.maskNode, frame: strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)) transition.updateFrame(node: strongSelf.maskNode, frame: strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0))
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight)))
} }
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size) let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size)
@ -531,7 +541,14 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
let switchFrame = strongSelf.switchNode.frame let switchFrame = strongSelf.switchNode.frame
if let icon = lockedIconNode.image { if let icon = lockedIconNode.image {
lockedIconTransition.updateFrame(node: lockedIconNode, frame: CGRect(origin: CGPoint(x: item.value ? switchFrame.maxX - icon.size.width - 11.0 : switchFrame.minX + 11.0, y: switchFrame.minY + 9.0), size: icon.size)) let iconOrigin: CGPoint
switch item.systemStyle {
case .glass:
iconOrigin = CGPoint(x: item.value ? switchFrame.maxX - icon.size.width - 16.0 + UIScreenPixel : switchFrame.minX + 16.0 - UIScreenPixel, y: switchFrame.minY + 8.0)
case .legacy:
iconOrigin = CGPoint(x: item.value ? switchFrame.maxX - icon.size.width - 11.0 : switchFrame.minX + 11.0, y: switchFrame.minY + 9.0)
}
lockedIconTransition.updateFrame(node: lockedIconNode, frame: CGRect(origin: iconOrigin, size: icon.size))
} }
} else if let lockedIconNode = strongSelf.lockedIconNode { } else if let lockedIconNode = strongSelf.lockedIconNode {
strongSelf.lockedIconNode = nil strongSelf.lockedIconNode = nil

View file

@ -16,6 +16,7 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem {
} }
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let engine: TelegramEngine let engine: TelegramEngine
let venue: TelegramMediaMap? let venue: TelegramMediaMap?
let title: String? let title: String?
@ -28,8 +29,9 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem {
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
let header: ListViewItemHeader? let header: ListViewItemHeader?
public init(presentationData: ItemListPresentationData, engine: TelegramEngine, venue: TelegramMediaMap?, title: String? = nil, subtitle: String? = nil, icon: ItemListVenueItem.InfoIcon = .info, sectionId: ItemListSectionId = 0, style: ItemListStyle, action: (() -> Void)?, infoAction: (() -> Void)? = nil, header: ListViewItemHeader? = nil) { public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, engine: TelegramEngine, venue: TelegramMediaMap?, title: String? = nil, subtitle: String? = nil, icon: ItemListVenueItem.InfoIcon = .info, sectionId: ItemListSectionId = 0, style: ItemListStyle, action: (() -> Void)?, infoAction: (() -> Void)? = nil, header: ListViewItemHeader? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.engine = engine self.engine = engine
self.venue = venue self.venue = venue
self.title = title self.title = title
@ -201,8 +203,8 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
var updatedTheme: PresentationTheme? var updatedTheme: PresentationTheme?
var updatedVenueType: String? var updatedVenueType: String?
let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize) let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let addressFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) let addressFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
if currentItem?.presentationData.theme !== item.presentationData.theme { if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme updatedTheme = item.presentationData.theme
@ -265,6 +267,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight))
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let separatorRightInset = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -328,7 +331,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
} else { } else {
stripeInset = leftInset stripeInset = leftInset
} }
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: stripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - stripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: stripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - stripeInset - params.rightInset - separatorRightInset, height: separatorHeight))
strongSelf.bottomStripeNode.isHidden = last strongSelf.bottomStripeNode.isHidden = last
case .blocks: case .blocks:
placeholderBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor placeholderBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
@ -371,7 +374,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))
} }
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)) transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size))

View file

@ -607,6 +607,8 @@ public final class ListMessageFileItemNode: ListMessageNode {
let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0))
let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
let iconCornerRadius: CGFloat = item.systemStyle == .glass ? 12.0 : 6.0
let leftInset: CGFloat = 65.0 + params.leftInset let leftInset: CGFloat = 65.0 + params.leftInset
let rightInset: CGFloat = 8.0 + params.rightInset let rightInset: CGFloat = 8.0 + params.rightInset
@ -1020,7 +1022,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
switch iconImage { switch iconImage {
case let .imageRepresentation(_, representation): case let .imageRepresentation(_, representation):
let iconSize = CGSize(width: 40.0, height: 40.0) let iconSize = CGSize(width: 40.0, height: 40.0)
let imageCorners = ImageCorners(radius: 6.0) let imageCorners = ImageCorners(radius: iconCornerRadius)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor)
iconImageApply = iconImageLayout(arguments) iconImageApply = iconImageLayout(arguments)
case .albumArt: case .albumArt:
@ -1067,7 +1069,14 @@ public final class ListMessageFileItemNode: ListMessageNode {
insets.bottom += 35.0 insets.bottom += 35.0
} }
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 8.0 * 2.0 + titleNodeLayout.height - 5.0 + descriptionNodeLayout.height + (textNodeLayout.size.height > 0.0 ? textNodeLayout.size.height + 3.0 : 0.0)), insets: insets) let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
var verticalInset: CGFloat = 0.0
if case .glass = item.systemStyle {
verticalInset += 2.0 + UIScreenPixel
}
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: verticalInset * 2.0 + 8.0 * 2.0 + titleNodeLayout.height - 5.0 + descriptionNodeLayout.height + (textNodeLayout.size.height > 0.0 ? textNodeLayout.size.height + 3.0 : 0.0)), insets: insets)
return (nodeLayout, { animation in return (nodeLayout, { animation in
if let strongSelf = self { if let strongSelf = self {
@ -1172,7 +1181,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
}) })
} }
transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel))) transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset - separatorRightInset - params.rightInset, height: UIScreenPixel)))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel - nodeLayout.insets.bottom)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel - nodeLayout.insets.bottom))
if let backgroundNode = strongSelf.backgroundNode { if let backgroundNode = strongSelf.backgroundNode {
@ -1203,13 +1212,13 @@ public final class ListMessageFileItemNode: ListMessageNode {
strongSelf.separatorNode.isHidden = false strongSelf.separatorNode.isHidden = false
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
if let backgroundNode = strongSelf.backgroundNode { if let backgroundNode = strongSelf.backgroundNode {
strongSelf.maskNode.frame = backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
} }
} }
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset - 1.0, y: 7.0), size: titleNodeLayout)) transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset - 1.0, y: 7.0 + verticalInset), size: titleNodeLayout))
let _ = titleNodeApply() let _ = titleNodeApply()
var descriptionOffset: CGFloat = 0.0 var descriptionOffset: CGFloat = 0.0
@ -1227,7 +1236,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
} }
} }
transition.updateFrame(node: strongSelf.textNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0), size: textNodeLayout.size)) transition.updateFrame(node: strongSelf.textNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0 + verticalInset), size: textNodeLayout.size))
let _ = textNodeApply() let _ = textNodeApply()
transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset - 1.0, y: strongSelf.titleNode.frame.maxY - 3.0 + (textNodeLayout.size.height > 0.0 ? textNodeLayout.size.height + 3.0 : 0.0)), size: descriptionNodeLayout)) transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset - 1.0, y: strongSelf.titleNode.frame.maxY - 3.0 + (textNodeLayout.size.height > 0.0 ? textNodeLayout.size.height + 3.0 : 0.0)), size: descriptionNodeLayout))
@ -1238,7 +1247,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
strongSelf.dateNode.isHidden = !item.isGlobalSearchResult strongSelf.dateNode.isHidden = !item.isGlobalSearchResult
let iconSize = CGSize(width: 40.0, height: 40.0) let iconSize = CGSize(width: 40.0, height: 40.0)
let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + leftOffset + 12.0, y: 8.0), size: iconSize) let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + leftOffset + 12.0, y: 8.0 + verticalInset), size: iconSize)
transition.updateFrame(node: strongSelf.extensionIconNode, frame: iconFrame) transition.updateFrame(node: strongSelf.extensionIconNode, frame: iconFrame)
strongSelf.extensionIconNode.image = extensionIconImage strongSelf.extensionIconNode.image = extensionIconImage
transition.updateFrame(node: strongSelf.extensionIconText, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - extensionTextLayout.size.width) / 2.0), y: iconFrame.minY + 7.0 + floorToScreenPixels((iconFrame.height - extensionTextLayout.size.height) / 2.0)), size: extensionTextLayout.size)) transition.updateFrame(node: strongSelf.extensionIconText, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - extensionTextLayout.size.width) / 2.0), y: iconFrame.minY + 7.0 + floorToScreenPixels((iconFrame.height - extensionTextLayout.size.height) / 2.0)), size: extensionTextLayout.size))
@ -1347,7 +1356,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
if let media = selectedMedia as? TelegramMediaFile, media.isInstantVideo { if let media = selectedMedia as? TelegramMediaFile, media.isInstantVideo {
shapes.append(.circle(iconFrame)) shapes.append(.circle(iconFrame))
} else { } else {
shapes.append(.roundedRect(rect: iconFrame, cornerRadius: 6.0)) shapes.append(.roundedRect(rect: iconFrame, cornerRadius: iconCornerRadius))
} }
shimmerNode.update(backgroundColor: item.presentationData.theme.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: nodeLayout.contentSize) shimmerNode.update(backgroundColor: item.presentationData.theme.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: nodeLayout.contentSize)

View file

@ -46,6 +46,7 @@ public final class ListMessageItemInteraction {
public final class ListMessageItem: ListViewItem { public final class ListMessageItem: ListViewItem {
let presentationData: ChatPresentationData let presentationData: ChatPresentationData
let systemStyle: ItemListSystemStyle
let context: AccountContext let context: AccountContext
let chatLocation: ChatLocation let chatLocation: ChatLocation
let interaction: ListMessageItemInteraction let interaction: ListMessageItemInteraction
@ -65,8 +66,9 @@ public final class ListMessageItem: ListViewItem {
public let selectable: Bool = true public let selectable: Bool = true
public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message?, translateToLanguage: String? = nil, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false, isDownloadList: Bool = false, isSavedMusic: Bool = false, displayFileInfo: Bool = true, displayBackground: Bool = false, canReorder: Bool = false, style: ItemListStyle = .plain) { public init(presentationData: ChatPresentationData, systemStyle: ItemListSystemStyle = .legacy, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message?, translateToLanguage: String? = nil, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false, isDownloadList: Bool = false, isSavedMusic: Bool = false, displayFileInfo: Bool = true, displayBackground: Bool = false, canReorder: Bool = false, style: ItemListStyle = .plain) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.context = context self.context = context
self.chatLocation = chatLocation self.chatLocation = chatLocation
self.interaction = interaction self.interaction = interaction

View file

@ -147,7 +147,7 @@ public final class ListSectionHeaderNode: ASDisplayNode {
} }
} }
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, showBackground: Bool = true) {
self.validLayout = (size, leftInset, rightInset) self.validLayout = (size, leftInset, rightInset)
let labelSize = self.label.updateLayout(CGSize(width: max(0.0, size.width - leftInset - rightInset - 18.0), height: size.height)) let labelSize = self.label.updateLayout(CGSize(width: max(0.0, size.width - leftInset - rightInset - 18.0), height: size.height))
self.label.frame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0 + UIScreenPixel), size: CGSize(width: labelSize.width, height: size.height)) self.label.frame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0 + UIScreenPixel), size: CGSize(width: labelSize.width, height: size.height))
@ -158,6 +158,7 @@ public final class ListSectionHeaderNode: ASDisplayNode {
actionButton.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize) actionButton.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize)
} }
self.backgroundLayer.isHidden = !showBackground
self.backgroundLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel)) self.backgroundLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel))
} }

View file

@ -80,9 +80,9 @@ private func venueIconData(engine: TelegramEngine, resource: VenueIconResource)
private let randomColors = [UIColor(rgb: 0xe56cd5), UIColor(rgb: 0xf89440), UIColor(rgb: 0x9986ff), UIColor(rgb: 0x44b3f5), UIColor(rgb: 0x6dc139), UIColor(rgb: 0xff5d5a), UIColor(rgb: 0xf87aad), UIColor(rgb: 0x6e82b3), UIColor(rgb: 0xf5ba21)] private let randomColors = [UIColor(rgb: 0xe56cd5), UIColor(rgb: 0xf89440), UIColor(rgb: 0x9986ff), UIColor(rgb: 0x44b3f5), UIColor(rgb: 0x6dc139), UIColor(rgb: 0xff5d5a), UIColor(rgb: 0xf87aad), UIColor(rgb: 0x6e82b3), UIColor(rgb: 0xf5ba21)]
private let venueColors: [String: UIColor] = [ private let venueColors: [String: UIColor] = [
"building/medical": UIColor(rgb: 0x43b3f4), "building/medical": UIColor(rgb: 0x43b3f4), // light blue?
"building/gym": UIColor(rgb: 0x43b3f4), "building/gym": UIColor(rgb: 0x43b3f4), // light blue?
"arts_entertainment": UIColor(rgb: 0xe56dd6), "arts_entertainment": UIColor(rgb: 0xaf52de), // purple
"travel/bedandbreakfast": UIColor(rgb: 0x9987ff), "travel/bedandbreakfast": UIColor(rgb: 0x9987ff),
"travel/hotel": UIColor(rgb: 0x9987ff), "travel/hotel": UIColor(rgb: 0x9987ff),
"travel/hostel": UIColor(rgb: 0x9987ff), "travel/hostel": UIColor(rgb: 0x9987ff),
@ -90,11 +90,11 @@ private let venueColors: [String: UIColor] = [
"building": UIColor(rgb: 0x6e81b2), "building": UIColor(rgb: 0x6e81b2),
"education": UIColor(rgb: 0xa57348), "education": UIColor(rgb: 0xa57348),
"event": UIColor(rgb: 0x959595), "event": UIColor(rgb: 0x959595),
"food": UIColor(rgb: 0xf7943f), "food": UIColor(rgb: 0xff9500), // orange
"education/cafeteria": UIColor(rgb: 0xf7943f), "education/cafeteria": UIColor(rgb: 0xff9500), // orange
"nightlife": UIColor(rgb: 0xe56dd6), "nightlife": UIColor(rgb: 0xaf52de), // purple
"travel/hotel_bar": UIColor(rgb: 0xe56dd6), "travel/hotel_bar": UIColor(rgb: 0xaf52de), // purple
"parks_outdoors": UIColor(rgb: 0x6cc039), "parks_outdoors": UIColor(rgb: 0x6cc138), // green
"shops": UIColor(rgb: 0xffb300), "shops": UIColor(rgb: 0xffb300),
"travel": UIColor(rgb: 0x1c9fff), "travel": UIColor(rgb: 0x1c9fff),
"work": UIColor(rgb: 0xad7854), "work": UIColor(rgb: 0xad7854),

View file

@ -45,6 +45,14 @@ swift_library(
"//submodules/AttachmentUI:AttachmentUI", "//submodules/AttachmentUI:AttachmentUI",
"//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/ComponentFlow",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -82,13 +82,13 @@ private func generateLiveLocationIcon(theme: PresentationTheme, type: LiveLocati
switch type { switch type {
case .start: case .start:
imageName = "Location/SendLiveLocationIcon" imageName = "Location/SendLiveLocationIcon"
color = UIColor(rgb: 0x6cc139) color = UIColor(rgb: 0x6cc138)
case .stop: case .stop:
imageName = "Location/SendLocationIcon" imageName = "Location/SendLocationIcon"
color = UIColor(rgb: 0xff6464) color = UIColor(rgb: 0xff6464)
case .extend: case .extend:
imageName = "Location/SendLocationIcon" imageName = "Location/SendLocationIcon"
color = UIColor(rgb: 0x6cc139) color = UIColor(rgb: 0x6cc138)
} }
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
@ -283,8 +283,8 @@ final class LocationActionListItemNode: ListViewItemNode {
let verticalInset: CGFloat = 8.0 let verticalInset: CGFloat = 8.0
let iconSize: CGFloat = 40.0 let iconSize: CGFloat = 40.0
let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize) let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
let titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) let titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -392,6 +392,7 @@ final class LocationActionListItemNode: ListViewItemNode {
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let topHighlightInset: CGFloat = separatorHeight let topHighlightInset: CGFloat = separatorHeight
let separatorRightInset: CGFloat = 16.0
let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize)) let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize))
strongSelf.iconNode.frame = iconNodeFrame strongSelf.iconNode.frame = iconNodeFrame
@ -401,7 +402,7 @@ final class LocationActionListItemNode: ListViewItemNode {
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height)) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset))
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - leftInset - params.rightInset - separatorRightInset, height: separatorHeight))
strongSelf.separatorNode.isHidden = !hasSeparator strongSelf.separatorNode.isHidden = !hasSeparator
if let (beginTimestamp, timeout) = item.beginTimeAndTimeout { if let (beginTimestamp, timeout) = item.beginTimeAndTimeout {

View file

@ -173,8 +173,8 @@ final class LocationLiveListItemNode: ListViewItemNode {
let rightInset: CGFloat = params.rightInset let rightInset: CGFloat = params.rightInset
let verticalInset: CGFloat = 8.0 let verticalInset: CGFloat = 8.0
let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize) let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
var title: String = "" var title: String = ""
if let author = item.message.author { if let author = item.message.author {
@ -302,6 +302,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let topHighlightInset: CGFloat = separatorHeight let topHighlightInset: CGFloat = separatorHeight
let separatorRightInset: CGFloat = 16.0
let avatarSize: CGFloat = 40.0 let avatarSize: CGFloat = 40.0
if let peer = item.message.author { if let peer = item.message.author {
@ -312,7 +313,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height)) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset))
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - leftInset - params.rightInset - separatorRightInset, height: separatorHeight))
strongSelf.separatorNode.isHidden = !hasSeparator strongSelf.separatorNode.isHidden = !hasSeparator
var liveBroadcastingTimeout: Int32 = 0 var liveBroadcastingTimeout: Int32 = 0

View file

@ -5,9 +5,16 @@ import Display
import TelegramPresentationData import TelegramPresentationData
import AppBundle import AppBundle
import CoreLocation import CoreLocation
import ComponentFlow
import GlassBackgroundComponent
import PlainButtonComponent
import BundleIconComponent
import MultilineTextComponent
import EdgeEffect
private let panelInset: CGFloat = 4.0 private let panelInset: CGFloat = 4.0
private let panelButtonSize = CGSize(width: 46.0, height: 46.0) private let panelButtonSize = CGSize(width: 46.0, height: 46.0)
private let glassPanelButtonSize = CGSize(width: 40.0, height: 40.0)
private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? { private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? {
let cornerRadius: CGFloat = 9.0 let cornerRadius: CGFloat = 9.0
@ -36,7 +43,9 @@ private func generateShadowImage(theme: PresentationTheme, highlighted: Bool) ->
public final class LocationMapHeaderNode: ASDisplayNode { public final class LocationMapHeaderNode: ASDisplayNode {
private var presentationData: PresentationData private var presentationData: PresentationData
private let glass: Bool
private let toggleMapModeSelection: () -> Void private let toggleMapModeSelection: () -> Void
private let updateMapMode: (LocationMapMode) -> Void
private let goToUserLocation: () -> Void private let goToUserLocation: () -> Void
private let showPlacesInThisArea: () -> Void private let showPlacesInThisArea: () -> Void
private let setupProximityNotification: (Bool) -> Void private let setupProximityNotification: (Bool) -> Void
@ -47,52 +56,76 @@ public final class LocationMapHeaderNode: ASDisplayNode {
public let mapNode: LocationMapNode public let mapNode: LocationMapNode
public var trackingMode: LocationTrackingMode = .none public var trackingMode: LocationTrackingMode = .none
private let edgeEffectView: EdgeEffectView
private let options: ComponentView<Empty>?
private let optionsBackgroundView: GlassBackgroundView?
private let optionsBackgroundNode: ASImageNode private let optionsBackgroundNode: ASImageNode
private let optionsSeparatorNode: ASDisplayNode private let optionsSeparatorNode: ASDisplayNode
private let optionsSecondSeparatorNode: ASDisplayNode private let optionsSecondSeparatorNode: ASDisplayNode
private let infoButtonNode: HighlightableButtonNode private let infoButtonNode: HighlightableButtonNode
private let locationButtonNode: HighlightableButtonNode private let locationButtonNode: HighlightableButtonNode
private let notificationButtonNode: HighlightableButtonNode private let notificationButtonNode: HighlightableButtonNode
private let placesBackgroundView: GlassBackgroundView?
private let placesBackgroundNode: ASImageNode private let placesBackgroundNode: ASImageNode
private let placesButtonNode: HighlightableButtonNode private let placesButtonNode: HighlightableButtonNode
private let shadowNode: ASImageNode private let shadowNode: ASImageNode
private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGFloat, CGSize)? private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGSize)?
public init(presentationData: PresentationData, toggleMapModeSelection: @escaping () -> Void, goToUserLocation: @escaping () -> Void, setupProximityNotification: @escaping (Bool) -> Void = { _ in }, showPlacesInThisArea: @escaping () -> Void = {}) { public init(
presentationData: PresentationData,
glass: Bool,
toggleMapModeSelection: @escaping () -> Void,
updateMapMode: @escaping (LocationMapMode) -> Void,
goToUserLocation: @escaping () -> Void,
setupProximityNotification: @escaping (Bool) -> Void = { _ in },
showPlacesInThisArea: @escaping () -> Void = {}
) {
self.presentationData = presentationData self.presentationData = presentationData
self.glass = glass
self.toggleMapModeSelection = toggleMapModeSelection self.toggleMapModeSelection = toggleMapModeSelection
self.updateMapMode = updateMapMode
self.goToUserLocation = goToUserLocation self.goToUserLocation = goToUserLocation
self.setupProximityNotification = setupProximityNotification self.setupProximityNotification = setupProximityNotification
self.showPlacesInThisArea = showPlacesInThisArea self.showPlacesInThisArea = showPlacesInThisArea
self.mapNode = LocationMapNode() self.mapNode = LocationMapNode()
if glass {
self.options = ComponentView()
} else {
self.options = nil
}
self.optionsBackgroundNode = ASImageNode() self.optionsBackgroundNode = ASImageNode()
self.optionsBackgroundNode.contentMode = .scaleToFill self.optionsBackgroundNode.contentMode = .scaleToFill
self.optionsBackgroundNode.displaysAsynchronously = false self.optionsBackgroundNode.displaysAsynchronously = false
self.optionsBackgroundNode.displayWithoutProcessing = true self.optionsBackgroundNode.displayWithoutProcessing = true
self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
self.optionsBackgroundNode.isUserInteractionEnabled = true self.optionsBackgroundNode.isUserInteractionEnabled = true
self.optionsSeparatorNode = ASDisplayNode() self.optionsSeparatorNode = ASDisplayNode()
self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.optionsSecondSeparatorNode = ASDisplayNode() self.optionsSecondSeparatorNode = ASDisplayNode()
self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
let buttonColor = self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62) : presentationData.theme.rootController.navigationBar.buttonColor
self.infoButtonNode = HighlightableButtonNode() self.infoButtonNode = HighlightableButtonNode()
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Location/OptionMap" : "Location/InfoIcon"), color: buttonColor), for: .normal)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: .selected)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: [.selected, .highlighted])
self.locationButtonNode = HighlightableButtonNode() self.locationButtonNode = HighlightableButtonNode()
self.locationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/TrackIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) self.locationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/TrackIcon"), color: buttonColor), for: .normal)
self.notificationButtonNode = HighlightableButtonNode() self.notificationButtonNode = HighlightableButtonNode()
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: buttonColor), for: .normal)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: .selected)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: [.selected, .highlighted])
self.placesBackgroundNode = ASImageNode() self.placesBackgroundNode = ASImageNode()
self.placesBackgroundNode.contentMode = .scaleToFill self.placesBackgroundNode.contentMode = .scaleToFill
@ -102,7 +135,7 @@ public final class LocationMapHeaderNode: ASDisplayNode {
self.placesBackgroundNode.isUserInteractionEnabled = true self.placesBackgroundNode.isUserInteractionEnabled = true
self.placesButtonNode = HighlightableButtonNode() self.placesButtonNode = HighlightableButtonNode()
self.placesButtonNode.setTitle(presentationData.strings.Map_PlacesInThisArea, with: Font.regular(17.0), with: presentationData.theme.rootController.navigationBar.buttonColor, for: .normal) self.placesButtonNode.setTitle(presentationData.strings.Map_PlacesInThisArea, with: Font.medium(17.0), with: self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor : buttonColor, for: .normal)
self.shadowNode = ASImageNode() self.shadowNode = ASImageNode()
self.shadowNode.contentMode = .scaleToFill self.shadowNode.contentMode = .scaleToFill
@ -110,20 +143,48 @@ public final class LocationMapHeaderNode: ASDisplayNode {
self.shadowNode.displayWithoutProcessing = true self.shadowNode.displayWithoutProcessing = true
self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false) self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false)
if glass {
self.optionsBackgroundView = GlassBackgroundView()
self.optionsBackgroundNode.image = nil
self.placesBackgroundView = GlassBackgroundView()
self.placesBackgroundNode.image = nil
} else {
self.optionsBackgroundView = nil
self.placesBackgroundView = nil
}
self.edgeEffectView = EdgeEffectView()
self.edgeEffectView.isUserInteractionEnabled = false
super.init() super.init()
self.clipsToBounds = true self.clipsToBounds = true
self.addSubnode(self.mapNode) self.addSubnode(self.mapNode)
self.addSubnode(self.optionsBackgroundNode)
self.optionsBackgroundNode.addSubnode(self.optionsSeparatorNode) self.view.addSubview(self.edgeEffectView)
self.optionsBackgroundNode.addSubnode(self.optionsSecondSeparatorNode)
self.optionsBackgroundNode.addSubnode(self.infoButtonNode) if glass {
self.optionsBackgroundNode.addSubnode(self.locationButtonNode) if let placesBackgroundView = self.placesBackgroundView {
self.optionsBackgroundNode.addSubnode(self.notificationButtonNode) self.placesBackgroundNode.view.addSubview(placesBackgroundView)
}
} else {
if let optionsBackgroundView = self.optionsBackgroundView {
self.optionsSeparatorNode.isHidden = true
self.view.addSubview(optionsBackgroundView)
}
self.addSubnode(self.optionsBackgroundNode)
self.optionsBackgroundNode.addSubnode(self.optionsSeparatorNode)
self.optionsBackgroundNode.addSubnode(self.optionsSecondSeparatorNode)
self.optionsBackgroundNode.addSubnode(self.infoButtonNode)
self.optionsBackgroundNode.addSubnode(self.locationButtonNode)
self.optionsBackgroundNode.addSubnode(self.notificationButtonNode)
}
self.addSubnode(self.placesBackgroundNode) self.addSubnode(self.placesBackgroundNode)
self.placesBackgroundNode.addSubnode(self.placesButtonNode) self.placesBackgroundNode.addSubnode(self.placesButtonNode)
self.addSubnode(self.shadowNode) //self.addSubnode(self.shadowNode)
self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside) self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside)
self.locationButtonNode.addTarget(self, action: #selector(self.locationPressed), forControlEvents: .touchUpInside) self.locationButtonNode.addTarget(self, action: #selector(self.locationPressed), forControlEvents: .touchUpInside)
@ -132,44 +193,52 @@ public final class LocationMapHeaderNode: ASDisplayNode {
} }
public func updateState(mapMode: LocationMapMode, trackingMode: LocationTrackingMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) { public func updateState(mapMode: LocationMapMode, trackingMode: LocationTrackingMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) {
let mapModeUpdated = self.mapNode.mapMode != mapMode
let displayingMapModesUpdated = self.infoButtonNode.isSelected != displayingMapModeOptions
self.mapNode.mapMode = mapMode self.mapNode.mapMode = mapMode
self.trackingMode = trackingMode self.trackingMode = trackingMode
self.infoButtonNode.isSelected = displayingMapModeOptions self.infoButtonNode.isSelected = displayingMapModeOptions
self.notificationButtonNode.isSelected = proximityNotification ?? false self.notificationButtonNode.isSelected = proximityNotification ?? false
self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: self.presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) let buttonColor = self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62) : presentationData.theme.rootController.navigationBar.buttonColor
let updateLayout = self.displayingPlacesButton != displayingPlacesButton || self.proximityNotification != proximityNotification self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: buttonColor), for: .normal)
let updateLayout = self.displayingPlacesButton != displayingPlacesButton || self.proximityNotification != proximityNotification || mapModeUpdated || displayingMapModesUpdated
self.displayingPlacesButton = displayingPlacesButton self.displayingPlacesButton = displayingPlacesButton
self.proximityNotification = proximityNotification self.proximityNotification = proximityNotification
if updateLayout, let (layout, navigationBarHeight, topPadding, controlsTopPadding, offset, size) = self.validLayout { if updateLayout, let (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size) = self.validLayout {
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .spring) : .immediate let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .spring) : .immediate
self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, offset: offset, size: size, transition: transition) self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, controlsBottomPadding: controlsBottomPadding, offset: offset, size: size, transition: transition)
} }
} }
public func updatePresentationData(_ presentationData: PresentationData) { public func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData self.presentationData = presentationData
let buttonColor = self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62) : presentationData.theme.rootController.navigationBar.buttonColor
self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Location/OptionMap" : "Location/InfoIcon"), color: buttonColor), for: .normal)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: .selected)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: [.selected, .highlighted])
self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: buttonColor), for: .normal)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: buttonColor), for: .normal)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: .selected)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: [.selected, .highlighted])
self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) if !self.glass {
self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
}
self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false) self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false)
} }
private func iconForTracking() -> UIImage? { private func iconForTracking() -> UIImage? {
switch self.trackingMode { switch self.trackingMode {
case .none: case .none:
return UIImage(bundleImageName: "Location/TrackIcon") return UIImage(bundleImageName: self.glass ? "Location/OptionLocate" : "Location/TrackIcon")
case .follow: case .follow:
return UIImage(bundleImageName: "Location/TrackActiveIcon") return UIImage(bundleImageName: "Location/TrackActiveIcon")
case .followWithHeading: case .followWithHeading:
@ -177,8 +246,8 @@ public final class LocationMapHeaderNode: ASDisplayNode {
} }
} }
public func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, topPadding: CGFloat, controlsTopPadding: CGFloat, offset: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) { public func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, topPadding: CGFloat, controlsTopPadding: CGFloat, controlsBottomPadding: CGFloat, offset: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight, topPadding, controlsTopPadding, offset, size) self.validLayout = (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size)
let mapHeight: CGFloat = floor(layout.size.height * 1.3) + layout.intrinsicInsets.top * 2.0 let mapHeight: CGFloat = floor(layout.size.height * 1.3) + layout.intrinsicInsets.top * 2.0
let mapFrame = CGRect(x: 0.0, y: floorToScreenPixels((size.height - mapHeight + navigationBarHeight) / 2.0) + offset + floor(layout.intrinsicInsets.top * 0.5), width: size.width, height: mapHeight) let mapFrame = CGRect(x: 0.0, y: floorToScreenPixels((size.height - mapHeight + navigationBarHeight) / 2.0) + offset + floor(layout.intrinsicInsets.top * 0.5), width: size.width, height: mapHeight)
@ -188,38 +257,98 @@ public final class LocationMapHeaderNode: ASDisplayNode {
let inset: CGFloat = 6.0 let inset: CGFloat = 6.0
let placesButtonSize = CGSize(width: 180.0 + panelInset * 2.0, height: 45.0 + panelInset * 2.0) let placesButtonSize = CGSize(width: 180.0 + panelInset * 2.0, height: 45.0 + panelInset * 2.0)
let placesButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - placesButtonSize.width) / 2.0), y: self.displayingPlacesButton ? navigationBarHeight + topPadding + inset : 0.0), size: placesButtonSize) let placesButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - placesButtonSize.width) / 2.0), y: navigationBarHeight + topPadding - 6.0), size: placesButtonSize)
transition.updateFrame(node: self.placesBackgroundNode, frame: placesButtonFrame) transition.updateFrame(node: self.placesBackgroundNode, frame: placesButtonFrame)
if let placesBackgroundView = self.placesBackgroundView {
let backgroundViewFrame = CGRect(origin: .zero, size: placesButtonFrame.size).insetBy(dx: 5.0, dy: 6.0)
transition.updateFrame(view: placesBackgroundView, frame: backgroundViewFrame)
placesBackgroundView.update(size: backgroundViewFrame.size, cornerRadius: backgroundViewFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor), transition: .immediate)
}
transition.updateFrame(node: self.placesButtonNode, frame: CGRect(origin: CGPoint(), size: placesButtonSize)) transition.updateFrame(node: self.placesButtonNode, frame: CGRect(origin: CGPoint(), size: placesButtonSize))
transition.updateAlpha(node: self.placesBackgroundNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0) transition.updateAlpha(node: self.placesBackgroundNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0)
transition.updateAlpha(node: self.placesButtonNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0) transition.updateAlpha(node: self.placesButtonNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0)
transition.updateFrame(node: self.shadowNode, frame: CGRect(x: 0.0, y: size.height - 14.0, width: size.width, height: 14.0)) transition.updateFrame(node: self.shadowNode, frame: CGRect(x: 0.0, y: size.height - 14.0, width: size.width, height: 14.0))
transition.updateFrame(node: self.infoButtonNode, frame: CGRect(x: panelInset, y: panelInset, width: panelButtonSize.width, height: panelButtonSize.height)) let edgeEffectHeight: CGFloat = 80.0
transition.updateFrame(node: self.locationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height, width: panelButtonSize.width, height: panelButtonSize.height)) let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: edgeEffectHeight))
transition.updateFrame(node: self.notificationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height * 2.0, width: panelButtonSize.width, height: panelButtonSize.height)) transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
transition.updateFrame(node: self.optionsSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height, width: panelButtonSize.width, height: UIScreenPixel)) self.edgeEffectView.update(content: self.mapNode.mapMode == .map ? self.presentationData.theme.list.plainBackgroundColor : .clear, blur: true, alpha: 0.65, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: ComponentTransition(transition))
transition.updateFrame(node: self.optionsSecondSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height * 2.0, width: panelButtonSize.width, height: UIScreenPixel))
var panelHeight: CGFloat = panelButtonSize.height * 2.0 if let options = self.options {
if self.proximityNotification != nil { let optionsSize = options.update(
panelHeight += panelButtonSize.height transition: ComponentTransition(transition),
component: AnyComponent(
LocationOptionsComponent(
theme: self.presentationData.theme,
strings: self.presentationData.strings,
mapMode: self.mapNode.mapMode,
showMapModes: self.infoButtonNode.isSelected,
updateMapMode: { [weak self] mode in
guard let self else {
return
}
self.updateMapMode(mode)
},
goToUserLocation: { [weak self] in
guard let self else {
return
}
self.goToUserLocation()
},
requestedMapModes: { [weak self] in
guard let self else {
return
}
self.toggleMapModeSelection()
}
)
),
environment: {},
containerSize: layout.size
)
if let optionsView = options.view {
if optionsView.superview == nil {
self.view.addSubview(optionsView)
}
transition.updateFrame(view: optionsView, frame: CGRect(origin: CGPoint(x: size.width - optionsSize.width - inset - 10.0, y: size.height - optionsSize.height - inset - controlsBottomPadding - 10.0), size: optionsSize))
}
} else {
let buttonSize = self.glass ? glassPanelButtonSize : panelButtonSize
transition.updateFrame(node: self.infoButtonNode, frame: CGRect(x: panelInset, y: panelInset, width: buttonSize.width, height: buttonSize.height))
transition.updateFrame(node: self.locationButtonNode, frame: CGRect(x: panelInset, y: panelInset + buttonSize.height, width: buttonSize.width, height: buttonSize.height))
transition.updateFrame(node: self.notificationButtonNode, frame: CGRect(x: panelInset, y: panelInset + buttonSize.height * 2.0, width: buttonSize.width, height: buttonSize.height))
transition.updateFrame(node: self.optionsSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + buttonSize.height, width: buttonSize.width, height: UIScreenPixel))
transition.updateFrame(node: self.optionsSecondSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + buttonSize.height * 2.0, width: buttonSize.width, height: UIScreenPixel))
var panelHeight: CGFloat = buttonSize.height * 2.0
if self.proximityNotification != nil {
panelHeight += buttonSize.height
}
transition.updateAlpha(node: self.notificationButtonNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0)
transition.updateAlpha(node: self.optionsSecondSeparatorNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0)
let backgroundFrame = CGRect(x: size.width - inset - buttonSize.width - panelInset * 2.0 - layout.safeInsets.right - 6.0, y: size.height - panelHeight - inset - 14.0 - controlsBottomPadding, width: buttonSize.width + panelInset * 2.0, height: panelHeight + panelInset * 2.0)
transition.updateFrame(node: self.optionsBackgroundNode, frame: backgroundFrame)
if let optionsBackgroundView = self.optionsBackgroundView {
let backgroundViewFrame = backgroundFrame.insetBy(dx: 4.0, dy: 4.0)
transition.updateFrame(view: optionsBackgroundView, frame: backgroundViewFrame)
optionsBackgroundView.update(size: backgroundViewFrame.size, cornerRadius: backgroundViewFrame.width * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor), transition: .immediate)
}
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
let optionsAlpha: CGFloat = size.height > 160.0 + navigationBarHeight && !self.forceIsHidden ? 1.0 : 0.0
alphaTransition.updateAlpha(node: self.optionsBackgroundNode, alpha: optionsAlpha)
} }
transition.updateAlpha(node: self.notificationButtonNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0)
transition.updateAlpha(node: self.optionsSecondSeparatorNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0)
transition.updateFrame(node: self.optionsBackgroundNode, frame: CGRect(x: size.width - inset - panelButtonSize.width - panelInset * 2.0 - layout.safeInsets.right, y: navigationBarHeight + controlsTopPadding + inset, width: panelButtonSize.width + panelInset * 2.0, height: panelHeight + panelInset * 2.0))
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
let optionsAlpha: CGFloat = size.height > 160.0 + navigationBarHeight && !self.forceIsHidden ? 1.0 : 0.0
alphaTransition.updateAlpha(node: self.optionsBackgroundNode, alpha: optionsAlpha)
} }
public var forceIsHidden: Bool = false { public var forceIsHidden: Bool = false {
didSet { didSet {
if let (layout, navigationBarHeight, topPadding, controlsTopPadding, offset, size) = self.validLayout { if let (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size) = self.validLayout {
self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, offset: offset, size: size, transition: .immediate) self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, controlsBottomPadding: controlsBottomPadding, offset: offset, size: size, transition: .immediate)
} }
} }
} }
@ -254,3 +383,283 @@ public final class LocationMapHeaderNode: ASDisplayNode {
self.showPlacesInThisArea() self.showPlacesInThisArea()
} }
} }
public final class LocationOptionsComponent: Component {
public let theme: PresentationTheme
public let strings: PresentationStrings
public let mapMode: LocationMapMode
public let showMapModes: Bool
public let updateMapMode: (LocationMapMode) -> Void
public let goToUserLocation: () -> Void
public let requestedMapModes: () -> Void
public init(
theme: PresentationTheme,
strings: PresentationStrings,
mapMode: LocationMapMode,
showMapModes: Bool,
updateMapMode: @escaping (LocationMapMode) -> Void,
goToUserLocation: @escaping () -> Void,
requestedMapModes: @escaping () -> Void
) {
self.theme = theme
self.strings = strings
self.mapMode = mapMode
self.showMapModes = showMapModes
self.updateMapMode = updateMapMode
self.goToUserLocation = goToUserLocation
self.requestedMapModes = requestedMapModes
}
public static func ==(lhs: LocationOptionsComponent, rhs: LocationOptionsComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.mapMode != rhs.mapMode {
return false
}
if lhs.showMapModes != rhs.showMapModes {
return false
}
return true
}
public final class View: HighlightTrackingButton {
private let backgroundView: GlassBackgroundView
private let clippingView: UIView
private let collapsedContainerView = UIView()
private var mapModeButton = ComponentView<Empty>()
private var trackingButton = ComponentView<Empty>()
private let expandedContainerView = UIView()
private let checkIcon = UIImageView()
private var mapButton = ComponentView<Empty>()
private var satelliteButton = ComponentView<Empty>()
private var hybridButton = ComponentView<Empty>()
private var component: LocationOptionsComponent?
public override init(frame: CGRect) {
self.backgroundView = GlassBackgroundView()
self.clippingView = UIView()
self.clippingView.clipsToBounds = true
self.checkIcon.image = UIImage(bundleImageName: "Media Gallery/Check")?.withRenderingMode(.alwaysTemplate)
super.init(frame: frame)
self.addSubview(self.backgroundView)
self.addSubview(self.clippingView)
self.clippingView.addSubview(self.collapsedContainerView)
self.clippingView.addSubview(self.expandedContainerView)
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: LocationOptionsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
let mapButtonSize = self.mapButton.update(
transition: transition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: component.strings.Map_Map, font: Font.regular(17.0), textColor: component.theme.rootController.navigationBar.primaryTextColor)))
),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.updateMapMode(.map)
},
animateScale: false
)
),
environment: {},
containerSize: availableSize
)
let satelliteButtonSize = self.satelliteButton.update(
transition: transition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: component.strings.Map_Satellite, font: Font.regular(17.0), textColor: component.theme.rootController.navigationBar.primaryTextColor)))
),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.updateMapMode(.satellite)
},
animateScale: false
)
),
environment: {},
containerSize: availableSize
)
let hybridButtonSize = self.hybridButton.update(
transition: transition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: component.strings.Map_Hybrid, font: Font.regular(17.0), textColor: component.theme.rootController.navigationBar.primaryTextColor)))
),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.updateMapMode(.hybrid)
},
animateScale: false
)
),
environment: {},
containerSize: availableSize
)
self.checkIcon.tintColor = component.theme.rootController.navigationBar.primaryTextColor
if let image = self.checkIcon.image {
self.checkIcon.frame = CGRect(origin: CGPoint(x: -34.0, y: floorToScreenPixels((mapButtonSize.height - image.size.height) / 2.0)), size: image.size)
}
let leftInset: CGFloat = 60.0
let rightInset: CGFloat = 44.0
let verticalInset: CGFloat = 23.0
let maxWidth = max(mapButtonSize.width, max(satelliteButtonSize.width, hybridButtonSize.width))
let cornerRadius: CGFloat = component.showMapModes ? 27.0 : 20.0
let expandedSize = CGSize(width: leftInset + maxWidth + rightInset, height: 150.0)
let mapButtonFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: mapButtonSize)
if let mapButtonView = self.mapButton.view {
if mapButtonView.superview == nil {
self.expandedContainerView.addSubview(mapButtonView)
}
if component.mapMode == .map && component.showMapModes {
mapButtonView.addSubview(self.checkIcon)
}
transition.setFrame(view: mapButtonView, frame: mapButtonFrame)
}
let satelliteButtonFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((expandedSize.height - satelliteButtonSize.height) / 2.0)), size: satelliteButtonSize)
if let satelliteButtonView = self.satelliteButton.view {
if satelliteButtonView.superview == nil {
self.expandedContainerView.addSubview(satelliteButtonView)
}
if component.mapMode == .satellite && component.showMapModes {
satelliteButtonView.addSubview(self.checkIcon)
}
transition.setFrame(view: satelliteButtonView, frame: satelliteButtonFrame)
}
let hybridButtonFrame = CGRect(origin: CGPoint(x: leftInset, y: expandedSize.height - hybridButtonSize.height - verticalInset), size: hybridButtonSize)
if let hybridButtonView = self.hybridButton.view {
if hybridButtonView.superview == nil {
self.expandedContainerView.addSubview(hybridButtonView)
}
if component.mapMode == .hybrid && component.showMapModes {
hybridButtonView.addSubview(self.checkIcon)
}
transition.setFrame(view: hybridButtonView, frame: hybridButtonFrame)
}
let normalSize = CGSize(width: 40.0, height: 80.0)
let expandedFrame = CGRect(origin: .zero, size: expandedSize)
let collapsedFrame = CGRect(origin: CGPoint(x: expandedSize.width - normalSize.width, y: expandedSize.height - normalSize.height), size: normalSize)
let effectiveBackgroundFrame = component.showMapModes ? expandedFrame : collapsedFrame
self.backgroundView.update(size: effectiveBackgroundFrame.size, cornerRadius: cornerRadius, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.rootController.navigationBar.glassBarButtonBackgroundColor), transition: transition)
transition.setFrame(view: self.backgroundView, frame: effectiveBackgroundFrame)
transition.setFrame(view: self.clippingView, frame: effectiveBackgroundFrame)
transition.setFrame(view: self.expandedContainerView, frame: expandedFrame.offsetBy(dx: effectiveBackgroundFrame.width - expandedFrame.width, dy: effectiveBackgroundFrame.height - expandedFrame.height))
transition.setFrame(view: self.collapsedContainerView, frame: collapsedFrame.offsetBy(dx: -effectiveBackgroundFrame.minX, dy: -effectiveBackgroundFrame.minY))
let mapModeButtonSize = self.mapModeButton.update(
transition: transition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
BundleIconComponent(name: "Location/OptionMap", tintColor: component.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62))
),
minSize: CGSize(width: 40.0, height: 40.0),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.requestedMapModes()
},
animateAlpha: true,
animateScale: false
)
),
environment: {},
containerSize: CGSize(width: 40.0, height: 40.0)
)
let mapModeButtonFrame = CGRect(origin: .zero, size: mapModeButtonSize)
if let mapModeButtonView = self.mapModeButton.view {
if mapModeButtonView.superview == nil {
self.collapsedContainerView.addSubview(mapModeButtonView)
}
transition.setFrame(view: mapModeButtonView, frame: mapModeButtonFrame)
}
let trackingButtonSize = self.trackingButton.update(
transition: transition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
BundleIconComponent(name: "Location/OptionLocate", tintColor: component.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62))
),
minSize: CGSize(width: 40.0, height: 40.0),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.goToUserLocation()
},
animateAlpha: true,
animateScale: false
)
),
environment: {},
containerSize: CGSize(width: 40.0, height: 40.0)
)
let trackingButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: 40.0), size: trackingButtonSize)
if let trackingButtonView = self.trackingButton.view {
if trackingButtonView.superview == nil {
self.collapsedContainerView.addSubview(trackingButtonView)
}
transition.setFrame(view: trackingButtonView, frame: trackingButtonFrame)
}
transition.setAlpha(view: self.collapsedContainerView, alpha: component.showMapModes ? 0.0 : 1.0)
transition.setAlpha(view: self.expandedContainerView, alpha: component.showMapModes ? 1.0 : 0.0)
return expandedSize
}
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return self.backgroundView.frame.contains(point)
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -9,12 +9,12 @@ private let pinOffset = CGPoint(x: 0.0, y: 33.0)
public enum LocationMapMode { public enum LocationMapMode {
case map case map
case sattelite case satellite
case hybrid case hybrid
var mapType: MKMapType { var mapType: MKMapType {
switch self { switch self {
case .sattelite: case .satellite:
return .satellite return .satellite
case .hybrid: case .hybrid:
return .hybrid return .hybrid

View file

@ -36,7 +36,7 @@ public final class LocationOptionsNode: ASDisplayNode {
case 0: case 0:
updateMapMode(.map) updateMapMode(.map)
case 1: case 1:
updateMapMode(.sattelite) updateMapMode(.satellite)
case 2: case 2:
updateMapMode(.hybrid) updateMapMode(.hybrid)
default: default:

View file

@ -90,8 +90,15 @@ public final class LocationPickerController: ViewController, AttachmentContainab
public var isContainerExpanded: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false }
public var isMinimized: Bool = false public var isMinimized: Bool = false
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: LocationPickerMode, source: Source = .generic, initialLocation: CLLocationCoordinate2D? = nil, completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void) { private let style: Style
public enum Style {
case glass
case legacy
}
public init(context: AccountContext, style: Style = .legacy, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: LocationPickerMode, source: Source = .generic, initialLocation: CLLocationCoordinate2D? = nil, completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void) {
self.context = context self.context = context
self.style = style
self.mode = mode self.mode = mode
self.source = source self.source = source
self.initialLocation = initialLocation self.initialLocation = initialLocation
@ -99,12 +106,18 @@ public final class LocationPickerController: ViewController, AttachmentContainab
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
self.updatedPresentationData = updatedPresentationData self.updatedPresentationData = updatedPresentationData
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) let navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: style == .glass, hideSeparator: true), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
super.init(navigationBarPresentationData: navigationBarPresentationData)
self.navigationPresentation = .modal self.navigationPresentation = .modal
self.title = self.presentationData.strings.Map_ChooseLocationTitle if case .glass = style {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
} else {
self.title = self.presentationData.strings.Map_ChooseLocationTitle
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
}
self.updateBarButtons() self.updateBarButtons()
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
@ -114,7 +127,9 @@ public final class LocationPickerController: ViewController, AttachmentContainab
} }
strongSelf.presentationData = presentationData strongSelf.presentationData = presentationData
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) let navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme, hideBackground: style == .glass, hideSeparator: true), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))
strongSelf.navigationBar?.updatePresentationData(navigationBarPresentationData)
strongSelf.searchNavigationContentNode?.updatePresentationData(strongSelf.presentationData) strongSelf.searchNavigationContentNode?.updatePresentationData(strongSelf.presentationData)
strongSelf.updateBarButtons() strongSelf.updateBarButtons()
@ -243,45 +258,55 @@ public final class LocationPickerController: ViewController, AttachmentContainab
return state return state
} }
}, openSearch: { [weak self] in }, openSearch: { [weak self] in
guard let strongSelf = self, let interaction = strongSelf.interaction, let navigationBar = strongSelf.navigationBar else { guard let self, let interaction = self.interaction, let navigationBar = self.navigationBar else {
return return
} }
strongSelf.controllerNode.updateState { state in self.controllerNode.updateState { state in
var state = state var state = state
state.displayingMapModeOptions = false state.displayingMapModeOptions = false
return state return state
} }
let contentNode = LocationSearchNavigationContentNode(presentationData: strongSelf.presentationData, interaction: interaction)
strongSelf.searchNavigationContentNode = contentNode switch style {
navigationBar.setContentNode(contentNode, animated: true) case .glass:
let isSearching = strongSelf.controllerNode.activateSearch(navigationBar: navigationBar) let _ = self.controllerNode.activateSearch(navigationBar: navigationBar)
contentNode.activate() self.updateTabBarVisibility(false, .animated(duration: 0.4, curve: .spring))
case .legacy:
let contentNode = LocationSearchNavigationContentNode(presentationData: self.presentationData, interaction: interaction)
self.searchNavigationContentNode = contentNode
navigationBar.setContentNode(contentNode, animated: true)
let isSearching = self.controllerNode.activateSearch(navigationBar: navigationBar)
contentNode.activate()
strongSelf.isSearchingDisposable.set((isSearching self.isSearchingDisposable.set((isSearching
|> deliverOnMainQueue).start(next: { [weak self] value in |> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self, let searchNavigationContentNode = strongSelf.searchNavigationContentNode { if let strongSelf = self, let searchNavigationContentNode = strongSelf.searchNavigationContentNode {
searchNavigationContentNode.updateActivity(value) searchNavigationContentNode.updateActivity(value)
} }
})) }))
}
}, updateSearchQuery: { [weak self] query in }, updateSearchQuery: { [weak self] query in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.controllerNode.searchContainerNode?.searchTextUpdated(text: query) strongSelf.controllerNode.searchContainerNode?.searchTextUpdated(text: query)
}, dismissSearch: { [weak self] in }, dismissSearch: { [weak self] in
guard let strongSelf = self, let navigationBar = strongSelf.navigationBar else { guard let self, let navigationBar = self.navigationBar else {
return return
} }
strongSelf.isSearchingDisposable.set(nil) self.isSearchingDisposable.set(nil)
strongSelf.searchNavigationContentNode?.deactivate() self.searchNavigationContentNode?.deactivate()
strongSelf.searchNavigationContentNode = nil self.searchNavigationContentNode = nil
navigationBar.setContentNode(nil, animated: true) navigationBar.setContentNode(nil, animated: true)
strongSelf.controllerNode.deactivateSearch() self.controllerNode.deactivateSearch()
self.updateTabBarVisibility(true, .animated(duration: 0.4, curve: .spring))
}, dismissInput: { [weak self] in }, dismissInput: { [weak self] in
guard let strongSelf = self else { guard let self else {
return return
} }
strongSelf.searchNavigationContentNode?.deactivate() self.searchNavigationContentNode?.deactivate()
self.controllerNode.deactivateInput()
}, updateSendActionHighlight: { [weak self] highlighted in }, updateSendActionHighlight: { [weak self] highlighted in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -322,8 +347,12 @@ public final class LocationPickerController: ViewController, AttachmentContainab
if self.locationAccessDenied { if self.locationAccessDenied {
self.navigationItem.rightBarButtonItem = nil self.navigationItem.rightBarButtonItem = nil
} else { } else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.searchPressed)) if case .glass = self.style {
self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.Common_Search
} else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.searchPressed))
self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.Common_Search
}
} }
} }
@ -379,12 +408,20 @@ public final class LocationPickerController: ViewController, AttachmentContainab
self.dismiss() self.dismiss()
} }
@objc private func searchPressed() { @objc func searchPressed() {
self.requestAttachmentMenuExpansion() self.requestAttachmentMenuExpansion()
self.interaction?.openSearch() self.interaction?.openSearch()
} }
func dismissSearchPressed() {
self.interaction?.dismissSearch()
}
func updateSearchQuery(_ query: String) {
self.interaction?.updateSearchQuery(query)
}
public func resetForReuse() { public func resetForReuse() {
self.interaction?.updateMapMode(.map) self.interaction?.updateMapMode(.map)
self.interaction?.dismissSearch() self.interaction?.dismissSearch()

View file

@ -16,6 +16,13 @@ import CoreLocation
import Geocoding import Geocoding
import PhoneNumberFormat import PhoneNumberFormat
import DeviceAccess import DeviceAccess
import GlassBarButtonComponent
import ComponentFlow
import BundleIconComponent
import MultilineTextComponent
import SearchInputPanelComponent
import ButtonComponent
import EdgeEffect
private struct LocationPickerTransaction { private struct LocationPickerTransaction {
let deletions: [ListViewDeleteItem] let deletions: [ListViewDeleteItem]
@ -175,9 +182,9 @@ private enum LocationPickerEntry: Comparable, Identifiable {
icon = .location icon = .location
} }
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: { return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: {
if let venue = venue { if let venue {
interaction?.sendVenue(venue, queryId, resultId) interaction?.sendVenue(venue, queryId, resultId)
} else if let coordinate = coordinate { } else if let coordinate {
interaction?.sendLocation(coordinate, name, address) interaction?.sendLocation(coordinate, name, address)
} }
}, highlighted: { highlighted in }, highlighted: { highlighted in
@ -192,10 +199,10 @@ private enum LocationPickerEntry: Comparable, Identifiable {
} }
}) })
case let .header(_, title): case let .header(_, title):
return LocationSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title) return LocationSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .legacy, title: title)
case let .venue(_, venue, queryId, resultId, _): case let .venue(_, venue, queryId, resultId, _):
let venueType = venue?.venue?.type ?? "" let venueType = venue?.venue?.type ?? ""
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), engine: engine, venue: venue, style: .plain, action: venue.flatMap { venue in return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, engine: engine, venue: venue, style: .plain, action: venue.flatMap { venue in
return { interaction?.sendVenue(venue, queryId, resultId) } return { interaction?.sendVenue(venue, queryId, resultId) }
}, infoAction: ["home", "work"].contains(venueType) ? { }, infoAction: ["home", "work"].contains(venueType) ? {
interaction?.openHomeWorkInfo() interaction?.openHomeWorkInfo()
@ -344,6 +351,14 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
private let shadeNode: ASDisplayNode private let shadeNode: ASDisplayNode
private let innerShadeNode: ASDisplayNode private let innerShadeNode: ASDisplayNode
private let cancelButton = ComponentView<Empty>()
private let searchButton = ComponentView<Empty>()
private let title = ComponentView<Empty>()
fileprivate let bottomEdgeEffectView: EdgeEffectView
private var sendButton: ComponentView<Empty>?
private let optionsNode: LocationOptionsNode private let optionsNode: LocationOptionsNode
private(set) var searchContainerNode: LocationSearchContainerNode? private(set) var searchContainerNode: LocationSearchContainerNode?
@ -360,6 +375,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
private let searchVenuesPromise = Promise<CLLocationCoordinate2D?>() private let searchVenuesPromise = Promise<CLLocationCoordinate2D?>()
private var searchInput: ComponentView<Empty>?
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
private var listOffset: CGFloat? private var listOffset: CGFloat?
@ -394,9 +411,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.emptyResultsTextNode.textAlignment = .center self.emptyResultsTextNode.textAlignment = .center
self.emptyResultsTextNode.isHidden = true self.emptyResultsTextNode.isHidden = true
self.headerNode = LocationMapHeaderNode(presentationData: presentationData, toggleMapModeSelection: interaction.toggleMapModeSelection, goToUserLocation: interaction.goToUserLocation, showPlacesInThisArea: interaction.showPlacesInThisArea) self.headerNode = LocationMapHeaderNode(
presentationData: presentationData,
glass: true,
toggleMapModeSelection: interaction.toggleMapModeSelection,
updateMapMode: interaction.updateMapMode,
goToUserLocation: interaction.goToUserLocation,
showPlacesInThisArea: interaction.showPlacesInThisArea
)
self.headerNode.mapNode.isRotateEnabled = false self.headerNode.mapNode.isRotateEnabled = false
self.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode) self.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode)
self.shadeNode = ASDisplayNode() self.shadeNode = ASDisplayNode()
@ -405,13 +429,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.innerShadeNode = ASDisplayNode() self.innerShadeNode = ASDisplayNode()
self.innerShadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.innerShadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.bottomEdgeEffectView = EdgeEffectView()
self.bottomEdgeEffectView.isUserInteractionEnabled = false
super.init() super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.listNode) self.addSubnode(self.listNode)
self.addSubnode(self.headerNode) self.addSubnode(self.headerNode)
self.addSubnode(self.optionsNode) //self.addSubnode(self.optionsNode)
self.listNode.addSubnode(self.emptyResultsTextNode) self.listNode.addSubnode(self.emptyResultsTextNode)
self.shadeNode.addSubnode(self.innerShadeNode) self.shadeNode.addSubnode(self.innerShadeNode)
self.addSubnode(self.shadeNode) self.addSubnode(self.shadeNode)
@ -780,36 +807,34 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
if annotations != previousAnnotations { if annotations != previousAnnotations {
strongSelf.headerNode.mapNode.annotations = annotations strongSelf.headerNode.mapNode.annotations = annotations
} }
var updateLayout = false
if [.denied, .restricted].contains(access) {
if !strongSelf.locationAccessDenied {
strongSelf.locationAccessDenied = true
strongSelf.locationAccessDeniedUpdated(true)
updateLayout = true
}
} else {
if strongSelf.locationAccessDenied {
strongSelf.locationAccessDenied = false
strongSelf.locationAccessDeniedUpdated(false)
updateLayout = true
}
}
if let (layout, navigationBarHeight) = strongSelf.validLayout { if case let .location(_, previousAddress, _) = previousState.selectedLocation, case let .location(_, newAddress, _) = state.selectedLocation, previousAddress != newAddress {
var updateLayout = false updateLayout = true
let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) } else if previousState.displayingMapModeOptions != state.displayingMapModeOptions {
updateLayout = true
} else if previousState.selectedLocation.isCustom != state.selectedLocation.isCustom {
updateLayout = true
} else if previousState.searchingVenuesAround != state.searchingVenuesAround {
updateLayout = true
}
if [.denied, .restricted].contains(access) { if updateLayout {
if !strongSelf.locationAccessDenied { strongSelf.requestLayout(transition: .animated(duration: 0.45, curve: .spring))
strongSelf.locationAccessDenied = true
strongSelf.locationAccessDeniedUpdated(true)
updateLayout = true
}
} else {
if strongSelf.locationAccessDenied {
strongSelf.locationAccessDenied = false
strongSelf.locationAccessDeniedUpdated(false)
updateLayout = true
}
}
if previousState.displayingMapModeOptions != state.displayingMapModeOptions {
updateLayout = true
} else if previousState.selectedLocation.isCustom != state.selectedLocation.isCustom {
updateLayout = true
} else if previousState.searchingVenuesAround != state.searchingVenuesAround {
updateLayout = true
}
if updateLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationBarHeight, transition: transition)
}
} }
let locale = localeWithStrings(presentationData.strings) let locale = localeWithStrings(presentationData.strings)
@ -907,7 +932,13 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
setupGeocoding(coordinate, false, { [weak self] geoAddress, _, address, cityName, streetName, countryCode, isStreet in setupGeocoding(coordinate, false, { [weak self] geoAddress, _, address, cityName, streetName, countryCode, isStreet in
self?.updateState { state in self?.updateState { state in
var state = state var state = state
state.selectedLocation = .location(coordinate, address, global)
var shortAddress = ""
if let streetName {
shortAddress.append(streetName)
}
state.selectedLocation = .location(coordinate, shortAddress, global)
state.geoAddress = geoAddress state.geoAddress = geoAddress
state.city = cityName state.city = cityName
state.street = streetName state.street = streetName
@ -950,15 +981,15 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in
guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout, strongSelf.listNode.scrollEnabled else { guard let self, let (layout, navigationBarHeight) = self.validLayout, self.listNode.scrollEnabled else {
return return
} }
let overlap: CGFloat = 6.0 let overlap: CGFloat = 0.0 //6.0
strongSelf.listOffset = max(0.0, offset) self.listOffset = max(0.0, offset)
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap))) let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap)))
listTransition.updateFrame(node: strongSelf.headerNode, frame: headerFrame) listTransition.updateFrame(node: self.headerNode, frame: headerFrame)
strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsTopPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition) self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: self.state.displayingMapModeOptions ? 38.0 : 0.0, controlsTopPadding: self.state.displayingMapModeOptions ? 38.0 : 0.0, controlsBottomPadding: self.isPickingLocation ? 94.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition)
strongSelf.layoutEmptyResultsPlaceholder(transition: listTransition) self.layoutEmptyResultsPlaceholder(transition: listTransition)
} }
self.listNode.beganInteractiveDragging = { [weak self] _ in self.listNode.beganInteractiveDragging = { [weak self] _ in
@ -973,17 +1004,19 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
self.headerNode.mapNode.beganInteractiveDragging = { [weak self] in self.headerNode.mapNode.beganInteractiveDragging = { [weak self] in
guard let strongSelf = self else { guard let self, let controller = self.controller else {
return return
} }
strongSelf.beganInteractiveDragging() self.beganInteractiveDragging()
strongSelf.updateState { state in self.updateState { state in
var state = state var state = state
state.displayingMapModeOptions = false state.displayingMapModeOptions = false
state.selectedLocation = .selecting state.selectedLocation = .selecting
state.searchingVenuesAround = false state.searchingVenuesAround = false
return state return state
} }
controller.updateTabBarVisibility(false, .animated(duration: 0.4, curve: .spring))
} }
self.headerNode.mapNode.endedInteractiveDragging = { [weak self] coordinate in self.headerNode.mapNode.endedInteractiveDragging = { [weak self] coordinate in
@ -1081,7 +1114,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
if let strongSelf = self { if let strongSelf = self {
strongSelf.emptyResultsTextNode.isHidden = transition.isLoading || !transition.isEmpty strongSelf.emptyResultsTextNode.isHidden = transition.isLoading || !transition.isEmpty
strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.Map_NoPlacesNearby, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.Map_NoPlacesNearby, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.itemSecondaryTextColor)
strongSelf.layoutEmptyResultsPlaceholder(transition: .immediate) strongSelf.layoutEmptyResultsPlaceholder(transition: .immediate)
} }
@ -1089,17 +1122,17 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
func activateSearch(navigationBar: NavigationBar) -> Signal<Bool, NoError> { func activateSearch(navigationBar: NavigationBar) -> Signal<Bool, NoError> {
guard let (layout, navigationBarHeight) = self.validLayout, self.searchContainerNode == nil, let coordinate = self.headerNode.mapNode.mapCenterCoordinate else { guard self.searchContainerNode == nil, let coordinate = self.headerNode.mapNode.mapCenterCoordinate else {
return .complete() return .complete()
} }
let searchContainerNode = LocationSearchContainerNode(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, coordinate: coordinate, interaction: self.interaction, story: self.source == .story) let searchContainerNode = LocationSearchContainerNode(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, coordinate: coordinate, interaction: self.interaction, story: self.source == .story)
self.insertSubnode(searchContainerNode, belowSubnode: navigationBar) self.insertSubnode(searchContainerNode, belowSubnode: navigationBar)
self.searchContainerNode = searchContainerNode self.searchContainerNode = searchContainerNode
searchContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) searchContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.containerLayoutUpdated(layout, navigationHeight: navigationBarHeight, transition: .immediate) self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
return searchContainerNode.isSearching return searchContainerNode.isSearching
} }
@ -1112,6 +1145,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
searchContainerNode?.removeFromSupernode() searchContainerNode?.removeFromSupernode()
}) })
self.searchContainerNode = nil self.searchContainerNode = nil
self.deactivateInput()
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
func deactivateInput() {
if let searchInputView = self.searchInput?.view as? SearchInputPanelComponent.View {
let _ = searchInputView.deactivateInput()
}
} }
func scrollToTop() { func scrollToTop() {
@ -1140,12 +1183,27 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
transition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - emptyTextSize.width) / 2.0), y: headerHeight + actionsInset + floor((layout.size.height - headerHeight - actionsInset - emptyTextSize.height - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom) / 2.0)), size: emptyTextSize)) transition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - emptyTextSize.width) / 2.0), y: headerHeight + actionsInset + floor((layout.size.height - headerHeight - actionsInset - emptyTextSize.height - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom) / 2.0)), size: emptyTextSize))
} }
private var isPickingLocation: Bool {
return (self.state.selectedLocation.isCustom || self.state.forceSelection) && !self.state.searchingVenuesAround
}
func requestLayout(transition: ContainedViewLayoutTransition) {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
}
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let isFirstLayout = self.validLayout == nil let isFirstLayout = self.validLayout == nil
self.validLayout = (layout, navigationHeight) self.validLayout = (layout, navigationHeight)
let isPickingLocation = (self.state.selectedLocation.isCustom || self.state.forceSelection) && !self.state.searchingVenuesAround var glass = false
if let controller = self.controller, controller._hasGlassStyle {
glass = true
}
let isPickingLocation = self.isPickingLocation
let optionsHeight: CGFloat = 38.0 let optionsHeight: CGFloat = 38.0
var actionHeight: CGFloat? var actionHeight: CGFloat?
self.listNode.forEachItemNode { itemNode in self.listNode.forEachItemNode { itemNode in
@ -1155,14 +1213,17 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
} }
} }
// let topInset: CGFloat = floor((layout.size.height - navigationHeight) / 2.0 + navigationHeight) let topInset: CGFloat = glass ? 300.0 : 240.0
let topInset: CGFloat = 240.0 let overlap: CGFloat = glass ? 0.0 : 6.0
let overlap: CGFloat = 6.0
let headerHeight: CGFloat let headerHeight: CGFloat
if isPickingLocation, let actionHeight = actionHeight { if isPickingLocation, let actionHeight = actionHeight {
self.listOffset = topInset self.listOffset = topInset
headerHeight = layout.size.height - actionHeight - layout.intrinsicInsets.bottom + overlap - 2.0 if glass {
headerHeight = layout.size.height
} else {
headerHeight = layout.size.height - actionHeight - layout.intrinsicInsets.bottom + overlap - 2.0
}
} else if let listOffset = self.listOffset { } else if let listOffset = self.listOffset {
headerHeight = max(0.0, listOffset + overlap) headerHeight = max(0.0, listOffset + overlap)
} else { } else {
@ -1171,7 +1232,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight)) let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight))
transition.updateFrame(node: self.headerNode, frame: headerFrame) transition.updateFrame(node: self.headerNode, frame: headerFrame)
self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsTopPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, offset: 0.0, size: headerFrame.size, transition: transition) self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions && !glass ? optionsHeight : 0.0, controlsTopPadding: self.state.displayingMapModeOptions && !glass ? optionsHeight : 0.0, controlsBottomPadding: isPickingLocation ? 94.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: transition)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let scrollToItem: ListViewScrollToItem? let scrollToItem: ListViewScrollToItem?
@ -1267,6 +1328,248 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.controller?.updateTabBarAlpha(1.0, .immediate) self.controller?.updateTabBarAlpha(1.0, .immediate)
} }
if glass {
let titleSize = self.title.update(
transition: ComponentTransition(transition),
component: AnyComponent(
MultilineTextComponent(
text: .plain(
NSAttributedString(
string: self.presentationData.strings.Map_ChooseLocationTitle,
font: Font.semibold(17.0),
textColor: self.headerNode.mapNode.mapMode == .map ? self.presentationData.theme.rootController.navigationBar.primaryTextColor : .white
)
)
)
),
environment: {},
containerSize: CGSize(width: 200.0, height: 40.0)
)
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.view.addSubview(titleView)
}
transition.updateFrame(view: titleView, frame: titleFrame)
}
let barButtonSize = CGSize(width: 40.0, height: 40.0)
let cancelButtonSize = self.cancelButton.update(
transition: ComponentTransition(transition),
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
isDark: self.presentationData.theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: isPickingLocation ? "back" : "close", component: AnyComponent(
BundleIconComponent(
name: isPickingLocation ? "Navigation/Back" : "Navigation/Close",
tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor
)
)),
action: { [weak self] _ in
guard let self else {
return
}
if isPickingLocation {
self.goToUserLocation()
} else {
self.controller?.dismiss()
}
}
)),
environment: {},
containerSize: barButtonSize
)
let cancelButtonFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize)
if let cancelButtonView = self.cancelButton.view {
if cancelButtonView.superview == nil {
self.view.addSubview(cancelButtonView)
}
transition.updateFrame(view: cancelButtonView, frame: cancelButtonFrame)
}
let searchButtonSize = self.searchButton.update(
transition: ComponentTransition(transition),
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
isDark: self.presentationData.theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "search", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Search",
tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor
)
)),
action: { [weak self] _ in
self?.controller?.searchPressed()
}
)),
environment: {},
containerSize: barButtonSize
)
let searchButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - 16.0 - searchButtonSize.width, y: 16.0), size: searchButtonSize)
if let searchButtonView = self.searchButton.view {
if searchButtonView.superview == nil {
self.view.addSubview(searchButtonView)
}
transition.updateFrameAsPositionAndBounds(layer: searchButtonView.layer, frame: searchButtonFrame)
if let _ = self.searchContainerNode {
transition.updateAlpha(layer: searchButtonView.layer, alpha: 0.0)
transition.updateTransformScale(layer: searchButtonView.layer, scale: 0.01)
} else {
transition.updateAlpha(layer: searchButtonView.layer, alpha: 1.0)
transition.updateTransformScale(layer: searchButtonView.layer, scale: 1.0)
}
}
if let _ = self.searchContainerNode {
let searchInput: ComponentView<Empty>
if let current = self.searchInput {
searchInput = current
} else {
searchInput = ComponentView()
self.searchInput = searchInput
}
let searchInputTransition: ComponentTransition = self.searchInput?.view == nil ? .immediate : ComponentTransition(transition)
let searchInputSize = searchInput.update(
transition: searchInputTransition,
component: AnyComponent(
SearchInputPanelComponent(
theme: self.presentationData.theme,
strings: self.presentationData.strings,
placeholder: self.presentationData.strings.Map_Search,
resetText: nil,
updated: { [weak self] query in
guard let self, let controller = self.controller else {
return
}
controller.updateSearchQuery(query)
},
cancel: { [weak self] in
guard let self, let controller = self.controller else {
return
}
controller.dismissSearchPressed()
}
)
),
environment: {},
containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)
)
let bottomInset: CGFloat = layout.insets(options: .input).bottom
let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize)
if let searchInputView = searchInput.view as? SearchInputPanelComponent.View {
if searchInputView.superview == nil {
self.view.addSubview(searchInputView)
searchInputView.frame = CGRect(origin: CGPoint(x: searchInputFrame.minX, y: layout.size.height), size: searchInputFrame.size)
searchInputView.activateInput()
}
transition.updateFrame(view: searchInputView, frame: searchInputFrame)
}
} else if let searchInput = self.searchInput {
self.searchInput = nil
if let searchInputView = searchInput.view {
transition.updateFrame(view: searchInputView, frame: CGRect(origin: CGPoint(x: searchInputView.frame.minX, y: layout.size.height), size: searchInputView.frame.size), completion: { _ in
searchInputView.removeFromSuperview()
})
}
}
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 88.0 - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: 88.0))
transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
self.bottomEdgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
if self.bottomEdgeEffectView.superview == nil {
self.view.addSubview(self.bottomEdgeEffectView)
}
if isPickingLocation {
let sendButton: ComponentView<Empty>
if let current = self.sendButton {
sendButton = current
} else {
sendButton = ComponentView()
self.sendButton = sendButton
}
let buttonBackground = ButtonComponent.Background(
style: .glass,
color: self.presentationData.theme.list.itemCheckColors.fillColor,
foreground: self.presentationData.theme.list.itemCheckColors.foregroundColor,
pressedColor: self.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
)
//TODO:localize
var buttonContents: [AnyComponentWithIdentity<Empty>] = [
AnyComponentWithIdentity(
id: AnyHashable("label"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Send Location", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
)
]
var address: String?
if case let .location(_, addressValue, _) = self.state.selectedLocation {
address = addressValue
}
buttonContents.append(
AnyComponentWithIdentity(
id: AnyHashable(address ?? "locating"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: address ?? self.presentationData.strings.Map_Locating, font: Font.medium(13.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7), paragraphAlignment: .center))))
)
)
let sendButtonSize = sendButton.update(
transition: ComponentTransition(transition),
component: AnyComponent(
ButtonComponent(
background: buttonBackground,
content: AnyComponentWithIdentity(
id: AnyHashable("send"),
component: AnyComponent(
VStack(buttonContents, spacing: 1.0)
)
),
action: { [weak self] in
guard let self else {
return
}
if case let .location(coordinate, _, _) = self.state.selectedLocation {
self.interaction.sendLocation(coordinate, nil, nil)
}
}
)
),
environment: {},
containerSize: CGSize(width: layout.size.width - 36.0 * 2.0, height: 52.0)
)
let sendButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - sendButtonSize.width) / 2.0), y: layout.size.height - insets.bottom - sendButtonSize.height - 14.0), size: sendButtonSize)
if let sendButtonView = sendButton.view {
if sendButtonView.superview == nil {
self.view.addSubview(sendButtonView)
sendButtonView.alpha = 0.0
sendButtonView.transform = .identity
transition.animateTransformScale(view: sendButtonView, from: 0.01)
transition.updateAlpha(layer: sendButtonView.layer, alpha: 1.0)
}
transition.updateFrameAsPositionAndBounds(layer: sendButtonView.layer, frame: sendButtonFrame)
}
} else if let sendButton = self.sendButton {
self.sendButton = nil
if let sendButtonView = sendButton.view {
transition.updateAlpha(layer: sendButtonView.layer, alpha: 0.0, completion: { _ in
sendButtonView.removeFromSuperview()
})
transition.updateTransformScale(layer: sendButtonView.layer, scale: 0.01)
}
}
}
} }
func updateSendActionHighlight(_ highlighted: Bool) { func updateSendActionHighlight(_ highlighted: Bool) {
@ -1275,6 +1578,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
func goToUserLocation() { func goToUserLocation() {
guard let controller = self.controller else {
return
}
self.searchVenuesPromise.set(.single(nil)) self.searchVenuesPromise.set(.single(nil))
self.updateState { state in self.updateState { state in
var state = state var state = state
@ -1283,6 +1590,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
state.searchingVenuesAround = false state.searchingVenuesAround = false
return state return state
} }
controller.updateTabBarVisibility(true, .animated(duration: 0.4, curve: .spring))
} }
func requestPlacesAtSelectedLocation() { func requestPlacesAtSelectedLocation() {

View file

@ -136,10 +136,11 @@ final class LocationSearchContainerNode: ASDisplayNode {
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings))
self.dimNode = ASDisplayNode() self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) self.dimNode.backgroundColor = .clear // UIColor.black.withAlphaComponent(0.5)
self.listNode = ListView() self.listNode = ListView()
self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.listNode.isHidden = true self.listNode.alpha = 0.0
self.listNode.accessibilityPageScrolledString = { row, count in self.listNode.accessibilityPageScrolledString = { row, count in
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
} }
@ -164,9 +165,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
self.addSubnode(self.emptyResultsTitleNode) self.addSubnode(self.emptyResultsTitleNode)
self.addSubnode(self.emptyResultsTextNode) self.addSubnode(self.emptyResultsTextNode)
self.listNode.isHidden = true
let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
let themeAndStringsPromise = self.themeAndStringsPromise let themeAndStringsPromise = self.themeAndStringsPromise
@ -278,7 +277,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
} }
func scrollToTop() { func scrollToTop() {
if !self.listNode.isHidden { if self.listNode.alpha > 0.0 {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
} }
} }
@ -354,14 +353,16 @@ final class LocationSearchContainerNode: ASDisplayNode {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.listNode.isHidden = !transition.isSearching
let containerTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
containerTransition.updateAlpha(node: strongSelf.listNode, alpha: transition.isSearching ? 1.0 : 0.0)
strongSelf.dimNode.isHidden = transition.isSearching strongSelf.dimNode.isHidden = transition.isSearching
strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.Map_SearchNoResultsDescription(transition.query).string, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.Map_SearchNoResultsDescription(transition.query).string, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor)
let emptyResults = transition.isSearching && transition.isEmpty let emptyResults = transition.isSearching && transition.isEmpty
strongSelf.emptyResultsTitleNode.isHidden = !emptyResults containerTransition.updateAlpha(node: strongSelf.emptyResultsTitleNode, alpha: emptyResults ? 1.0 : 0.0)
strongSelf.emptyResultsTextNode.isHidden = !emptyResults containerTransition.updateAlpha(node: strongSelf.emptyResultsTextNode, alpha: emptyResults ? 1.0 : 0.0)
if let (layout, navigationBarHeight) = strongSelf.validLayout { if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)

View file

@ -9,10 +9,12 @@ import ItemListUI
class LocationSectionHeaderItem: ListViewItem { class LocationSectionHeaderItem: ListViewItem {
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String let title: String
public init(presentationData: ItemListPresentationData, title: String) { public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String) {
self.presentationData = presentationData self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title self.title = title
} }
@ -102,7 +104,7 @@ private class LocationSectionHeaderItemNode: ListViewItemNode {
} }
headerNode.frame = CGRect(origin: CGPoint(), size: contentSize) headerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
headerNode.updateLayout(size: contentSize, leftInset: params.leftInset, rightInset: params.rightInset) headerNode.updateLayout(size: contentSize, leftInset: params.leftInset, rightInset: params.rightInset, showBackground: item.systemStyle == .legacy)
} }
}) })
}) })

View file

@ -318,7 +318,13 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
} }
var setupProximityNotificationImpl: ((Bool) -> Void)? var setupProximityNotificationImpl: ((Bool) -> Void)?
self.headerNode = LocationMapHeaderNode(presentationData: presentationData, toggleMapModeSelection: interaction.toggleMapModeSelection, goToUserLocation: interaction.toggleTrackingMode, setupProximityNotification: { reset in self.headerNode = LocationMapHeaderNode(
presentationData: presentationData,
glass: false,
toggleMapModeSelection: interaction.toggleMapModeSelection,
updateMapMode: interaction.updateMapMode,
goToUserLocation: interaction.toggleTrackingMode,
setupProximityNotification: { reset in
setupProximityNotificationImpl?(reset) setupProximityNotificationImpl?(reset)
}) })
//self.headerNode.mapNode.isRotateEnabled = false //self.headerNode.mapNode.isRotateEnabled = false
@ -714,7 +720,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
strongSelf.listOffset = max(0.0, offset) strongSelf.listOffset = max(0.0, offset)
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap))) let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap)))
listTransition.updateFrame(node: strongSelf.headerNode, frame: headerFrame) listTransition.updateFrame(node: strongSelf.headerNode, frame: headerFrame)
strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsTopPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition) strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsTopPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsBottomPadding: 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition)
} }
self.listNode.beganInteractiveDragging = { [weak self] _ in self.listNode.beganInteractiveDragging = { [weak self] _ in
@ -952,7 +958,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight)) let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight))
transition.updateFrame(node: self.headerNode, frame: headerFrame) transition.updateFrame(node: self.headerNode, frame: headerFrame)
self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsTopPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, offset: 0.0, size: headerFrame.size, transition: transition) self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsTopPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsBottomPadding: 0.0, offset: 0.0, size: headerFrame.size, transition: transition)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)

View file

@ -54,6 +54,10 @@ swift_library(
"//submodules/TelegramUI/Components/MediaAssetsContext", "//submodules/TelegramUI/Components/MediaAssetsContext",
"//submodules/TelegramUI/Components/AvatarBackground", "//submodules/TelegramUI/Components/AvatarBackground",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/TelegramUI/Components/EmojiTextAttachmentView",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/LottieComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -28,6 +28,12 @@ import ImageObjectSeparation
import ChatSendMessageActionUI import ChatSendMessageActionUI
import AnimatedCountLabelNode import AnimatedCountLabelNode
import MediaAssetsContext import MediaAssetsContext
import GlassBackgroundComponent
import EdgeEffect
import ComponentFlow
import BundleIconComponent
import LottieComponent
import GlassBarButtonComponent
final class MediaPickerInteraction { final class MediaPickerInteraction {
let downloadManager: AssetDownloadManager let downloadManager: AssetDownloadManager
@ -131,6 +137,11 @@ struct Month: Equatable {
private var savedStoriesContentOffset: CGFloat? private var savedStoriesContentOffset: CGFloat?
public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, AttachmentContainable { public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, AttachmentContainable {
public enum Style {
case glass
case legacy
}
public enum Subject { public enum Subject {
public enum Media: Equatable { public enum Media: Equatable {
case image(UIImage) case image(UIImage)
@ -173,6 +184,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private let style: Style
fileprivate var interaction: MediaPickerInteraction? fileprivate var interaction: MediaPickerInteraction?
@ -193,6 +205,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private let titleView: MediaPickerTitleView private let titleView: MediaPickerTitleView
private let cancelButtonNode: WebAppCancelButtonNode private let cancelButtonNode: WebAppCancelButtonNode
private var cancelButton: ComponentView<Empty>?
private var rightButton: ComponentView<Empty>?
private let moreButtonPlayOnce = ActionSlot<Void>()
private let moreButtonNode: MoreButtonNode private let moreButtonNode: MoreButtonNode
private let selectedButtonNode: SelectedButtonNode private let selectedButtonNode: SelectedButtonNode
@ -258,8 +275,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private var requestedCameraAccess = false private var requestedCameraAccess = false
private let containerNode: ASDisplayNode private let containerNode: ASDisplayNode
private let backgroundView: GlassBackgroundView?
private let backgroundNode: NavigationBackgroundNode private let backgroundNode: NavigationBackgroundNode
fileprivate let gridNode: GridNode fileprivate let gridNode: GridNode
fileprivate let topEdgeEffectView: EdgeEffectView
fileprivate let bottomEdgeEffectView: EdgeEffectView
fileprivate let cameraWrapperView: UIView fileprivate let cameraWrapperView: UIView
fileprivate var cameraView: TGAttachmentCameraView? fileprivate var cameraView: TGAttachmentCameraView?
@ -297,6 +317,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private var fastScrollContentOffset = ValuePromise<CGPoint>(ignoreRepeated: true) private var fastScrollContentOffset = ValuePromise<CGPoint>(ignoreRepeated: true)
private var fastScrollDisposable: Disposable? private var fastScrollDisposable: Disposable?
fileprivate var scrolledToTop = true
fileprivate var scrolledExactlyToTop = true
private var didSetReady = false private var didSetReady = false
private let _ready = Promise<Bool>() private let _ready = Promise<Bool>()
var ready: Promise<Bool> { var ready: Promise<Bool> {
@ -323,6 +346,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.containerNode = ASDisplayNode() self.containerNode = ASDisplayNode()
self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor) self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor)
self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
if case .glass = controller.style, !"".isEmpty {
self.backgroundView = GlassBackgroundView()
} else {
self.backgroundView = nil
}
self.gridNode = GridNode() self.gridNode = GridNode()
self.scrollingArea = SparseItemGridScrollingArea() self.scrollingArea = SparseItemGridScrollingArea()
@ -333,6 +363,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.cameraActivateAreaNode.accessibilityLabel = "Camera" self.cameraActivateAreaNode.accessibilityLabel = "Camera"
self.cameraActivateAreaNode.accessibilityTraits = [.button] self.cameraActivateAreaNode.accessibilityTraits = [.button]
self.topEdgeEffectView = EdgeEffectView()
self.topEdgeEffectView.isUserInteractionEnabled = false
self.bottomEdgeEffectView = EdgeEffectView()
self.bottomEdgeEffectView.isUserInteractionEnabled = false
super.init() super.init()
if case .assets(nil, .default) = controller.subject { if case .assets(nil, .default) = controller.subject {
@ -342,10 +378,23 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundNode)
if let backgroundView = self.backgroundView {
self.containerNode.view.addSubview(backgroundView)
} else {
self.containerNode.addSubnode(self.backgroundNode)
}
self.containerNode.addSubnode(self.gridNode) self.containerNode.addSubnode(self.gridNode)
self.containerNode.addSubnode(self.scrollingArea) self.containerNode.addSubnode(self.scrollingArea)
if case .glass = controller.style {
self.containerNode.view.addSubview(self.topEdgeEffectView)
if case let .assets(_, mode) = controller.subject, case .default = mode {
self.containerNode.view.addSubview(self.bottomEdgeEffectView)
}
}
self.gridNode.scrollView.addSubview(self.cameraWrapperView) self.gridNode.scrollView.addSubview(self.cameraWrapperView)
let selectedCollection = controller.selectedCollection.get() let selectedCollection = controller.selectedCollection.get()
@ -399,8 +448,37 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self?.dismissInput() self?.dismissInput()
} }
self.gridNode.visibleContentOffsetChanged = { [weak self] _ in self.gridNode.visibleContentOffsetChanged = { [weak self] offset in
self?.updateNavigation(transition: .immediate) guard let self else {
return
}
self.updateNavigation(transition: .immediate)
var scrolledToTop = false
var scrolledExactlyToTop = false
if case let .known(contentOffset) = offset {
if contentOffset < 30.0 {
scrolledToTop = true
}
if contentOffset < 5.0 {
scrolledExactlyToTop = true
}
}
var updated = false
var transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .easeInOut)
if self.scrolledToTop != scrolledToTop {
self.scrolledToTop = scrolledToTop
updated = true
}
if self.scrolledExactlyToTop != scrolledExactlyToTop {
self.scrolledExactlyToTop = scrolledExactlyToTop
updated = true
transition = .animated(duration: 0.25, curve: .easeInOut)
}
if updated {
self.controller?.updateNavigationButtons(transition: transition)
}
} }
self.hiddenMediaDisposable = (self.hiddenMediaId.get() self.hiddenMediaDisposable = (self.hiddenMediaId.get()
@ -1018,7 +1096,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
private func updateSelectionState(animated: Bool = false) { private func updateSelectionState(animated: Bool = false, updateLayout: Bool = true) {
self.gridNode.forEachItemNode { itemNode in self.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? MediaPickerGridItemNode { if let itemNode = itemNode as? MediaPickerGridItemNode {
itemNode.updateSelectionState(animated: animated) itemNode.updateSelectionState(animated: animated)
@ -1029,7 +1107,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let count = Int32(self.controller?.interaction?.selectionState?.count() ?? 0) let count = Int32(self.controller?.interaction?.selectionState?.count() ?? 0)
self.controller?.updateSelectionState(count: count) self.controller?.updateSelectionState(count: count)
if let (layout, navigationBarHeight) = self.validLayout { if updateLayout, let (layout, navigationBarHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .spring)) self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .spring))
} }
} }
@ -1039,6 +1117,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate)
self.updateSelectionState(animated: true)
} }
private(set) var currentDisplayMode: DisplayMode = .all { private(set) var currentDisplayMode: DisplayMode = .all {
@ -1085,17 +1165,16 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
return nil return nil
} }
} }
self.containerNode.insertSubnode(selectionNode, aboveSubnode: self.gridNode)
self.selectionNode = selectionNode self.selectionNode = selectionNode
}
if let (layout, navigationBarHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) if let (layout, navigationBarHeight) = self.validLayout {
} self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.25, curve: .easeInOut))
} }
self.gridNode.isUserInteractionEnabled = displayMode == .all self.gridNode.isUserInteractionEnabled = displayMode == .all
self.selectionNode?.isUserInteractionEnabled = displayMode == .selected self.selectionNode?.isUserInteractionEnabled = displayMode == .selected
var completion: () -> Void = {} var completion: () -> Void = {}
if updated && displayMode == .all { if updated && displayMode == .all {
completion = { completion = {
@ -1538,6 +1617,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let firstTime = self.validLayout == nil let firstTime = self.validLayout == nil
self.validLayout = (layout, navigationBarHeight) self.validLayout = (layout, navigationBarHeight)
if firstTime {
self.updateSelectionState(animated: false, updateLayout: false)
}
var insets = layout.insets(options: []) var insets = layout.insets(options: [])
insets.top += navigationBarHeight insets.top += navigationBarHeight
@ -1656,8 +1739,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
transition.updateFrame(node: self.gridNode, frame: innerBounds) transition.updateFrame(node: self.gridNode, frame: innerBounds)
self.scrollingArea.frame = innerBounds self.scrollingArea.frame = innerBounds
transition.updateFrame(node: self.backgroundNode, frame: innerBounds) if let backgroundView = self.backgroundView {
self.backgroundNode.update(size: bounds.size, transition: transition) backgroundView.update(size: bounds.size, cornerRadius: 0.0, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: self.presentationData.theme.list.plainBackgroundColor), transition: ComponentTransition(transition))
transition.updateFrame(view: backgroundView, frame: innerBounds)
} else {
transition.updateFrame(node: self.backgroundNode, frame: innerBounds)
self.backgroundNode.update(size: bounds.size, transition: transition)
}
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: bounds.height))) transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: bounds.height)))
@ -1699,9 +1787,14 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
if let selectionNode = self.selectionNode, let controller = self.controller { if let selectionNode = self.selectionNode, let controller = self.controller {
let selectionTransition = selectionNode.supernode == nil ? .immediate : transition
if selectionNode.supernode == nil {
self.containerNode.insertSubnode(selectionNode, aboveSubnode: self.gridNode)
}
let selectedItems = controller.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? [] let selectedItems = controller.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? []
let updateSelectionNode = { let updateSelectionNode = {
selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: transition) selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: selectionTransition)
} }
if selectedItems.count < 1 && self.currentDisplayMode == .selected { if selectedItems.count < 1 && self.currentDisplayMode == .selected {
@ -1710,10 +1803,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} else { } else {
updateSelectionNode() updateSelectionNode()
} }
transition.updateFrame(node: selectionNode, frame: innerBounds) selectionTransition.updateFrame(node: selectionNode, frame: innerBounds)
} }
var cameraView: UIView? var cameraView: UIView?
if let view = self.cameraView { if let view = self.cameraView {
cameraView = view cameraView = view
@ -1801,6 +1893,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.placeholderNode = nil self.placeholderNode = nil
placeholderNode.removeFromSupernode() placeholderNode.removeFromSupernode()
} }
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 88.0 - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: 88.0))
transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
self.bottomEdgeEffectView.update(content: self.currentDisplayMode == .all ? self.presentationData.theme.list.plainBackgroundColor : .clear, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
} }
} }
@ -1840,6 +1936,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
public init( public init(
context: AccountContext, context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
style: Style = .legacy,
peer: EnginePeer?, peer: EnginePeer?,
threadTitle: String?, threadTitle: String?,
chatLocation: ChatLocation?, chatLocation: ChatLocation?,
@ -1864,6 +1961,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
self.presentationData = presentationData self.presentationData = presentationData
self.updatedPresentationData = updatedPresentationData self.updatedPresentationData = updatedPresentationData
self.style = style
self.peer = peer self.peer = peer
self.threadTitle = threadTitle self.threadTitle = threadTitle
self.chatLocation = chatLocation self.chatLocation = chatLocation
@ -1882,7 +1980,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let selectionContext = selectionContext ?? TGMediaSelectionContext(groupingAllowed: false, selectionLimit: enableMultiselection ? 100 : 1)! let selectionContext = selectionContext ?? TGMediaSelectionContext(groupingAllowed: false, selectionLimit: enableMultiselection ? 100 : 1)!
self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, glass: style == .glass, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
if case let .assets(collection, mode) = subject { if case let .assets(collection, mode) = subject {
if let collection = collection { if let collection = collection {
@ -1916,15 +2014,26 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
self.cancelButtonNode = WebAppCancelButtonNode(theme: self.presentationData.theme, strings: self.presentationData.strings) self.cancelButtonNode = WebAppCancelButtonNode(theme: self.presentationData.theme, strings: self.presentationData.strings)
if case .glass = style {
self.cancelButton = ComponentView()
self.rightButton = ComponentView()
}
self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme) self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme)
self.moreButtonNode.iconNode.enqueueState(.more, animated: false) self.moreButtonNode.iconNode.enqueueState(.more, animated: false)
self.selectedButtonNode = SelectedButtonNode(theme: self.presentationData.theme) self.selectedButtonNode = SelectedButtonNode(theme: self.presentationData.theme, glass: self.style == .glass)
self.selectedButtonNode.alpha = 0.0 self.selectedButtonNode.alpha = 0.0
self.selectedButtonNode.transform = CATransform3DMakeScale(0.01, 0.01, 1.0) self.selectedButtonNode.transform = CATransform3DMakeScale(0.01, 0.01, 1.0)
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData)) var navigationBarPresentationData: NavigationBarPresentationData?
if case .glass = style {
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: true, hideSeparator: true)
} else {
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData)
}
super.init(navigationBarPresentationData: navigationBarPresentationData)
self.statusBar.statusBarStyle = .Ignore self.statusBar.statusBarStyle = .Ignore
@ -2011,7 +2120,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if case .wallpaper = mode { if case .wallpaper = mode {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed))
} else if collection == nil { } else if collection == nil {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) if let _ = self.cancelButton {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
} else {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
}
var hasSelect = false var hasSelect = false
if forCollage { if forCollage {
@ -2024,12 +2137,19 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
if hasSelect { if hasSelect {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Select, target: self, action: #selector(self.selectPressed)) if let _ = self.rightButton {
} else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Select, target: self, action: #selector(self.selectPressed))
}
} else { } else {
if [.createSticker].contains(mode) { if [.createSticker].contains(mode) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) if let _ = self.rightButton {
self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed)
self.navigationItem.rightBarButtonItem?.target = self } else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode)
self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed)
self.navigationItem.rightBarButtonItem?.target = self
}
} }
} }
} else { } else {
@ -2039,24 +2159,32 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if case let .assets(collection, _) = self.subject, collection != nil { if case let .assets(collection, _) = self.subject, collection != nil {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed))
} else { } else {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode) if let _ = self.cancelButton {
self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed) self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
self.navigationItem.leftBarButtonItem?.target = self } else {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode)
self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed)
self.navigationItem.leftBarButtonItem?.target = self
}
} }
if self.bannedSendPhotos != nil && self.bannedSendVideos != nil { if self.bannedSendPhotos != nil && self.bannedSendVideos != nil {
} else { } else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) if let _ = self.rightButton {
self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed) self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: UIView())
self.navigationItem.rightBarButtonItem?.target = self } else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode)
self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed)
self.navigationItem.rightBarButtonItem?.target = self
}
} }
} }
self.moreButtonNode.action = { [weak self] _, gesture in // self.moreButtonNode.action = { [weak self] _, gesture in
if let strongSelf = self { // if let strongSelf = self {
strongSelf.searchOrMorePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture) // strongSelf.searchOrMorePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture)
} // }
} // }
self.selectedButtonNode.addTarget(self, action: #selector(self.selectedPressed), forControlEvents: .touchUpInside) self.selectedButtonNode.addTarget(self, action: #selector(self.selectedPressed), forControlEvents: .touchUpInside)
@ -2199,8 +2327,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
guard let self else { guard let self else {
return return
} }
let count = Int32(self.interaction?.selectionState?.count() ?? 0) self.updateNavigationButtons()
self.updateSelectionState(count: count)
} }
if case .media = self.subject { if case .media = self.subject {
self.controllerNode.updateDisplayMode(.selected, animated: false) self.controllerNode.updateDisplayMode(.selected, animated: false)
@ -2213,6 +2340,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.controllerNode.closeGalleryController() self.controllerNode.closeGalleryController()
} }
private func updateNavigationButtons(transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)) {
let count = Int32(self.interaction?.selectionState?.count() ?? 0)
self.updateSelectionState(count: count, transition: transition)
}
public var groupsPresented: () -> Void = {} public var groupsPresented: () -> Void = {}
private var didSetupGroups = false private var didSetupGroups = false
@ -2252,7 +2384,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.titleView.isHighlighted = true self.titleView.isHighlighted = true
let contextController = ContextController( let contextController = ContextController(
presentationData: self.presentationData, presentationData: self.presentationData,
source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceNode: self.titleView.contextSourceNode)), source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: self.titleView)),
items: .single(ContextController.Items(content: .custom(content))), items: .single(ContextController.Items(content: .custom(content))),
gesture: nil gesture: nil
) )
@ -2360,21 +2492,20 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
fileprivate var selectionCount: Int32 = 0 fileprivate var selectionCount: Int32 = 0
fileprivate func updateSelectionState(count: Int32) { fileprivate func updateSelectionState(count: Int32, transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)) {
self.selectionCount = count self.selectionCount = count
guard let layout = self.validLayout else { guard let layout = self.validLayout else {
return return
} }
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
var moreIsVisible = false var moreIsVisible = false
var isBack = false
if case let .assets(_, mode) = self.subject, [.story, .createSticker].contains(mode) { if case let .assets(_, mode) = self.subject, [.story, .createSticker].contains(mode) {
moreIsVisible = true moreIsVisible = true
} else if case let .media(media) = self.subject { } else if case let .media(media) = self.subject {
self.titleView.title = media.count == 1 ? self.presentationData.strings.Attachment_Pasteboard : self.presentationData.strings.Attachment_SelectedMedia(count) self.titleView.title = media.count == 1 ? self.presentationData.strings.Attachment_Pasteboard : self.presentationData.strings.Attachment_SelectedMedia(count)
self.titleView.segmentsHidden = true self.titleView.segmentsHidden = true
moreIsVisible = true moreIsVisible = true
// self.moreButtonNode.iconNode.enqueueState(.more, animated: false)
} else { } else {
let title: String let title: String
let isEnabled: Bool let isEnabled: Bool
@ -2387,6 +2518,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
self.titleView.updateTitle(title: title, isEnabled: isEnabled, animated: true) self.titleView.updateTitle(title: title, isEnabled: isEnabled, animated: true)
self.cancelButtonNode.setState(isEnabled ? .cancel : .back, animated: true) self.cancelButtonNode.setState(isEnabled ? .cancel : .back, animated: true)
isBack = !isEnabled
let selectedSize = self.selectedButtonNode.update(count: count) let selectedSize = self.selectedButtonNode.update(count: count)
@ -2394,8 +2526,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if layout.safeInsets.right > 0.0 { if layout.safeInsets.right > 0.0 {
safeInset += layout.safeInsets.right + 16.0 safeInset += layout.safeInsets.right + 16.0
} }
let selectedButtonInset: CGFloat = self._hasGlassStyle ? 68.0 : 54.0
let navigationHeight = navigationLayout(layout: layout).navigationFrame.height let navigationHeight = navigationLayout(layout: layout).navigationFrame.height
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - 54.0 - selectedSize.width - safeInset, y: floorToScreenPixels((navigationHeight - selectedSize.height) / 2.0) + 1.0), size: selectedSize) self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - selectedButtonInset - selectedSize.width - safeInset, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - selectedSize.height) / 2.0) + 1.0), size: selectedSize)
let isSelectionButtonVisible = count > 0 && self.controllerNode.currentDisplayMode == .all let isSelectionButtonVisible = count > 0 && self.controllerNode.currentDisplayMode == .all
transition.updateAlpha(node: self.selectedButtonNode, alpha: isSelectionButtonVisible ? 1.0 : 0.0) transition.updateAlpha(node: self.selectedButtonNode, alpha: isSelectionButtonVisible ? 1.0 : 0.0)
@ -2409,8 +2542,109 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
moreIsVisible = count > 0 moreIsVisible = count > 0
} }
transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0) let useGlassButtons = isBack || !self.controllerNode.scrolledToTop
transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1)
let barButtonSideInset: CGFloat = 16.0
let barButtonSize = CGSize(width: 40.0, height: 40.0)
var buttonTransition = ComponentTransition.easeInOut(duration: 0.25)
if case let .animated(duration, _) = transition, duration > 0.25 {
buttonTransition = .easeInOut(duration: duration)
}
self.titleView.updateIsDark(isDark: self.controllerNode.currentDisplayMode == .all && useGlassButtons, animated: true)
let topEdgeColor: UIColor
if self.controllerNode.currentDisplayMode == .all {
if useGlassButtons {
topEdgeColor = UIColor(rgb: 0x000000, alpha: 0.45)
} else {
topEdgeColor = self.presentationData.theme.overallDarkAppearance ? self.presentationData.theme.list.modalBlocksBackgroundColor : self.presentationData.theme.list.plainBackgroundColor
}
} else {
topEdgeColor = .clear
}
let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 100.0))
transition.updateFrame(view: self.controllerNode.topEdgeEffectView, frame: topEdgeEffectFrame)
transition.updateAlpha(layer: self.controllerNode.topEdgeEffectView.layer, alpha: self.controllerNode.scrolledExactlyToTop && self.controllerNode.currentDisplayMode == .all ? 0.0 : 1.0)
self.controllerNode.topEdgeEffectView.update(content: topEdgeColor, blur: true, alpha: 0.8, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
if let cancelButton = self.cancelButton {
if cancelButton.view == nil {
buttonTransition = .immediate
}
let cancelButtonSize = cancelButton.update(
transition: buttonTransition,
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
isDark: self.presentationData.theme.overallDarkAppearance,
state: useGlassButtons ? .glass : .generic,
component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent(
BundleIconComponent(
name: isBack ? "Navigation/Back" : "Navigation/Close",
tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor
)
)),
action: { [weak self] _ in
self?.cancelPressed()
}
)),
environment: {},
containerSize: CGSize(width: 40.0, height: 40.0)
)
let cancelButtonFrame = CGRect(origin: CGPoint(x: barButtonSideInset, y: barButtonSideInset), size: cancelButtonSize)
if let view = cancelButton.view {
if view.superview == nil {
self.view.addSubview(view)
}
view.bounds = CGRect(origin: .zero, size: cancelButtonFrame.size)
view.center = cancelButtonFrame.center
}
}
if let moreButton = self.rightButton {
let moreButtonSize = moreButton.update(
transition: buttonTransition,
component: AnyComponent(GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
isDark: self.presentationData.theme.overallDarkAppearance,
state: useGlassButtons ? .glass : .generic,
component: AnyComponentWithIdentity(id: "more", component: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(
name: "anim_morewide"
),
color: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor,
size: CGSize(width: 34.0, height: 34.0),
playOnce: self.moreButtonPlayOnce
)
)),
action: { [weak self] view in
self?.searchOrMorePressed(view: view, gesture: nil)
self?.moreButtonPlayOnce.invoke(Void())
})),
environment: {},
containerSize: CGSize(width: 40.0, height: 40.0)
)
let moreButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - moreButtonSize.width - barButtonSideInset, y: barButtonSideInset), size: moreButtonSize)
if let view = moreButton.view {
if view.superview == nil {
self.view.addSubview(view)
}
view.bounds = CGRect(origin: .zero, size: moreButtonFrame.size)
view.center = moreButtonFrame.center
}
}
if let moreButtonView = self.rightButton?.view {
transition.updateAlpha(layer: moreButtonView.layer, alpha: moreIsVisible ? 1.0 : 0.0)
transition.updateTransformScale(layer: moreButtonView.layer, scale: moreIsVisible ? 1.0 : 0.1)
} else {
transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0)
transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1)
}
if case .assets(_, .story) = self.subject, self.selectionCount > 0 { if case .assets(_, .story) = self.subject, self.selectionCount > 0 {
let text = self.presentationData.strings.MediaPicker_CreateStory(self.selectionCount) let text = self.presentationData.strings.MediaPicker_CreateStory(self.selectionCount)
@ -2428,7 +2662,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} }
private func updateThemeAndStrings() { private func updateThemeAndStrings() {
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) var navigationBarPresentationData: NavigationBarPresentationData
if case .glass = style {
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: true, hideBadge: true, hideSeparator: true)
} else {
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData)
}
self.navigationBar?.updatePresentationData(navigationBarPresentationData)
self.titleView.theme = self.presentationData.theme self.titleView.theme = self.presentationData.theme
self.cancelButtonNode.theme = self.presentationData.theme self.cancelButtonNode.theme = self.presentationData.theme
self.moreButtonNode.theme = self.presentationData.theme self.moreButtonNode.theme = self.presentationData.theme
@ -2644,7 +2884,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.controllerNode.updateDisplayMode(.selected, animated: true) self.controllerNode.updateDisplayMode(.selected, animated: true)
} }
@objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { @objc private func searchOrMorePressed(view: UIView, gesture: ContextGesture?) {
guard self.moreButtonNode.iconNode.alpha > 0.0 else { guard self.moreButtonNode.iconNode.alpha > 0.0 else {
return return
} }
@ -2669,7 +2909,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self?.presentFilePicker() self?.presentFilePicker()
}))) })))
let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: view)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.presentInGlobalOverlay(contextController) self.presentInGlobalOverlay(contextController)
return return
@ -2830,7 +3070,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
return ContextController.Items(content: .list(items)) return ContextController.Items(content: .list(items))
} }
let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceNode: node)), items: items, gesture: gesture) let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: view)), items: items, gesture: gesture)
self.presentInGlobalOverlay(contextController) self.presentInGlobalOverlay(contextController)
} }
} }
@ -2866,7 +3106,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
safeInset += layout.safeInsets.right + 16.0 safeInset += layout.safeInsets.right + 16.0
} }
let navigationHeight = navigationLayout(layout: layout).navigationFrame.height let navigationHeight = navigationLayout(layout: layout).navigationFrame.height
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - 54.0 - self.selectedButtonNode.frame.width - safeInset, y: floorToScreenPixels((navigationHeight - self.selectedButtonNode.frame.height) / 2.0) + 1.0), size: self.selectedButtonNode.frame.size)
let selectedButtonInset: CGFloat = self._hasGlassStyle ? 68.0 : 54.0
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - selectedButtonInset - self.selectedButtonNode.frame.width - safeInset, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - self.selectedButtonNode.frame.height) / 2.0) + 1.0), size: self.selectedButtonNode.frame.size)
} }
public func dismissAnimated() { public func dismissAnimated() {
@ -3051,15 +3293,15 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
private final class MediaPickerContextReferenceContentSource: ContextReferenceContentSource { private final class MediaPickerContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController private let controller: ViewController
private let sourceNode: ContextReferenceContentNode private let sourceView: UIView
init(controller: ViewController, sourceNode: ContextReferenceContentNode) { init(controller: ViewController, sourceView: UIView) {
self.controller = controller self.controller = controller
self.sourceNode = sourceNode self.sourceView = sourceView
} }
func transitionInfo() -> ContextControllerReferenceViewInfo? { func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
} }
} }
@ -3167,7 +3409,15 @@ public func wallpaperMediaPickerController(
completion: @escaping (MediaPickerScreenImpl, Any) -> Void = { _, _ in }, completion: @escaping (MediaPickerScreenImpl, Any) -> Void = { _, _ in },
openColors: @escaping () -> Void openColors: @escaping () -> Void
) -> ViewController { ) -> ViewController {
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { let controller = AttachmentController(
context: context,
updatedPresentationData: updatedPresentationData,
chatLocation: nil,
buttons: [.standalone],
initialButton: .standalone,
fromMenu: false,
hasTextInput: false,
makeEntityInputView: {
return nil return nil
}) })
controller.animateAppearance = animateAppearance controller.animateAppearance = animateAppearance
@ -3256,6 +3506,7 @@ public func storyMediaPickerController(
let controller = AttachmentController( let controller = AttachmentController(
context: context, context: context,
updatedPresentationData: updatedPresentationData, updatedPresentationData: updatedPresentationData,
style: .glass,
chatLocation: nil, chatLocation: nil,
buttons: [.standalone], buttons: [.standalone],
initialButton: .standalone, initialButton: .standalone,
@ -3271,6 +3522,7 @@ public func storyMediaPickerController(
let mediaPickerController = MediaPickerScreenImpl( let mediaPickerController = MediaPickerScreenImpl(
context: context, context: context,
updatedPresentationData: updatedPresentationData, updatedPresentationData: updatedPresentationData,
style: .glass,
peer: nil, peer: nil,
threadTitle: nil, threadTitle: nil,
chatLocation: nil, chatLocation: nil,
@ -3370,13 +3622,34 @@ public func stickerMediaPickerController(
) -> ViewController { ) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData)) let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { let controller = AttachmentController(
context: context,
updatedPresentationData: updatedPresentationData,
style: .glass,
chatLocation: nil,
buttons: [.standalone],
initialButton: .standalone,
fromMenu: false,
hasTextInput: false,
makeEntityInputView: {
return nil return nil
}) })
controller.forceSourceRect = true controller.forceSourceRect = true
controller.getSourceRect = getSourceRect controller.getSourceRect = getSourceRect
controller.requestController = { [weak controller] _, present in controller.requestController = { [weak controller] _, present in
let mediaPickerController = MediaPickerScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .createSticker), mainButtonState: nil, mainButtonAction: nil) let mediaPickerController = MediaPickerScreenImpl(
context: context,
updatedPresentationData: updatedPresentationData,
style: .glass,
peer: nil,
threadTitle: nil,
chatLocation: nil,
bannedSendPhotos: nil,
bannedSendVideos: nil,
subject: .assets(nil, .createSticker),
mainButtonState: nil,
mainButtonAction: nil
)
mediaPickerController.customSelection = { controller, result in mediaPickerController.customSelection = { controller, result in
if let result = result as? PHAsset { if let result = result as? PHAsset {
controller.updateHiddenMediaId(result.localIdentifier) controller.updateHiddenMediaId(result.localIdentifier)
@ -3500,13 +3773,21 @@ public func avatarMediaPickerController(
) -> ViewController { ) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData)) let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { let controller = AttachmentController(
context: context,
updatedPresentationData: updatedPresentationData,
style: .glass,
chatLocation: nil,
buttons: [.standalone],
initialButton: .standalone,
fromMenu: false,
hasTextInput: false,
makeEntityInputView: {
return nil return nil
}) })
controller.forceSourceRect = true controller.forceSourceRect = true
controller.getSourceRect = getSourceRect controller.getSourceRect = getSourceRect
controller.requestController = { [weak controller] _, present in controller.requestController = { [weak controller] _, present in
var mainButtonState: AttachmentMainButtonState? var mainButtonState: AttachmentMainButtonState?
if canDelete { if canDelete {
@ -3516,6 +3797,7 @@ public func avatarMediaPickerController(
let mediaPickerController = MediaPickerScreenImpl( let mediaPickerController = MediaPickerScreenImpl(
context: context, context: context,
updatedPresentationData: updatedPresentationData, updatedPresentationData: updatedPresentationData,
style: .glass,
peer: nil, peer: nil,
threadTitle: nil, threadTitle: nil,
chatLocation: nil, chatLocation: nil,
@ -3670,43 +3952,89 @@ public func coverMediaPickerController(
return controller return controller
} }
private class SelectedButtonNode: HighlightableButtonNode { private class SelectedButtonNode: HighlightTrackingButtonNode {
private let background = ASImageNode() private let containerView: UIView
private let backgroundView: GlassBackgroundView?
private let background: ASImageNode?
private let icon = ASImageNode() private let icon = ASImageNode()
private let label = ImmediateAnimatedCountLabelNode() private let label = ImmediateAnimatedCountLabelNode()
private let glass: Bool
var theme: PresentationTheme { var theme: PresentationTheme {
didSet { didSet {
self.icon.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor) self.icon.image = generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Media Gallery/Check" : "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor)
self.background.image = generateStretchableFilledCircleImage(radius: 21.0 / 2.0, color: self.theme.list.itemCheckColors.fillColor) if let background = self.background {
background.image = generateStretchableFilledCircleImage(radius: 21.0 / 2.0, color: self.theme.list.itemCheckColors.fillColor)
}
let _ = self.update(count: self.count) let _ = self.update(count: self.count)
} }
} }
private var count: Int32 = 0 private var count: Int32 = 0
init(theme: PresentationTheme) { init(theme: PresentationTheme, glass: Bool) {
self.theme = theme self.theme = theme
self.glass = glass
self.containerView = UIView()
if glass {
self.backgroundView = GlassBackgroundView()
self.background = nil
} else {
let background = ASImageNode()
background.displaysAsynchronously = false
self.background = background
self.backgroundView = nil
}
super.init() super.init()
self.background.displaysAsynchronously = false
self.icon.displaysAsynchronously = false self.icon.displaysAsynchronously = false
self.label.displaysAsynchronously = false self.label.displaysAsynchronously = false
self.icon.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor) self.icon.image = generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Media Gallery/Check" : "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor)
self.background.image = generateStretchableFilledCircleImage(radius: 21.0 / 2.0, color: self.theme.list.itemCheckColors.fillColor)
self.addSubnode(self.background) self.view.addSubview(self.containerView)
self.addSubnode(self.icon) if let backgroundView = self.backgroundView {
self.addSubnode(self.label) self.containerView.addSubview(backgroundView)
}
if let background = self.background {
background.image = generateStretchableFilledCircleImage(radius: 21.0 / 2.0, color: self.theme.list.itemCheckColors.fillColor)
self.containerView.addSubnode(background)
}
self.containerView.addSubnode(self.icon)
self.containerView.addSubnode(self.label)
self.highligthedChanged = { [weak self] highlighted in
if let self {
if glass {
let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring))
if highlighted {
transition.setScale(view: self.containerView, scale: 1.2)
} else {
transition.setScale(view: self.containerView, scale: 1.0)
}
} else {
if highlighted {
self.containerView.layer.removeAnimation(forKey: "opacity")
self.containerView.alpha = 0.4
} else {
self.containerView.alpha = 1.0
self.containerView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
}
} }
func update(count: Int32) -> CGSize { func update(count: Int32) -> CGSize {
self.count = count self.count = count
let diameter: CGFloat = 21.0 let diameter: CGFloat = self.glass ? 40.0 : 21.0
let font = Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]) let font = self.glass ? Font.with(size: 17.0, weight: .medium, traits: [.monospacedNumbers]) : Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers])
let stringValue = "\(max(1, count))" let stringValue = "\(max(1, count))"
var segments: [AnimatedCountLabelNode.Segment] = [] var segments: [AnimatedCountLabelNode.Segment] = []
@ -3717,18 +4045,36 @@ private class SelectedButtonNode: HighlightableButtonNode {
} }
self.label.segments = segments self.label.segments = segments
let textSize = self.label.updateLayout(size: CGSize(width: 100.0, height: diameter), animated: true) let textSize = self.label.updateLayout(size: CGSize(width: 120.0, height: diameter), animated: true)
let size = CGSize(width: textSize.width + 28.0, height: diameter) var size = CGSize(width: textSize.width + 28.0, height: diameter)
if self.glass {
if let _ = self.icon.image { size.width += 10.0
let iconSize = CGSize(width: 14.0, height: 11.0)
let iconFrame = CGRect(origin: CGPoint(x: 5.0, y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
self.icon.frame = iconFrame
} }
self.label.frame = CGRect(origin: CGPoint(x: 21.0, y: floor((size.height - textSize.height) / 2.0) - UIScreenPixel), size: textSize) if let _ = self.icon.image {
self.background.frame = CGRect(origin: .zero, size: size) if self.glass {
let iconSize = CGSize(width: 24.0, height: 24.0)
let iconFrame = CGRect(origin: CGPoint(x: 6.0 + UIScreenPixel, y: floor((size.height - iconSize.height) / 2.0) - UIScreenPixel), size: iconSize)
self.icon.frame = iconFrame
} else {
let iconSize = CGSize(width: 14.0, height: 11.0)
let iconFrame = CGRect(origin: CGPoint(x: 5.0, y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
self.icon.frame = iconFrame
}
}
self.label.frame = CGRect(origin: CGPoint(x: self.glass ? 27.0 - UIScreenPixel : 21.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
let backgroundFrame = CGRect(origin: .zero, size: size)
self.containerView.frame = backgroundFrame
if let backgroundView = self.backgroundView {
backgroundView.frame = backgroundFrame
backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.size.height * 0.5, isDark: false, tintColor: .init(kind: .custom, color: self.theme.list.itemCheckColors.fillColor), transition: .immediate)
}
if let background = self.background {
background.frame = backgroundFrame
}
return size return size
} }
} }

View file

@ -14,18 +14,37 @@ final class MediaPickerTitleView: UIView {
private let arrowNode: ASImageNode private let arrowNode: ASImageNode
private let segmentedControlNode: SegmentedControlNode private let segmentedControlNode: SegmentedControlNode
private let glass: Bool
public var theme: PresentationTheme { public var theme: PresentationTheme {
didSet { didSet {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: theme.rootController.navigationBar.primaryTextColor) self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.isDark ? .white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.secondaryTextColor)
self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme))
if self.glass {
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/TitleExpand"), color: self.isDark ? UIColor.white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.4))
}
self.setNeedsLayout()
}
}
public var isDark: Bool = false {
didSet {
if self.isDark != oldValue {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.isDark ? .white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.secondaryTextColor)
if self.glass {
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/TitleExpand"), color: self.isDark ? UIColor.white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.4))
}
self.setNeedsLayout()
}
} }
} }
public var title: String = "" { public var title: String = "" {
didSet { didSet {
if self.title != oldValue { if self.title != oldValue {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor) self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor)
self.setNeedsLayout() self.setNeedsLayout()
} }
} }
@ -34,7 +53,7 @@ final class MediaPickerTitleView: UIView {
public var subtitle: String = "" { public var subtitle: String = "" {
didSet { didSet {
if self.subtitle != oldValue { if self.subtitle != oldValue {
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.isDark ? .white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.secondaryTextColor)
self.setNeedsLayout() self.setNeedsLayout()
} }
} }
@ -47,6 +66,41 @@ final class MediaPickerTitleView: UIView {
} }
} }
public func updateIsDark(isDark: Bool, animated: Bool) {
if animated {
if self.isDark != isDark {
if let snapshotView = self.titleNode.view.snapshotContentTree() {
snapshotView.frame = self.titleNode.frame
self.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.5, removeOnCompletion: false, completion: { _ in
snapshotView.removeFromSuperview()
})
self.titleNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.5)
}
if let snapshotView = self.subtitleNode.view.snapshotContentTree() {
snapshotView.frame = self.subtitleNode.frame
self.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.5, removeOnCompletion: false, completion: { _ in
snapshotView.removeFromSuperview()
})
self.subtitleNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.5)
}
if let snapshotView = self.arrowNode.view.snapshotContentTree() {
snapshotView.frame = self.arrowNode.frame
self.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.5, removeOnCompletion: false, completion: { _ in
snapshotView.removeFromSuperview()
})
self.arrowNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.5)
}
}
}
self.isDark = isDark
}
public func updateTitle(title: String, subtitle: String = "", isEnabled: Bool, animated: Bool) { public func updateTitle(title: String, subtitle: String = "", isEnabled: Bool, animated: Bool) {
if animated { if animated {
if self.title != title { if self.title != title {
@ -129,8 +183,9 @@ final class MediaPickerTitleView: UIView {
public var indexUpdated: ((Int) -> Void)? public var indexUpdated: ((Int) -> Void)?
public var action: () -> Void = {} public var action: () -> Void = {}
public init(theme: PresentationTheme, segments: [String], selectedIndex: Int) { public init(theme: PresentationTheme, glass: Bool, segments: [String], selectedIndex: Int) {
self.theme = theme self.theme = theme
self.glass = glass
self.segments = segments self.segments = segments
self.contextSourceNode = ContextReferenceContentNode() self.contextSourceNode = ContextReferenceContentNode()
@ -144,7 +199,11 @@ final class MediaPickerTitleView: UIView {
self.arrowNode = ASImageNode() self.arrowNode = ASImageNode()
self.arrowNode.displaysAsynchronously = false self.arrowNode.displaysAsynchronously = false
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/DownArrow"), color: theme.rootController.navigationBar.secondaryTextColor) if glass {
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/TitleExpand"), color: theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.4))
} else {
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/DownArrow"), color: theme.rootController.navigationBar.secondaryTextColor)
}
self.arrowNode.isHidden = true self.arrowNode.isHidden = true
self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: segments.map { SegmentedControlItem(title: $0) }, selectedIndex: selectedIndex) self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: segments.map { SegmentedControlItem(title: $0) }, selectedIndex: selectedIndex)
@ -201,11 +260,19 @@ final class MediaPickerTitleView: UIView {
totalHeight += subtitleSize.height totalHeight += subtitleSize.height
} }
self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0)), size: titleSize) let verticalOffset: CGFloat = self.glass ? 3.0 : 0.0
self.subtitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0) + subtitleSize.height + 7.0), size: subtitleSize) let arrowOffset: CGFloat = self.glass ? 1.0 : 5.0
var totalWidth = titleSize.width
if let arrowSize = self.arrowNode.image?.size, !self.arrowNode.isHidden {
totalWidth += arrowOffset + arrowSize.width
}
self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0) + verticalOffset), size: titleSize)
self.subtitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0) + subtitleSize.height + 7.0 + verticalOffset), size: subtitleSize)
if let arrowSize = self.arrowNode.image?.size { if let arrowSize = self.arrowNode.image?.size {
self.arrowNode.frame = CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + 5.0, y: floorToScreenPixels((size.height - totalHeight) / 2.0) + titleSize.height / 2.0 - arrowSize.height / 2.0 + 1.0 - UIScreenPixel), size: arrowSize) self.arrowNode.frame = CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + arrowOffset, y: floorToScreenPixels((size.height - totalHeight) / 2.0) + titleSize.height / 2.0 - arrowSize.height / 2.0 + 1.0 - UIScreenPixel + verticalOffset), size: arrowSize)
} }
self.buttonNode.frame = CGRect(origin: .zero, size: size) self.buttonNode.frame = CGRect(origin: .zero, size: size)
} }

View file

@ -183,7 +183,7 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .uploadSound(text): case let .uploadSound(text):
let icon = PresentationResourcesItemList.uploadToneIcon(presentationData.theme) let icon = PresentationResourcesItemList.uploadToneIcon(presentationData.theme)
return ItemListCheckboxItem(presentationData: presentationData, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.upload() arguments.upload()
}) })
case let .cloudInfo(text): case let .cloudInfo(text):
@ -193,15 +193,15 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
case let .classicHeader(_, text): case let .classicHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .none(_, _, text, selected): case let .none(_, _, text, selected):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: true, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: true, sectionId: self.section, action: {
arguments.selectSound(.none) arguments.selectSound(.none)
}) })
case let .default(_, _, text, selected): case let .default(_, _, text, selected):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectSound(.default) arguments.selectSound(.default)
}) })
case let .sound(_, _, _, text, sound, selected, canBeDeleted): case let .sound(_, _, _, text, sound, selected, canBeDeleted):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectSound(sound) arguments.selectSound(sound)
}, deleteAction: canBeDeleted ? { }, deleteAction: canBeDeleted ? {
arguments.deleteSound(sound, text) arguments.deleteSound(sound, text)

View file

@ -72,7 +72,7 @@ private enum ResetPasswordEntry: ItemListNodeEntry, Equatable {
let arguments = arguments as! ResetPasswordControllerArguments let arguments = arguments as! ResetPasswordControllerArguments
switch self { switch self {
case let .code(theme, _, text, value): case let .code(theme, _, text, value):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .number, spacing: 10.0, tag: ResetPasswordEntryTag.code, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .number, spacing: 10.0, tag: ResetPasswordEntryTag.code, sectionId: self.section, textUpdated: { updatedText in
arguments.updateCodeText(updatedText) arguments.updateCodeText(updatedText)
}, action: { }, action: {
}) })

View file

@ -117,12 +117,13 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .addMethod(text): case let .addMethod(text):
let icon = PresentationResourcesItemList.plusIconImage(presentationData.theme) let icon = PresentationResourcesItemList.plusIconImage(presentationData.theme)
return ItemListCheckboxItem(presentationData: presentationData, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.addMethod() arguments.addMethod()
}) })
case let .item(_, info, isSelected): case let .item(_, info, isSelected):
return ItemListCheckboxItem( return ItemListCheckboxItem(
presentationData: presentationData, presentationData: presentationData,
systemStyle: .glass,
icon: STPPaymentCardTextField.brandImage(for: .masterCard), iconSize: nil, icon: STPPaymentCardTextField.brandImage(for: .masterCard), iconSize: nil,
iconPlacement: .default, iconPlacement: .default,
title: "•••• " + info.number.suffix(4), title: "•••• " + info.number.suffix(4),

View file

@ -674,10 +674,10 @@ private final class VariableBlurView: UIVisualEffectView {
variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius") variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius")
variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage") variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage")
variableBlur.setValue(true, forKey: "inputNormalizeEdges") variableBlur.setValue(true, forKey: "inputNormalizeEdges")
variableBlur.setValue(UIScreenScale, forKey: "scale")
let backdropLayer = self.subviews.first?.layer let backdropLayer = self.subviews.first?.layer
backdropLayer?.filters = [variableBlur] backdropLayer?.filters = [variableBlur]
backdropLayer?.setValue(UIScreenScale, forKey: "scale")
} }
} }

View file

@ -265,11 +265,11 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
let arguments = arguments as! ChannelAdminsControllerArguments let arguments = arguments as! ChannelAdminsControllerArguments
switch self { switch self {
case let .recentActions(_, text): case let .recentActions(_, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openRecentActions() arguments.openRecentActions()
}) })
case let .antiSpam(_, text, value): case let .antiSpam(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/AntiSpam")?.precomposed(), title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Chat/Info/AntiSpam")?.precomposed(), title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateAntiSpamEnabled(value) arguments.updateAntiSpamEnabled(value)
}) })
case let .antiSpamInfo(_, text): case let .antiSpamInfo(_, text):
@ -302,23 +302,23 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
arguments.openAdmin(participant.participant) arguments.openAdmin(participant.participant)
} }
} }
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id) arguments.setPeerIdWithRevealedOptions(previousId, id)
}, removePeer: { peerId in }, removePeer: { peerId in
arguments.removeAdmin(peerId) arguments.removeAdmin(peerId)
}) })
case let .addAdmin(theme, text, editing): case let .addAdmin(theme, text, editing):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: editing, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: editing, action: {
arguments.addAdmin() arguments.addAdmin()
}) })
case let .adminsInfo(_, text): case let .adminsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .signMessages(_, text, value, profiles): case let .signMessages(_, text, value, profiles):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSignaturesAndProfilesEnabled(value, profiles) arguments.updateSignaturesAndProfilesEnabled(value, profiles)
}) })
case let .showAuthorProfiles(_, text, value, signatures): case let .showAuthorProfiles(_, text, value, signatures):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSignaturesAndProfilesEnabled(signatures, value) arguments.updateSignaturesAndProfilesEnabled(signatures, value)
}) })
case let .signMessagesInfo(_, text): case let .signMessagesInfo(_, text):

View file

@ -150,7 +150,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry {
let arguments = arguments as! ChannelBlacklistControllerArguments let arguments = arguments as! ChannelBlacklistControllerArguments
switch self { switch self {
case let .add(_, text): case let .add(_, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.addPeer() arguments.addPeer()
}) })
case let .addInfo(_, text): case let .addInfo(_, text):
@ -167,7 +167,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry {
default: default:
break break
} }
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: {
arguments.openPeer(participant) arguments.openPeer(participant)
}, setPeerIdWithRevealedOptions: { previousId, id in }, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id) arguments.setPeerIdWithRevealedOptions(previousId, id)

View file

@ -145,7 +145,7 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry {
} }
return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animation: .discussionGroupSetup, sectionId: self.section) return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animation: .discussionGroupSetup, sectionId: self.section)
case let .create(theme, text): case let .create(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.createGroup() arguments.createGroup()
}) })
case let .group(_, _, strings, peer, nameOrder): case let .group(_, _, strings, peer, nameOrder):
@ -157,13 +157,13 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry {
} else { } else {
text = strings.Channel_DiscussionGroup_PrivateGroup text = strings.Channel_DiscussionGroup_PrivateGroup
} }
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: nameOrder, context: arguments.context, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: nameOrder, context: arguments.context, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
arguments.selectGroup(peer.id) arguments.selectGroup(peer.id)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
case let .groupsInfo(_, title): case let .groupsInfo(_, title):
return ItemListTextItem(presentationData: presentationData, text: .plain(title), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(title), sectionId: self.section)
case let .unlink(_, title): case let .unlink(_, title):
return ItemListActionItem(presentationData: presentationData, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
arguments.unlinkGroup() arguments.unlinkGroup()
}) })
} }

View file

@ -39,7 +39,7 @@ final class ChannelDiscussionGroupSetupSearchItem: ItemListControllerSearch {
} }
} }
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
if let current = current as? ChannelDiscussionSearchNavigationContentNode { if let current = current as? ChannelDiscussionSearchNavigationContentNode {
current.updateTheme(presentationData.theme) current.updateTheme(presentationData.theme)

View file

@ -254,7 +254,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
let arguments = arguments as! ChannelMembersControllerArguments let arguments = arguments as! ChannelMembersControllerArguments
switch self { switch self {
case let .hideMembers(text, disabledReason, isInteractive, value): case let .hideMembers(text, disabledReason, isInteractive, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: isInteractive, enabled: true, displayLocked: !value && disabledReason != nil, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: isInteractive, enabled: true, displayLocked: !value && disabledReason != nil, sectionId: self.section, style: .blocks, updated: { value in
if let disabledReason { if let disabledReason {
arguments.displayHideMembersTip(disabledReason) arguments.displayHideMembersTip(disabledReason)
} else { } else {
@ -268,11 +268,11 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
case let .hideMembersInfo(text): case let .hideMembersInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .addMember(theme, text): case let .addMember(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: {
arguments.addMember() arguments.addMember()
}) })
case let .inviteLink(theme, text): case let .inviteLink(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.linkIcon(theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: {
arguments.inviteViaLink() arguments.inviteViaLink()
}) })
case let .addMemberInfo(_, text): case let .addMemberInfo(_, text):
@ -286,7 +286,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
} else { } else {
text = .presence text = .presence
} }
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: { return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
arguments.openPeer(EnginePeer(participant.peer)) arguments.openPeer(EnginePeer(participant.peer))
}, setPeerIdWithRevealedOptions: { previousId, id in }, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id) arguments.setPeerIdWithRevealedOptions(previousId, id)

View file

@ -1148,7 +1148,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext,
switch subject { switch subject {
case let .create(peer, _, share, shareViaException, _): case let .create(peer, _, share, shareViaException, _):
if share, filteredPhoneNumbers.count <= 1, let peer = peer { if share, filteredPhoneNumbers.count <= 1, let peer = peer {
addContactDisposable.set((context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions) addContactDisposable.set((context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", noteText: "", noteEntities: [], addToPrivacyExceptions: shareViaException && addToPrivacyExceptions)
|> deliverOnMainQueue).start(error: { _ in |> deliverOnMainQueue).start(error: { _ in
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: (environment.presentationData, updatedPresentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) presentControllerImpl?(textAlertController(context: context, updatedPresentationData: (environment.presentationData, updatedPresentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}, completed: { }, completed: {
@ -1182,7 +1182,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext,
switch subject { switch subject {
case let .create(peer, _, share, shareViaException, _): case let .create(peer, _, share, shareViaException, _):
if share, let peer = peer { if share, let peer = peer {
return context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions) return context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", noteText: "", noteEntities: [], addToPrivacyExceptions: shareViaException && addToPrivacyExceptions)
|> mapToSignal { _ -> Signal<(DeviceContactStableId, DeviceContactExtendedData, EnginePeer?)?, AddContactError> in |> mapToSignal { _ -> Signal<(DeviceContactStableId, DeviceContactExtendedData, EnginePeer?)?, AddContactError> in
} }
|> then( |> then(

View file

@ -60,7 +60,7 @@ final class ChannelMembersSearchItem: ItemListControllerSearch {
} }
} }
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
if let current = current as? GroupInfoSearchNavigationContentNode { if let current = current as? GroupInfoSearchNavigationContentNode {
current.updateTheme(presentationData.theme) current.updateTheme(presentationData.theme)

View file

@ -84,11 +84,11 @@ private enum GroupPreHistorySetupEntry: ItemListNodeEntry {
case let .header(_, text): case let .header(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .visible(_, text, value): case let .visible(_, text, value):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.toggle(true) arguments.toggle(true)
}) })
case let .hidden(_, text, value): case let .hidden(_, text, value):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.toggle(false) arguments.toggle(false)
}) })
case let .info(_, text): case let .info(_, text):

View file

@ -59,7 +59,7 @@ private enum PhoneLabelEntry: ItemListNodeEntry {
let arguments = arguments as! PhoneLabelArguments let arguments = arguments as! PhoneLabelArguments
switch self { switch self {
case let .label(_, _, value, text, selected): case let .label(_, _, value, text, selected):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectLabel(value) arguments.selectLabel(value)
}) })
} }

View file

@ -2261,6 +2261,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let perksSection = perksSection.update( let perksSection = perksSection.update(
component: ListSectionComponent( component: ListSectionComponent(
theme: environment.theme, theme: environment.theme,
style: .glass,
header: AnyComponent(MultilineTextComponent( header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: strings.Premium_WhatsIncluded.uppercased(), string: strings.Premium_WhatsIncluded.uppercased(),
@ -2504,6 +2505,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let businessSection = businessSection.update( let businessSection = businessSection.update(
component: ListSectionComponent( component: ListSectionComponent(
theme: environment.theme, theme: environment.theme,
style: .glass,
header: nil, header: nil,
footer: nil, footer: nil,
items: perksItems items: perksItems
@ -2531,6 +2533,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
if let accountContext = context.component.screenContext.context { if let accountContext = context.component.screenContext.context {
perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent(
theme: environment.theme, theme: environment.theme,
style: .glass,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
@ -2573,6 +2576,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent(
theme: environment.theme, theme: environment.theme,
style: .glass,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
@ -2607,6 +2611,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent(
theme: environment.theme, theme: environment.theme,
style: .glass,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
@ -2642,6 +2647,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let moreBusinessSection = moreBusinessSection.update( let moreBusinessSection = moreBusinessSection.update(
component: ListSectionComponent( component: ListSectionComponent(
theme: environment.theme, theme: environment.theme,
style: .glass,
header: AnyComponent(MultilineTextComponent( header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: strings.Business_MoreFeaturesTitle.uppercased(), string: strings.Business_MoreFeaturesTitle.uppercased(),
@ -2689,6 +2695,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
var adsSettingsItems: [AnyComponentWithIdentity<Empty>] = [] var adsSettingsItems: [AnyComponentWithIdentity<Empty>] = []
adsSettingsItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( adsSettingsItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
theme: environment.theme, theme: environment.theme,
style: .glass,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
@ -2726,6 +2733,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let adsSettingsSection = adsSettingsSection.update( let adsSettingsSection = adsSettingsSection.update(
component: ListSectionComponent( component: ListSectionComponent(
theme: environment.theme, theme: environment.theme,
style: .glass,
header: AnyComponent(MultilineTextComponent( header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: strings.Business_AdsTitle.uppercased(), string: strings.Business_AdsTitle.uppercased(),
@ -2805,7 +2813,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
} else { } else {
layoutPerks() layoutPerks()
let textPadding: CGFloat = 13.0 let textPadding: CGFloat = 17.0
let infoTitle = infoTitle.update( let infoTitle = infoTitle.update(
component: MultilineTextComponent( component: MultilineTextComponent(
@ -2844,7 +2852,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let infoBackground = infoBackground.update( let infoBackground = infoBackground.update(
component: RoundedRectangle( component: RoundedRectangle(
color: environment.theme.list.itemBlocksBackgroundColor, color: environment.theme.list.itemBlocksBackgroundColor,
cornerRadius: 10.0 cornerRadius: 26.0
), ),
environment: {}, environment: {},
availableSize: CGSize(width: availableWidth - sideInsets, height: infoText.size.height + textPadding * 2.0), availableSize: CGSize(width: availableWidth - sideInsets, height: infoText.size.height + textPadding * 2.0),

View file

@ -6,12 +6,12 @@ import SwiftSignalKit
import ItemListUI import ItemListUI
public extension ItemListController { public extension ItemListController {
convenience init<ItemGenerationArguments>(context: AccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>? = nil) { convenience init<ItemGenerationArguments>(context: AccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>? = nil, hideNavigationBarBackground: Bool = false) {
self.init(sharedContext: context.sharedContext, state: state, tabBarItem: tabBarItem) self.init(sharedContext: context.sharedContext, state: state, tabBarItem: tabBarItem, hideNavigationBarBackground: hideNavigationBarBackground)
} }
convenience init<ItemGenerationArguments>(sharedContext: SharedAccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>? = nil) { convenience init<ItemGenerationArguments>(sharedContext: SharedAccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>? = nil, hideNavigationBarBackground: Bool = false) {
let presentationData = sharedContext.currentPresentationData.with { $0 } let presentationData = sharedContext.currentPresentationData.with { $0 }
self.init(presentationData: ItemListPresentationData(presentationData), updatedPresentationData: sharedContext.presentationData |> map(ItemListPresentationData.init(_:)), state: state, tabBarItem: tabBarItem) self.init(presentationData: ItemListPresentationData(presentationData), updatedPresentationData: sharedContext.presentationData |> map(ItemListPresentationData.init(_:)), state: state, tabBarItem: tabBarItem, hideNavigationBarBackground: hideNavigationBarBackground)
} }
} }

View file

@ -127,6 +127,9 @@ swift_library(
"//submodules/TelegramUI/Components/Settings/GenerateThemeName", "//submodules/TelegramUI/Components/Settings/GenerateThemeName",
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem", "//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
"//submodules/TelegramUI/Components/FaceScanScreen", "//submodules/TelegramUI/Components/FaceScanScreen",
"//submodules/ComponentFlow",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View file

@ -91,7 +91,7 @@ private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry {
let arguments = arguments as! ChangePhoneNumberCodeControllerArguments let arguments = arguments as! ChangePhoneNumberCodeControllerArguments
switch self { switch self {
case let .codeEntry(theme, _, title, text): case let .codeEntry(theme, _, title, text):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: title, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ChangePhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: title, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ChangePhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in
arguments.updateEntryText(updatedText) arguments.updateEntryText(updatedText)
}, action: { }, action: {
arguments.next() arguments.next()

View file

@ -183,10 +183,10 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll
if let (_, countryCode) = lookupCountryIdByNumber(phone, configuration: context.currentCountriesConfiguration.with { $0 }), let codeValue = Int32(countryCode.code) { if let (_, countryCode) = lookupCountryIdByNumber(phone, configuration: context.currentCountriesConfiguration.with { $0 }), let codeValue = Int32(countryCode.code) {
initialCountryCode = codeValue initialCountryCode = codeValue
} else { } else {
initialCountryCode = AuthorizationSequenceController.defaultCountryCode() initialCountryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
} }
} else { } else {
initialCountryCode = AuthorizationSequenceController.defaultCountryCode() initialCountryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
} }
controller.updateData(countryCode: initialCountryCode, countryName: nil, number: "") controller.updateData(countryCode: initialCountryCode, countryName: nil, number: "")
controller.updateCountryCode() controller.updateCountryCode()

View file

@ -153,31 +153,31 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
let arguments = arguments as! AutodownloadMediaConnectionTypeControllerArguments let arguments = arguments as! AutodownloadMediaConnectionTypeControllerArguments
switch self { switch self {
case let .master(_, text, value): case let .master(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleMaster(value) arguments.toggleMaster(value)
}) })
case let .dataUsageHeader(_, text): case let .dataUsageHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .dataUsageItem(theme, strings, value, customPosition, enabled): case let .dataUsageItem(theme, strings, value, customPosition, enabled):
return AutodownloadDataUsagePickerItem(theme: theme, strings: strings, value: value, customPosition: customPosition, enabled: enabled, sectionId: self.section, updated: { preset in return AutodownloadDataUsagePickerItem(theme: theme, strings: strings, systemStyle: .glass, value: value, customPosition: customPosition, enabled: enabled, sectionId: self.section, updated: { preset in
arguments.changePreset(preset) arguments.changePreset(preset)
}) })
case let .typesHeader(_, text): case let .typesHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .photos(_, text, value, enabled): case let .photos(_, text, value, enabled):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Photos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Photos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.customize(.photo) arguments.customize(.photo)
}) })
case let .stories(_, text, value, enabled): case let .stories(_, text, value, enabled):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Stories")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Stories")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.customize(.story) arguments.customize(.story)
}) })
case let .videos(_, text, value, enabled): case let .videos(_, text, value, enabled):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Videos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Videos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.customize(.video) arguments.customize(.video)
}) })
case let .files(_, text, value, enabled): case let .files(_, text, value, enabled):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Files")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Files")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: {
arguments.customize(.file) arguments.customize(.file)
}) })
case let .voiceMessagesInfo(_, text): case let .voiceMessagesInfo(_, text):

View file

@ -33,15 +33,17 @@ enum AutomaticDownloadDataUsage: Int {
final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem { final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let systemStyle: ItemListSystemStyle
let value: AutomaticDownloadDataUsage let value: AutomaticDownloadDataUsage
let customPosition: Int? let customPosition: Int?
let enabled: Bool let enabled: Bool
let sectionId: ItemListSectionId let sectionId: ItemListSectionId
let updated: (AutomaticDownloadDataUsage) -> Void let updated: (AutomaticDownloadDataUsage) -> Void
init(theme: PresentationTheme, strings: PresentationStrings, value: AutomaticDownloadDataUsage, customPosition: Int?, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (AutomaticDownloadDataUsage) -> Void) { init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, value: AutomaticDownloadDataUsage, customPosition: Int?, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (AutomaticDownloadDataUsage) -> Void) {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.systemStyle = systemStyle
self.value = value self.value = value
self.customPosition = customPosition self.customPosition = customPosition
self.enabled = enabled self.enabled = enabled
@ -296,7 +298,7 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners strongSelf.bottomStripeNode.isHidden = hasCorners
} }
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)

Some files were not shown because too many files have changed in this diff Show more