Location picker fixes

This commit is contained in:
Ilya Laktyushin 2019-11-26 21:14:58 +04:00
parent 3b931cfd96
commit f4dcc9cb92
27 changed files with 4197 additions and 3784 deletions

View File

@ -5114,6 +5114,7 @@ Any member of this group will be able to see messages in the channel.";
"Theme.Colors.ColorWallpaperWarning" = "Are you sure you want to change your chat wallpaper to a color?";
"Theme.Colors.ColorWallpaperWarningProceed" = "Proceed";
"ChatSettings.IntentsSettings" = "Share Sheet";
"IntentsSettings.Title" = "Share Sheet";
"IntentsSettings.MainAccount" = "Main Account";
"IntentsSettings.MainAccountInfo" = "Choose an account for Siri and share suggestions.";
@ -5146,3 +5147,9 @@ Any member of this group will be able to see messages in the channel.";
"Map.SendThisPlace" = "Send This Place";
"Map.SetThisPlace" = "Set This Place";
"Map.AddressOnMap" = "Address On Map";
"Map.PlacesNearby" = "Places Nearby";
"Map.Home" = "Home";
"Map.Work" = "Work";
"Map.HomeAndWorkTitle" = "Home & Work Addresses";
"Map.HomeAndWorkInfo" = "Telegram uses the Home and Work addresses from your Contact Card.\n\nKeep your Contact Card up to date for quick access to sending Home and Work addresses.";

View File

@ -17,6 +17,8 @@ public enum ChatListSearchItemHeaderType: Int32 {
case phoneNumber
case exceptions
case addToExceptions
case mapAddress
case nearbyVenues
}
public final class ChatListSearchItemHeader: ListViewItemHeader {
@ -30,7 +32,7 @@ public final class ChatListSearchItemHeader: ListViewItemHeader {
public let height: CGFloat = 28.0
public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String?, action: (() -> Void)?) {
public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String? = nil, action: (() -> Void)? = nil) {
self.type = type
self.id = Int64(self.type.rawValue)
self.theme = theme
@ -93,6 +95,10 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
}
self.sectionHeaderNode.action = actionTitle

View File

@ -244,7 +244,7 @@ public final class DeviceAccess {
}
}
public static func authorizeAccess(to subject: DeviceAccessSubject, registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil, requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil, presentationData: PresentationData? = nil, present: @escaping (ViewController, Any?) -> Void = { _, _ in }, openSettings: @escaping () -> Void = { }, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void = { _ in }) {
public static func authorizeAccess(to subject: DeviceAccessSubject, registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil, requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil, locationManager: CLLocationManager? = nil, presentationData: PresentationData? = nil, present: @escaping (ViewController, Any?) -> Void = { _, _ in }, openSettings: @escaping () -> Void = { }, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void = { _ in }) {
switch subject {
case .camera:
let status = PGCamera.cameraAuthorizationStatus()
@ -382,7 +382,14 @@ public final class DeviceAccess {
})]), nil)
}
case .notDetermined:
completion(true)
switch locationSubject {
case .send:
locationManager?.requestWhenInUseAuthorization()
case .live:
locationManager?.requestAlwaysAuthorization()
default:
break
}
@unknown default:
fatalError()
}

View File

@ -14,6 +14,7 @@ public class ImmediateTextNode: TextNode {
public var lineSpacing: CGFloat = 0.0
public var insets: UIEdgeInsets = UIEdgeInsets()
public var textShadowColor: UIColor?
public var textStroke: (UIColor, CGFloat)?
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
private var linkHighlightingNode: LinkHighlightingNode?
@ -35,7 +36,7 @@ public class ImmediateTextNode: TextNode {
public func updateLayout(_ constrainedSize: CGSize) -> CGSize {
let makeLayout = TextNode.asyncLayout(self)
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets, textShadowColor: self.textShadowColor))
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets, textShadowColor: self.textShadowColor, textStroke: self.textStroke))
let _ = apply()
if layout.numberOfLines > 1 {
self.trailingLineWidth = layout.trailingLineWidth

View File

@ -95,8 +95,9 @@ public final class TextNodeLayoutArguments {
public let insets: UIEdgeInsets
public let lineColor: UIColor?
public let textShadowColor: UIColor?
public let textStroke: (UIColor, CGFloat)?
public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, minimumNumberOfLines: Int = 0, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil, textShadowColor: UIColor? = nil) {
public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, minimumNumberOfLines: Int = 0, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil, textShadowColor: UIColor? = nil, textStroke: (UIColor, CGFloat)? = nil) {
self.attributedString = attributedString
self.backgroundColor = backgroundColor
self.minimumNumberOfLines = minimumNumberOfLines
@ -109,6 +110,7 @@ public final class TextNodeLayoutArguments {
self.insets = insets
self.lineColor = lineColor
self.textShadowColor = textShadowColor
self.textStroke = textStroke
}
}
@ -130,9 +132,10 @@ public final class TextNodeLayout: NSObject {
fileprivate let blockQuotes: [TextNodeBlockQuote]
fileprivate let lineColor: UIColor?
fileprivate let textShadowColor: UIColor?
fileprivate let textStroke: (UIColor, CGFloat)?
public let hasRTL: Bool
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?) {
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?) {
self.attributedString = attributedString
self.maximumNumberOfLines = maximumNumberOfLines
self.truncationType = truncationType
@ -150,6 +153,7 @@ public final class TextNodeLayout: NSObject {
self.backgroundColor = backgroundColor
self.lineColor = lineColor
self.textShadowColor = textShadowColor
self.textStroke = textStroke
var hasRTL = false
for line in lines {
if line.isRTL {
@ -780,7 +784,7 @@ public class TextNode: ASDisplayNode {
}
}
private class func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?) -> TextNodeLayout {
private class func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?) -> TextNodeLayout {
if let attributedString = attributedString {
let stringLength = attributedString.length
@ -806,7 +810,7 @@ public class TextNode: ASDisplayNode {
var maybeTypesetter: CTTypesetter?
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
if maybeTypesetter == nil {
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke)
}
let typesetter = maybeTypesetter!
@ -1010,9 +1014,9 @@ public class TextNode: ASDisplayNode {
}
}
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke)
} else {
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textStroke: textStroke)
}
}
@ -1050,6 +1054,14 @@ public class TextNode: ASDisplayNode {
context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 0.0, color: textShadowColor.cgColor)
}
// if let (textStrokeColor, textStrokeWidth) = layout.textStroke {
// context.setBlendMode(.normal)
// context.setLineCap(.round)
// context.setLineJoin(.round)
// context.setStrokeColor(textStrokeColor.cgColor)
// context.setLineWidth(textStrokeWidth)
// }
let textMatrix = context.textMatrix
let textPosition = context.textPosition
context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0)
@ -1156,11 +1168,11 @@ public class TextNode: ASDisplayNode {
if stringMatch {
layout = existingLayout
} else {
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor)
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke)
updated = true
}
} else {
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor)
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textStroke: arguments.textStroke)
updated = true
}

View File

@ -2,6 +2,19 @@ import Foundation
import CoreLocation
import SwiftSignalKit
public func geocodeLocation(address: String) -> Signal<[CLPlacemark]?, NoError> {
return Signal { subscriber in
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { (placemarks, _) in
subscriber.putNext(placemarks)
subscriber.putCompletion()
}
return ActionDisposable {
geocoder.cancelGeocode()
}
}
}
public func geocodeLocation(dictionary: [String: String]) -> Signal<(Double, Double)?, NoError> {
return Signal { subscriber in
let geocoder = CLGeocoder()

View File

@ -14,24 +14,46 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let account: Account
let venue: TelegramMediaMap
public let sectionId: ItemListSectionId
let title: String?
let subtitle: String?
let style: ItemListStyle
let action: (() -> Void)?
let infoAction: (() -> Void)?
public init(presentationData: ItemListPresentationData, account: Account, venue: TelegramMediaMap, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?) {
public let sectionId: ItemListSectionId
let header: ListViewItemHeader?
public init(presentationData: ItemListPresentationData, account: Account, venue: TelegramMediaMap, title: String? = nil, subtitle: String? = nil, sectionId: ItemListSectionId = 0, style: ItemListStyle, action: (() -> Void)?, infoAction: (() -> Void)? = nil, header: ListViewItemHeader? = nil) {
self.presentationData = presentationData
self.account = account
self.venue = venue
self.title = title
self.subtitle = subtitle
self.sectionId = sectionId
self.style = style
self.action = action
self.infoAction = infoAction
self.header = header
}
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) {
async {
var firstWithHeader = false
var last = false
if self.style == .plain {
if previousItem == nil {
firstWithHeader = true
} else if let previousItem = previousItem as? ItemListVenueItem, self.header != nil && previousItem.header?.id != self.header?.id {
firstWithHeader = true
}
if nextItem == nil {
last = true
} else if let nextItem = nextItem as? ItemListVenueItem, self.header != nil && nextItem.header?.id != self.header?.id {
last = true
}
}
let node = ItemListVenueItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), firstWithHeader, last)
node.contentSize = layout.contentSize
node.insets = layout.insets
@ -55,7 +77,21 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem {
}
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
var firstWithHeader = false
var last = false
if self.style == .plain {
if previousItem == nil {
firstWithHeader = true
} else if let previousItem = previousItem as? ItemListVenueItem, self.header != nil && previousItem.header?.id != self.header?.id {
firstWithHeader = true
}
if nextItem == nil {
last = true
} else if let nextItem = nextItem as? ItemListVenueItem, self.header != nil && nextItem.header?.id != self.header?.id {
last = true
}
}
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), firstWithHeader, last)
Queue.mainQueue().async {
completion(layout, { _ in
apply()
@ -84,8 +120,10 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
private let iconNode: TransformImageNode
private let titleNode: TextNode
private let addressNode: TextNode
private let infoButton: HighlightableButtonNode
private var layoutParams: (ItemListVenueItem, ListViewItemLayoutParams, ItemListNeighbors)?
private var item: ItemListVenueItem?
private var layoutParams: (ItemListVenueItem, ListViewItemLayoutParams, ItemListNeighbors, Bool, Bool)?
public var tag: ItemListItemTag?
@ -119,6 +157,8 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
self.addressNode.isUserInteractionEnabled = false
self.addressNode.contentMode = .left
self.addressNode.contentsScale = UIScreen.main.scale
self.infoButton = HighlightableButtonNode()
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
@ -130,16 +170,19 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.addressNode)
self.addSubnode(self.infoButton)
self.infoButton.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside)
}
public func asyncLayout() -> (_ item: ItemListVenueItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
public func asyncLayout() -> (_ item: ItemListVenueItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors, _ firstWithHeader: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeAddressLayout = TextNode.asyncLayout(self.addressNode)
let iconLayout = self.iconNode.asyncLayout()
let currentItem = self.layoutParams?.0
return { item, params, neighbors in
return { item, params, neighbors, firstWithHeader, last in
var updatedTheme: PresentationTheme?
var updatedVenueType: String?
@ -155,11 +198,29 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
updatedVenueType = venueType
}
let titleAttributedString = NSAttributedString(string: item.venue.venue?.title ?? "", font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
let addressAttributedString = NSAttributedString(string: item.venue.venue?.address ?? "", font: addressFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let title: String
if let venueTitle = item.venue.venue?.title {
title = venueTitle
} else if let customTitle = item.title {
title = customTitle
} else {
title = ""
}
let subtitle: String
if let address = item.venue.venue?.address {
subtitle = address
} else if let customSubtitle = item.subtitle {
subtitle = customSubtitle
} else {
subtitle = ""
}
let titleAttributedString = NSAttributedString(string: title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
let addressAttributedString = NSAttributedString(string: subtitle, font: addressFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let leftInset: CGFloat = 65.0 + params.leftInset
let rightInset: CGFloat = 16.0 + params.rightInset
let rightInset: CGFloat = 16.0 + params.rightInset + (item.infoAction != nil ? 48.0 : 0.0)
let verticalInset: CGFloat = addressAttributedString.string.isEmpty ? 14.0 : 8.0
let iconSize: CGFloat = 40.0
@ -179,6 +240,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
insets = itemListNeighborsPlainInsets(neighbors)
insets.top = firstWithHeader ? 29.0 : 0.0
insets.bottom = 0.0
case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
@ -193,7 +255,8 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.layoutParams = (item, params, neighbors)
strongSelf.item = item
strongSelf.layoutParams = (item, params, neighbors, firstWithHeader, last)
strongSelf.accessibilityLabel = titleAttributedString.string
strongSelf.accessibilityValue = addressAttributedString.string
@ -203,6 +266,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
strongSelf.infoButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: item.presentationData.theme.list.itemAccentColor), for: .normal)
}
let transition = ContainedViewLayoutTransition.immediate
@ -239,7 +303,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
stripeInset = leftInset
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: stripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - stripeInset, height: separatorHeight))
strongSelf.bottomStripeNode.isHidden = false
strongSelf.bottomStripeNode.isHidden = last
case .blocks:
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
@ -287,6 +351,9 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((layout.contentSize.height - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize)))
transition.updateFrame(node: strongSelf.infoButton, frame: CGRect(x: layout.contentSize.width - params.rightInset - 60.0, y: 0.0, width: 60.0, height: layout.contentSize.height))
strongSelf.infoButton.isHidden = item.infoAction == nil
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel))
}
})
@ -338,4 +405,12 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
@objc private func infoPressed() {
self.item?.infoAction?()
}
override public func header() -> ListViewItemHeader? {
return self.item?.header
}
}

