mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various improvements
This commit is contained in:
parent
babf5cc9d7
commit
0915a42e64
289 changed files with 9723 additions and 2071 deletions
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
|
||||
public protocol PeersNearbyManager {
|
||||
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ swift_library(
|
|||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ swift_library(
|
|||
"//submodules/TelegramIntents",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, *) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public final class LocationOptionsNode: ASDisplayNode {
|
|||
case 0:
|
||||
updateMapMode(.map)
|
||||
case 1:
|
||||
updateMapMode(.sattelite)
|
||||
updateMapMode(.satellite)
|
||||
case 2:
|
||||
updateMapMode(.hybrid)
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue