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 makeNewContactScreen(context: AccountContext, firstName: String?, lastName: String?, phoneNumber: String?) -> ViewController
func navigateToCurrentCall()
var hasOngoingCall: ValuePromise<Bool> { get }
var immediateHasOngoingCall: Bool { get }
@ -1486,7 +1488,6 @@ public protocol AccountContext: AnyObject {
var engine: TelegramEngine { get }
var liveLocationManager: LiveLocationManager? { get }
var peersNearbyManager: PeersNearbyManager? { get }
var fetchManager: FetchManager { get }
var prefetchManager: PrefetchManager? { 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 displayProgress: Bool { 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()
}
@ -105,7 +105,13 @@ public final class ContactSelectionControllerParams {
case always
}
public enum Style {
case glass
case legacy
}
public let context: AccountContext
public let style: Style
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
public let mode: ContactSelectionControllerMode
public let autoDismiss: Bool
@ -123,6 +129,7 @@ public final class ContactSelectionControllerParams {
public init(
context: AccountContext,
style: Style = .legacy,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
mode: ContactSelectionControllerMode = .generic,
autoDismiss: Bool = true,
@ -139,6 +146,7 @@ public final class ContactSelectionControllerParams {
sendMessage: ((EnginePeer) -> Void)? = nil
) {
self.context = context
self.style = style
self.updatedPresentationData = updatedPresentationData
self.mode = mode
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/Chat/ChatInputTextNode",
"//submodules/AnimatedCountLabelNode",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
],
visibility = [
"//visibility:public",

View file

@ -9,12 +9,15 @@ import ChatPresentationInterfaceState
import ComponentFlow
import AccountContext
import AnimatedCountLabelNode
import GlassBackgroundComponent
final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode {
private let strings: PresentationStrings
private let glass: Bool
let sendContainerNode: ASDisplayNode
let backgroundNode: ASDisplayNode
let backgroundView: GlassBackgroundView?
let backgroundNode: ASDisplayNode?
let sendButton: HighlightTrackingButtonNode
var sendButtonHasApplyIcon = false
var animatingSendButton = false
@ -35,16 +38,24 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
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.strings = presentationInterfaceState.strings
self.glass = glass
self.sendContainerNode = ASDisplayNode()
self.sendContainerNode.layer.allowsGroupOpacity = true
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor
self.backgroundNode.clipsToBounds = true
if glass {
self.backgroundView = GlassBackgroundView()
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.textNode = ImmediateAnimatedCountLabelNode()
@ -78,7 +89,11 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
}
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.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) {
self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor
self.backgroundNode?.backgroundColor = theme.chat.inputPanel.actionControlFillColor
}
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)
if minimized {
width = 44.0
width = 53.0
} else {
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(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: buttonSize))
let backgroundSize = CGSize(width: width - 11.0, height: 33.0)
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))
self.backgroundNode.cornerRadius = backgroundSize.height / 2.0
if let backgroundView = self.backgroundView {
let backgroundSize = CGSize(width: width - 13.0, height: 40.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
}

View file

@ -28,7 +28,7 @@ import ChatInputTextNode
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
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)
var result: CGFloat
if baseFontSize.isEqual(to: 26.0) {
@ -49,25 +49,34 @@ private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPres
result = max(33.0, result)
}
if glass {
result = max(38.0, 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 top: CGFloat
let bottom: CGFloat
if baseFontSize.isEqual(to: 14.0) {
top = 2.0
if glass {
top = 4.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
if baseFontSize.isEqual(to: 14.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)
}
@ -243,15 +252,16 @@ private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPre
public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate {
private let context: AccountContext
private let glass: Bool
private let isCaption: Bool
private let isAttachment: Bool
private let presentController: (ViewController) -> Void
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
private var textPlaceholderNode: ImmediateTextNode
public var textPlaceholderNode: ImmediateTextNode
private let textInputContainerBackgroundNode: ASImageNode
private let textInputContainer: ASDisplayNode
public let textInputContainer: ASDisplayNode
public var textInputNode: ChatInputTextNode?
private var dustNode: InvisibleInkDustNode?
private var customEmojiContainerView: CustomEmojiContainerView?
@ -265,7 +275,11 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private let actionButtons: AttachmentTextInputActionButtonsNode
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)?
@ -379,9 +393,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
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.presentationInterfaceState = presentationInterfaceState
self.glass = glass
self.isCaption = isCaption
self.isAttachment = isAttachment
self.presentController = presentController
@ -401,7 +416,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.textInputContainerBackgroundNode.displaysAsynchronously = false
self.textInputContainer = ASDisplayNode()
if !isCaption {
if !isCaption && !glass {
self.textInputContainer.addSubnode(self.textInputContainerBackgroundNode)
}
@ -420,7 +435,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.oneLineNode = TextNodeWithEntities()
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.textAlignment = .center
@ -441,7 +456,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.addSubnode(self.textInputContainer)
self.addSubnode(self.textInputBackgroundNode)
self.textInputBackgroundNode.addSubnode(self.textInputBackgroundImageNode)
if !glass {
self.textInputBackgroundNode.addSubnode(self.textInputBackgroundImageNode)
}
self.addSubnode(self.textPlaceholderNode)
@ -589,7 +607,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
if let presentationInterfaceState = self.presentationInterfaceState {
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 {
@ -628,7 +646,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat {
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) {
@ -641,8 +659,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
var textFieldMinHeight: CGFloat = 35.0
var textFieldRealInsets = UIEdgeInsets()
if let presentationInterfaceState = self.presentationInterfaceState {
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics)
textFieldRealInsets = calculateTextFieldRealInsets(presentationInterfaceState)
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics)
textFieldRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass)
}
let textFieldHeight: CGFloat
@ -666,7 +684,12 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private func textFieldInsets(metrics: LayoutMetrics) -> UIEdgeInsets {
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.bottom += 1.0
}
@ -680,7 +703,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
}
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
if case .regular = metrics.widthClass, case .regular = metrics.heightClass {
minimalHeight += 2.0
@ -689,6 +712,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
}
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) {
return result
}
@ -703,7 +729,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let leftInset = leftInset + 8.0
let rightInset = rightInset + 8.0
var transition = transition
if let previousAdditionalSideInsets = previousAdditionalSideInsets, previousAdditionalSideInsets.right != additionalSideInsets.right {
if case .animated = transition {
@ -758,7 +784,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
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 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 {
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics)
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics)
}
let minimalHeight: CGFloat = 14.0 + textFieldMinHeight
@ -852,7 +878,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
var textInputViewRealInsets = UIEdgeInsets()
if let presentationInterfaceState = self.presentationInterfaceState {
textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState)
textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass)
}
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 shouldUpdateLayout = textFieldFrame.size != textInputNode.frame.size
if let presentationInterfaceState = self.presentationInterfaceState {
textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState)
textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass)
}
transition.updateFrame(node: textInputNode, frame: textFieldFrame)
if shouldUpdateLayout {
@ -932,6 +958,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.actionButtons.updateAccessibility()
if self.glass {
panelHeight += 11.0
}
return panelHeight
}
@ -943,11 +973,14 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let leftInset = leftInsetValue + 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 {
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 composeButtonsOffset: CGFloat = 0.0
@ -983,16 +1016,18 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
isMinimized = !self.isAttachment || inputHasText
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
} else {
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)
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(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()
if let presentationInterfaceState = self.presentationInterfaceState {
textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState)
textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass)
var colors: [String: UIColor] = [:]
let colorKeys: [String] = [
"__allcolors__"
]
let color = defaultDarkPresentationTheme.chat.inputPanel.inputControlColor
let color = self.theme?.chat.inputPanel.inputControlColor ?? defaultDarkPresentationTheme.chat.inputPanel.inputControlColor
for colorKey in colorKeys {
colors[colorKey] = color
}
@ -1028,7 +1063,11 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
environment: {},
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
@ -1294,7 +1333,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics)
var textFieldMinHeight: CGFloat = 33.0
if let presentationInterfaceState = self.presentationInterfaceState {
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics)
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics)
}
let minimalHeight: CGFloat = 14.0 + textFieldMinHeight

View file

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

View file

@ -8,6 +8,8 @@ import DirectionalPanGesture
import TelegramPresentationData
import MapKit
import WebKit
import ComponentFlow
import EdgeEffect
private let overflowInset: CGFloat = 0.0
@ -27,10 +29,13 @@ public func attachmentDefaultTopInset(layout: ContainerViewLayout?) -> CGFloat {
}
final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
private let glass: Bool
let wrappingNode: ASDisplayNode
let clipNode: ASDisplayNode
let bottomClipNode: ASDisplayNode
let container: NavigationContainer
private let pillView: UIImageView
private(set) var isReady: Bool = false
private(set) var dismissProgress: CGFloat = 0.0
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?
var isPanningUpdated: (Bool) -> Void = { _ in }
@ -74,14 +85,19 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
var isInnerPanGestureEnabled: (() -> Bool)?
var onExpandAnimationCompleted: () -> Void = {}
init(isFullSize: Bool) {
init(presentationData: PresentationData, isFullSize: Bool, glass: Bool) {
self.presentationData = presentationData
self.isFullSize = isFullSize
if isFullSize {
self.isExpanded = true
}
self.glass = glass
self.wrappingNode = ASDisplayNode()
self.clipNode = ASDisplayNode()
self.bottomClipNode = ASDisplayNode()
self.bottomClipNode.clipsToBounds = true
self.bottomClipNode.cornerRadius = 62.0
var controllerRemovedImpl: ((ViewController) -> Void)?
self.container = NavigationContainer(isFlat: false, controllerRemoved: { c in
@ -90,12 +106,18 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
self.container.clipsToBounds = true
self.container.overflowInset = overflowInset
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()
self.addSubnode(self.wrappingNode)
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.container.isReadyUpdated = { [weak self] in
@ -126,6 +148,8 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
panRecognizer.cancelsTouchesInView = true
self.panGestureRecognizer = panRecognizer
self.wrappingNode.view.addGestureRecognizer(panRecognizer)
self.clipNode.view.addSubview(self.pillView)
}
func cancelPanGesture() {
@ -480,13 +504,17 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
})
let modalProgress: CGFloat
let scaleProgress: CGFloat
if isLandscape {
modalProgress = 0.0
scaleProgress = 1.0
} else {
if self.isFullSize, self.panGestureArguments != nil {
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 {
modalProgress = 1.0 - topInset / defaultTopInset
scaleProgress = topInset / defaultTopInset
}
}
@ -514,7 +542,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
if isLandscape {
self.clipNode.cornerRadius = 0.0
} else {
self.clipNode.cornerRadius = 10.0
self.clipNode.cornerRadius = self.glass ? 38.0 : 10.0
}
if #available(iOS 11.0, *) {
@ -562,7 +590,10 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition
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 {
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
}
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)
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
}
@ -647,7 +689,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
}
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) {
return false
}

View file