View File

@ -167,12 +167,14 @@ const CGFloat TGLocationInfoCellHeight = 134.0f;
_titleLabel.text = location.venue.title.length > 0 ? location.venue.title : TGLocalized(@"Map.Location");
UIColor *pinColor = _pallete != nil ? _pallete.iconColor : [UIColor whiteColor];
if (color != nil) {
[_circleView setImage:TGTintedImage([TGLocationVenueCell circleImage], color)];
pinColor = [UIColor whiteColor];
}
if (location.venue.type.length > 0 && [location.venue.provider isEqualToString:@"foursquare"])
[_iconView loadUri:[NSString stringWithFormat:@"location-venue-icon://type=%@&width=%d&height=%d&color=%d", location.venue.type, 48, 48, TGColorHexCode(_pallete != nil ? _pallete.iconColor : [UIColor whiteColor])] withOptions:nil];
[_iconView loadUri:[NSString stringWithFormat:@"location-venue-icon://type=%@&width=%d&height=%d&color=%d", location.venue.type, 48, 48, TGColorHexCode(pinColor)] withOptions:nil];
SSignal *addressSignal = [SSignal single:@""];
if (location.venue.address.length > 0)

View File

@ -266,14 +266,16 @@ NSString *const TGLocationPinAnnotationKind = @"TGLocationPinAnnotation";
_iconView.hidden = false;
UIColor *color = _pallete != nil ? _pallete.locationColor : UIColorRGB(0x008df2);
UIColor *pinColor = _pallete != nil ? _pallete.iconColor : [UIColor whiteColor];
if (locationAnnotation.color != nil) {
color = locationAnnotation.color;
pinColor = [UIColor whiteColor];
}
_backgroundView.image = TGTintedImage(TGComponentsImageNamed(@"LocationPinBackground"), color);
if (location.venue.type.length > 0)
{
[_iconView loadUri:[NSString stringWithFormat:@"location-venue-icon://type=%@&width=%d&height=%d&color=%d", location.venue.type, 64, 64, TGColorHexCode(_pallete != nil ? _pallete.iconColor : [UIColor whiteColor])] withOptions:nil];
[_iconView loadUri:[NSString stringWithFormat:@"location-venue-icon://type=%@&width=%d&height=%d&color=%d", location.venue.type, 64, 64, TGColorHexCode(pinColor)] withOptions:nil];
}
else
{

View File

@ -134,9 +134,14 @@ private let venueColors: [String: UIColor] = [
"parks_outdoors": UIColor(rgb: 0x6cc039),
"shops": UIColor(rgb: 0xffb300),
"travel": UIColor(rgb: 0x1c9fff),
"work": UIColor(rgb: 0xad7854),
"home": UIColor(rgb: 0x00aeef)
]
public func venueIconColor(type: String) -> UIColor {
if type.isEmpty {
return UIColor(rgb: 0x008df2)
}
if let color = venueColors[type] {
return color
}
@ -150,7 +155,8 @@ public func venueIconColor(type: String) -> UIColor {
}
public func venueIcon(postbox: Postbox, type: String, background: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let data: Signal<Data?, NoError> = type.isEmpty ? .single(nil) : venueIconData(postbox: postbox, resource: VenueIconResource(type: type))
let isBuiltinIcon = ["", "home", "work"].contains(type)
let data: Signal<Data?, NoError> = isBuiltinIcon ? .single(nil) : venueIconData(postbox: postbox, resource: VenueIconResource(type: type))
return data |> map { data in
return { arguments in
let context = DrawingContext(size: arguments.drawingSize, clear: true)
@ -172,8 +178,21 @@ public func venueIcon(postbox: Postbox, type: String, background: Bool) -> Signa
let boundsSize = CGSize(width: arguments.drawingRect.size.width - 4.0 * 2.0, height: arguments.drawingRect.size.height - 4.0 * 2.0)
let fittedSize = image.size.aspectFitted(boundsSize)
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - fittedSize.width) / 2.0), y: floor((arguments.drawingRect.height - fittedSize.height) / 2.0)), size: fittedSize))
} else if type.isEmpty, let pinImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/LocationPinForeground"), color: foregroundColor), let cgImage = pinImage.cgImage {
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - pinImage.size.width) / 2.0), y: floor((arguments.drawingRect.height - pinImage.size.height) / 2.0)), size: pinImage.size))
} else if isBuiltinIcon {
let image: UIImage?
switch type {
case "":
image = UIImage(bundleImageName: "Chat/Message/LocationPinForeground")
case "home":
image = UIImage(bundleImageName: "Location/HomeIcon")
case "work":
image = UIImage(bundleImageName: "Location/WorkIcon")
default:
image = nil
}
if let image = image, let pinImage = generateTintedImage(image: image, color: foregroundColor), let cgImage = pinImage.cgImage {
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - pinImage.size.width) / 2.0), y: floor((arguments.drawingRect.height - pinImage.size.height) / 2.0)), size: pinImage.size))
}
}
}

View File

