Various improvements

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

View File

@@ -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)
}
}