@ -4,6 +4,7 @@ import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import ComponentFlow
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
@ -16,6 +17,8 @@ import LegacyMessageInputPanelInputView
import AttachmentTextInputPanelNode
import ChatSendMessageActionUI
import MinimizedContainer
import ComponentFlow
import GlassBackgroundComponent
public enum AttachmentButtonType: Equatable {
case gallery
@ -308,42 +311,74 @@ public extension AttachmentMediaPickerContext {
}
}
private func generateShadowImage() -> UIImage? {
return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in
private func generateShadowImage(glass: Bool) -> UIImage? {
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.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
context.addPath(path)
context.fillPath()
context.restoreGState()
context.setBlendMode(.clear)
context.addPath(path)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 70, topCapHeight: 70)
if glass {
let firstPath = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide + middle, height: innerSide + middle), cornerRadius: 62.0).cgPath
let secondPath = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide + middle, height: innerSide * 0.65), cornerRadius: 38.0).cgPath
context.addPath(firstPath)
context.addPath(secondPath)
context.fillPath()
context.restoreGState()
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
context.clear(CGRect(origin: CGPoint(), size: size))
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
context.addPath(path)
context.fillPath()
if glass {
let firstPath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 62.0).cgPath
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 ")
})?.stretchableImage(withLeftCapWidth: 195, topCapHeight: 110)
}
public class AttachmentController: ViewController, MinimizableController {
public enum Style {
case glass
case legacy
}
private let context: AccountContext
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private let style: Style
private let chatLocation: ChatLocation?
private let isScheduledMessages: Bool
private var buttons: [AttachmentButtonType]
@ -380,6 +415,8 @@ public class AttachmentController: ViewController, MinimizableController {
public var isFullscreen: Bool {
return self.mainController.isFullscreen
}
public weak var attachmentButton: UIView?
private final class Node: ASDisplayNode {
private weak var controller: AttachmentController?
@ -426,7 +463,7 @@ public class AttachmentController: ViewController, MinimizableController {
if let strongSelf = self {
strongSelf.panel.updateLoadingProgress(progress)
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 {
strongSelf.panel.updateMainButtonState(mainButtonState)
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 {
strongSelf.panel.updateSecondaryButtonState(mainButtonState)
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 var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private var isMinimizing = false
init(controller: AttachmentController, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
self.controller = controller
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.alpha = 0.0
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
@ -507,9 +553,18 @@ public class AttachmentController: ViewController, MinimizableController {
self.wrapperNode = ASDisplayNode()
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.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.isStandalone = controller.isStandalone
@ -529,10 +584,10 @@ public class AttachmentController: ViewController, MinimizableController {
}
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
if strongSelf.container.supernode == nil {
transition = .animated(duration: 0.4, curve: .spring)
transition = .animated(duration: 0.5, curve: .spring)
}
strongSelf.modalProgress = progress
@ -540,33 +595,37 @@ public class AttachmentController: ViewController, MinimizableController {
strongSelf.controller?.minimizedBounds = bounds
if !strongSelf.isMinimizing {
strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition)
if controller.style != .glass {
strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition)
}
strongSelf.containerLayoutUpdated(layout, transition: transition)
}
}
}
self.container.isReadyUpdated = { [weak self] in
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
if let strongSelf = self, let layout = strongSelf.validLayout {
if let controller = strongSelf.controller, controller.shouldMinimizeOnSwipe?(strongSelf.currentType) == true {
var delta = layout.size.height
if let minimizedTopEdgeOffset = controller.minimizedTopEdgeOffset {
delta -= minimizedTopEdgeOffset
}
let damping: CGFloat = 180.0
let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0
strongSelf.minimize(damping: damping, initialVelocity: initialVelocity)
return false
} else {
strongSelf.controller?.dismiss(animated: true)
}
guard let self, let controller = self.controller, let layout = self.validLayout else {
return true
}
let damping: CGFloat = 180.0
var delta = layout.size.height
if let minimizedTopEdgeOffset = controller.minimizedTopEdgeOffset {
delta -= minimizedTopEdgeOffset
}
let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0
if controller.shouldMinimizeOnSwipe?(self.currentType) == true {
self.minimize(damping: damping, initialVelocity: initialVelocity)
return false
} else {
self.animateOut(damping: damping, initialVelocity: initialVelocity, completion: {
self.controller?.dismiss(animated: false)
})
}
return true
}
@ -637,7 +696,7 @@ public class AttachmentController: ViewController, MinimizableController {
self.panel.beganTextEditing = { [weak self] in
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?()
}
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 {
@ -708,6 +778,7 @@ public class AttachmentController: ViewController, MinimizableController {
self.mainButtonStateDisposable.dispose()
self.secondaryButtonStateDisposable.dispose()
self.bottomPanelBackgroundColorDisposable.dispose()
self.presentationDataDisposable?.dispose()
}
private var inputContainerHeight: CGFloat?
@ -783,7 +854,7 @@ public class AttachmentController: ViewController, MinimizableController {
fileprivate func updateSelectionCount(_ count: Int, animated: Bool = true) {
self.selectionCount = count
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 {
guard self.currentType != type else {
if self.animating {
if self.isAnimating {
return false
}
if let controller = self.currentControllers.last {
@ -822,12 +893,15 @@ public class AttachmentController: ViewController, MinimizableController {
self.controller?.requestController(type, { [weak self] controller, mediaPickerContext in
if let strongSelf = self {
if let controller = controller {
if case .glass = strongSelf.controller?.style {
controller._hasGlassStyle = true
}
strongSelf.controller?._ready.set(controller.ready.get())
controller._presentedInModal = true
controller.navigation_setPresenting(strongSelf.controller)
controller.requestAttachmentMenuExpansion = { [weak self] in
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
@ -836,7 +910,7 @@ public class AttachmentController: ViewController, MinimizableController {
strongSelf.currentControllers = controllers
strongSelf.mediaPickerContext = mediaPickerContext
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 {
return
}
snapshotView.alpha = 0.0
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()
|> filter {
@ -915,22 +989,22 @@ public class AttachmentController: ViewController, MinimizableController {
guard let strongSelf = self, let layout = strongSelf.validLayout else {
return
}
let _ = layout
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)
var startPosition = initialPosition
if let presentation = strongSelf.container.clipNode.layer.presentation() {
if let presentation = strongSelf.container.wrappingNode.layer.presentation() {
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 {
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 {
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() {
guard let layout = self.validLayout, let controller = self.controller else {
return
}
self.animating = true
self.isAnimating = true
if case .regular = layout.metrics.widthClass {
if controller.animateAppearance {
let targetPosition = self.position
@ -960,30 +1034,103 @@ public class AttachmentController: ViewController, MinimizableController {
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
transition.animateView(allowUserInteraction: true, {
self.position = targetPosition
}, completion: { _ in
self.animating = false
}, completion: { _ in
self.isAnimating = false
})
} else {
self.animating = false
self.isAnimating = false
}
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 0.1)
} else {
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
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.animating = false
})
if case .glass = controller.style, let attachmentButton = controller.attachmentButton {
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .customSpring(damping: 115.0, initialVelocity: 0.0))
let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut)
let targetFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view)
let sourceButtonFrame = attachmentButton.convert(attachmentButton.bounds, to: self.view)
let sourceButtonScale = sourceButtonFrame.width / targetFrame.width
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 {
return
}
@ -993,28 +1140,102 @@ public class AttachmentController: ViewController, MinimizableController {
return
}
self.animating = true
if case .regular = layout.metrics.widthClass {
self.isAnimating = true
switch layout.metrics.widthClass {
case .regular:
self.layer.allowsGroupOpacity = true
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)
self?.animating = false
self?.isAnimating = false
self?.layer.removeAllAnimations()
})
} 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?.animating = false
})
case .compact:
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0)
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)
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))
let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
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 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?() {
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 {
position = CGPoint(x: masterWidth - 174.0, y: positionY)
}
@ -1104,7 +1325,7 @@ public class AttachmentController: ViewController, MinimizableController {
self.wrapperNode.cornerRadius = 10.0
} else if self.wrapperNode.view.mask == nil {
let maskView = UIImageView()
maskView.image = generateMaskImage()
maskView.image = generateMaskImage(glass: controller.style == .glass)
maskView.contentMode = .scaleToFill
self.wrapperNode.view.mask = maskView
}
@ -1115,7 +1336,7 @@ public class AttachmentController: ViewController, MinimizableController {
self.shadowNode.alpha = 1.0
if self.shadowNode.image == nil {
self.shadowNode.image = generateShadowImage()
self.shadowNode.image = generateShadowImage(glass: controller.style == .glass)
}
}
} else {
@ -1148,22 +1369,36 @@ public class AttachmentController: ViewController, MinimizableController {
if !self.isPanelVisible {
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 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 {
containerInsets.bottom = panelHeight
containerInsets.bottom = panelHeight + panelOffset
}
var panelTransition = transition
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
if case .glass = controller.style {
panelY -= panelOffset
}
if !hasPanel && !hasButton {
panelY = containerRect.height
}
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)
@ -1182,7 +1417,7 @@ public class AttachmentController: ViewController, MinimizableController {
}
let controllers = self.currentControllers
if !self.animating {
if !self.isAnimating {
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)
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)
if fromMenu, let _ = controller.getInputContainerNode() {
self.addSubnode(self.panel)
} else {
@ -1220,6 +1455,7 @@ public class AttachmentController: ViewController, MinimizableController {
public init(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
style: Style = .legacy,
chatLocation: ChatLocation?,
isScheduledMessages: Bool = false,
buttons: [AttachmentButtonType],
@ -1231,6 +1467,7 @@ public class AttachmentController: ViewController, MinimizableController {
{
self.context = context
self.updatedPresentationData = updatedPresentationData
self.style = style
self.chatLocation = chatLocation
self.isScheduledMessages = isScheduledMessages
self.buttons = buttons
@ -1242,6 +1479,8 @@ public class AttachmentController: ViewController, MinimizableController {
super.init(navigationBarPresentationData: nil)
self._hasGlassStyle = style == .glass
self.statusBar.statusBarStyle = .Ignore
self.blocksBackgroundWhenInOverlay = true
self.acceptsFocusWhenInOverlay = true
@ -1446,3 +1685,12 @@ public class AttachmentController: ViewController, MinimizableController {
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 ReactionSelectionNode
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 iconSize = CGSize(width: 30.0, height: 30.0)
private let sideInset: CGFloat = 3.0
@ -124,7 +127,12 @@ private final class IconComponent: Component {
private final class AttachButtonComponent: CombinedComponent {
enum Style {
case glass
case legacy
}
let context: AccountContext
let style: Style
let type: AttachmentButtonType
let isSelected: Bool
let strings: PresentationStrings
@ -134,6 +142,7 @@ private final class AttachButtonComponent: CombinedComponent {
init(
context: AccountContext,
style: Style,
type: AttachmentButtonType,
isSelected: Bool,
strings: PresentationStrings,
@ -142,6 +151,7 @@ private final class AttachButtonComponent: CombinedComponent {
longPressAction: @escaping () -> Void
) {
self.context = context
self.style = style
self.type = type
self.isSelected = isSelected
self.strings = strings
@ -154,6 +164,9 @@ private final class AttachButtonComponent: CombinedComponent {
if lhs.context !== rhs.context {
return false
}
if lhs.style != rhs.style {
return false
}
if lhs.type != rhs.type {
return false
}
@ -227,11 +240,21 @@ private final class AttachButtonComponent: CombinedComponent {
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 topInset: CGFloat = 4.0 + UIScreenPixel
let spacing: CGFloat = 15.0 + UIScreenPixel
var topInset: CGFloat = 4.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)
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(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: name,
font: Font.regular(10.0),
font: titleFont,
textColor: context.component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor,
paragraphAlignment: .center)),
horizontalAlignment: .center,
@ -476,6 +507,7 @@ private final class BadgeNode: ASDisplayNode {
private final class MainButtonNode: HighlightTrackingButtonNode {
private var state: AttachmentMainButtonState
private var panelStyle: AttachmentPanel.Style?
private var size: CGSize?
private let backgroundAnimationNode: ASImageNode
@ -512,13 +544,13 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
self.addSubnode(self.statusNode)
self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self, strongSelf.state.isEnabled {
if let self, self.state.isEnabled {
if highlighted {
strongSelf.layer.removeAnimation(forKey: "opacity")
strongSelf.alpha = 0.65
self.layer.removeAnimation(forKey: "opacity")
self.alpha = 0.65
} else {
strongSelf.alpha = 1.0
strongSelf.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2)
self.alpha = 1.0
self.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2)
}
}
}
@ -527,7 +559,6 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
override func didLoad() {
super.didLoad()
self.cornerRadius = 12.0
if #available(iOS 13.0, *) {
self.layer.cornerCurve = .continuous
}
@ -624,8 +655,8 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
self.updateShimmerParameters()
if let size = self.size {
self.updateLayout(size: size, state: state, transition: .immediate)
if let size = self.size, let style = self.panelStyle {
self.updateLayout(size: size, style: style, state: state, transition: .immediate)
}
}
} 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
self.state = state
self.panelStyle = style
self.size = size
switch style {
case .glass:
self.cornerRadius = size.height * 0.5
case .legacy:
self.cornerRadius = 12.0
}
self.isUserInteractionEnabled = state.isVisible
self.setupShimmering()
@ -842,6 +881,13 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
}
final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
enum Style {
case glass
case legacy
}
private let panelStyle: Style
private weak var controller: AttachmentController?
private let context: AccountContext
private let isScheduledMessages: Bool
@ -858,9 +904,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
private let containerNode: ASDisplayNode
private var backgroundView: GlassBackgroundView?
private let backgroundNode: NavigationBackgroundNode
private let scrollNode: ASScrollNode
private let separatorNode: ASDisplayNode
private let selectionNode: ASImageNode
private var buttonViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var textInputPanelNode: AttachmentTextInputPanelNode?
@ -904,10 +953,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
var onMainButtonPressed: () -> 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.context = context
self.panelStyle = style
self.updatedPresentationData = updatedPresentationData
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
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.containerNode = ASDisplayNode()
self.containerNode.clipsToBounds = true
self.containerNode.clipsToBounds = false
self.scrollNode = ASScrollNode()
self.scrollNode.clipsToBounds = true
self.selectionNode = ASImageNode()
self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor)
self.separatorNode = ASDisplayNode()
@ -931,9 +984,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
super.init()
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.separatorNode)
self.containerNode.addSubnode(self.scrollNode)
switch style {
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.mainButtonNode)
@ -1294,6 +1354,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
strongSelf.backgroundNode.updateColor(color: strongSelf.customBottomPanelBackgroundColor ?? presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate)
strongSelf.separatorNode.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor
strongSelf.selectionNode.backgroundColor = presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05)
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)))
}
var buttonSize: CGSize {
switch self.panelStyle {
case .glass:
return glassButtonSize
case .legacy:
return legacyButtonSize
}
}
func updateViews(transition: ComponentTransition) {
guard let layout = self.validLayout else {
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)
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)
var leftNodeOriginX = (layout.size.width - internalWidth) / 2.0
var buttonWidth = buttonSize.width
if self.buttons.count > 6 && layout.size.width < layout.size.height {
buttonWidth = smallButtonWidth
distanceBetweenNodes = buttonWidth
var buttonWidth = self.buttonSize.width
var leftNodeOriginX: CGFloat
switch self.panelStyle {
case .glass:
leftNodeOriginX = layout.safeInsets.left + sideInset + buttonWidth / 2.0
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
}
var validIds = Set<AnyHashable>()
var selectionFrame = CGRect()
var mostRightX = 0.0
for i in 0 ..< self.buttons.count {
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) {
continue
}
@ -1470,6 +1563,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
transition: buttonTransition,
component: AnyComponent(AttachButtonComponent(
context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy,
type: type,
isSelected: i == self.selectedIndex,
strings: self.presentationData.strings,
@ -1480,7 +1574,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
strongSelf.selectedIndex = i
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)
}
}
@ -1492,8 +1586,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
})
),
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)
var accessibilityTitle = ""
switch type {
@ -1532,34 +1630,24 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
for id in removeIds {
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)
var buttonWidth = buttonSize.width
if self.buttons.count > 6 && layout.size.width < layout.size.height {
buttonWidth = smallButtonWidth
contentSize.width = layout.safeInsets.left + layout.safeInsets.right + sideInset * 2.0 + CGFloat(self.buttons.count) * buttonWidth
selectionFrame = selectionFrame.insetBy(dx: 1.0, dy: 4.0)
self.selectionNode.cornerRadius = selectionFrame.height * 0.5
transition.setFrame(view: self.selectionNode.view, frame: selectionFrame)
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() {
if let _ = self.textInputPanelNode {
} 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 {
strongSelf.present(c)
}
@ -1788,7 +1876,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
} else {
self.textInputPanelNode?.ensureUnfocused()
}
let panelSideInset: CGFloat = isSelecting ? 8.0 : 22.0
var textPanelHeight: CGFloat = 0.0
var textPanelWidth: CGFloat = 0.0
if let textInputPanelNode = self.textInputPanelNode {
textInputPanelNode.isUserInteractionEnabled = isSelecting
@ -1805,16 +1896,52 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
if panelFrame.height > 0.0 {
textPanelHeight = panelFrame.height
} 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))
var containerTransition: ContainedViewLayoutTransition
let containerFrame: CGRect
let glassPanelHeight: CGFloat = 62.0
let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom))
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
let buttonHeight: CGFloat = 50.0
var containerTransition: ContainedViewLayoutTransition
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 {
var height: CGFloat
@ -1833,10 +1960,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
if isTwoVerticalButtons && self.secondaryButtonState.smallSpacing {
} else if !isNarrowButton {
height += 9.0
if case .glass = self.panelStyle {
} else {
height += 9.0
}
}
if isTwoVerticalButtons {
height += buttonHeight + sideInset
height += buttonHeight + buttonSpacing
}
containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: height))
} else if isSelecting {
@ -1846,26 +1976,74 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}
let containerBounds = CGRect(origin: CGPoint(), size: containerFrame.size)
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 {
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)
if let backgroundView = self.backgroundView {
containerTransition.updateTransformScale(layer: backgroundView.layer, scale: isAnyButtonVisible ? 0.85 : 1.0)
}
if isSelectingUpdated {
if isSelecting {
self.loadTextNodeIfNeeded()
if let textInputPanelNode = self.textInputPanelNode {
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)
textInputPanelNode.isUserInteractionEnabled = true
if case .glass = self.panelStyle {
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 {
if let textInputPanelNode = self.textInputPanelNode {
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)
textInputPanelNode.isUserInteractionEnabled = false
if case .glass = self.panelStyle {
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)
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)
@ -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 {
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 {
let buttonOriginX = layout.safeInsets.left + sideInset
let buttonOriginX = layout.safeInsets.left + buttonSideInset
let buttonOriginY = isAnyButtonVisible || self.fromMenu ? buttonTopInset : containerFrame.height
var mainButtonFrame: CGRect?
var secondaryButtonFrame: CGRect?
@ -1919,17 +2106,17 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
switch position {
case .top:
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:
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
let buttonSpacing = self.secondaryButtonState.smallSpacing ? 8.0 : sideInset
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + buttonSpacing + buttonSize.height), size: buttonSize)
let effectiveButtonSpacing = self.secondaryButtonState.smallSpacing ? 8.0 : buttonSpacing
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + effectiveButtonSpacing + buttonSize.height), size: buttonSize)
case .left:
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:
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 {
if self.mainButtonState.isVisible {
@ -1942,7 +2129,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
if let mainButtonFrame {
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 {
self.mainButtonNode.frame = mainButtonFrame
@ -1955,7 +2142,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}
if let secondaryButtonFrame {
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 {
self.secondaryButtonNode.frame = secondaryButtonFrame

View file

@ -149,7 +149,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
let masterDatacenterId = strongSelf.account.masterDatacenterId
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()
}
@ -338,7 +338,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else {
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()
})
@ -705,7 +705,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else {
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()
})
@ -772,7 +772,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let self else {
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()
})
return controller
@ -889,7 +889,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else {
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()
})
@ -1034,7 +1034,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else {
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()
})
@ -1094,7 +1094,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let strongSelf = self else {
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()
}, displayCancel: displayCancel)
@ -1213,7 +1213,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
if self.otherAccountPhoneNumbers.1.isEmpty {
controllers.append(self.splashController())
} 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)
}
@ -1241,7 +1241,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
if !self.otherAccountPhoneNumbers.1.isEmpty {
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
if case let .emailSetupRequired(appleSignInAllowed) = type {

View file

@ -65,6 +65,7 @@ private func callListNeighbors(item: ListViewItem, topItem: ListViewItem?, botto
class CallListCallItem: ListViewItem {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let dateTimeFormat: PresentationDateTimeFormat
let context: AccountContext
let style: ItemListStyle
@ -78,8 +79,9 @@ class CallListCallItem: ListViewItem {
let headerAccessoryItem: ListViewAccessoryItem?
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.systemStyle = systemStyle
self.dateTimeFormat = dateTimeFormat
self.context = context
self.style = style
@ -345,6 +347,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: 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 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))
@ -672,12 +682,12 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
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.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))
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
switch entry.entry {
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)
}), directionHint: entry.directionHint)
case let .displayTabInfo(_, text):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
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()
})
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
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, _):
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):
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
switch entry.entry {
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)
}), directionHint: entry.directionHint)
case let .displayTabInfo(_, text):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
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()
})
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
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, _):
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):
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 {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let context: AccountContext
let style: ItemListStyle
let peer: EnginePeer
@ -68,8 +69,9 @@ class CallListGroupCallItem: ListViewItem {
let headerAccessoryItem: ListViewAccessoryItem?
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.systemStyle = systemStyle
self.context = context
self.style = style
self.peer = peer

View file

@ -23,6 +23,7 @@ enum ChatListFilterCategoryIcon {
final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String
let icon: ChatListFilterCategoryIcon
let isRevealed: Bool
@ -33,6 +34,7 @@ final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem {
init(
presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
title: String,
icon: ChatListFilterCategoryIcon,
isRevealed: Bool,
@ -41,6 +43,7 @@ final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem {
remove: @escaping () -> Void
) {
self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title
self.icon = icon
self.isRevealed = isRevealed
@ -185,11 +188,14 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: titleColor)
let leftInset: CGFloat
let verticalInset: CGFloat
var verticalInset: CGFloat = 14.0
let verticalOffset: CGFloat
let avatarSize: CGFloat
verticalInset = 14.0
if case .glass = item.systemStyle {
verticalInset += 4.0
}
verticalOffset = 0.0
avatarSize = 40.0
leftInset = 65.0 + params.leftInset
@ -208,6 +214,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight))
let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
@ -340,12 +347,12 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
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.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.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))

View file

@ -389,6 +389,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
return ItemListFilterTitleInputItem(
context: arguments.context,
presentationData: presentationData,
systemStyle: .glass,
text: value,
enableAnimations: enableAnimations,
placeholder: placeholder,
@ -407,12 +408,13 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case .includePeerInfo(let text), .excludePeerInfo(let text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
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()
})
case let .includeCategory(_, category, title, isRevealed):
return ChatListFilterPresetCategoryItem(
presentationData: presentationData,
systemStyle: .glass,
title: title,
icon: ChatListFilterCategoryIcon(category: category),
isRevealed: isRevealed,
@ -429,12 +431,13 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
}
)
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()
})
case let .excludeCategory(_, category, title, isRevealed):
return ChatListFilterPresetCategoryItem(
presentationData: presentationData,
systemStyle: .glass,
title: title,
icon: ChatListFilterCategoryIcon(category: category),
isRevealed: isRevealed,
@ -451,7 +454,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
}
)
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)
})]), 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) })
@ -465,7 +468,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
arguments.peerContextAction(peer, sourceNode, gesture, nil)
})
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)
})]), 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) })
@ -479,11 +482,11 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
arguments.peerContextAction(peer, sourceNode, gesture, nil)
})
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)
})
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)
})
case let .tagColorHeader(name, color, isPremium):
@ -507,6 +510,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case let .tagColor(colors, color, isPremium):
return PeerNameColorItem(
theme: presentationData.theme,
systemStyle: .glass,
colors: colors,
mode: .folderTag,
displayEmptyColor: true,
@ -526,11 +530,11 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case .inviteLinkHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ChatListFilter_SectionShare, badge: nil, sectionId: self.section)
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()
})
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)
}, removeAction: { invite in
arguments.removeLink(invite)

View file

@ -176,11 +176,11 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
case let .suggestedListHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
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)
}, tag: nil)
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()
})
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 {
arguments.addNew()
} else {
@ -206,13 +206,13 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
arguments.removePreset(preset.id)
})
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()
})
case let .listFooter(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
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 {
arguments.updateDisplayTags(updatedValue)
} else {

View file

@ -19,6 +19,7 @@ struct ChatListFilterPresetListItemEditing: Equatable {
final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
let context: AccountContext
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let preset: ChatListFilter
let title: ChatFolderTitle
let label: String
@ -36,6 +37,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
init(
context: AccountContext,
presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
preset: ChatListFilter,
title: ChatFolderTitle,
label: String,
@ -52,6 +54,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
) {
self.context = context
self.presentationData = presentationData
self.systemStyle = systemStyle
self.preset = preset
self.title = title
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 (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 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 separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
@ -405,17 +417,17 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
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.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size)
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.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
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 {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String
let label: String
public let sectionId: ItemListSectionId
@ -17,6 +18,7 @@ public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem {
public init(
presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
title: String,
label: String,
sectionId: ItemListSectionId,
@ -25,6 +27,7 @@ public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem {
tag: ItemListItemTag? = nil
) {
self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title
self.label = label
self.sectionId = sectionId
@ -180,6 +183,8 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: 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 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 height: CGFloat
@ -301,15 +312,15 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi
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.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.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
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 {
let context: AccountContext
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let text: NSAttributedString
let enableAnimations: Bool
let placeholder: String
@ -29,6 +30,7 @@ public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem {
public init(
context: AccountContext,
presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
text: NSAttributedString,
enableAnimations: Bool,
placeholder: String,
@ -43,6 +45,7 @@ public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem {
) {
self.context = context
self.presentationData = presentationData
self.systemStyle = systemStyle
self.text = text
self.enableAnimations = enableAnimations
self.placeholder = placeholder
@ -144,8 +147,9 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
let _ = rightInset
let separatorHeight = UIScreenPixel
let contentSize = CGSize(width: params.width, height: 44.0)
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let contentSize = CGSize(width: params.width, height: item.systemStyle == .glass ? 52.0 : 44.0)
let insets = itemListNeighborsGroupedInsets(neighbors, params)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -200,12 +204,12 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
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.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.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.componentState._updated = { [weak self] transition, _ in
@ -249,7 +253,7 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
environment: {},
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 textFieldView.superview == nil {
self.view.addSubview(textFieldView)

View file

@ -221,7 +221,13 @@ public class InteractiveCheckNode: CheckNode {
}
}
private var lastPressTime: Double?
@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.valueChanged?(self.selected)
}

View file

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

View file

@ -4,6 +4,7 @@ import Display
import ComponentFlow
import ViewControllerComponent
import SwiftSignalKit
import DynamicCornerRadiusView
public final class SheetComponentEnvironment: Equatable {
public let isDisplaying: Bool
@ -59,8 +60,14 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
case blur(BlurStyle)
}
public enum Style: Equatable {
case glass
case legacy
}
public let content: AnyComponent<ChildEnvironmentType>
public let headerContent: AnyComponent<Empty>?
public let style: Style
public let backgroundColor: BackgroundColor
public let followContentSizeChanges: Bool
public let clipsContent: Bool
@ -75,6 +82,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
public init(
content: AnyComponent<ChildEnvironmentType>,
headerContent: AnyComponent<Empty>? = nil,
style: Style = .legacy,
backgroundColor: BackgroundColor,
followContentSizeChanges: Bool = false,
clipsContent: Bool = false,
@ -88,6 +96,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
) {
self.content = content
self.headerContent = headerContent
self.style = style
self.backgroundColor = backgroundColor
self.followContentSizeChanges = followContentSizeChanges
self.clipsContent = clipsContent
@ -107,6 +116,9 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
if lhs.headerContent != rhs.headerContent {
return false
}
if lhs.style != rhs.style {
return false
}
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
@ -162,7 +174,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
private let dimView: UIView
private let scrollView: ScrollView
private let backgroundView: UIView
private let backgroundView: DynamicCornerRadiusView
private var effectView: UIVisualEffectView?
private let contentView: ComponentView<ChildEnvironmentType>
private var headerView: ComponentView<Empty>?
@ -186,9 +198,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceVertical = true
self.backgroundView = UIView()
self.backgroundView.layer.cornerRadius = 12.0
self.backgroundView.layer.masksToBounds = true
self.backgroundView = DynamicCornerRadiusView()
self.contentView = ComponentView<ChildEnvironmentType>()
@ -354,6 +364,19 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
self.component = component
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 {
case let .blur(style):
self.backgroundView.isHidden = true
@ -365,7 +388,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
self.effectView = effectView
}
case let .color(color):
self.backgroundView.backgroundColor = color
self.backgroundView.updateColor(color: color, transition: .immediate)
self.backgroundView.isHidden = false
self.effectView?.removeFromSuperview()
self.effectView = nil
@ -383,7 +406,12 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
containerSize = regularMetricsSize
}
} 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
@ -403,7 +431,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
self.scrollView.addSubview(contentView)
}
contentView.clipsToBounds = component.clipsContent
contentView.layer.cornerRadius = self.backgroundView.layer.cornerRadius
contentView.layer.cornerRadius = topCornerRadius
if sheetEnvironment.isCentered {
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)
@ -411,12 +439,20 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
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)
}
self.backgroundView.update(size: contentSize, corners: .init(minXMinY: topCornerRadius, maxXMinY: topCornerRadius, minXMaxY: bottomCornerRadius, maxXMaxY: bottomCornerRadius), transition: transition)
} else {
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)
switch component.style {
case .glass:
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: 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)

View file

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

View file

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

View file

@ -27,6 +27,8 @@ import EmojiSuggestionsComponent
import TextFormat
import TextFieldComponent
import ListComposePollOptionComponent
import EdgeEffect
import GlassBarButtonComponent
public final class ComposedPoll {
public struct Text {
@ -110,6 +112,8 @@ final class ComposePollScreenComponent: Component {
final class View: UIView, UIScrollViewDelegate {
private let scrollView: UIScrollView
private let edgeEffectView: EdgeEffectView
private var reactionInput: ComponentView<Empty>?
private let pollTextSection = ComponentView<Empty>()
private let quizAnswerSection = ComponentView<Empty>()
@ -120,7 +124,10 @@ final class ComposePollScreenComponent: Component {
private var pollOptionsSectionContainer: ListSectionContentView
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>?
@ -178,6 +185,8 @@ final class ComposePollScreenComponent: Component {
self.scrollView.contentInsetAdjustmentBehavior = .never
self.scrollView.alwaysBounceVertical = true
self.edgeEffectView = EdgeEffectView()
self.pollOptionsSectionContainer = ListSectionContentView(frame: CGRect())
super.init(frame: frame)
@ -185,6 +194,8 @@ final class ComposePollScreenComponent: Component {
self.scrollView.delegate = self
self.addSubview(self.scrollView)
self.addSubview(self.edgeEffectView)
let reorderRecognizer = ReorderGestureRecognizer(
shouldBegin: { [weak self] point in
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(
externalState: self.pollTextInputState,
context: component.context,
style: .glass,
theme: theme,
strings: environment.strings,
resetText: self.resetPollText.flatMap { resetText in
@ -894,6 +906,7 @@ final class ComposePollScreenComponent: Component {
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: theme,
style: .glass,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.CreatePoll_TextHeader,
@ -951,6 +964,7 @@ final class ComposePollScreenComponent: Component {
pollOptionsSectionItems.append(AnyComponentWithIdentity(id: pollOption.id, component: AnyComponent(ListComposePollOptionComponent(
externalState: pollOption.textInputState,
context: component.context,
style: .glass,
theme: theme,
strings: environment.strings,
resetText: pollOption.resetText.flatMap { resetText in
@ -1092,6 +1106,7 @@ final class ComposePollScreenComponent: Component {
let pollOptionsSectionUpdateResult = self.pollOptionsSectionContainer.update(
configuration: ListSectionContentView.Configuration(
theme: theme,
style: .glass,
displaySeparators: true,
extendsItemHighlightToSection: false,
background: .all
@ -1231,6 +1246,7 @@ final class ComposePollScreenComponent: Component {
if canBePublic {
pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "anonymous", component: AnyComponent(ListActionItemComponent(
theme: theme,
style: .glass,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
@ -1253,6 +1269,7 @@ final class ComposePollScreenComponent: Component {
}
pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "multiAnswer", component: AnyComponent(ListActionItemComponent(
theme: theme,
style: .glass,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
@ -1277,6 +1294,7 @@ final class ComposePollScreenComponent: Component {
))))
pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "quiz", component: AnyComponent(ListActionItemComponent(
theme: theme,
style: .glass,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
@ -1304,6 +1322,7 @@ final class ComposePollScreenComponent: Component {
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: theme,
style: .glass,
header: nil,
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
@ -1334,6 +1353,7 @@ final class ComposePollScreenComponent: Component {
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: theme,
style: .glass,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.CreatePoll_ExplanationHeader,
@ -1354,6 +1374,7 @@ final class ComposePollScreenComponent: Component {
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent(
externalState: self.quizAnswerTextInputState,
context: component.context,
style: .glass,
theme: theme,
strings: environment.strings,
resetText: self.resetQuizAnswerText.flatMap { resetText in
@ -1620,6 +1641,104 @@ final class ComposePollScreenComponent: Component {
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 targetView = self.collectTextInputStates().first(where: { $0.view.currentTag === recenterOnTag })?.view {
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 {
DispatchQueue.main.async { [weak self] in
guard let self else {
@ -1708,7 +1815,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
}
private let context: AccountContext
private let completion: (ComposedPoll) -> Void
fileprivate let completion: (ComposedPoll) -> Void
private var isDismissed: Bool = false
fileprivate private(set) var sendButtonItem: UIBarButtonItem?
@ -1761,17 +1868,25 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
isQuiz: isQuiz,
initialData: initialData,
completion: completion
), navigationBarAppearance: .default, theme: .default)
), navigationBarAppearance: .transparent, theme: .default)
self._hasGlassStyle = true
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.title = isQuiz == true ? presentationData.strings.CreatePoll_QuizTitle : presentationData.strings.CreatePoll_Title
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false)
if self._hasGlassStyle {
self.navigationItem.setLeftBarButton(UIBarButtonItem(customView: UIView()), 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))
self.sendButtonItem = sendButtonItem
self.navigationItem.setRightBarButton(sendButtonItem, animated: false)
if self._hasGlassStyle {
} else {
self.navigationItem.setRightBarButton(sendButtonItem, animated: false)
}
sendButtonItem.isEnabled = false
self.scrollToTop = { [weak self] in

View file

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

View file

@ -50,6 +50,7 @@ private enum ContactListNodeEntryId: Hashable {
case sort
case permission(index: Int)
case option(index: Int)
case header(index: Int64)
case peerId(peerId: Int64, section: ContactListNodeEntrySection)
case deviceContact(DeviceContactStableId)
}
@ -101,7 +102,8 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
case permissionEnable(PresentationTheme, String)
case permissionLimited(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 {
switch self {
@ -117,6 +119,8 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
return .permission(index: 2)
case let .option(index, _, _, _, _):
return .option(index: index)
case let .header(index, _):
return .header(index: index)
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, storyData, _, _):
switch 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 {
case let .search(theme, strings):
return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: {
@ -164,6 +168,16 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
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)
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):
var status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer
@ -236,8 +250,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
if let customSubtitle {
status = .custom(string: NSAttributedString(string: customSubtitle), multiline: false, isActive: false, icon: nil)
}
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
var sectionId: Int32 = 0
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)
}, disabledAction: { _ in
if case let .peer(peer, _, _) = peer {
@ -289,6 +306,12 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
} else {
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):
switch rhs {
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, _, _, _, _):
switch rhs {
case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited:
return false
case let .option(rhsIndex, _, _, _, _):
return lhsIndex < rhsIndex
case .peer:
return true
return false
case let .option(rhsIndex, _, _, _, _):
return lhsIndex < rhsIndex
case .header, .peer:
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 {
case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option:
return false
case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, rhsStoryData, _, _):
if (lhsStoryData == nil) != (rhsStoryData == nil) {
if lhsStoryData != nil {
return true
} else {
return false
}
}
case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _), let .header(rhsIndex, _):
// if (lhsStoryData == nil) != (rhsStoryData == nil) {
// if lhsStoryData != nil {
// return true
// } else {
// return false
// }
// }
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 commonHeader: ListViewItemHeader?
@ -579,7 +633,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
interaction.deselectAll()
})
var index: Int = 0
var index: Int64 = 0
for peer in topPeers.prefix(15) {
if peer.isDeleted {
continue
@ -601,7 +655,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
}
case let .custom(showSelf, selfSubtitle, sections):
if !topPeers.isEmpty {
var index: Int = 0
var index: Int64 = 0
var sectionId: Int = 2
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 {
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 {
let peer = orderedPeers[i]
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))
index += 1
}
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 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 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 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, listStyle: listStyle), directionHint: nil) }
var shouldFixScroll = false
var indexSections: [String] = []
@ -974,6 +1035,7 @@ public struct ContactListNodeGroupSelectionState: Equatable {
public final class ContactListNode: ASDisplayNode {
private let context: AccountContext
private let listStyle: ItemListStyle
private var presentation: ContactListPresentation?
private let filters: [ContactListFilter]
private let onlyWriteable: Bool
@ -1161,8 +1223,25 @@ public final class ContactListNode: ASDisplayNode {
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.listStyle = listStyle
self.filters = filters
self.displayPermissionPlaceholder = displayPermissionPlaceholder
self.contextAction = contextAction
@ -1217,8 +1296,13 @@ public final class ContactListNode: ASDisplayNode {
super.init()
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
self.backgroundColor = listStyle == .blocks ? self.presentationData.theme.list.blocksBackgroundColor : self.presentationData.theme.chatList.backgroundColor
if listStyle == .blocks {
self.listNode.verticalScrollIndicatorColor = .clear
} else {
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
}
self.selectionStateValue = 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())
|> 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 remotePeers = foundPeers.foundRemoteContacts
@ -1535,7 +1626,7 @@ public final class ContactListNode: ASDisplayNode {
var existingPeerIds = Set<EnginePeer.Id>()
var disabledPeerIds = Set<EnginePeer.Id>()
var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>()
var excludeSelf = false
var requirePhoneNumbers = false
@ -1580,7 +1671,9 @@ public final class ContactListNode: ASDisplayNode {
}
existingPeerIds.insert(peer.peer.id)
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)))
}
}
@ -1615,7 +1708,9 @@ public final class ContactListNode: ASDisplayNode {
}
existingPeerIds.insert(peer.peer.id)
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)))
}
}
@ -1651,7 +1746,9 @@ public final class ContactListNode: ASDisplayNode {
}
existingPeerIds.insert(peer.peer.id)
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)))
}
}
@ -1672,9 +1769,33 @@ public final class ContactListNode: ASDisplayNode {
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)
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) {
@ -1808,115 +1929,151 @@ public final class ContactListNode: ASDisplayNode {
topPeers = .single([])
}
return (combineLatest(
self.contactPeersViewPromise.get(),
chatListSignal,
selectionStateSignal,
pendingRemovalPeerIdsSignal,
presentationDataPromise.get(),
contactsAuthorization.get(),
contactsWarningSuppressed.get(),
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
if !view.2.isEmpty {
context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys))
}
var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) })
for (peer, memberCount) in chatListPeers {
peers.append(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: memberCount))
}
var existingPeerIds = Set<EnginePeer.Id>()
var disabledPeerIds = Set<EnginePeer.Id>()
var requirePhoneNumbers = 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
return (
combineLatest(
self.contactPeersViewPromise.get(),
chatListSignal,
selectionStateSignal,
pendingRemovalPeerIdsSignal,
presentationDataPromise.get(),
contactsAuthorization.get(),
contactsWarningSuppressed.get(),
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
if !view.2.isEmpty {
context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys))
}
}
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
var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) })
for (peer, memberCount) in chatListPeers {
peers.append(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: memberCount))
}
var existingPeerIds = Set<EnginePeer.Id>()
var disabledPeerIds = Set<EnginePeer.Id>()
var requirePhoneNumbers = 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 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(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 {
var hasPermissionInfo = false
var optionsCount = 0
for entry in entries {
if case .permissionInfo = entry {
hadPermissionInfo = true
hasPermissionInfo = true
}
if case .option = entry {
previousOptionsCount += 1
optionsCount += 1
}
}
}
var hasPermissionInfo = false
var optionsCount = 0
for entry in entries {
if case .permissionInfo = entry {
hasPermissionInfo = true
}
if case .option = entry {
optionsCount += 1
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, 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) {
return signal |> runOn(Queue.mainQueue())
@ -2082,7 +2239,10 @@ public final class ContactListNode: ASDisplayNode {
insets.left = layout.safeInsets.left
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)
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
}
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.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 {
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 {
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
}
if let peer = peer {
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) {
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
navigationController.pushViewController(infoController)
}
}
}
} 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))
let controller = strongSelf.context.sharedContext.makeNewContactScreen(
context: strongSelf.context,
firstName: nil,
lastName: nil,
phoneNumber: nil
)
navigationController.pushViewController(controller)
// 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
// }
// if let peer = peer {
// 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) {
// if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
// navigationController.pushViewController(infoController)
// }
// }
// }
// } 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:
DeviceAccess.authorizeAccess(to: .contacts)

View file

@ -16,6 +16,8 @@ import PhoneNumberFormat
import ItemListUI
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ComponentFlow
import SearchInputPanelComponent
private enum ContactListSearchGroup {
case contacts
@ -229,6 +231,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
}
private let context: AccountContext
private let glass: Bool
private let isPeerEnabled: (ContactListPeer) -> Bool
private let addContact: ((String) -> Void)?
private let openPeer: (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void
@ -252,12 +255,28 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
private var containerViewLayout: (ContainerViewLayout, CGFloat)?
private var enqueuedTransitions: [ContactListSearchContainerTransition] = []
private let searchInput = ComponentView<Empty>()
public override var hasDim: Bool {
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.glass = glass
self.isPeerEnabled = isPeerEnabled
self.addContact = addContact
self.openPeer = openPeer
@ -270,10 +289,11 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings))
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.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.listNode.isHidden = true
self.listNode.alpha = 0.0
self.listNode.accessibilityPageScrolledString = { row, count in
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
}
@ -307,9 +327,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
self.addSubnode(self.emptyResultsAnimationNode)
self.addSubnode(self.emptyResultsTitleNode)
self.addSubnode(self.emptyResultsTextNode)
self.listNode.isHidden = true
let themeAndStringsPromise = self.themeAndStringsPromise
let previousFoundRemoteContacts = Atomic<([FoundPeer], [FoundPeer])?>(value: nil)
@ -615,7 +633,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
}
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 })
}
}
@ -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) {
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))
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 {
while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
@ -713,7 +779,9 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
if let (layout, navigationBarHeight) = strongSelf.containerViewLayout {
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.emptyResultsAnimationNode.isHidden = !emptyResults

View file

@ -176,6 +176,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
let presentationData: ItemListPresentationData
let style: ItemListStyle
let systemStyle: ItemListSystemStyle
public let sectionId: ItemListSectionId
let sortOrder: PresentationPersonNameOrder
let displayOrder: PresentationPersonNameOrder
@ -221,6 +222,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
public init(
presentationData: ItemListPresentationData,
style: ItemListStyle = .plain,
systemStyle: ItemListSystemStyle = .legacy,
sectionId: ItemListSectionId = 0,
sortOrder: PresentationPersonNameOrder,
displayOrder: PresentationPersonNameOrder,
@ -260,6 +262,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
) {
self.presentationData = presentationData
self.style = style
self.systemStyle = systemStyle
self.sectionId = sectionId
self.sortOrder = sortOrder
self.displayOrder = displayOrder
@ -1847,16 +1850,17 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
}
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
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.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.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - leftInset), height: separatorHeight))
if !item.alwaysShowLastSeparator {
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 && item.style != .blocks {
strongSelf.separatorNode.isHidden = last
}

View file

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

View file

@ -8,6 +8,9 @@ import TelegramStringFormatting
import SearchBarNode
import AppBundle
import TelegramCore
import ComponentFlow
import BundleIconComponent
import GlassBarButtonComponent
private func loadCountryCodes() -> [Country] {
guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else {
@ -289,10 +292,31 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
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 strings: PresentationStrings
private let displayCodes: Bool
private let glass: Bool
private var closeButtonNode: BarComponentHostNode?
private var searchButtonNode: BarComponentHostNode?
private var navigationContentNode: AuthorizationSequenceCountrySelectionNavigationContentNode?
private var controllerNode: AuthorizationSequenceCountrySelectionControllerNode {
@ -302,29 +326,39 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
public var completeWithCountryCode: ((Int, String) -> 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.strings = strings
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.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in
self?.dismissed?()
self?.dismiss()
})
self.navigationContentNode = navigationContentNode
navigationContentNode.setQueryUpdated { [weak self] query in
guard let strongSelf = self, strongSelf.isNodeLoaded else {
return
//TODO:localize
self.title = "Select Country"
if glass {
} else {
let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in
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) {
@ -332,11 +366,15 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
}
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
self?.completeWithCountryCode?(code, countryId)
self?.dismiss()
})
self.controllerNode.deactivateSearch = { [weak self] in
self?.controllerNode.isSearching = false
self?.requestLayout(transition: .animated(duration: 0.5, curve: .spring))
}
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) {
super.containerLayoutUpdated(layout, transition: transition)
self.updateNavigationButtons()
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
}

View file

@ -6,6 +6,9 @@ import TelegramCore
import TelegramPresentationData
import TelegramStringFormatting
import AppBundle
import ComponentFlow
import EdgeEffect
import SearchInputPanelComponent
private func loadCountryCodes() -> [(String, Int)] {
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 {
let itemSelected: (((String, String), String, Int)) -> Void
var deactivateSearch: () -> Void = {}
private let theme: PresentationTheme
private let strings: PresentationStrings
private let displayCodes: Bool
private let glass: Bool
private let needsSubtitle: Bool
private let tableView: UITableView
@ -201,19 +206,28 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode,
private var searchResults: [((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.strings = strings
self.displayCodes = displayCodes
self.glass = glass
self.itemSelected = itemSelected
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, *) {
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
if #available(iOS 11.0, *) {
@ -239,28 +253,32 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode,
self.sections = sections
self.sectionTitles = sections.map { $0.0 }
self.topEdgeEffectView = EdgeEffectView()
super.init()
self.setViewBlock({
return UITracingLayerView()
})
self.backgroundColor = theme.list.plainBackgroundColor
self.tableView.backgroundColor = theme.list.plainBackgroundColor
self.tableView.backgroundColor = self.theme.list.plainBackgroundColor
self.tableView.separatorColor = self.theme.list.itemPlainSeparatorColor
self.tableView.backgroundView = UIView()
self.tableView.sectionIndexColor = self.theme.list.itemAccentColor
self.searchTableView.backgroundColor = self.theme.list.plainBackgroundColor
self.searchTableView.backgroundColor = self.theme.list.plainBackgroundColor
self.searchTableView.separatorColor = self.theme.list.itemPlainSeparatorColor
self.searchTableView.backgroundView = UIView()
self.searchTableView.sectionIndexColor = self.theme.list.itemAccentColor
if glass {
self.backgroundColor = theme.list.blocksBackgroundColor
self.tableView.backgroundColor = theme.list.blocksBackgroundColor
self.searchTableView.backgroundColor = theme.list.blocksBackgroundColor
} else {
self.backgroundColor = theme.list.plainBackgroundColor
self.tableView.backgroundColor = self.theme.list.plainBackgroundColor
self.tableView.separatorColor = self.theme.list.itemPlainSeparatorColor
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.backgroundView = UIView()
self.searchTableView.sectionIndexColor = self.theme.list.itemAccentColor
}
self.tableView.delegate = self
self.tableView.dataSource = self
@ -269,13 +287,76 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode,
self.view.addSubview(self.tableView)
self.view.addSubview(self.searchTableView)
if glass {
self.view.addSubview(self.topEdgeEffectView)
}
}
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.searchTableView.contentInset = UIEdgeInsets(top: 0.0, 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)))
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)))
self.validLayout = layout
self.tableView.contentInset = UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0)
self.searchTableView.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: 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() {

View file

@ -63,17 +63,17 @@ public final class DatePickerTheme: Equatable {
public extension DatePickerTheme {
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 upperLimitDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
private let controlFont = Font.regular(17.0)
private let dayFont = Font.regular(13.0)
private let dateFont = Font.with(size: 17.0, design: .regular, traits: .monospacedNumbers)
private let selectedDateFont = Font.with(size: 17.0, design: .regular, weight: .bold, traits: .monospacedNumbers)
private let controlFont = Font.semibold(17.0)
private let dayFont = Font.medium(13.0)
private let dateFont = Font.with(size: 19.0, design: .regular, traits: .monospacedNumbers)
private let selectedDateFont = Font.with(size: 19.0, design: .regular, weight: .bold, traits: .monospacedNumbers)
private var calendar: Calendar = {
var calendar = Calendar(identifier: .gregorian)
@ -295,6 +295,7 @@ public final class DatePickerNode: ASDisplayNode {
private let strings: PresentationStrings
private let dateTimeFormat: PresentationDateTimeFormat
private let title: String
private let hasValueRow: Bool
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.strings = strings
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.title = title
@ -483,10 +485,11 @@ public final class DatePickerNode: ASDisplayNode {
self.addSubnode(self.timePickerBackgroundNode)
self.timePickerBackgroundNode.addSubnode(self.timePickerNode)
self.addSubnode(self.timeTitleNode)
self.addSubnode(self.dateButtonNode)
self.addSubnode(self.timeButtonNode)
if hasValueRow {
self.addSubnode(self.timeTitleNode)
self.addSubnode(self.dateButtonNode)
self.addSubnode(self.timeButtonNode)
}
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal)
@ -497,21 +500,6 @@ public final class DatePickerNode: ASDisplayNode {
self.setupItems()
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.nextButtonNode.addTarget(self, action: #selector(self.nextButtonPressed), forControlEvents: .touchUpInside)
@ -697,7 +685,7 @@ public final class DatePickerNode: ASDisplayNode {
}
self.transitionFraction = transitionFraction
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)
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 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 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)
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
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)
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.isUserInteractionEnabled = self.state.displayingMonthSelection
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
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 = {}

View file

@ -76,11 +76,11 @@ private enum DebugAccountsControllerEntry: ItemListNodeEntry {
let arguments = arguments as! DebugAccountsControllerArguments
switch self {
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)
})
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()
})
}

View file

@ -287,7 +287,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
let arguments = arguments as! DebugControllerArguments
switch self {
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 {
return
}
@ -302,7 +302,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}
})
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()
|> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -386,7 +386,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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()
|> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -468,7 +468,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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")
|> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -552,7 +552,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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")
|> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -636,7 +636,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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 _ = (Logger(rootPath: logsPath, basePath: logsPath).collectLogs()
|> deliverOnMainQueue).start(next: { logs in
@ -721,7 +721,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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()
|> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
@ -774,7 +774,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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] = [
"app-logs",
"broadcast-logs",
@ -870,7 +870,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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 {
return
}
@ -925,32 +925,32 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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 {
return
}
arguments.pushController(debugAccountsController(context: context, accountManager: arguments.sharedContext.accountManager))
})
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, {
$0.withUpdatedLogToFile(value)
}).start()
})
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, {
$0.withUpdatedLogToConsole(value)
}).start()
})
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, {
$0.withUpdatedRedactSensitiveData(value)
}).start()
})
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
var settings = settings
settings.keepChatNavigationStack = value
@ -958,7 +958,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
var settings = settings
settings.skipReadHistory = value
@ -966,7 +966,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
var settings = settings
settings.alwaysDisplayTyping = value
@ -974,7 +974,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
var settings = settings
settings.debugRatingLayout = value
@ -982,7 +982,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
var settings = settings
settings.crashOnLongQueries = value
@ -990,7 +990,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
var settings = settings
settings.crashOnMemoryPressure = value
@ -998,7 +998,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.clearNotices()
}).start()
@ -1012,7 +1012,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}
})
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
var settings = settings
settings.logLanguageRecognition = value
@ -1020,7 +1020,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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 {
let _ = context.engine.itemCache.clear(collectionIds: [
ApplicationSpecificItemCacheCollectionId.translationState
@ -1028,7 +1028,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}
})
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()
if let context = arguments.context {
@ -1037,11 +1037,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}
})
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()
})
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 {
return
}
@ -1054,7 +1054,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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 {
return
}
@ -1076,7 +1076,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
arguments.presentController(actionSheet, nil)
})
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 {
return
}
@ -1098,7 +1098,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
arguments.presentController(actionSheet, nil)
})
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 {
return
}
@ -1111,7 +1111,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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 {
return
}
@ -1124,7 +1124,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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 {
return
}
@ -1137,7 +1137,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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 {
return
}
@ -1145,7 +1145,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
context.account.postbox.mediaBox.storageBox.reset()
})
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 {
return
}
@ -1185,13 +1185,13 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}))
})
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
return settings.withUpdatedBiometricsDomainState(nil).withUpdatedShareBiometricsDomainState(nil)
}).start()
})
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
var settings = settings
settings.allowWebViewInspection = value
@ -1199,11 +1199,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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:{ })
})
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 {
return
}
@ -1211,15 +1211,15 @@ private enum DebugControllerEntry: ItemListNodeEntry {
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
arguments.presentController(controller, nil)
let _ = (context.account.postbox.optimizeStorage()
|> deliverOnMainQueue).start(completed: {
controller.dismiss()
let controller = OverlayStatusController(theme: presentationData.theme, type: .success)
arguments.presentController(controller, nil)
})
|> deliverOnMainQueue).start(completed: {
controller.dismiss()
let controller = OverlayStatusController(theme: presentationData.theme, type: .success)
arguments.presentController(controller, nil)
})
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1229,7 +1229,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1239,7 +1239,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1249,7 +1249,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1259,7 +1259,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1269,7 +1269,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1279,7 +1279,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1289,7 +1289,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1303,7 +1303,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1313,7 +1313,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1323,7 +1323,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1333,7 +1333,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1343,7 +1343,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1353,7 +1353,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1363,7 +1363,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1373,7 +1373,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1383,7 +1383,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1393,7 +1393,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1403,7 +1403,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1413,7 +1413,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1423,7 +1423,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
@ -1433,7 +1433,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
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 {
let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in
var settings = settings
@ -1443,7 +1443,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}
})
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 {
let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in
var settings = settings
@ -1453,7 +1453,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}
})
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
let text: String
switch state {
@ -1469,7 +1469,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
})
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
var settings = settings
settings.disableReloginTokens = value