@ -29,6 +29,8 @@ static_library(
"//submodules/MergeLists:MergeLists",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/SearchBarNode:SearchBarNode",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/DeviceAccess:DeviceAccess",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -80,6 +80,7 @@ class LocationPinAnnotationView: MKAnnotationView {
let smallIconNode: TransformImageNode
let dotNode: ASImageNode
var avatarNode: AvatarNode?
var labelNode: ImmediateTextNode?
var appeared = false
var animating = false
@ -226,6 +227,29 @@ class LocationPinAnnotationView: MKAnnotationView {
self.dotNode.alpha = 1.0
self.dotNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
if let annotation = self.annotation as? LocationPinAnnotation, let venue = annotation.location?.venue {
let labelNode = ImmediateTextNode()
labelNode.displaysAsynchronously = false
labelNode.isUserInteractionEnabled = false
labelNode.attributedText = NSAttributedString(string: venue.title, font: Font.medium(10), textColor: .black)
labelNode.maximumNumberOfLines = 2
labelNode.textAlignment = .center
labelNode.truncationType = .end
labelNode.textStroke = (UIColor.white, 1.0)
self.labelNode = labelNode
self.addSubnode(labelNode)
let size = labelNode.updateLayout(CGSize(width: 120.0, height: CGFloat.greatestFiniteMagnitude))
labelNode.bounds = CGRect(origin: CGPoint(), size: size)
labelNode.position = CGPoint(x: 0.0, y: 10.0)
labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
} else {
self.labelNode?.removeFromSupernode()
self.labelNode = nil
}
} else {
let avatarSnapshot = self.avatarNode?.view.snapshotContentTree()
if let avatarSnapshot = avatarSnapshot, let avatarNode = self.avatarNode {
@ -268,6 +292,13 @@ class LocationPinAnnotationView: MKAnnotationView {
let previousAlpha = self.dotNode.alpha
self.dotNode.alpha = 0.0
self.dotNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2)
if let labelNode = self.labelNode {
self.labelNode = nil
labelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
labelNode.removeFromSupernode()
})
}
}
} else {
self.smallNode.isHidden = selected
@ -279,50 +310,13 @@ class LocationPinAnnotationView: MKAnnotationView {
}
}
override func layoutSubviews() {
super.layoutSubviews()
guard !self.animating else {
return
}
self.dotNode.position = CGPoint()
self.smallNode.position = CGPoint()
self.shadowNode.position = CGPoint(x: UIScreenPixel, y: -36.0)
self.backgroundNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0)
self.iconNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0 - 5.0)
let smallIconLayout = self.smallIconNode.asyncLayout()
let smallIconApply = smallIconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: self.smallIconNode.bounds.size, boundingSize: self.smallIconNode.bounds.size, intrinsicInsets: UIEdgeInsets()))
smallIconApply()
let iconLayout = self.iconNode.asyncLayout()
let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: self.iconNode.bounds.size, boundingSize: self.iconNode.bounds.size, intrinsicInsets: UIEdgeInsets()))
iconApply()
if let avatarNode = self.avatarNode {
avatarNode.position = self.isSelected ? CGPoint(x: UIScreenPixel, y: -41.0) : CGPoint()
avatarNode.transform = self.isSelected ? CATransform3DIdentity : CATransform3DMakeScale(0.64, 0.64, 1.0)
avatarNode.view.superview?.bringSubviewToFront(avatarNode.view)
}
if !self.appeared {
self.appeared = true
self.smallNode.transform = CATransform3DMakeScale(0.1, 0.1, 1.0)
UIView.animate(withDuration: 0.55, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], animations: {
self.smallNode.transform = CATransform3DIdentity
}) { _ in
}
}
}
func setPeer(account: Account, theme: PresentationTheme, peer: Peer) {
let avatarNode: AvatarNode
if let currentAvatarNode = self.avatarNode {
avatarNode = currentAvatarNode
} else {
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 24.0))
avatarNode.isLayerBacked = false
avatarNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 55.0, height: 55.0))
avatarNode.position = CGPoint()
self.avatarNode = avatarNode
@ -332,19 +326,23 @@ class LocationPinAnnotationView: MKAnnotationView {
avatarNode.setPeer(account: account, theme: theme, peer: peer)
}
private var raised = false
var isRaised = false
func setRaised(_ raised: Bool, animated: Bool, completion: @escaping () -> Void = {}) {
guard raised != self.raised else {
guard raised != self.isRaised else {
return
}
self.raised = raised
self.isRaised = raised
self.shadowNode.layer.removeAllAnimations()
if animated {
self.animating = true
if raised {
let previousPosition = self.shadowNode.position
self.shadowNode.layer.animatePosition(from: previousPosition, to: CGPoint(x: UIScreenPixel, y: -66.0), duration: 0.2, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring) { finished in
self.shadowNode.position = CGPoint(x: UIScreenPixel, y: -66.0)
self.shadowNode.layer.animatePosition(from: previousPosition, to: self.shadowNode.position, duration: 0.2, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring) { finished in
self.animating = false
if finished {
completion()
}
@ -353,6 +351,7 @@ class LocationPinAnnotationView: MKAnnotationView {
UIView.animate(withDuration: 0.2, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.0, options: [.allowAnimatedContent], animations: {
self.shadowNode.position = CGPoint(x: UIScreenPixel, y: -36.0)
}) { finished in
self.animating = false
if finished {
completion()
}
@ -396,7 +395,6 @@ class LocationPinAnnotationView: MKAnnotationView {
self.addSubnode(avatarNode)
}
self.animating = false
self.setNeedsLayout()
}
}
@ -405,4 +403,42 @@ class LocationPinAnnotationView: MKAnnotationView {
self.dotNode.isHidden = !custom
}
override func layoutSubviews() {
super.layoutSubviews()
guard !self.animating else {
return
}
self.dotNode.position = CGPoint()
self.smallNode.position = CGPoint()
self.shadowNode.position = CGPoint(x: UIScreenPixel, y: self.isRaised ? -66.0 : -36.0)
self.backgroundNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0)
self.iconNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0 - 5.0)
let smallIconLayout = self.smallIconNode.asyncLayout()
let smallIconApply = smallIconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: self.smallIconNode.bounds.size, boundingSize: self.smallIconNode.bounds.size, intrinsicInsets: UIEdgeInsets()))
smallIconApply()
let iconLayout = self.iconNode.asyncLayout()
let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: self.iconNode.bounds.size, boundingSize: self.iconNode.bounds.size, intrinsicInsets: UIEdgeInsets()))
iconApply()
if let avatarNode = self.avatarNode {
avatarNode.position = self.isSelected ? CGPoint(x: UIScreenPixel, y: -41.0) : CGPoint()
avatarNode.transform = self.isSelected ? CATransform3DIdentity : CATransform3DMakeScale(0.64, 0.64, 1.0)
avatarNode.view.superview?.bringSubviewToFront(avatarNode.view)
}
if !self.appeared {
self.appeared = true
self.smallNode.transform = CATransform3DMakeScale(0.1, 0.1, 1.0)
UIView.animate(withDuration: 0.55, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], animations: {
self.smallNode.transform = CATransform3DIdentity
}) { _ in
}
}
}
}

View File

@ -100,19 +100,21 @@ final class LocationMapHeaderNode: ASDisplayNode {
self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false)
}
func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, padding: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.mapNode, frame: CGRect(x: 0.0, y: floorToScreenPixels((size.height - layout.size.height + navigationBarHeight) / 2.0), width: size.width, height: layout.size.height))
func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, topPadding: CGFloat, offset: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) {
let mapHeight: CGFloat = floor(layout.size.height * 1.5)
let mapFrame = CGRect(x: 0.0, y: floorToScreenPixels((size.height - mapHeight + navigationBarHeight) / 2.0) + offset, width: size.width, height: mapHeight)
transition.updateFrame(node: self.mapNode, frame: mapFrame)
self.mapNode.updateLayout(size: mapFrame.size)
transition.updateFrame(node: self.shadowNode, frame: CGRect(x: 0.0, y: size.height - 14.0, width: size.width, height: 14.0))
let inset: CGFloat = 6.0
transition.updateFrame(node: self.optionsBackgroundNode, frame: CGRect(x: size.width - inset - panelSize.width - panelInset * 2.0, y: navigationBarHeight + padding + inset, width: panelSize.width + panelInset * 2.0, height: panelSize.height + panelInset * 2.0))
transition.updateFrame(node: self.optionsBackgroundNode, frame: CGRect(x: size.width - inset - panelSize.width - panelInset * 2.0, y: navigationBarHeight + topPadding + inset, width: panelSize.width + panelInset * 2.0, height: panelSize.height + panelInset * 2.0))
transition.updateFrame(node: self.infoButtonNode, frame: CGRect(x: panelInset, y: panelInset, width: panelSize.width, height: panelSize.height / 2.0))
transition.updateFrame(node: self.locationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelSize.height / 2.0, width: panelSize.width, height: panelSize.height / 2.0))
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
let optionsAlpha: CGFloat = size.height > 124.0 + navigationBarHeight ? 1.0 : 0.0
let optionsAlpha: CGFloat = size.height > 160.0 + navigationBarHeight ? 1.0 : 0.0
alphaTransition.updateAlpha(node: self.optionsBackgroundNode, alpha: optionsAlpha)
}

View File