View file

@ -250,7 +250,7 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega
bottomController.viewWillAppear(true)
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 top = strongSelf.state.top {
strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition)
@ -490,7 +490,7 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega
}
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 {
return
}

View file

@ -388,7 +388,11 @@ final class NavigationModalContainer: ASDisplayNode, ASScrollViewDelegate, ASGes
if isStandaloneModal || isLandscape || (self.isFlat && !flatReceivesModalTransition) {
self.container.cornerRadius = 0.0
} 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, *) {

View file

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

View file

@ -1503,7 +1503,12 @@ open class NavigationBar: ASDisplayNode {
transition = .immediate
}
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 customTransitionNode: CustomNavigationTransitionNode?
private var topNodeInitialParameters: (clipsToBounds: Bool, cornerRadius: CGFloat)?
private let inlineNavigationBarTransition: Bool
private(set) var animatingCompletion = false
@ -54,7 +56,7 @@ final class NavigationTransitionCoordinator {
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.isInteractive = isInteractive
self.isFlat = isFlat
@ -109,6 +111,15 @@ final class NavigationTransitionCoordinator {
if !self.isFlat {
self.container.insertSubnode(self.dimNode, belowSubnode: topNode)
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 {
self.container.addSubnode(customTransitionNode)
@ -159,7 +170,7 @@ final class NavigationTransitionCoordinator {
}
})
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.updateAlpha(node: self.dimNode, alpha: (1.0 - position) * 0.15)
transition.updateAlpha(node: self.shadowNode, alpha: (1.0 - position) * 0.9)
@ -241,6 +252,8 @@ final class NavigationTransitionCoordinator {
strongSelf.endNavigationBarTransition()
strongSelf.restoreTopNodeCorners()
if let currentCompletion = strongSelf.currentCompletion {
strongSelf.currentCompletion = nil
currentCompletion()
@ -261,6 +274,8 @@ final class NavigationTransitionCoordinator {
self.endNavigationBarTransition()
self.restoreTopNodeCorners()
if let currentCompletion = self.currentCompletion {
self.currentCompletion = nil
currentCompletion()
@ -278,6 +293,8 @@ final class NavigationTransitionCoordinator {
strongSelf.endNavigationBarTransition()
strongSelf.restoreTopNodeCorners()
if let currentCompletion = strongSelf.currentCompletion {
strongSelf.currentCompletion = nil
currentCompletion()
@ -300,6 +317,8 @@ final class NavigationTransitionCoordinator {
self.endNavigationBarTransition()
self.restoreTopNodeCorners()
if let currentCompletion = self.currentCompletion {
self.currentCompletion = nil
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)
}
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 {
let alpha = min(1.0, max(0.0, alpha))
let oneMinusAlpha = 1.0 - alpha

View file

@ -163,6 +163,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
open var navigationPresentation: ViewControllerNavigationPresentation = .default
open var _presentedInModal: Bool = false
open var _hasGlassStyle: Bool = false
open var flatReceivesModalTransition: Bool = false
public var presentedOverCoveringView: Bool = false
@ -241,7 +242,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
var defaultNavigationBarHeight: CGFloat
if self._presentedInModal && layout.orientation == .portrait {
defaultNavigationBarHeight = 56.0
defaultNavigationBarHeight = self._hasGlassStyle ? 66.0 : 56.0
} else {
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 }
if let current = current as? SearchNavigationContentNode {
current.updateTheme(presentationData.theme)

View file

@ -33,6 +33,7 @@ private enum ItemBackgroundColor: Equatable {
public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let invite: ExportedChatFolderLink?
let share: Bool
public let sectionId: ItemListSectionId
@ -44,6 +45,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
public init(
presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle,
invite: ExportedChatFolderLink?,
share: Bool,
sectionId: ItemListSectionId,
@ -54,6 +56,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
tag: ItemListItemTag? = nil
) {
self.presentationData = presentationData
self.systemStyle = systemStyle
self.invite = invite
self.share = share
self.sectionId = sectionId
@ -319,7 +322,10 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode
let leftInset: CGFloat = 65.0 + params.leftInset
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 (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 separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -453,12 +460,12 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode
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.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.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)

View file

@ -145,6 +145,7 @@ public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem {
}
let itemContext: ItemContext
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let dateTimeFormat: PresentationDateTimeFormat
let mode: ItemListAvatarAndNameInfoItemMode
let peer: EnginePeer?
@ -166,9 +167,10 @@ public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem {
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.presentationData = presentationData
self.systemStyle = systemStyle
self.dateTimeFormat = dateTimeFormat
self.mode = mode
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 separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.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))
insets = itemListNeighborsPlainInsets(neighbors)
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
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
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.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.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.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()
@ -820,7 +829,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
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.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.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))
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 {
let presentationData: ItemListPresentationData
let style: ItemListStyle
let systemStyle: ItemListSystemStyle
let icon: UIImage?
let iconSignal: Signal<UIImage?, NoError>?
let title: String
@ -35,9 +36,10 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
public let sectionId: ItemListSectionId
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.style = style
self.systemStyle = systemStyle
self.icon = icon
self.iconSignal = iconSignal
self.title = title
@ -186,22 +188,33 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
let leftInset: CGFloat
let iconOffset: CGFloat
let verticalInset: CGFloat
let verticalOffset: CGFloat
switch item.height {
case .generic:
iconOffset = 1.0
verticalInset = 12.0
verticalOffset = 0.0
switch item.systemStyle {
case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 12.0
}
leftInset = (item.icon == nil && item.iconSignal == nil ? 16.0 : 59.0) + params.leftInset
case .peerList:
iconOffset = 3.0
verticalInset = 14.0
verticalOffset = 0.0
switch item.systemStyle {
case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 14.0
}
leftInset = 65.0 + params.leftInset
case .compactPeerList:
iconOffset = 3.0
verticalInset = 11.0
verticalOffset = 0.0
switch item.systemStyle {
case .glass:
verticalInset = 15.0
case .legacy:
verticalInset = 11.0
}
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 separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
var insets = itemListNeighborsGroupedInsets(neighbors, params)
if item.noInsets {
@ -264,7 +278,7 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
strongSelf.iconNode.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 {
let imageSize = CGSize(width: 28.0, height: 28.0)
strongSelf.iconDisposable.set((iconSignal
@ -330,15 +344,15 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
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.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))
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)
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 systemStyle: ItemListSystemStyle
let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder
let context: Context
@ -482,6 +483,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
public init(
presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle = .legacy,
dateTimeFormat: PresentationDateTimeFormat,
nameDisplayOrder: PresentationPersonNameOrder,
context: AccountContext,
@ -523,6 +525,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
openStories: ((UIView) -> Void)? = nil
) {
self.presentationData = presentationData
self.systemStyle = systemStyle
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.context = .account(context)
@ -566,6 +569,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
public init(
presentationData: ItemListPresentationData,
systemStyle: ItemListSystemStyle = .legacy,
dateTimeFormat: PresentationDateTimeFormat,
nameDisplayOrder: PresentationPersonNameOrder,
context: Context,
@ -607,6 +611,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
openStories: ((UIView) -> Void)? = nil
) {
self.presentationData = presentationData
self.systemStyle = systemStyle
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.context = context
@ -1153,7 +1158,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
switch item.height {
case .generic:
if case .none = item.text {
verticalInset = 11.0
if case .glass = item.systemStyle {
verticalInset = 15.0
} else {
verticalInset = 11.0
}
} else {
verticalInset = 6.0
}
@ -1163,9 +1172,17 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
avatarFontSize = floor(31.0 * 16.0 / 37.0)
case .peerList:
if case .none = item.text {
verticalInset = 14.0
if case .glass = item.systemStyle {
verticalInset = 15.0
} else {
verticalInset = 14.0
}
} else {
verticalInset = 8.0
if case .glass = item.systemStyle {
verticalInset = 10.0
} else {
verticalInset = 8.0
}
}
verticalOffset = 0.0
avatarSize = 40.0
@ -1266,6 +1283,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight))
let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
@ -1438,12 +1456,12 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
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.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.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)

View file

@ -40,6 +40,7 @@ public enum ItemListStickerPackItemControl: Equatable {
public final class ItemListStickerPackItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let context: AccountContext
let systemStyle: ItemListSystemStyle
let packInfo: StickerPackCollectionInfo.Accessor
let itemCount: String
let topItem: StickerPackItem?
@ -56,9 +57,10 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem {
let removePack: () -> 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.context = context
self.systemStyle = systemStyle
self.packInfo = packInfo
self.itemCount = itemCount
self.topItem = topItem
@ -409,10 +411,19 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
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 separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let insets: UIEdgeInsets
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
@ -775,13 +786,13 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
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.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size)
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.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 {
strongSelf.unreadNode.image = unreadImage

View file

@ -121,6 +121,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
private var tabsNavigationContentNode: ItemListControllerTabsContentNode?
private var presentationData: ItemListPresentationData
private let hideNavigationBarBackground: Bool
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
|> map { controllerState, nodeStateAndArgument -> (ItemListControllerState, (ItemListNodeState, Any)) in
return (controllerState, (nodeStateAndArgument.0, nodeStateAndArgument.1))
}
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.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)
let themeUpdated = strongSelf.presentationData != controllerState.presentationData
if strongSelf.leftNavigationButtonTitleAndStyle?.0 != controllerState.leftNavigationButton?.content || strongSelf.leftNavigationButtonTitleAndStyle?.1 != controllerState.leftNavigationButton?.style || themeUpdated {
if let leftNavigationButton = controllerState.leftNavigationButton {
@ -510,7 +518,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
if 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.segmentedTitleView?.theme = controllerState.presentationData.theme

View file

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

View file

@ -12,7 +12,7 @@ public protocol ItemListControllerSearchNavigationContentNode {
public protocol ItemListControllerSearch {
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
}

View file

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

View file

@ -19,6 +19,7 @@ public enum ItemListActionAlignment {
public class ItemListActionItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String
let kind: ItemListActionKind
let alignment: ItemListActionAlignment
@ -29,8 +30,9 @@ public class ItemListActionItem: ListViewItem, ItemListItem {
let clearHighlightAutomatically: Bool
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.systemStyle = systemStyle
self.title = title
self.kind = kind
self.alignment = alignment
@ -161,6 +163,15 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
let contentSize: CGSize
let insets: UIEdgeInsets
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 itemSeparatorColor: UIColor
@ -168,12 +179,12 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
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)
case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
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)
}
@ -259,19 +270,19 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
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.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.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 {
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:
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))

View file

@ -27,6 +27,7 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
}
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let icon: UIImage?
let iconSize: CGSize?
let iconPlacement: IconPlacement
@ -42,8 +43,9 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
let action: () -> 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.systemStyle = systemStyle
self.icon = icon
self.iconSize = iconSize
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 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)
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 {
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.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.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)
if let icon = item.icon {

View file

@ -49,6 +49,7 @@ public enum ItemListDisclosureItemDetailLabelColor {
public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let icon: UIImage?
let context: AccountContext?
let iconPeer: EnginePeer?
@ -73,8 +74,9 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemCompone
public let tag: ItemListItemTag?
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.systemStyle = systemStyle
self.icon = icon
self.context = context
self.iconPeer = iconPeer
@ -364,6 +366,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
let contentSize: CGSize
var insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
@ -472,16 +476,34 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
if item.iconPeer != nil {
verticalInset = 6.0
} 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
var height: CGFloat
switch item.labelStyle {
case .detailText:
height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height
case .multilineDetailText:
case .detailText, .multilineDetailText:
height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height
default:
height = verticalInset * 2.0 + titleLayout.size.height
@ -646,12 +668,12 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
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.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.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

View file

@ -20,6 +20,7 @@ public class InfoListItem: ListViewItem {
public let selectable: Bool = false
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String
let text: InfoListItemText
let style: ItemListStyle
@ -28,8 +29,9 @@ public class InfoListItem: ListViewItem {
let linkAction: ((InfoListItemLinkAction) -> 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.systemStyle = systemStyle
self.title = title
self.text = text
self.style = style
@ -76,9 +78,18 @@ public class InfoListItem: ListViewItem {
public class ItemListInfoItem: InfoListItem, ItemListItem {
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
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) {
@ -237,6 +248,7 @@ public class InfoItemNode: ListViewItemNode {
insets = UIEdgeInsets()
}
let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: 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 (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)
return (layout, { [weak self] in
@ -331,12 +348,12 @@ public class InfoItemNode: ListViewItemNode {
strongSelf.badgeNode.isHidden = item.isWarning
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.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.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 strongSelf.badgeNode.supernode == nil {
@ -349,15 +366,15 @@ public class InfoItemNode: ListViewItemNode {
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.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.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 {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let text: String
let placeholder: String
public let sectionId: ItemListSectionId
@ -52,8 +53,9 @@ public class ItemListMultilineInputItem: ListViewItem, ItemListItem {
let noInsets: Bool
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.systemStyle = systemStyle
self.text = text
self.placeholder = placeholder
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 separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let textTopInset: CGFloat = 11.0
let textBottomInset: CGFloat = 11.0
let textTopInset: CGFloat
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
if let minimalHeight = item.minimalHeight {
@ -332,11 +343,11 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
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 {
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) {

View file

@ -46,6 +46,7 @@ public enum ItemListSingleLineInputAlignment {
public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let context: AccountContext?
let systemStyle: ItemListSystemStyle
let presentationData: ItemListPresentationData
let title: NSAttributedString
let text: String
@ -69,9 +70,10 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let cleared: (() -> Void)?
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.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title
self.text = text
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 separatorHeight = UIScreenPixel
let contentSize = CGSize(width: params.width, height: max(titleLayout.size.height, measureTitleLayout.size.height) + 22.0)
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 contentSize = CGSize(width: params.width, height: max(titleLayout.size.height, measureTitleLayout.size.height) + verticalInset * 2.0)
let insets = itemListNeighborsGroupedInsets(neighbors, params)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -444,12 +455,12 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
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.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.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) {
strongSelf.textNode.textField.attributedPlaceholder = attributedPlaceholderText

View file

@ -20,6 +20,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
}
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let icon: UIImage?
let title: String
let text: String?
@ -40,8 +41,9 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
let action: (() -> Void)?
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.systemStyle = systemStyle
self.icon = icon
self.title = title
self.text = text
@ -243,6 +245,8 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
var contentSize: CGSize
var insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
@ -277,12 +281,18 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
insets = itemListNeighborsGroupedInsets(neighbors, params)
}
let topInset: CGFloat
var topInset: CGFloat
if item.text != nil {
topInset = 9.0
} else {
topInset = 11.0
}
if case .glass = item.systemStyle {
contentSize.height = 52.0
topInset += 4.0
}
var leftInset = 16.0 + params.leftInset
if let _ = item.icon {
@ -452,12 +462,12 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
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.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.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)
@ -531,7 +541,14 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
let switchFrame = strongSelf.switchNode.frame
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 {
strongSelf.lockedIconNode = nil

View file

@ -16,6 +16,7 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem {
}
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let engine: TelegramEngine
let venue: TelegramMediaMap?
let title: String?
@ -28,8 +29,9 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem {
public let sectionId: ItemListSectionId
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.systemStyle = systemStyle
self.engine = engine
self.venue = venue
self.title = title
@ -201,8 +203,8 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
var updatedTheme: PresentationTheme?
var updatedVenueType: String?
let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize)
let addressFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let addressFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
if currentItem?.presentationData.theme !== 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 separatorHeight = UIScreenPixel
let separatorRightInset = item.systemStyle == .glass ? 16.0 : 0.0
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -328,7 +331,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
} else {
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
case .blocks:
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.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.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))

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 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 rightInset: CGFloat = 8.0 + params.rightInset
@ -1020,7 +1022,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
switch iconImage {
case let .imageRepresentation(_, representation):
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)
iconImageApply = iconImageLayout(arguments)
case .albumArt:
@ -1067,7 +1069,14 @@ public final class ListMessageFileItemNode: ListMessageNode {
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
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))
if let backgroundNode = strongSelf.backgroundNode {
@ -1203,13 +1212,13 @@ public final class ListMessageFileItemNode: ListMessageNode {
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 {
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()
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()
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
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)
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))
@ -1347,7 +1356,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
if let media = selectedMedia as? TelegramMediaFile, media.isInstantVideo {
shapes.append(.circle(iconFrame))
} 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)

View file

@ -46,6 +46,7 @@ public final class ListMessageItemInteraction {
public final class ListMessageItem: ListViewItem {
let presentationData: ChatPresentationData
let systemStyle: ItemListSystemStyle
let context: AccountContext
let chatLocation: ChatLocation
let interaction: ListMessageItemInteraction
@ -65,8 +66,9 @@ public final class ListMessageItem: ListViewItem {
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.systemStyle = systemStyle
self.context = context
self.chatLocation = chatLocation
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)
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))
@ -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)
}
self.backgroundLayer.isHidden = !showBackground
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 venueColors: [String: UIColor] = [
"building/medical": UIColor(rgb: 0x43b3f4),
"building/gym": UIColor(rgb: 0x43b3f4),
"arts_entertainment": UIColor(rgb: 0xe56dd6),
"building/medical": UIColor(rgb: 0x43b3f4), // light blue?
"building/gym": UIColor(rgb: 0x43b3f4), // light blue?
"arts_entertainment": UIColor(rgb: 0xaf52de), // purple
"travel/bedandbreakfast": UIColor(rgb: 0x9987ff),
"travel/hotel": UIColor(rgb: 0x9987ff),
"travel/hostel": UIColor(rgb: 0x9987ff),
@ -90,11 +90,11 @@ private let venueColors: [String: UIColor] = [
"building": UIColor(rgb: 0x6e81b2),
"education": UIColor(rgb: 0xa57348),
"event": UIColor(rgb: 0x959595),
"food": UIColor(rgb: 0xf7943f),
"education/cafeteria": UIColor(rgb: 0xf7943f),
"nightlife": UIColor(rgb: 0xe56dd6),
"travel/hotel_bar": UIColor(rgb: 0xe56dd6),
"parks_outdoors": UIColor(rgb: 0x6cc039),
"food": UIColor(rgb: 0xff9500), // orange
"education/cafeteria": UIColor(rgb: 0xff9500), // orange
"nightlife": UIColor(rgb: 0xaf52de), // purple
"travel/hotel_bar": UIColor(rgb: 0xaf52de), // purple
"parks_outdoors": UIColor(rgb: 0x6cc138), // green
"shops": UIColor(rgb: 0xffb300),
"travel": UIColor(rgb: 0x1c9fff),
"work": UIColor(rgb: 0xad7854),

View file

@ -45,6 +45,14 @@ swift_library(
"//submodules/AttachmentUI:AttachmentUI",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//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:public",

View file

@ -82,13 +82,13 @@ private func generateLiveLocationIcon(theme: PresentationTheme, type: LiveLocati
switch type {
case .start:
imageName = "Location/SendLiveLocationIcon"
color = UIColor(rgb: 0x6cc139)
color = UIColor(rgb: 0x6cc138)
case .stop:
imageName = "Location/SendLocationIcon"
color = UIColor(rgb: 0xff6464)
case .extend:
imageName = "Location/SendLocationIcon"
color = UIColor(rgb: 0x6cc139)
color = UIColor(rgb: 0x6cc138)
}
context.clear(CGRect(origin: CGPoint(), size: size))
@ -283,8 +283,8 @@ final class LocationActionListItemNode: ListViewItemNode {
let verticalInset: CGFloat = 8.0
let iconSize: CGFloat = 40.0
let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize)
let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
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 (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 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))
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.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
if let (beginTimestamp, timeout) = item.beginTimeAndTimeout {

View file

@ -173,8 +173,8 @@ final class LocationLiveListItemNode: ListViewItemNode {
let rightInset: CGFloat = params.rightInset
let verticalInset: CGFloat = 8.0
let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize)
let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
var title: String = ""
if let author = item.message.author {
@ -302,6 +302,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
let separatorHeight = UIScreenPixel
let topHighlightInset: CGFloat = separatorHeight
let separatorRightInset: CGFloat = 16.0
let avatarSize: CGFloat = 40.0
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.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
var liveBroadcastingTimeout: Int32 = 0

View file

@ -5,9 +5,16 @@ import Display
import TelegramPresentationData
import AppBundle
import CoreLocation
import ComponentFlow
import GlassBackgroundComponent
import PlainButtonComponent
import BundleIconComponent
import MultilineTextComponent
import EdgeEffect
private let panelInset: CGFloat = 4.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? {
let cornerRadius: CGFloat = 9.0
@ -36,7 +43,9 @@ private func generateShadowImage(theme: PresentationTheme, highlighted: Bool) ->
public final class LocationMapHeaderNode: ASDisplayNode {
private var presentationData: PresentationData
private let glass: Bool
private let toggleMapModeSelection: () -> Void
private let updateMapMode: (LocationMapMode) -> Void
private let goToUserLocation: () -> Void
private let showPlacesInThisArea: () -> Void
private let setupProximityNotification: (Bool) -> Void
@ -47,52 +56,76 @@ public final class LocationMapHeaderNode: ASDisplayNode {
public let mapNode: LocationMapNode
public var trackingMode: LocationTrackingMode = .none
private let edgeEffectView: EdgeEffectView
private let options: ComponentView<Empty>?
private let optionsBackgroundView: GlassBackgroundView?
private let optionsBackgroundNode: ASImageNode
private let optionsSeparatorNode: ASDisplayNode
private let optionsSecondSeparatorNode: ASDisplayNode
private let infoButtonNode: HighlightableButtonNode
private let locationButtonNode: HighlightableButtonNode
private let notificationButtonNode: HighlightableButtonNode
private let placesBackgroundView: GlassBackgroundView?
private let placesBackgroundNode: ASImageNode
private let placesButtonNode: HighlightableButtonNode
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.glass = glass
self.toggleMapModeSelection = toggleMapModeSelection
self.updateMapMode = updateMapMode
self.goToUserLocation = goToUserLocation
self.setupProximityNotification = setupProximityNotification
self.showPlacesInThisArea = showPlacesInThisArea
self.mapNode = LocationMapNode()
if glass {
self.options = ComponentView()
} else {
self.options = nil
}
self.optionsBackgroundNode = ASImageNode()
self.optionsBackgroundNode.contentMode = .scaleToFill
self.optionsBackgroundNode.displaysAsynchronously = false
self.optionsBackgroundNode.displayWithoutProcessing = true
self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
self.optionsBackgroundNode.isUserInteractionEnabled = true
self.optionsSeparatorNode = ASDisplayNode()
self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.optionsSecondSeparatorNode = ASDisplayNode()
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.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: presentationData.theme.rootController.navigationBar.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: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted])
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: buttonColor), for: .selected)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: [.selected, .highlighted])
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.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: presentationData.theme.rootController.navigationBar.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: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted])
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: buttonColor), for: .selected)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: [.selected, .highlighted])
self.placesBackgroundNode = ASImageNode()
self.placesBackgroundNode.contentMode = .scaleToFill
@ -102,7 +135,7 @@ public final class LocationMapHeaderNode: ASDisplayNode {
self.placesBackgroundNode.isUserInteractionEnabled = true
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.contentMode = .scaleToFill
@ -110,20 +143,48 @@ public final class LocationMapHeaderNode: ASDisplayNode {
self.shadowNode.displayWithoutProcessing = true
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()
self.clipsToBounds = true
self.addSubnode(self.mapNode)
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.view.addSubview(self.edgeEffectView)
if glass {
if let placesBackgroundView = self.placesBackgroundView {
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.placesBackgroundNode.addSubnode(self.placesButtonNode)
self.addSubnode(self.shadowNode)
//self.addSubnode(self.shadowNode)
self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), 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) {
let mapModeUpdated = self.mapNode.mapMode != mapMode
let displayingMapModesUpdated = self.infoButtonNode.isSelected != displayingMapModeOptions
self.mapNode.mapMode = mapMode
self.trackingMode = trackingMode
self.infoButtonNode.isSelected = displayingMapModeOptions
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.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
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) {
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.optionsSeparatorNode.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: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted])
self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: presentationData.theme.rootController.navigationBar.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: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted])
self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
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: buttonColor), for: .selected)
self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: [.selected, .highlighted])
self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: 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: buttonColor), for: .selected)
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: [.selected, .highlighted])
if !self.glass {
self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
}
self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false)
}
private func iconForTracking() -> UIImage? {
switch self.trackingMode {
case .none:
return UIImage(bundleImageName: "Location/TrackIcon")
return UIImage(bundleImageName: self.glass ? "Location/OptionLocate" : "Location/TrackIcon")
case .follow:
return UIImage(bundleImageName: "Location/TrackActiveIcon")
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) {
self.validLayout = (layout, navigationBarHeight, topPadding, controlsTopPadding, offset, size)
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, controlsBottomPadding, offset, size)
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)
@ -188,38 +257,98 @@ public final class LocationMapHeaderNode: ASDisplayNode {
let inset: CGFloat = 6.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)
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.updateAlpha(node: self.placesBackgroundNode, 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.infoButtonNode, frame: CGRect(x: panelInset, y: panelInset, width: panelButtonSize.width, height: panelButtonSize.height))
transition.updateFrame(node: self.locationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height, width: panelButtonSize.width, height: panelButtonSize.height))
transition.updateFrame(node: self.notificationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height * 2.0, width: panelButtonSize.width, height: panelButtonSize.height))
transition.updateFrame(node: self.optionsSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height, width: panelButtonSize.width, height: UIScreenPixel))
transition.updateFrame(node: self.optionsSecondSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height * 2.0, width: panelButtonSize.width, height: UIScreenPixel))
let edgeEffectHeight: CGFloat = 80.0
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
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))
var panelHeight: CGFloat = panelButtonSize.height * 2.0
if self.proximityNotification != nil {
panelHeight += panelButtonSize.height
if let options = self.options {
let optionsSize = options.update(
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 {
didSet {
if let (layout, navigationBarHeight, topPadding, controlsTopPadding, offset, size) = self.validLayout {
self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, offset: offset, size: size, transition: .immediate)
if let (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size) = self.validLayout {
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()
}
}
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 {
case map
case sattelite
case satellite
case hybrid
var mapType: MKMapType {
switch self {
case .sattelite:
case .satellite:
return .satellite
case .hybrid:
return .hybrid

View file

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

View file

@ -90,8 +90,15 @@ public final class LocationPickerController: ViewController, AttachmentContainab
public var isContainerExpanded: () -> Bool = { return 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.style = style
self.mode = mode
self.source = source
self.initialLocation = initialLocation
@ -99,12 +106,18 @@ public final class LocationPickerController: ViewController, AttachmentContainab
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
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.title = self.presentationData.strings.Map_ChooseLocationTitle
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
if case .glass = style {
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.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
@ -114,7 +127,9 @@ public final class LocationPickerController: ViewController, AttachmentContainab
}
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.updateBarButtons()
@ -243,45 +258,55 @@ public final class LocationPickerController: ViewController, AttachmentContainab
return state
}
}, 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
}
strongSelf.controllerNode.updateState { state in
self.controllerNode.updateState { state in
var state = state
state.displayingMapModeOptions = false
return state
}
let contentNode = LocationSearchNavigationContentNode(presentationData: strongSelf.presentationData, interaction: interaction)
strongSelf.searchNavigationContentNode = contentNode
navigationBar.setContentNode(contentNode, animated: true)
let isSearching = strongSelf.controllerNode.activateSearch(navigationBar: navigationBar)
contentNode.activate()
switch style {
case .glass:
let _ = self.controllerNode.activateSearch(navigationBar: navigationBar)
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
|> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self, let searchNavigationContentNode = strongSelf.searchNavigationContentNode {
searchNavigationContentNode.updateActivity(value)
}
}))
self.isSearchingDisposable.set((isSearching
|> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self, let searchNavigationContentNode = strongSelf.searchNavigationContentNode {
searchNavigationContentNode.updateActivity(value)
}
}))
}
}, updateSearchQuery: { [weak self] query in
guard let strongSelf = self else {
return
}
strongSelf.controllerNode.searchContainerNode?.searchTextUpdated(text: query)
}, dismissSearch: { [weak self] in
guard let strongSelf = self, let navigationBar = strongSelf.navigationBar else {
guard let self, let navigationBar = self.navigationBar else {
return
}
strongSelf.isSearchingDisposable.set(nil)
strongSelf.searchNavigationContentNode?.deactivate()
strongSelf.searchNavigationContentNode = nil
self.isSearchingDisposable.set(nil)
self.searchNavigationContentNode?.deactivate()
self.searchNavigationContentNode = nil
navigationBar.setContentNode(nil, animated: true)
strongSelf.controllerNode.deactivateSearch()
self.controllerNode.deactivateSearch()
self.updateTabBarVisibility(true, .animated(duration: 0.4, curve: .spring))
}, dismissInput: { [weak self] in
guard let strongSelf = self else {
guard let self else {
return
}
strongSelf.searchNavigationContentNode?.deactivate()
self.searchNavigationContentNode?.deactivate()
self.controllerNode.deactivateInput()
}, updateSendActionHighlight: { [weak self] highlighted in
guard let strongSelf = self else {
return
@ -322,8 +347,12 @@ public final class LocationPickerController: ViewController, AttachmentContainab
if self.locationAccessDenied {
self.navigationItem.rightBarButtonItem = nil
} 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
if case .glass = self.style {
} 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()
}
@objc private func searchPressed() {
@objc func searchPressed() {
self.requestAttachmentMenuExpansion()
self.interaction?.openSearch()
}
func dismissSearchPressed() {
self.interaction?.dismissSearch()
}
func updateSearchQuery(_ query: String) {
self.interaction?.updateSearchQuery(query)
}
public func resetForReuse() {
self.interaction?.updateMapMode(.map)
self.interaction?.dismissSearch()

View file

@ -16,6 +16,13 @@ import CoreLocation
import Geocoding
import PhoneNumberFormat
import DeviceAccess
import GlassBarButtonComponent
import ComponentFlow
import BundleIconComponent
import MultilineTextComponent
import SearchInputPanelComponent
import ButtonComponent
import EdgeEffect
private struct LocationPickerTransaction {
let deletions: [ListViewDeleteItem]
@ -175,9 +182,9 @@ private enum LocationPickerEntry: Comparable, Identifiable {
icon = .location
}
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)
} else if let coordinate = coordinate {
} else if let coordinate {
interaction?.sendLocation(coordinate, name, address)
}
}, highlighted: { highlighted in
@ -192,10 +199,10 @@ private enum LocationPickerEntry: Comparable, Identifiable {
}
})
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, _):
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) }
}, infoAction: ["home", "work"].contains(venueType) ? {
interaction?.openHomeWorkInfo()
@ -344,6 +351,14 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
private let shadeNode: 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(set) var searchContainerNode: LocationSearchContainerNode?
@ -360,6 +375,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
private let searchVenuesPromise = Promise<CLLocationCoordinate2D?>()
private var searchInput: ComponentView<Empty>?
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
private var listOffset: CGFloat?
@ -394,9 +411,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.emptyResultsTextNode.textAlignment = .center
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.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode)
self.shadeNode = ASDisplayNode()
@ -405,13 +429,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.innerShadeNode = ASDisplayNode()
self.innerShadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.bottomEdgeEffectView = EdgeEffectView()
self.bottomEdgeEffectView.isUserInteractionEnabled = false
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.listNode)
self.addSubnode(self.headerNode)
self.addSubnode(self.optionsNode)
//self.addSubnode(self.optionsNode)
self.listNode.addSubnode(self.emptyResultsTextNode)
self.shadeNode.addSubnode(self.innerShadeNode)
self.addSubnode(self.shadeNode)
@ -780,36 +807,34 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
if annotations != previousAnnotations {
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 {
var updateLayout = false
let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring)
if case let .location(_, previousAddress, _) = previousState.selectedLocation, case let .location(_, newAddress, _) = state.selectedLocation, previousAddress != newAddress {
updateLayout = true
} 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 !strongSelf.locationAccessDenied {
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)
}
if updateLayout {
strongSelf.requestLayout(transition: .animated(duration: 0.45, curve: .spring))
}
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
self?.updateState { state in
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.city = cityName
state.street = streetName
@ -950,15 +981,15 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
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
}
let overlap: CGFloat = 6.0
strongSelf.listOffset = max(0.0, offset)
let overlap: CGFloat = 0.0 //6.0
self.listOffset = max(0.0, offset)
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap)))
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.layoutEmptyResultsPlaceholder(transition: listTransition)
listTransition.updateFrame(node: self.headerNode, frame: headerFrame)
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)
self.layoutEmptyResultsPlaceholder(transition: listTransition)
}
self.listNode.beganInteractiveDragging = { [weak self] _ in
@ -973,17 +1004,19 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
self.headerNode.mapNode.beganInteractiveDragging = { [weak self] in
guard let strongSelf = self else {
guard let self, let controller = self.controller else {
return
}
strongSelf.beganInteractiveDragging()
strongSelf.updateState { state in
self.beganInteractiveDragging()
self.updateState { state in
var state = state
state.displayingMapModeOptions = false
state.selectedLocation = .selecting
state.searchingVenuesAround = false
return state
}
controller.updateTabBarVisibility(false, .animated(duration: 0.4, curve: .spring))
}
self.headerNode.mapNode.endedInteractiveDragging = { [weak self] coordinate in
@ -1081,7 +1114,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
if let strongSelf = self {
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)
}
@ -1089,17 +1122,17 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
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()
}
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.searchContainerNode = searchContainerNode
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
}
@ -1112,6 +1145,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
searchContainerNode?.removeFromSupernode()
})
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() {
@ -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))
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) {
let isFirstLayout = self.validLayout == nil
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
var actionHeight: CGFloat?
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 = 240.0
let overlap: CGFloat = 6.0
let topInset: CGFloat = glass ? 300.0 : 240.0
let overlap: CGFloat = glass ? 0.0 : 6.0
let headerHeight: CGFloat
if isPickingLocation, let actionHeight = actionHeight {
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 {
headerHeight = max(0.0, listOffset + overlap)
} else {
@ -1171,7 +1232,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight))
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 scrollToItem: ListViewScrollToItem?
@ -1267,6 +1328,248 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
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) {
@ -1275,6 +1578,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
func goToUserLocation() {
guard let controller = self.controller else {
return
}
self.searchVenuesPromise.set(.single(nil))
self.updateState { state in
var state = state
@ -1283,6 +1590,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
state.searchingVenuesAround = false
return state
}
controller.updateTabBarVisibility(true, .animated(duration: 0.4, curve: .spring))
}
func requestPlacesAtSelectedLocation() {

View file

@ -136,10 +136,11 @@ final class LocationSearchContainerNode: ASDisplayNode {
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings))
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.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.listNode.isHidden = true
self.listNode.alpha = 0.0
self.listNode.accessibilityPageScrolledString = { row, count in
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
}
@ -164,9 +165,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
self.addSubnode(self.emptyResultsTitleNode)
self.addSubnode(self.emptyResultsTextNode)
self.listNode.isHidden = true
let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
let themeAndStringsPromise = self.themeAndStringsPromise
@ -278,7 +277,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
}
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 })
}
}
@ -354,14 +353,16 @@ final class LocationSearchContainerNode: ASDisplayNode {
guard let strongSelf = self else {
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.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
strongSelf.emptyResultsTitleNode.isHidden = !emptyResults
strongSelf.emptyResultsTextNode.isHidden = !emptyResults
containerTransition.updateAlpha(node: strongSelf.emptyResultsTitleNode, alpha: emptyResults ? 1.0 : 0.0)
containerTransition.updateAlpha(node: strongSelf.emptyResultsTextNode, alpha: emptyResults ? 1.0 : 0.0)
if let (layout, navigationBarHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)

View file

@ -9,10 +9,12 @@ import ItemListUI
class LocationSectionHeaderItem: ListViewItem {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String
public init(presentationData: ItemListPresentationData, title: String) {
public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String) {
self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title
}
@ -102,7 +104,7 @@ private class LocationSectionHeaderItemNode: ListViewItemNode {
}
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)?
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)
})
//self.headerNode.mapNode.isRotateEnabled = false
@ -714,7 +720,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
strongSelf.listOffset = max(0.0, offset)
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap)))
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
@ -952,7 +958,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight))
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)