@ -39,10 +39,13 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
private let pickerAnnotationContainerView: PickerAnnotationContainerView
private weak var userLocationAnnotationView: MKAnnotationView?
private let pinDisposable = MetaDisposable()
private var mapView: MKMapView? {
return self.view as? MKMapView
}
var returnedToUserLocation = true
var ignoreRegionChanges = false
var isDragging = false
var beganInteractiveDragging: (() -> Void)?
@ -101,6 +104,11 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
self.mapView?.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsets(top: offset.y, left: offset.x, bottom: 0.0, right: 0.0), animated: animated)
}
self.ignoreRegionChanges = false
if isUserLocation && !self.returnedToUserLocation {
self.returnedToUserLocation = true
self.pickerAnnotationView?.setRaised(true, animated: true)
}
}
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
@ -111,13 +119,18 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
for gestureRecognizer in gestureRecognizers {
if gestureRecognizer.state == .began || gestureRecognizer.state == .ended {
self.isDragging = true
self.returnedToUserLocation = false
self.beganInteractiveDragging?()
if self.hasPickerAnnotation {
self.customUserLocationAnnotationView?.isHidden = true
self.pickerAnnotationContainerView.isHidden = false
self.pickerAnnotationView?.setCustom(true, animated: true)
if let pickerAnnotationView = self.pickerAnnotationView, !pickerAnnotationView.isRaised {
pickerAnnotationView.setCustom(true, animated: true)
pickerAnnotationView.setRaised(true, animated: true)
}
self.resetAnnotationSelection()
self.resetScheduledPin()
}
break
}
@ -125,9 +138,18 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if self.isDragging, let coordinate = self.mapCenterCoordinate {
let wasDragging = self.isDragging
if self.isDragging {
self.isDragging = false
self.endedInteractiveDragging?(coordinate)
if let coordinate = self.mapCenterCoordinate {
self.endedInteractiveDragging?(coordinate)
}
}
if let pickerAnnotationView = self.pickerAnnotationView {
if pickerAnnotationView.isRaised && (wasDragging || self.returnedToUserLocation) {
self.schedulePin(wasDragging: wasDragging)
}
}
}
@ -293,12 +315,41 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
override func layout() {
super.layout()
self.pickerAnnotationContainerView.frame = CGRect(x: 0.0, y: floorToScreenPixels((self.frame.size.height - self.frame.size.width) / 2.0), width: self.frame.size.width, height: self.frame.size.width)
private func schedulePin(wasDragging: Bool) {
let timeout: Double = wasDragging ? 0.38 : 0.05
let signal: Signal<Never, NoError> = .complete()
|> delay(timeout, queue: Queue.mainQueue())
self.pinDisposable.set(signal.start(completed: { [weak self] in
guard let strongSelf = self, let pickerAnnotationView = strongSelf.pickerAnnotationView else {
return
}
pickerAnnotationView.setRaised(false, animated: true) { [weak self] in
guard let strongSelf = self else {
return
}
if strongSelf.returnedToUserLocation {
strongSelf.pickerAnnotationContainerView.isHidden = true
strongSelf.customUserLocationAnnotationView?.isHidden = false
}
}
if strongSelf.returnedToUserLocation {
pickerAnnotationView.setCustom(false, animated: true)
}
}))
}
private func resetScheduledPin() {
self.pinDisposable.set(nil)
}
func updateLayout(size: CGSize) {
self.pickerAnnotationContainerView.frame = CGRect(x: 0.0, y: floorToScreenPixels((size.height - size.width) / 2.0), width: size.width, height: size.width)
if let pickerAnnotationView = self.pickerAnnotationView {
pickerAnnotationView.center = CGPoint(x: self.pickerAnnotationContainerView.frame.width / 2.0, y: self.pickerAnnotationContainerView.frame.height / 2.0 + 16.0)
pickerAnnotationView.center = CGPoint(x: self.pickerAnnotationContainerView.frame.width / 2.0, y: self.pickerAnnotationContainerView.frame.height / 2.0)
}
}
}

View File

@ -10,6 +10,8 @@ import TelegramPresentationData
import AccountContext
import AppBundle
import CoreLocation
import PresentationDataUtils
import DeviceAccess
public enum LocationPickerMode {
case share(peer: Peer?, selfPeer: Peer?, hasLiveLocation: Bool)
@ -23,24 +25,28 @@ class LocationPickerInteraction {
let toggleMapModeSelection: () -> Void
let updateMapMode: (LocationMapMode) -> Void
let goToUserLocation: () -> Void
let goToCoordinate: (CLLocationCoordinate2D) -> Void
let openSearch: () -> Void
let updateSearchQuery: (String) -> Void
let dismissSearch: () -> Void
let dismissInput: () -> Void
let updateSendActionHighlight: (Bool) -> Void
let openHomeWorkInfo: () -> Void
init(sendLocation: @escaping (CLLocationCoordinate2D) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, sendVenue: @escaping (TelegramMediaMap) -> Void, toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, openSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void, dismissSearch: @escaping () -> Void, dismissInput: @escaping () -> Void, updateSendActionHighlight: @escaping (Bool) -> Void) {
init(sendLocation: @escaping (CLLocationCoordinate2D) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, sendVenue: @escaping (TelegramMediaMap) -> Void, toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, openSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void, dismissSearch: @escaping () -> Void, dismissInput: @escaping () -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, openHomeWorkInfo: @escaping () -> Void) {
self.sendLocation = sendLocation
self.sendLiveLocation = sendLiveLocation
self.sendVenue = sendVenue
self.toggleMapModeSelection = toggleMapModeSelection
self.updateMapMode = updateMapMode
self.goToUserLocation = goToUserLocation
self.goToCoordinate = goToCoordinate
self.openSearch = openSearch
self.updateSearchQuery = updateSearchQuery
self.dismissSearch = dismissSearch
self.dismissInput = dismissInput
self.updateSendActionHighlight = updateSendActionHighlight
self.openHomeWorkInfo = openHomeWorkInfo
}
}
@ -56,6 +62,10 @@ public final class LocationPickerController: ViewController {
private var presentationDataDisposable: Disposable?
private var searchNavigationContentNode: LocationSearchNavigationContentNode?
private var isSearchingDisposable = MetaDisposable()
private let locationManager = CLLocationManager()
private var permissionDisposable: Disposable?
private var interaction: LocationPickerInteraction?
@ -88,6 +98,29 @@ public final class LocationPickerController: ViewController {
strongSelf.controllerNode.updatePresentationData(presentationData)
})
self.permissionDisposable = (DeviceAccess.authorizationStatus(subject: .location(.send))
|> deliverOnMainQueue).start(next: { [weak self] next in
guard let strongSelf = self else {
return
}
switch next {
case .notDetermined:
DeviceAccess.authorizeAccess(to: .location(.send), locationManager: strongSelf.locationManager, presentationData: strongSelf.presentationData, present: { c, a in
strongSelf.present(c, in: .window(.root), with: a)
}, openSettings: {
strongSelf.context.sharedContext.applicationBindings.openSettings()
})
case .denied:
strongSelf.controllerNode.updateState { state in
var state = state
state.forceSelection = true
return state
}
default:
break
}
})
let locationWithTimeout: (CLLocationCoordinate2D, Int32?) -> TelegramMediaMap = { coordinate, timeout in
return TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: timeout)
}
@ -143,7 +176,12 @@ public final class LocationPickerController: ViewController {
guard let strongSelf = self else {
return
}
completion(venue, nil)
let venueType = venue.venue?.type ?? ""
if ["home", "work"].contains(venueType) {
completion(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil), nil)
} else {
completion(venue, nil)
}
strongSelf.dismiss()
}, toggleMapModeSelection: { [weak self] in
guard let strongSelf = self else {
@ -174,6 +212,16 @@ public final class LocationPickerController: ViewController {
state.selectedLocation = .none
return state
}
}, goToCoordinate: { [weak self] coordinate in
guard let strongSelf = self else {
return
}
strongSelf.controllerNode.updateState { state in
var state = state
state.displayingMapModeOptions = false
state.selectedLocation = .location(coordinate, nil)
return state
}
}, openSearch: { [weak self] in
guard let strongSelf = self, let interaction = strongSelf.interaction, let navigationBar = strongSelf.navigationBar else {
return
@ -186,8 +234,15 @@ public final class LocationPickerController: ViewController {
let contentNode = LocationSearchNavigationContentNode(presentationData: strongSelf.presentationData, interaction: interaction)
strongSelf.searchNavigationContentNode = contentNode
navigationBar.setContentNode(contentNode, animated: true)
strongSelf.controllerNode.activateSearch(navigationBar: navigationBar)
let isSearching = strongSelf.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)
}
}))
}, updateSearchQuery: { [weak self] query in
guard let strongSelf = self else {
return
@ -197,6 +252,7 @@ public final class LocationPickerController: ViewController {
guard let strongSelf = self, let navigationBar = strongSelf.navigationBar else {
return
}
strongSelf.isSearchingDisposable.set(nil)
strongSelf.searchNavigationContentNode?.deactivate()
strongSelf.searchNavigationContentNode = nil
navigationBar.setContentNode(nil, animated: true)
@ -211,6 +267,13 @@ public final class LocationPickerController: ViewController {
return
}
strongSelf.controllerNode.updateSendActionHighlight(highlighted)
}, openHomeWorkInfo: { [weak self] in
guard let strongSelf = self else {
return
}
let controller = textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.Map_HomeAndWorkTitle, text: strongSelf.presentationData.strings.Map_HomeAndWorkInfo, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})])
strongSelf.present(controller, in: .window(.root))
})
self.scrollToTop = { [weak self] in
@ -228,6 +291,8 @@ public final class LocationPickerController: ViewController {
deinit {
self.presentationDataDisposable?.dispose()
self.permissionDisposable?.dispose()
self.isSearchingDisposable.dispose()
}
override public func loadDisplayNode() {

View File

@ -151,9 +151,12 @@ private enum LocationPickerEntry: Comparable, Identifiable {
case let .header(theme, title):
return LocationSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title)
case let .venue(theme, venue, _):
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), account: account, venue: venue, sectionId: 0, style: .plain, action: {
let venueType = venue.venue?.type ?? ""
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), account: account, venue: venue, style: .plain, action: {
interaction?.sendVenue(venue)
})
}, infoAction: ["home", "work"].contains(venueType) ? {
interaction?.openHomeWorkInfo()
} : nil)
case let .attribution(theme):
return LocationAttributionItem(presentationData: ItemListPresentationData(presentationData))
}
@ -220,11 +223,13 @@ struct LocationPickerState {
var mapMode: LocationMapMode
var displayingMapModeOptions: Bool
var selectedLocation: LocationPickerLocation
var forceSelection: Bool
init() {
self.mapMode = .map
self.displayingMapModeOptions = false
self.selectedLocation = .none
self.forceSelection = false
}
}
@ -415,10 +420,23 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
strongSelf.headerNode.mapNode.resetAnnotationSelection()
case .selecting:
strongSelf.headerNode.mapNode.resetAnnotationSelection()
case let .location(coordinate, _):
var updateMap = false
switch previousState.selectedLocation {
case .none, .venue:
updateMap = true
case let .location(previousCoordinate, address):
if previousCoordinate != coordinate {
updateMap = true
}
default:
break
}
if updateMap {
strongSelf.headerNode.mapNode.setMapCenter(coordinate: coordinate, isUserLocation: false, animated: true)
}
case let .venue(venue):
strongSelf.headerNode.mapNode.setMapCenter(coordinate: venue.coordinate, animated: true)
default:
break
}
let annotations: [LocationPinAnnotation]
@ -482,7 +500,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
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, padding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, size: headerFrame.size, transition: listTransition)
strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition)
strongSelf.layoutActivityIndicator(transition: listTransition)
}
@ -590,9 +608,9 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
})
}
func activateSearch(navigationBar: NavigationBar) {
func activateSearch(navigationBar: NavigationBar) -> Signal<Bool, NoError> {
guard let (layout, navigationBarHeight) = self.validLayout, self.searchContainerNode == nil, let coordinate = self.headerNode.mapNode.mapCenterCoordinate else {
return
return .complete()
}
let searchContainerNode = LocationSearchContainerNode(context: self.context, coordinate: coordinate, interaction: self.interaction)
@ -602,6 +620,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
searchContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.containerLayoutUpdated(layout, navigationHeight: navigationBarHeight, transition: .immediate)
return searchContainerNode.isSearching
}
func deactivateSearch() {
@ -643,10 +663,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
let isFirstLayout = self.validLayout == nil
self.validLayout = (layout, navigationHeight)
let isPickingLocation = self.state.selectedLocation.isCustom || self.state.forceSelection
let optionsHeight: CGFloat = 38.0
let pickingCustomLocation = self.state.selectedLocation.isCustom
var actionHeight: CGFloat?
self.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? LocationActionListItemNode {
@ -659,7 +677,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
let topInset: CGFloat = floor((layout.size.height - navigationHeight) / 2.0 + navigationHeight)
let overlap: CGFloat = 6.0
let headerHeight: CGFloat
if pickingCustomLocation, let actionHeight = actionHeight {
if isPickingLocation, let actionHeight = actionHeight {
self.listOffset = topInset
headerHeight = layout.size.height - actionHeight - layout.intrinsicInsets.bottom + overlap - 2.0
} else if let listOffset = self.listOffset {
@ -669,26 +687,29 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
}
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, padding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, size: headerFrame.size, transition: transition)
self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, offset: 0.0, size: headerFrame.size, transition: transition)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let scrollToItem: ListViewScrollToItem?
if pickingCustomLocation {
if isPickingLocation {
scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: curve, directionHint: .Up)
} else {
scrollToItem = nil
}
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), headerInsets: UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), scrollIndicatorInsets: UIEdgeInsets(top: topInset + 3.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.listNode.scrollEnabled = !pickingCustomLocation
let insets = UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), scrollIndicatorInsets: UIEdgeInsets(top: topInset + 3.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.listNode.scrollEnabled = !isPickingLocation
var listFrame: CGRect = CGRect(origin: CGPoint(), size: layout.size)
if pickingCustomLocation {
if isPickingLocation {
listFrame.origin.y = headerHeight - topInset - overlap
}
transition.updateFrame(node: self.listNode, frame: listFrame)
transition.updateAlpha(node: self.shadeNode, alpha: pickingCustomLocation ? 1.0 : 0.0)
transition.updateAlpha(node: self.shadeNode, alpha: isPickingLocation ? 1.0 : 0.0)
transition.updateFrame(node: self.shadeNode, frame: CGRect(x: 0.0, y: listFrame.minY + topInset + (actionHeight ?? 0.0) - 3.0, width: layout.size.width, height: 10000.0))
self.shadeNode.isUserInteractionEnabled = pickingCustomLocation
self.shadeNode.isUserInteractionEnabled = isPickingLocation
self.innerShadeNode.frame = CGRect(x: 0.0, y: 4.0, width: layout.size.width, height: 10000.0)
self.innerShadeNode.alpha = layout.intrinsicInsets.bottom > 0.0 ? 1.0 : 0.0

View File

@ -13,14 +13,18 @@ import AccountContext
import ItemListVenueItem
import ItemListUI
import MapKit
import Geocoding
import ChatListSearchItemHeader
private struct LocationSearchEntry: Identifiable, Comparable {
let index: Int
let theme: PresentationTheme
let venue: TelegramMediaMap
let location: TelegramMediaMap
let title: String?
let distance: Double
var stableId: String {
return self.venue.venue?.id ?? ""
return self.location.venue?.id ?? ""
}
static func ==(lhs: LocationSearchEntry, rhs: LocationSearchEntry) -> Bool {
@ -30,7 +34,13 @@ private struct LocationSearchEntry: Identifiable, Comparable {
if lhs.theme !== rhs.theme {
return false
}
if lhs.venue.venue?.id != rhs.venue.venue?.id {
if lhs.location.venue?.id != rhs.location.venue?.id {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.distance != rhs.distance {
return false
}
return true
@ -41,10 +51,19 @@ private struct LocationSearchEntry: Identifiable, Comparable {
}
func item(account: Account, presentationData: PresentationData, sendVenue: @escaping (TelegramMediaMap) -> Void) -> ListViewItem {
let venue = self.venue
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), account: account, venue: self.venue, sectionId: 0, style: .plain, action: {
let venue = self.location
let header: ChatListSearchItemHeader
let subtitle: String?
if let _ = venue.venue {
header = ChatListSearchItemHeader(type: .nearbyVenues, theme: presentationData.theme, strings: presentationData.strings)
subtitle = nil
} else {
header = ChatListSearchItemHeader(type: .mapAddress, theme: presentationData.theme, strings: presentationData.strings)
subtitle = presentationData.strings.Map_DistanceAway(stringForDistance(strings: presentationData.strings, distance: self.distance)).0
}
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), account: account, venue: self.location, title: self.title, subtitle: subtitle, style: .plain, action: {
sendVenue(venue)
})
}, header: header)
}
}
@ -81,10 +100,17 @@ final class LocationSearchContainerNode: ASDisplayNode {
private var containerViewLayout: (ContainerViewLayout, CGFloat)?
private var enqueuedTransitions: [LocationSearchContainerTransition] = []
private let _isSearching = ValuePromise<Bool>(false, ignoreRepeated: true)
var isSearching: Signal<Bool, NoError> {
return self._isSearching.get()
}
public init(context: AccountContext, coordinate: CLLocationCoordinate2D, interaction: LocationPickerInteraction) {
self.context = context
self.interaction = interaction
let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings))
@ -107,6 +133,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
let themeAndStringsPromise = self.themeAndStringsPromise
let isSearching = self._isSearching
let searchItems = self.searchQuery.get()
|> mapToSignal { query -> Signal<String?, NoError> in
if let query = query, !query.isEmpty {
@ -119,19 +146,43 @@ final class LocationSearchContainerNode: ASDisplayNode {
|> mapToSignal { query -> Signal<[LocationSearchEntry]?, NoError> in
if let query = query, !query.isEmpty {
let foundVenues = nearbyVenues(account: context.account, latitude: coordinate.latitude, longitude: coordinate.longitude, query: query)
return combineLatest(foundVenues, themeAndStringsPromise.get())
|> afterCompleted {
isSearching.set(false)
}
let foundPlacemarks = geocodeLocation(address: query)
return combineLatest(foundVenues, foundPlacemarks, themeAndStringsPromise.get())
|> delay(0.1, queue: Queue.concurrentDefaultQueue())
|> map { venues, themeAndStrings -> [LocationSearchEntry] in
|> beforeStarted {
isSearching.set(true)
}
|> map { venues, placemarks, themeAndStrings -> [LocationSearchEntry] in
var entries: [LocationSearchEntry] = []
var index: Int = 0
if let placemarks = placemarks {
for placemark in placemarks {
guard let placemarkLocation = placemark.location else {
continue
}
let location = TelegramMediaMap(latitude: placemarkLocation.coordinate.latitude, longitude: placemarkLocation.coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)
entries.append(LocationSearchEntry(index: index, theme: themeAndStrings.0, location: location, title: placemark.name ?? "Name", distance: placemarkLocation.distance(from: currentLocation)))
index += 1
}
}
for venue in venues {
entries.append(LocationSearchEntry(index: index, theme: themeAndStrings.0, venue: venue))
entries.append(LocationSearchEntry(index: index, theme: themeAndStrings.0, location: venue, title: nil, distance: 0.0))
index += 1
}
return entries
}
} else {
return .single(nil)
|> afterCompleted {
isSearching.set(true)
}
}
}
@ -141,7 +192,12 @@ final class LocationSearchContainerNode: ASDisplayNode {
if let strongSelf = self {
let previousItems = previousSearchItems.swap(items ?? [])
let transition = locationSearchContainerPreparedTransition(from: previousItems, to: items ?? [], isSearching: items != nil, account: context.account, presentationData: strongSelf.presentationData, sendVenue: { venue in self?.listNode.clearHighlightAnimated(true)
self?.interaction.sendVenue(venue)
if let _ = venue.venue {
self?.interaction.sendVenue(venue)
} else {
self?.interaction.goToCoordinate(venue.coordinate)
self?.interaction.dismissSearch()
}
})
strongSelf.enqueueTransition(transition)
}

View File

@ -54,6 +54,10 @@ final class LocationSearchNavigationContentNode: NavigationBarContentNode {
self.searchBar.deactivate(clear: false)
}
func updateActivity(_ activity: Bool) {
self.searchBar.activity = activity
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings)

View File

@ -468,7 +468,9 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat
entries.append(.useLessVoiceData(presentationData.theme, presentationData.strings.CallSettings_UseLessData, stringForUseLessDataSetting(dataSaving, strings: presentationData.strings)))
entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other))
entries.append(.shareSheet(presentationData.theme, "Share Sheet"))
if #available(iOSApplicationExtension 13.2, iOS 13.2, *) {
entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings))
}
entries.append(.saveIncomingPhotos(presentationData.theme, presentationData.strings.Settings_SaveIncomingPhotos))
entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos))
entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser))

View File

@ -150,7 +150,6 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .suggestHeader(lhsTheme, lhsText):
if case let .suggestHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_maphome.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_mapwork.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -82,7 +82,7 @@ public struct IntentsSettings: PreferencesEntry, Equatable {
return IntentsSettings(initiallyReset: self.initiallyReset, account: self.account, contacts: self.contacts, privateChats: self.privateChats, savedMessages: self.savedMessages, groups: groups, onlyShared: self.onlyShared)
}
public func withUpdatedOnlyShared(_ savedMessages: Bool) -> IntentsSettings {
public func withUpdatedOnlyShared(_ onlyShared: Bool) -> IntentsSettings {
return IntentsSettings(initiallyReset: self.initiallyReset, account: self.account, contacts: self.contacts, privateChats: self.privateChats, savedMessages: self.savedMessages, groups: self.groups, onlyShared: onlyShared)
}
}