View file

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

View file

@ -28,6 +28,12 @@ import ImageObjectSeparation
import ChatSendMessageActionUI
import AnimatedCountLabelNode
import MediaAssetsContext
import GlassBackgroundComponent
import EdgeEffect
import ComponentFlow
import BundleIconComponent
import LottieComponent
import GlassBarButtonComponent
final class MediaPickerInteraction {
let downloadManager: AssetDownloadManager
@ -131,6 +137,11 @@ struct Month: Equatable {
private var savedStoriesContentOffset: CGFloat?
public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, AttachmentContainable {
public enum Style {
case glass
case legacy
}
public enum Subject {
public enum Media: Equatable {
case image(UIImage)
@ -173,6 +184,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private let style: Style
fileprivate var interaction: MediaPickerInteraction?
@ -193,6 +205,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private let titleView: MediaPickerTitleView
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 selectedButtonNode: SelectedButtonNode
@ -258,8 +275,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private var requestedCameraAccess = false
private let containerNode: ASDisplayNode
private let backgroundView: GlassBackgroundView?
private let backgroundNode: NavigationBackgroundNode
fileprivate let gridNode: GridNode
fileprivate let topEdgeEffectView: EdgeEffectView
fileprivate let bottomEdgeEffectView: EdgeEffectView
fileprivate let cameraWrapperView: UIView
fileprivate var cameraView: TGAttachmentCameraView?
@ -297,6 +317,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private var fastScrollContentOffset = ValuePromise<CGPoint>(ignoreRepeated: true)
private var fastScrollDisposable: Disposable?
fileprivate var scrolledToTop = true
fileprivate var scrolledExactlyToTop = true
private var didSetReady = false
private let _ready = Promise<Bool>()
var ready: Promise<Bool> {
@ -323,6 +346,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.containerNode = ASDisplayNode()
self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor)
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.scrollingArea = SparseItemGridScrollingArea()
@ -333,6 +363,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.cameraActivateAreaNode.accessibilityLabel = "Camera"
self.cameraActivateAreaNode.accessibilityTraits = [.button]
self.topEdgeEffectView = EdgeEffectView()
self.topEdgeEffectView.isUserInteractionEnabled = false
self.bottomEdgeEffectView = EdgeEffectView()
self.bottomEdgeEffectView.isUserInteractionEnabled = false
super.init()
if case .assets(nil, .default) = controller.subject {
@ -342,10 +378,23 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
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.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)
let selectedCollection = controller.selectedCollection.get()
@ -399,8 +448,37 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self?.dismissInput()
}
self.gridNode.visibleContentOffsetChanged = { [weak self] _ in
self?.updateNavigation(transition: .immediate)
self.gridNode.visibleContentOffsetChanged = { [weak self] offset in
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()
@ -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
if let itemNode = itemNode as? MediaPickerGridItemNode {
itemNode.updateSelectionState(animated: animated)
@ -1029,7 +1107,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let count = Int32(self.controller?.interaction?.selectionState?.count() ?? 0)
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))
}
}
@ -1039,6 +1117,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate)
self.updateSelectionState(animated: true)
}
private(set) var currentDisplayMode: DisplayMode = .all {
@ -1085,17 +1165,16 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
return nil
}
}
self.containerNode.insertSubnode(selectionNode, aboveSubnode: self.gridNode)
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.selectionNode?.isUserInteractionEnabled = displayMode == .selected
var completion: () -> Void = {}
if updated && displayMode == .all {
completion = {
@ -1538,6 +1617,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let firstTime = self.validLayout == nil
self.validLayout = (layout, navigationBarHeight)
if firstTime {
self.updateSelectionState(animated: false, updateLayout: false)
}
var insets = layout.insets(options: [])
insets.top += navigationBarHeight
@ -1656,8 +1739,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
transition.updateFrame(node: self.gridNode, frame: innerBounds)
self.scrollingArea.frame = innerBounds
transition.updateFrame(node: self.backgroundNode, frame: innerBounds)
self.backgroundNode.update(size: bounds.size, transition: transition)
if let backgroundView = self.backgroundView {
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)))
@ -1699,9 +1787,14 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
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 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 {
@ -1710,10 +1803,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} else {
updateSelectionNode()
}
transition.updateFrame(node: selectionNode, frame: innerBounds)
selectionTransition.updateFrame(node: selectionNode, frame: innerBounds)
}
var cameraView: UIView?
if let view = self.cameraView {
cameraView = view
@ -1801,6 +1893,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.placeholderNode = nil
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(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
style: Style = .legacy,
peer: EnginePeer?,
threadTitle: String?,
chatLocation: ChatLocation?,
@ -1864,6 +1961,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
self.presentationData = presentationData
self.updatedPresentationData = updatedPresentationData
self.style = style
self.peer = peer
self.threadTitle = threadTitle
self.chatLocation = chatLocation
@ -1882,7 +1980,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
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 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)
if case .glass = style {
self.cancelButton = ComponentView()
self.rightButton = ComponentView()
}
self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme)
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.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
@ -2011,7 +2120,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if case .wallpaper = mode {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed))
} 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
if forCollage {
@ -2024,12 +2137,19 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
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 {
if [.createSticker].contains(mode) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode)
self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed)
self.navigationItem.rightBarButtonItem?.target = self
if let _ = self.rightButton {
} else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode)
self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed)
self.navigationItem.rightBarButtonItem?.target = self
}
}
}
} else {
@ -2039,24 +2159,32 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
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))
} else {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode)
self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed)
self.navigationItem.leftBarButtonItem?.target = self
if let _ = self.cancelButton {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
} 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 {
} else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode)
self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed)
self.navigationItem.rightBarButtonItem?.target = self
if let _ = self.rightButton {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: UIView())
} 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
if let strongSelf = self {
strongSelf.searchOrMorePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture)
}
}
// self.moreButtonNode.action = { [weak self] _, gesture in
// if let strongSelf = self {
// strongSelf.searchOrMorePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture)
// }
// }
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 {
return
}
let count = Int32(self.interaction?.selectionState?.count() ?? 0)
self.updateSelectionState(count: count)
self.updateNavigationButtons()
}
if case .media = self.subject {
self.controllerNode.updateDisplayMode(.selected, animated: false)
@ -2213,6 +2340,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
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 = {}
private var didSetupGroups = false
@ -2252,7 +2384,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.titleView.isHighlighted = true
let contextController = ContextController(
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))),
gesture: nil
)
@ -2360,21 +2492,20 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
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
guard let layout = self.validLayout else {
return
}
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
var moreIsVisible = false
var isBack = false
if case let .assets(_, mode) = self.subject, [.story, .createSticker].contains(mode) {
moreIsVisible = true
} 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.segmentsHidden = true
moreIsVisible = true
// self.moreButtonNode.iconNode.enqueueState(.more, animated: false)
} else {
let title: String
let isEnabled: Bool
@ -2387,6 +2518,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
self.titleView.updateTitle(title: title, isEnabled: isEnabled, animated: true)
self.cancelButtonNode.setState(isEnabled ? .cancel : .back, animated: true)
isBack = !isEnabled
let selectedSize = self.selectedButtonNode.update(count: count)
@ -2394,8 +2526,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if layout.safeInsets.right > 0.0 {
safeInset += layout.safeInsets.right + 16.0
}
let selectedButtonInset: CGFloat = self._hasGlassStyle ? 68.0 : 54.0
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
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
}
transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0)
transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1)
let useGlassButtons = isBack || !self.controllerNode.scrolledToTop
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 {
let text = self.presentationData.strings.MediaPicker_CreateStory(self.selectionCount)
@ -2428,7 +2662,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
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.cancelButtonNode.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)
}
@objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
@objc private func searchOrMorePressed(view: UIView, gesture: ContextGesture?) {
guard self.moreButtonNode.iconNode.alpha > 0.0 else {
return
}
@ -2669,7 +2909,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
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)
return
@ -2830,7 +3070,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
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)
}
}
@ -2866,7 +3106,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
safeInset += layout.safeInsets.right + 16.0
}
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() {
@ -3051,15 +3293,15 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
private final class MediaPickerContextReferenceContentSource: ContextReferenceContentSource {
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.sourceNode = sourceNode
self.sourceView = sourceView
}
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 },
openColors: @escaping () -> Void
) -> 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
})
controller.animateAppearance = animateAppearance
@ -3256,6 +3506,7 @@ public func storyMediaPickerController(
let controller = AttachmentController(
context: context,
updatedPresentationData: updatedPresentationData,
style: .glass,
chatLocation: nil,
buttons: [.standalone],
initialButton: .standalone,
@ -3271,6 +3522,7 @@ public func storyMediaPickerController(
let mediaPickerController = MediaPickerScreenImpl(
context: context,
updatedPresentationData: updatedPresentationData,
style: .glass,
peer: nil,
threadTitle: nil,
chatLocation: nil,
@ -3370,13 +3622,34 @@ public func stickerMediaPickerController(
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
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
})
controller.forceSourceRect = true
controller.getSourceRect = getSourceRect
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
if let result = result as? PHAsset {
controller.updateHiddenMediaId(result.localIdentifier)
@ -3500,13 +3773,21 @@ public func avatarMediaPickerController(
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
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
})
controller.forceSourceRect = true
controller.getSourceRect = getSourceRect
controller.requestController = { [weak controller] _, present in
var mainButtonState: AttachmentMainButtonState?
if canDelete {
@ -3516,6 +3797,7 @@ public func avatarMediaPickerController(
let mediaPickerController = MediaPickerScreenImpl(
context: context,
updatedPresentationData: updatedPresentationData,
style: .glass,
peer: nil,
threadTitle: nil,
chatLocation: nil,
@ -3670,43 +3952,89 @@ public func coverMediaPickerController(
return controller
}
private class SelectedButtonNode: HighlightableButtonNode {
private let background = ASImageNode()
private class SelectedButtonNode: HighlightTrackingButtonNode {
private let containerView: UIView
private let backgroundView: GlassBackgroundView?
private let background: ASImageNode?
private let icon = ASImageNode()
private let label = ImmediateAnimatedCountLabelNode()
private let glass: Bool
var theme: PresentationTheme {
didSet {
self.icon.image = generateTintedImage(image: UIImage(bundleImageName: "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.icon.image = generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Media Gallery/Check" : "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor)
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)
}
}
private var count: Int32 = 0
init(theme: PresentationTheme) {
init(theme: PresentationTheme, glass: Bool) {
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()
self.background.displaysAsynchronously = false
self.icon.displaysAsynchronously = false
self.label.displaysAsynchronously = false
self.icon.image = generateTintedImage(image: UIImage(bundleImageName: "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.icon.image = generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Media Gallery/Check" : "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor)
self.addSubnode(self.background)
self.addSubnode(self.icon)
self.addSubnode(self.label)
self.view.addSubview(self.containerView)
if let backgroundView = self.backgroundView {
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 {
self.count = count
let diameter: CGFloat = 21.0
let font = Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers])
let diameter: CGFloat = self.glass ? 40.0 : 21.0
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))"
var segments: [AnimatedCountLabelNode.Segment] = []
@ -3717,18 +4045,36 @@ private class SelectedButtonNode: HighlightableButtonNode {
}
self.label.segments = segments
let textSize = self.label.updateLayout(size: CGSize(width: 100.0, height: diameter), animated: true)
let size = CGSize(width: textSize.width + 28.0, height: diameter)
if let _ = self.icon.image {
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
let textSize = self.label.updateLayout(size: CGSize(width: 120.0, height: diameter), animated: true)
var size = CGSize(width: textSize.width + 28.0, height: diameter)
if self.glass {
size.width += 10.0
}
self.label.frame = CGRect(origin: CGPoint(x: 21.0, y: floor((size.height - textSize.height) / 2.0) - UIScreenPixel), size: textSize)
self.background.frame = CGRect(origin: .zero, size: size)
if let _ = self.icon.image {
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
}
}

View file

@ -14,18 +14,37 @@ final class MediaPickerTitleView: UIView {
private let arrowNode: ASImageNode
private let segmentedControlNode: SegmentedControlNode
private let glass: Bool
public var theme: PresentationTheme {
didSet {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
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)
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 = "" {
didSet {
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()
}
}
@ -34,7 +53,7 @@ final class MediaPickerTitleView: UIView {
public var subtitle: String = "" {
didSet {
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()
}
}
@ -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) {
if animated {
if self.title != title {
@ -129,8 +183,9 @@ final class MediaPickerTitleView: UIView {
public var indexUpdated: ((Int) -> 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.glass = glass
self.segments = segments
self.contextSourceNode = ContextReferenceContentNode()
@ -144,7 +199,11 @@ final class MediaPickerTitleView: UIView {
self.arrowNode = ASImageNode()
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.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
}
self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0)), 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), size: subtitleSize)
let verticalOffset: CGFloat = self.glass ? 3.0 : 0.0
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 {
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)
}

View file

@ -183,7 +183,7 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .uploadSound(text):
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()
})
case let .cloudInfo(text):
@ -193,15 +193,15 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry {
case let .classicHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
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)
})
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)
})
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)
}, deleteAction: canBeDeleted ? {
arguments.deleteSound(sound, text)

View file

@ -72,7 +72,7 @@ private enum ResetPasswordEntry: ItemListNodeEntry, Equatable {
let arguments = arguments as! ResetPasswordControllerArguments
switch self {
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)
}, action: {
})

View file

@ -117,12 +117,13 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .addMethod(text):
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()
})
case let .item(_, info, isSelected):
return ItemListCheckboxItem(
presentationData: presentationData,
systemStyle: .glass,
icon: STPPaymentCardTextField.brandImage(for: .masterCard), iconSize: nil,
iconPlacement: .default,
title: "•••• " + info.number.suffix(4),

View file

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

View file

@ -265,11 +265,11 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
let arguments = arguments as! ChannelAdminsControllerArguments
switch self {
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()
})
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)
})
case let .antiSpamInfo(_, text):
@ -302,23 +302,23 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
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)
}, removePeer: { peerId in
arguments.removeAdmin(peerId)
})
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()
})
case let .adminsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
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)
})
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)
})
case let .signMessagesInfo(_, text):

View file

@ -150,7 +150,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry {
let arguments = arguments as! ChannelBlacklistControllerArguments
switch self {
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()
})
case let .addInfo(_, text):
@ -167,7 +167,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry {
default:
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)
}, setPeerIdWithRevealedOptions: { previousId, id in
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)
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()
})
case let .group(_, _, strings, peer, nameOrder):
@ -157,13 +157,13 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry {
} else {
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)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
case let .groupsInfo(_, title):
return ItemListTextItem(presentationData: presentationData, text: .plain(title), sectionId: self.section)
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()
})
}

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 }
if let current = current as? ChannelDiscussionSearchNavigationContentNode {
current.updateTheme(presentationData.theme)

View file

@ -254,7 +254,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
let arguments = arguments as! ChannelMembersControllerArguments
switch self {
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 {
arguments.displayHideMembersTip(disabledReason)
} else {
@ -268,11 +268,11 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
case let .hideMembersInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
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()
})
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()
})
case let .addMemberInfo(_, text):
@ -286,7 +286,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
} else {
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))
}, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id)

View file

@ -1148,7 +1148,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext,
switch subject {
case let .create(peer, _, share, shareViaException, _):
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
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: {
@ -1182,7 +1182,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext,
switch subject {
case let .create(peer, _, share, shareViaException, _):
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
}
|> 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 }
if let current = current as? GroupInfoSearchNavigationContentNode {
current.updateTheme(presentationData.theme)

View file

@ -84,11 +84,11 @@ private enum GroupPreHistorySetupEntry: ItemListNodeEntry {
case let .header(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
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)
})
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)
})
case let .info(_, text):

View file

@ -59,7 +59,7 @@ private enum PhoneLabelEntry: ItemListNodeEntry {
let arguments = arguments as! PhoneLabelArguments
switch self {
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)
})
}

View file

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

View file

@ -6,12 +6,12 @@ import SwiftSignalKit
import ItemListUI
public extension ItemListController {
convenience init<ItemGenerationArguments>(context: AccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>? = nil) {
self.init(sharedContext: context.sharedContext, state: state, tabBarItem: tabBarItem)
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, 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 }
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/PeerNameColorItem",
"//submodules/TelegramUI/Components/FaceScanScreen",
"//submodules/ComponentFlow",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
],
visibility = [
"//visibility:public",

View file

@ -91,7 +91,7 @@ private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry {
let arguments = arguments as! ChangePhoneNumberCodeControllerArguments
switch self {
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)
}, action: {
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) {
initialCountryCode = codeValue
} else {
initialCountryCode = AuthorizationSequenceController.defaultCountryCode()
initialCountryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
}
} else {
initialCountryCode = AuthorizationSequenceController.defaultCountryCode()
initialCountryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode()
}
controller.updateData(countryCode: initialCountryCode, countryName: nil, number: "")
controller.updateCountryCode()

View file

@ -153,31 +153,31 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
let arguments = arguments as! AutodownloadMediaConnectionTypeControllerArguments
switch self {
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)
})
case let .dataUsageHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
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)
})
case let .typesHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
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)
})
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)
})
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)
})
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)
})
case let .voiceMessagesInfo(_, text):

View file

@ -33,15 +33,17 @@ enum AutomaticDownloadDataUsage: Int {
final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let strings: PresentationStrings
let systemStyle: ItemListSystemStyle
let value: AutomaticDownloadDataUsage
let customPosition: Int?
let enabled: Bool
let sectionId: ItemListSectionId
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.strings = strings
self.systemStyle = systemStyle
self.value = value
self.customPosition = customPosition
self.enabled = enabled
@ -296,7 +298,7 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
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.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