Add venues list in location group creation

This commit is contained in:
Ilya Laktyushin 2019-11-16 08:27:56 +04:00
parent 349e911e24
commit 8ebefb813d
34 changed files with 4797 additions and 4072 deletions

View File

@ -5106,3 +5106,11 @@ Any member of this group will be able to see messages in the channel.";
"GroupInfo.ShowMoreMembers_any" = "%@ more";
"ContactInfo.Note" = "note";
"Group.Location.CreateInThisPlace" = "Create a group in this place";
"Theme.Colors.Accent" = "Accent";
"Theme.Colors.Background" = "Background";
"Theme.Colors.Messages" = "Messages";
"Theme.Colors.ColorWallpaperWarning" = "Are you sure you want to change your chat wallpaper to a color?";

View File

@ -486,7 +486,7 @@ public final class AvatarNode: ASDisplayNode {
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIcon"), color: theme.list.freeMonoIconColor) {
if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIcon"), color: theme.list.itemAccentColor) {
context.draw(editAvatarIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - editAvatarIcon.size.width) / 2.0), y: floor((bounds.size.height - editAvatarIcon.size.height) / 2.0)), size: editAvatarIcon.size))
}
} else if case .archivedChatsIcon = parameters.icon {

View File

@ -0,0 +1,268 @@
import Foundation
import UIKit
import CoreGraphics
import SwiftSignalKit
private enum Corner: Hashable {
case TopLeft(Int), TopRight(Int), BottomLeft(Int), BottomRight(Int)
var hashValue: Int {
switch self {
case let .TopLeft(radius):
return radius | (1 << 24)
case let .TopRight(radius):
return radius | (2 << 24)
case let .BottomLeft(radius):
return radius | (3 << 24)
case let .BottomRight(radius):
return radius | (4 << 24)
}
}
var radius: Int {
switch self {
case let .TopLeft(radius):
return radius
case let .TopRight(radius):
return radius
case let .BottomLeft(radius):
return radius
case let .BottomRight(radius):
return radius
}
}
}
private func ==(lhs: Corner, rhs: Corner) -> Bool {
switch lhs {
case let .TopLeft(lhsRadius):
switch rhs {
case let .TopLeft(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
case let .TopRight(lhsRadius):
switch rhs {
case let .TopRight(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
case let .BottomLeft(lhsRadius):
switch rhs {
case let .BottomLeft(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
case let .BottomRight(lhsRadius):
switch rhs {
case let .BottomRight(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
}
}
private enum Tail: Hashable {
case BottomLeft(Int)
case BottomRight(Int)
var hashValue: Int {
switch self {
case let .BottomLeft(radius):
return radius | (1 << 24)
case let .BottomRight(radius):
return radius | (2 << 24)
}
}
var radius: Int {
switch self {
case let .BottomLeft(radius):
return radius
case let .BottomRight(radius):
return radius
}
}
}
private func ==(lhs: Tail, rhs: Tail) -> Bool {
switch lhs {
case let .BottomLeft(lhsRadius):
switch rhs {
case let .BottomLeft(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
case let .BottomRight(lhsRadius):
switch rhs {
case let .BottomRight(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
}
}
private var cachedCorners = Atomic<[Corner: DrawingContext]>(value: [:])
private var cachedTails = Atomic<[Tail: DrawingContext]>(value: [:])
private func cornerContext(_ corner: Corner) -> DrawingContext {
let cached: DrawingContext? = cachedCorners.with {
return $0[corner]
}
if let cached = cached {
return cached
} else {
let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), clear: true)
context.withContext { c in
c.setBlendMode(.copy)
c.setFillColor(UIColor.black.cgColor)
let rect: CGRect
switch corner {
case let .TopLeft(radius):
rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .TopRight(radius):
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .BottomLeft(radius):
rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .BottomRight(radius):
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
}
c.fillEllipse(in: rect)
}
let _ = cachedCorners.modify { current in
var current = current
current[corner] = context
return current
}
return context
}
}
private func tailContext(_ tail: Tail) -> DrawingContext {
let cached: DrawingContext? = cachedTails.with {
return $0[tail]
}
if let cached = cached {
return cached
} else {
let context = DrawingContext(size: CGSize(width: CGFloat(tail.radius) + 3.0, height: CGFloat(tail.radius)), clear: true)
context.withContext { c in
c.setBlendMode(.copy)
c.setFillColor(UIColor.black.cgColor)
let rect: CGRect
switch tail {
case let .BottomLeft(radius):
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
c.move(to: CGPoint(x: 3.0, y: 1.0))
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
c.closePath()
c.fillPath()
case let .BottomRight(radius):
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
c.translateBy(x: context.size.width / 2.0, y: context.size.height / 2.0)
c.scaleBy(x: -1.0, y: 1.0)
c.translateBy(x: -context.size.width / 2.0, y: -context.size.height / 2.0)
c.move(to: CGPoint(x: 3.0, y: 1.0))
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
c.closePath()
c.fillPath()
}
c.fillEllipse(in: rect)
}
let _ = cachedTails.modify { current in
var current = current
current[tail] = context
return current
}
return context
}
}
public func addCorners(_ context: DrawingContext, arguments: TransformImageArguments) {
let corners = arguments.corners
let drawingRect = arguments.drawingRect
if case let .Corner(radius) = corners.topLeft, radius > CGFloat.ulpOfOne {
let corner = cornerContext(.TopLeft(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.minY))
}
if case let .Corner(radius) = corners.topRight, radius > CGFloat.ulpOfOne {
let corner = cornerContext(.TopRight(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.minY))
}
switch corners.bottomLeft {
case let .Corner(radius):
if radius > CGFloat.ulpOfOne {
let corner = cornerContext(.BottomLeft(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
}
case let .Tail(radius, enabled):
if radius > CGFloat.ulpOfOne {
if enabled {
let tail = tailContext(.BottomLeft(Int(radius)))
let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0))
context.withContext { c in
c.clear(CGRect(x: drawingRect.minX - 3.0, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
c.setFillColor(color.cgColor)
c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
}
context.blt(tail, at: CGPoint(x: drawingRect.minX - 3.0, y: drawingRect.maxY - radius))
} else {
let corner = cornerContext(.BottomLeft(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
}
}
}
switch corners.bottomRight {
case let .Corner(radius):
if radius > CGFloat.ulpOfOne {
let corner = cornerContext(.BottomRight(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
}
case let .Tail(radius, enabled):
if radius > CGFloat.ulpOfOne {
if enabled {
let tail = tailContext(.BottomRight(Int(radius)))
let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0))
context.withContext { c in
c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
c.setFillColor(color.cgColor)
c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
}
context.blt(tail, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
} else {
let corner = cornerContext(.BottomRight(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
}
}
}
}

View File

@ -20,6 +20,7 @@ static_library(
"//submodules/MosaicLayout:MosaicLayout",
"//submodules/LocationUI:LocationUI",
"//submodules/AppBundle:AppBundle",
"//submodules/LocationResources:LocationResources",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -11,6 +11,7 @@ import AccountContext
import RadialStatusNode
import PhotoResources
import MediaResources
import LocationResources
import LiveLocationPositionNode
import AppBundle
@ -230,7 +231,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
let makePinLayout = self.pinNode.asyncLayout()
let theme = self.context.sharedContext.currentPresentationData.with { $0 }.theme
let (pinSize, pinApply) = makePinLayout(self.context.account, theme, nil, false)
let (pinSize, pinApply) = makePinLayout(self.context.account, theme, .location(nil))
self.pinNode.frame = CGRect(origin: CGPoint(x: floor((size.width - pinSize.width) / 2.0), y: floor(size.height * 0.5 - 10.0 - pinSize.height / 2.0)), size: pinSize)
pinApply()
} else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) {

View File

@ -807,12 +807,12 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
strongSelf.addSubnode(inputSeparator)
strongSelf.inputSeparator = inputSeparator
}
strongSelf.inputSeparator?.backgroundColor = itemSeparatorColor
strongSelf.inputSeparator?.backgroundColor = .clear
if strongSelf.inputFirstField == nil {
let inputFirstField = TextFieldNodeView()
inputFirstField.delegate = self
inputFirstField.font = Font.regular(17.0)
inputFirstField.font = Font.regular(19.0)
inputFirstField.autocorrectionType = .no
inputFirstField.attributedText = NSAttributedString(string: title, font: Font.regular(19.0), textColor: item.theme.list.itemPrimaryTextColor)
strongSelf.inputFirstField = inputFirstField
@ -845,8 +845,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
strongSelf.addSubnode(strongSelf.inputFirstClearButton!)
}
strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 62.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 100.0, height: separatorHeight))
strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 26.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 35.0))
strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 64.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 100.0, height: separatorHeight))
strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 28.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 35.0))
if let image = strongSelf.inputFirstClearButton?.image(for: []), let inputFieldFrame = strongSelf.inputFirstField?.frame {
strongSelf.inputFirstClearButton?.frame = CGRect(origin: CGPoint(x: inputFieldFrame.maxX, y: inputFieldFrame.minY + floor((inputFieldFrame.size.height - image.size.height) / 2.0) - 1.0 + UIScreenPixel), size: image.size)

View File

@ -0,0 +1,25 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "ItemListVenueItem",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/ItemListUI:ItemListUI",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/LocationResources:LocationResources",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
)

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View File

@ -0,0 +1,309 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import ItemListUI
import LocationResources
public final class ItemListVenueItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let account: Account
let venue: TelegramMediaMap
public let sectionId: ItemListSectionId
let action: (() -> Void)?
public init(theme: PresentationTheme, account: Account, venue: TelegramMediaMap, sectionId: ItemListSectionId, action: (() -> Void)?) {
self.theme = theme
self.account = account
self.venue = venue
self.sectionId = sectionId
self.action = action
}
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 {
let node = ItemListVenueItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? ItemListVenueItemNode {
let makeLayout = nodeValue.asyncLayout()
var animated = true
if case .None = animation {
animated = false
}
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
public var selectable: Bool = true
public func selected(listView: ListView){
listView.clearHighlightAnimated(true)
self.action?()
}
}
private let titleFont = Font.regular(17.0)
private let addressFont = Font.regular(14.0)
public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let iconNode: TransformImageNode
private let titleNode: TextNode
private let addressNode: TextNode
private var layoutParams: (ItemListVenueItem, ListViewItemLayoutParams, ItemListNeighbors)?
public var tag: ItemListItemTag?
override public var canBeSelected: Bool {
if let item = self.layoutParams?.0, let _ = item.action {
return true
} else {
return false
}
}
public init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.iconNode = TransformImageNode()
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.contentMode = .left
self.titleNode.contentsScale = UIScreen.main.scale
self.addressNode = TextNode()
self.addressNode.isUserInteractionEnabled = false
self.addressNode.contentMode = .left
self.addressNode.contentsScale = UIScreen.main.scale
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.isAccessibilityElement = true
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.addressNode)
}
public func asyncLayout() -> (_ item: ItemListVenueItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (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
var updatedTheme: PresentationTheme?
var updatedVenueType: String?
if currentItem?.theme !== item.theme {
updatedTheme = item.theme
}
let venueType = item.venue.venue?.type ?? ""
if currentItem?.venue.venue?.type != venueType {
updatedVenueType = venueType
}
let titleAttributedString = NSAttributedString(string: item.venue.venue?.title ?? "", font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
let addressAttributedString = NSAttributedString(string: item.venue.venue?.address ?? "", font: addressFont, textColor: item.theme.list.itemSecondaryTextColor)
let leftInset: CGFloat = 65.0 + params.leftInset
let rightInset: CGFloat = params.rightInset
let height: CGFloat = 50.0
let iconSize: CGFloat = 40.0
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: addressAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let insets = itemListNeighborsGroupedInsets(neighbors)
let contentSize = CGSize(width: params.width, height: height)
let separatorHeight = UIScreenPixel
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.layoutParams = (item, params, neighbors)
strongSelf.accessibilityLabel = titleAttributedString.string
strongSelf.accessibilityValue = addressAttributedString.string
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
}
let transition = ContainedViewLayoutTransition.immediate
let _ = titleApply()
let _ = addressApply()
if let updatedVenueType = updatedVenueType {
strongSelf.iconNode.setSignal(venueIcon(postbox: item.account.postbox, type: updatedVenueType, background: true))
}
let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets()))
iconApply()
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
bottomStripeOffset = -separatorHeight
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: (addressAttributedString.string.isEmpty ? 14.0 : 6.0)), size: titleLayout.size))
transition.updateFrame(node: strongSelf.addressNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 27.0), size: addressLayout.size))
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((height - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize)))
// if item.peer.id == item.account.peerId, case .threatSelfAsSaved = item.aliasHandling {
// strongSelf.avatarNode.setPeer(account: item.account, theme: item.theme, peer: item.peer, overrideImage: .savedMessagesIcon, emptyColor: item.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
// } else {
// var overrideImage: AvatarNodeImageOverride?
// if item.peer.isDeleted {
// overrideImage = .deletedIcon
// }
// strongSelf.avatarNode.setPeer(account: item.account, theme: item.theme, peer: item.peer, overrideImage: overrideImage, emptyColor: item.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
// }
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel + UIScreenPixel))
}
})
}
}
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
var anchorNode: ASDisplayNode?
if self.bottomStripeNode.supernode != nil {
anchorNode = self.bottomStripeNode
} else if self.topStripeNode.supernode != nil {
anchorNode = self.topStripeNode
} else if self.backgroundNode.supernode != nil {
anchorNode = self.backgroundNode
}
if let anchorNode = anchorNode {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
} else {
self.addSubnode(self.highlightedBackgroundNode)
}
}
} else {
if self.highlightedBackgroundNode.supernode != nil {
if animated {
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
if let strongSelf = self {
if completed {
strongSelf.highlightedBackgroundNode.removeFromSupernode()
}
}
})
self.highlightedBackgroundNode.alpha = 0.0
} else {
self.highlightedBackgroundNode.removeFromSupernode()
}
}
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
}

View File

@ -178,16 +178,16 @@ func importLegacyPreferences(accountManager: AccountManager, account: TemporaryA
//themeSpecificAccentColors: current.themeSpecificAccentColors
//settings.themeAccentColor = presentationState.userInfo
}
settings.chatWallpaper = .color(0xffffff)
settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0xffffff)
case 2:
settings.theme = .builtin(.night)
settings.chatWallpaper = .color(0x00000)
settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0x000000)
case 3:
settings.theme = .builtin(.nightAccent)
settings.chatWallpaper = .color(0x18222D)
settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0x18222d)
default:
settings.theme = .builtin(.dayClassic)
settings.chatWallpaper = .builtin(WallpaperSettings())
settings.themeSpecificChatWallpapers[settings.theme.index] = .builtin(WallpaperSettings())
}
let fontSizeMap: [Int32: PresentationFontSize] = [
14: .extraSmall,

View File

@ -14,6 +14,7 @@ static_library(
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AvatarNode:AvatarNode",
"//submodules/AppBundle:AppBundle",
"//submodules/LocationResources:LocationResources",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -7,6 +7,7 @@ import SyncCore
import Postbox
import TelegramPresentationData
import AvatarNode
import LocationResources
import AppBundle
private let avatarFont = avatarPlaceholderFont(size: 24.0)
@ -36,12 +37,31 @@ private func removePulseAnimations(layer: CALayer) {
layer.removeAnimation(forKey: "pulse-opacity")
}
private func chatBubbleMapPinImage(_ theme: PresentationTheme, color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 62.0, height: 74.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if let shadowImage = UIImage(bundleImageName: "Chat/Message/LocationPinShadow"), let cgImage = shadowImage.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: shadowImage.size))
}
if let backgroundImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/LocationPinBackground"), color: color), let cgImage = backgroundImage.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: backgroundImage.size))
}
})
}
public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
public enum Mode {
case liveLocation(Peer, Bool)
case location(TelegramMediaMap?)
}
private let backgroundNode: ASImageNode
private let iconNode: TransformImageNode
private let avatarNode: AvatarNode
private let pulseNode: ASImageNode
private var pulseImage: UIImage?
private var venueType: String?
override public init() {
let isLayerBacked = !smartInvertColorsEnabled()
@ -51,6 +71,9 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.iconNode = TransformImageNode()
self.iconNode.isLayerBacked = true
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = isLayerBacked
@ -66,23 +89,32 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
self.addSubnode(self.pulseNode)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.avatarNode)
}
public func asyncLayout() -> (_ account: Account, _ theme: PresentationTheme, _ peer: Peer?, _ liveActive: Bool?) -> (CGSize, () -> Void) {
let currentPulseImage = self.pulseImage
public func asyncLayout() -> (_ account: Account, _ theme: PresentationTheme, _ mode: Mode) -> (CGSize, () -> Void) {
let iconLayout = self.iconNode.asyncLayout()
return { [weak self] account, theme, peer, liveActive in
let currentPulseImage = self.pulseImage
let currentVenueType = self.venueType
return { [weak self] account, theme, mode in
var updatedVenueType: String?
let backgroundImage: UIImage?
var hasPulse = false
if let _ = peer {
backgroundImage = avatarBackgroundImage
if let liveActive = liveActive {
hasPulse = liveActive
}
} else {
backgroundImage = PresentationResourcesChat.chatBubbleMapPinImage(theme)
switch mode {
case let .liveLocation(_, active):
backgroundImage = avatarBackgroundImage
hasPulse = active
case let .location(location):
let venueType = location?.venue?.type ?? ""
let color = venueType.isEmpty ? theme.list.itemAccentColor : venueIconColor(type: venueType)
backgroundImage = chatBubbleMapPinImage(theme, color: color)
if currentVenueType != venueType {
updatedVenueType = venueType
}
}
let pulseImage: UIImage?
@ -99,19 +131,28 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
}
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 74.0))
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
if let peer = peer {
strongSelf.avatarNode.setPeer(account: account, theme: theme, peer: peer)
strongSelf.avatarNode.isHidden = false
if let liveActive = liveActive {
strongSelf.avatarNode.alpha = liveActive ? 1.0 : 0.6
} else {
strongSelf.avatarNode.alpha = 1.0
}
} else {
strongSelf.avatarNode.isHidden = true
switch mode {
case let .liveLocation(peer, active):
strongSelf.avatarNode.setPeer(account: account, theme: theme, peer: peer)
strongSelf.avatarNode.isHidden = false
strongSelf.iconNode.isHidden = true
strongSelf.avatarNode.alpha = active ? 1.0 : 0.6
case let .location(location):
strongSelf.iconNode.isHidden = false
strongSelf.avatarNode.isHidden = true
}
if let updatedVenueType = updatedVenueType {
strongSelf.venueType = updatedVenueType
strongSelf.iconNode.setSignal(venueIcon(postbox: account.postbox, type: updatedVenueType, background: false))
}
let iconSize = CGSize(width: 44.0, height: 44.0)
let apply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()))
apply()
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 14.0), size: iconSize)
strongSelf.pulseImage = pulseImage
strongSelf.pulseNode.image = pulseImage
strongSelf.pulseNode.frame = CGRect(origin: CGPoint(x: floor((62.0 - 60.0) / 2.0), y: 34.0), size: CGSize(width: 60.0, height: 60.0))

View File

@ -0,0 +1,21 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "LocationResources",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/Display:Display#shared",
"//submodules/AppBundle:AppBundle",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
"$SDKROOT/System/Library/Frameworks/MapKit.framework",
],
)

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View File

@ -1,12 +0,0 @@
//
// LocationIconResources.swift
// LocationResources
//
// Created by Ilya Laktyushin on 13.11.2019.
//
import UIKit
class LocationIconResources: NSObject {
}

View File

@ -1,5 +1,6 @@
import Foundation
import UIKit
import Display
import Postbox
import TelegramCore
import SyncCore
@ -138,3 +139,75 @@ public func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Sign
}
}
public func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaResource) -> Signal<Data?, NoError> {
return Signal<Data?, NoError> { subscriber in
let dataDisposable = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: MapSnapshotMediaResourceRepresentation(), complete: true).start(next: { next in
if next.size != 0 {
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}
}, error: subscriber.putError, completed: subscriber.putCompletion)
return ActionDisposable {
dataDisposable.dispose()
}
}
}
public func chatMapSnapshotImage(account: Account, resource: MapSnapshotMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = chatMapSnapshotData(account: account, resource: resource)
return signal |> map { fullSizeData in
return { arguments in
let context = DrawingContext(size: arguments.drawingSize, clear: true)
var fullSizeImage: CGImage?
var imageOrientation: UIImage.Orientation = .up
if let fullSizeData = fullSizeData {
let options = NSMutableDictionary()
options[kCGImageSourceShouldCache as NSString] = false as NSNumber
if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) {
fullSizeImage = image
}
if let fullSizeImage = fullSizeImage {
let drawingRect = arguments.drawingRect
var fittedSize = CGSize(width: CGFloat(fullSizeImage.width), height: CGFloat(fullSizeImage.height)).aspectFilled(drawingRect.size)
if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) {
fittedSize.width = arguments.boundingSize.width
}
if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) {
fittedSize.height = arguments.boundingSize.height
}
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
context.withFlippedContext { c in
c.setBlendMode(.copy)
if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height {
c.fill(arguments.drawingRect)
}
c.setBlendMode(.copy)
c.interpolationQuality = .medium
c.draw(fullSizeImage, in: fittedRect)
c.setBlendMode(.normal)
}
} else {
context.withFlippedContext { c in
c.setBlendMode(.copy)
c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor)
c.fill(arguments.drawingRect)
c.setBlendMode(.normal)
}
}
}
addCorners(context, arguments: arguments)
return context
}
}
}

View File

@ -0,0 +1,19 @@
//
// MediaResources.h
// MediaResources
//
// Created by Peter on 8/2/19.
// Copyright © 2019 Telegram Messenger LLP. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for MediaResources.
FOUNDATION_EXPORT double MediaResourcesVersionNumber;
//! Project version string for MediaResources.
FOUNDATION_EXPORT const unsigned char MediaResourcesVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <MediaResources/PublicHeader.h>

View File

@ -0,0 +1,156 @@
import Foundation
import UIKit
import Display
import Postbox
import TelegramCore
import SyncCore
import SwiftSignalKit
import AppBundle
public struct VenueIconResourceId: MediaResourceId {
public let type: String
public init(type: String) {
self.type = type
}
public var uniqueId: String {
return "venue-icon-\(self.type.replacingOccurrences(of: "/", with: "_"))"
}
public var hashValue: Int {
return self.type.hashValue
}
public func isEqual(to: MediaResourceId) -> Bool {
if let to = to as? VenueIconResourceId {
return self.type == to.type
} else {
return false
}
}
}
public class VenueIconResource: TelegramMediaResource {
public let type: String
public init(type: String) {
self.type = type
}
public required init(decoder: PostboxDecoder) {
self.type = decoder.decodeStringForKey("t", orElse: "")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.type, forKey: "t")
}
public var id: MediaResourceId {
return VenueIconResourceId(type: self.type)
}
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? VenueIconResource {
return self.type == to.type
} else {
return false
}
}
}
public func fetchVenueIconResource(account: Account, resource: VenueIconResource) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
return Signal { subscriber in
subscriber.putNext(.reset)
let url = "https://ss3.4sqi.net/img/categories_v2/\(resource.type)_88.png"
let fetchDisposable = MetaDisposable()
fetchDisposable.set(fetchHttpResource(url: url).start(next: { next in
subscriber.putNext(next)
}, completed: {
subscriber.putCompletion()
}))
return ActionDisposable {
fetchDisposable.dispose()
}
}
}
private func venueIconData(postbox: Postbox, resource: MediaResource) -> Signal<Data?, NoError> {
let resourceData = postbox.mediaBox.resourceData(resource)
let signal = resourceData
|> take(1)
|> mapToSignal { maybeData -> Signal<Data?, NoError> in
if maybeData.complete {
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
return .single((loadedData))
} else {
let fetched = postbox.mediaBox.fetchedResource(resource, parameters: nil)
let data = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetched.start()
let resourceDisposable = resourceData.start(next: { next in
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}, error: subscriber.putError, completed: subscriber.putCompletion)
return ActionDisposable {
fetchedDisposable.dispose()
resourceDisposable.dispose()
}
}
return data
}
} |> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs == nil && rhs == nil {
return true
} else {
return false
}
})
return signal
}
private let colors = [UIColor(rgb: 0xe56cd5), UIColor(rgb: 0xf89440), UIColor(rgb: 0x9986ff), UIColor(rgb: 0x44b3f5), UIColor(rgb: 0x6dc139), UIColor(rgb: 0xff5d5a), UIColor(rgb: 0xf87aad), UIColor(rgb: 0x6e82b3), UIColor(rgb: 0xf5ba21)]
public func venueIconColor(type: String) -> UIColor {
let parentType = type.components(separatedBy: "/").first ?? type
let index = Int(abs(persistentHash32(parentType)) % Int32(colors.count))
return colors[index]
}
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))
return data |> map { data in
return { arguments in
let context = DrawingContext(size: arguments.drawingSize, clear: true)
var iconImage: UIImage?
if let data = data, let image = UIImage(data: data) {
iconImage = image
}
let backgroundColor = venueIconColor(type: type)
let foregroundColor = UIColor.white
context.withFlippedContext { c in
if background {
c.setFillColor(backgroundColor.cgColor)
c.fillEllipse(in: CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
}
if let image = iconImage, let cgImage = generateTintedImage(image: image, color: foregroundColor)?.cgImage {
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))
}
}
return context
}
}
}

View File

@ -14,6 +14,5 @@ static_library(
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
"$SDKROOT/System/Library/Frameworks/MapKit.framework",
],
)

View File

@ -41,6 +41,7 @@ static_library(
"//submodules/TelegramNotices:TelegramNotices",
"//submodules/PhotoResources:PhotoResources",
"//submodules/MediaResources:MediaResources",
"//submodules/LocationResources:LocationResources",
"//submodules/Geocoding:Geocoding",
"//submodules/LegacyComponents:LegacyComponents",
"//submodules/LocationUI:LocationUI",

View File

@ -15,6 +15,7 @@ import AlertUI
import PresentationDataUtils
import PhotoResources
import MediaResources
import LocationResources
import ItemListAvatarAndNameInfoItem
import Geocoding
import ItemListAddressItem

View File

@ -21,6 +21,7 @@ import AlertUI
import PresentationDataUtils
import MediaResources
import PhotoResources
import LocationResources
import GalleryUI
import LegacyUI
import LocationUI

View File

@ -403,270 +403,6 @@ private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaRef
return signal
}
private enum Corner: Hashable {
case TopLeft(Int), TopRight(Int), BottomLeft(Int), BottomRight(Int)
var hashValue: Int {
switch self {
case let .TopLeft(radius):
return radius | (1 << 24)
case let .TopRight(radius):
return radius | (2 << 24)
case let .BottomLeft(radius):
return radius | (3 << 24)
case let .BottomRight(radius):
return radius | (4 << 24)
}
}
var radius: Int {
switch self {
case let .TopLeft(radius):
return radius
case let .TopRight(radius):
return radius
case let .BottomLeft(radius):
return radius
case let .BottomRight(radius):
return radius
}
}
}
private func ==(lhs: Corner, rhs: Corner) -> Bool {
switch lhs {
case let .TopLeft(lhsRadius):
switch rhs {
case let .TopLeft(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
case let .TopRight(lhsRadius):
switch rhs {
case let .TopRight(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
case let .BottomLeft(lhsRadius):
switch rhs {
case let .BottomLeft(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
case let .BottomRight(lhsRadius):
switch rhs {
case let .BottomRight(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
}
}
private enum Tail: Hashable {
case BottomLeft(Int)
case BottomRight(Int)
var hashValue: Int {
switch self {
case let .BottomLeft(radius):
return radius | (1 << 24)
case let .BottomRight(radius):
return radius | (2 << 24)
}
}
var radius: Int {
switch self {
case let .BottomLeft(radius):
return radius
case let .BottomRight(radius):
return radius
}
}
}
private func ==(lhs: Tail, rhs: Tail) -> Bool {
switch lhs {
case let .BottomLeft(lhsRadius):
switch rhs {
case let .BottomLeft(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
case let .BottomRight(lhsRadius):
switch rhs {
case let .BottomRight(rhsRadius) where rhsRadius == lhsRadius:
return true
default:
return false
}
}
}
private var cachedCorners = Atomic<[Corner: DrawingContext]>(value: [:])
private var cachedTails = Atomic<[Tail: DrawingContext]>(value: [:])
private func cornerContext(_ corner: Corner) -> DrawingContext {
let cached: DrawingContext? = cachedCorners.with {
return $0[corner]
}
if let cached = cached {
return cached
} else {
let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), clear: true)
context.withContext { c in
c.setBlendMode(.copy)
c.setFillColor(UIColor.black.cgColor)
let rect: CGRect
switch corner {
case let .TopLeft(radius):
rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .TopRight(radius):
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .BottomLeft(radius):
rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
case let .BottomRight(radius):
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
}
c.fillEllipse(in: rect)
}
let _ = cachedCorners.modify { current in
var current = current
current[corner] = context
return current
}
return context
}
}
private func tailContext(_ tail: Tail) -> DrawingContext {
let cached: DrawingContext? = cachedTails.with {
return $0[tail]
}
if let cached = cached {
return cached
} else {
let context = DrawingContext(size: CGSize(width: CGFloat(tail.radius) + 3.0, height: CGFloat(tail.radius)), clear: true)
context.withContext { c in
c.setBlendMode(.copy)
c.setFillColor(UIColor.black.cgColor)
let rect: CGRect
switch tail {
case let .BottomLeft(radius):
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
c.move(to: CGPoint(x: 3.0, y: 1.0))
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
c.closePath()
c.fillPath()
case let .BottomRight(radius):
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
c.translateBy(x: context.size.width / 2.0, y: context.size.height / 2.0)
c.scaleBy(x: -1.0, y: 1.0)
c.translateBy(x: -context.size.width / 2.0, y: -context.size.height / 2.0)
c.move(to: CGPoint(x: 3.0, y: 1.0))
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
c.closePath()
c.fillPath()
}
c.fillEllipse(in: rect)
}
let _ = cachedTails.modify { current in
var current = current
current[tail] = context
return current
}
return context
}
}
public func addCorners(_ context: DrawingContext, arguments: TransformImageArguments) {
let corners = arguments.corners
let drawingRect = arguments.drawingRect
if case let .Corner(radius) = corners.topLeft, radius > CGFloat.ulpOfOne {
let corner = cornerContext(.TopLeft(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.minY))
}
if case let .Corner(radius) = corners.topRight, radius > CGFloat.ulpOfOne {
let corner = cornerContext(.TopRight(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.minY))
}
switch corners.bottomLeft {
case let .Corner(radius):
if radius > CGFloat.ulpOfOne {
let corner = cornerContext(.BottomLeft(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
}
case let .Tail(radius, enabled):
if radius > CGFloat.ulpOfOne {
if enabled {
let tail = tailContext(.BottomLeft(Int(radius)))
let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0))
context.withContext { c in
c.clear(CGRect(x: drawingRect.minX - 3.0, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
c.setFillColor(color.cgColor)
c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
}
context.blt(tail, at: CGPoint(x: drawingRect.minX - 3.0, y: drawingRect.maxY - radius))
} else {
let corner = cornerContext(.BottomLeft(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
}
}
}
switch corners.bottomRight {
case let .Corner(radius):
if radius > CGFloat.ulpOfOne {
let corner = cornerContext(.BottomRight(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
}
case let .Tail(radius, enabled):
if radius > CGFloat.ulpOfOne {
if enabled {
let tail = tailContext(.BottomRight(Int(radius)))
let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0))
context.withContext { c in
c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
c.setFillColor(color.cgColor)
c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
}
context.blt(tail, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
} else {
let corner = cornerContext(.BottomRight(Int(radius)))
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
}
}
}
}
public func rawMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference) -> Signal<UIImage?, NoError> {
return chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, autoFetchFullSize: true)
|> map { value -> UIImage? in
@ -2469,90 +2205,6 @@ public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepr
}
}
public func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaResource) -> Signal<Data?, NoError> {
return Signal<Data?, NoError> { subscriber in
let dataDisposable = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: MapSnapshotMediaResourceRepresentation(), complete: true).start(next: { next in
if next.size != 0 {
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}
}, error: subscriber.putError, completed: subscriber.putCompletion)
return ActionDisposable {
dataDisposable.dispose()
}
}
}
private let locationPinImage = UIImage(named: "ModernMessageLocationPin")?.precomposed()
public func chatMapSnapshotImage(account: Account, resource: MapSnapshotMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = chatMapSnapshotData(account: account, resource: resource)
return signal |> map { fullSizeData in
return { arguments in
let context = DrawingContext(size: arguments.drawingSize, clear: true)
var fullSizeImage: CGImage?
var imageOrientation: UIImage.Orientation = .up
if let fullSizeData = fullSizeData {
let options = NSMutableDictionary()
options[kCGImageSourceShouldCache as NSString] = false as NSNumber
if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) {
imageOrientation = imageOrientationFromSource(imageSource)
fullSizeImage = image
}
if let fullSizeImage = fullSizeImage {
let drawingRect = arguments.drawingRect
var fittedSize = CGSize(width: CGFloat(fullSizeImage.width), height: CGFloat(fullSizeImage.height)).aspectFilled(drawingRect.size)
if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) {
fittedSize.width = arguments.boundingSize.width
}
if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) {
fittedSize.height = arguments.boundingSize.height
}
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
context.withFlippedContext { c in
c.setBlendMode(.copy)
if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height {
c.fill(arguments.drawingRect)
}
c.setBlendMode(.copy)
c.interpolationQuality = .medium
drawImage(context: c, image: fullSizeImage, orientation: imageOrientation, in: fittedRect)
c.setBlendMode(.normal)
if let locationPinImage = locationPinImage {
c.draw(locationPinImage.cgImage!, in: CGRect(origin: CGPoint(x: floor((arguments.drawingSize.width - locationPinImage.size.width) / 2.0), y: floor((arguments.drawingSize.height - locationPinImage.size.height) / 2.0) - 5.0), size: locationPinImage.size))
}
}
} else {
context.withFlippedContext { c in
c.setBlendMode(.copy)
c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor)
c.fill(arguments.drawingRect)
c.setBlendMode(.normal)
if let locationPinImage = locationPinImage {
c.draw(locationPinImage.cgImage!, in: CGRect(origin: CGPoint(x: floor((arguments.drawingSize.width - locationPinImage.size.width) / 2.0), y: floor((arguments.drawingSize.height - locationPinImage.size.height) / 2.0) - 5.0), size: locationPinImage.size))
}
}
}
}
addCorners(context, arguments: arguments)
return context
}
}
}
public func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
return account.postbox.mediaBox.resourceData(file.resource)
|> map { fullSizeData in

View File

@ -69,7 +69,7 @@ enum PrivacyIntroControllerMode {
}
}
final public class PrivacyIntroControllerPresentationArguments {
public final class PrivacyIntroControllerPresentationArguments {
let fadeIn: Bool
let animateIn: Bool

View File

@ -83,7 +83,7 @@ private enum CallFeedbackControllerEntryTag: ItemListItemTag {
case comment
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? CallFeedbackControllerEntryTagv {
if let other = other as? CallFeedbackControllerEntryTag {
return self == other
} else {
return false
@ -322,7 +322,7 @@ public func callFeedbackController(sharedContext: SharedAccountContext, account:
}
return false
})
if let resultItemNode = resultItemNode {
controller.ensureItemNodeVisible(resultItemNode, animated: animated)
}

View File

@ -203,9 +203,7 @@ public enum PresentationResourceKey: Int32 {
case chatBubbleIncomingCallButtonImage
case chatBubbleOutgoingCallButtonImage
case chatBubbleMapPinImage
case callListOutgoingIcon
case callListInfoButton

View File

@ -644,23 +644,6 @@ public struct PresentationResourcesChat {
})
}
public static func chatBubbleMapPinImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatBubbleMapPinImage.rawValue, { theme in
return generateImage(CGSize(width: 62.0, height: 74.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if let shadowImage = UIImage(bundleImageName: "Chat/Message/LocationPinShadow"), let cgImage = shadowImage.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: shadowImage.size))
}
if let backgroundImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/LocationPinBackground"), color: theme.list.itemAccentColor), let cgImage = backgroundImage.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: backgroundImage.size))
}
if let foregroundImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/LocationPinForeground"), color: theme.list.plainBackgroundColor), let cgImage = foregroundImage.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: 15.0, y: 26.0), size: foregroundImage.size))
}
})
})
}
public static func chatInputSearchPanelUpImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatInputSearchPanelUpImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/UpButton"), color: theme.chat.inputPanel.panelControlAccentColor)

View File

@ -198,6 +198,8 @@ framework(
"//submodules/NotificationsPresentationData:NotificationsPresentationData",
"//submodules/UrlWhitelist:UrlWhitelist",
"//submodules/AppIntents:AppIntents",
"//submodules/LocationResources:LocationResources",
"//submodules/ItemListVenueItem:ItemListVenueItem",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -9,6 +9,7 @@ import SyncCore
import LiveLocationTimerNode
import PhotoResources
import MediaResources
import LocationResources
import LiveLocationPositionNode
private let titleFont = Font.medium(14.0)
@ -141,15 +142,13 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 5.0, hidesBackground: (activeLiveBroadcastingTimeout == nil && selectedMedia?.venue == nil) ? .emptyWallpaper : .never, forceFullCorners: false, forceAlignment: .none)
var pinPeer: Peer?
var pinLiveLocationActive: Bool?
if let selectedMedia = selectedMedia {
var mode: ChatMessageLiveLocationPositionNode.Mode = .location(selectedMedia)
if let selectedMedia = selectedMedia, let peer = item.message.author {
if selectedMedia.liveBroadcastingTimeout != nil {
pinPeer = item.message.author
pinLiveLocationActive = activeLiveBroadcastingTimeout != nil
mode = .liveLocation(peer, activeLiveBroadcastingTimeout != nil)
}
}
let (pinSize, pinApply) = makePinLayout(item.context.account, item.presentationData.theme.theme, pinPeer, pinLiveLocationActive)
let (pinSize, pinApply) = makePinLayout(item.context.account, item.presentationData.theme.theme, mode)
return (contentProperties, nil, maximumWidth, { constrainedSize, position in
let imageCorners: ImageCorners

View File

@ -15,6 +15,7 @@ import AlertUI
import PresentationDataUtils
import MediaResources
import PhotoResources
import LocationResources
import LegacyUI
import LocationUI
import ItemListPeerItem
@ -24,6 +25,7 @@ import Geocoding
import PeerInfoUI
import MapResourceToAvatarSizes
import ItemListAddressItem
import ItemListVenueItem
private struct CreateGroupArguments {
let account: Account
@ -32,12 +34,14 @@ private struct CreateGroupArguments {
let done: () -> Void
let changeProfilePhoto: () -> Void
let changeLocation: () -> Void
let updateWithVenue: (TelegramMediaMap) -> Void
}
private enum CreateGroupSection: Int32 {
case info
case members
case location
case venues
}
private enum CreateGroupEntryTag: ItemListItemTag {
@ -67,6 +71,8 @@ private enum CreateGroupEntry: ItemListNodeEntry {
case location(PresentationTheme, PeerGeoLocation)
case changeLocation(PresentationTheme, String)
case locationInfo(PresentationTheme, String)
case venueHeader(PresentationTheme, String)
case venue(Int32, PresentationTheme, TelegramMediaMap)
var section: ItemListSectionId {
switch self {
@ -76,6 +82,8 @@ private enum CreateGroupEntry: ItemListNodeEntry {
return CreateGroupSection.members.rawValue
case .locationHeader, .location, .changeLocation, .locationInfo:
return CreateGroupSection.location.rawValue
case .venueHeader, .venue:
return CreateGroupSection.venues.rawValue
}
}
@ -95,6 +103,10 @@ private enum CreateGroupEntry: ItemListNodeEntry {
return 10002
case .locationInfo:
return 10003
case .venueHeader:
return 10004
case let .venue(index, _, _):
return 10005 + index
}
}
@ -189,6 +201,27 @@ private enum CreateGroupEntry: ItemListNodeEntry {
} else {
return false
}
case let .venueHeader(lhsTheme, lhsTitle):
if case let .venueHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
case let .venue(lhsIndex, lhsTheme, lhsVenue):
if case let .venue(rhsIndex, rhsTheme, rhsVenue) = rhs {
if lhsIndex != rhsIndex {
return false
}
if lhsTheme !== rhsTheme {
return false
}
if !lhsVenue.isEqual(to: rhsVenue) {
return false
}
return true
} else {
return false
}
}
}
@ -200,9 +233,10 @@ private enum CreateGroupEntry: ItemListNodeEntry {
let arguments = arguments as! CreateGroupArguments
switch self {
case let .groupInfo(theme, strings, dateTimeFormat, peer, state, avatar):
return ItemListAvatarAndNameInfoItem(account: arguments.account, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: nil, cachedData: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
return ItemListAvatarAndNameInfoItem(account: arguments.account, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer, presence: nil, cachedData: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
arguments.updateEditingName(editingName)
}, avatarTapped: {
arguments.changeProfilePhoto()
}, updatingImage: avatar, tag: CreateGroupEntryTag.info)
case let .setProfilePhoto(theme, text):
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
@ -221,6 +255,12 @@ private enum CreateGroupEntry: ItemListNodeEntry {
}, clearHighlightAutomatically: false)
case let .locationInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .venueHeader(theme, title):
return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section)
case let .venue(_, theme, venue):
return ItemListVenueItem(theme: theme, account: arguments.account, venue: venue, sectionId: self.section, action: {
arguments.updateWithVenue(venue)
})
}
}
}
@ -228,6 +268,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
private struct CreateGroupState: Equatable {
var creating: Bool
var editingName: ItemListAvatarAndNameInfoItemName
var nameSetFromVenue: Bool
var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
var location: PeerGeoLocation?
@ -238,6 +279,9 @@ private struct CreateGroupState: Equatable {
if lhs.editingName != rhs.editingName {
return false
}
if lhs.nameSetFromVenue != rhs.nameSetFromVenue {
return false
}
if lhs.avatar != rhs.avatar {
return false
}
@ -248,7 +292,7 @@ private struct CreateGroupState: Equatable {
}
}
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView) -> [CreateGroupEntry] {
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView, venues: [TelegramMediaMap]?) -> [CreateGroupEntry] {
var entries: [CreateGroupEntry] = []
let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil)
@ -256,7 +300,7 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
let peer = TelegramGroup(id: PeerId(namespace: -1, id: 0), title: state.editingName.composedTitle, photo: [], participantCount: 0, role: .creator(rank: nil), membership: .Member, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
entries.append(.groupInfo(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, groupInfoState, state.avatar))
entries.append(.setProfilePhoto(presentationData.theme, presentationData.strings.GroupInfo_SetGroupPhoto))
//entries.append(.setProfilePhoto(presentationData.theme, presentationData.strings.GroupInfo_SetGroupPhoto))
var peers: [Peer] = []
for peerId in peerIds {
@ -294,6 +338,21 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
entries.append(.location(presentationData.theme, location))
entries.append(.changeLocation(presentationData.theme, presentationData.strings.Group_Location_ChangeLocation))
entries.append(.locationInfo(presentationData.theme, presentationData.strings.Group_Location_Info))
entries.append(.venueHeader(presentationData.theme, presentationData.strings.Group_Location_CreateInThisPlace.uppercased()))
if let venues = venues {
if !venues.isEmpty {
var index: Int32 = 0
for venue in venues {
entries.append(.venue(index, presentationData.theme, venue))
index += 1
}
} else {
}
} else {
}
}
return entries
@ -305,7 +364,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
location = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address ?? "")
}
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), avatar: nil, location: location)
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), nameSetFromVenue: false, avatar: nil, location: location)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in
@ -318,6 +377,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
var pushImpl: ((ViewController) -> Void)?
var endEditingImpl: (() -> Void)?
var clearHighlightImpl: (() -> Void)?
var ensureItemVisibleImpl: ((CreateGroupEntryTag, Bool) -> Void)?
let actionsDisposable = DisposableSet()
@ -326,6 +386,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
let addressPromise = Promise<String?>(nil)
let venuesPromise = Promise<[TelegramMediaMap]?>(nil)
if case let .locatedGroup(latitude, longitude, address) = mode {
if let address = address {
addressPromise.set(.single(address))
@ -335,12 +396,42 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
return placemark?.fullAddress ?? "\(latitude), \(longitude)"
})
}
venuesPromise.set(resolvePeerByName(account: context.account, name: "foursquare")
|> take(1)
|> mapToSignal { peerId -> Signal<ChatContextResultCollection?, NoError> in
guard let peerId = peerId else {
return .single(nil)
}
return requestChatContextResults(account: context.account, botId: peerId, peerId: context.account.peerId, query: "", location: .single((latitude, longitude)), offset: "")
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
return .single(nil)
}
}
|> map { contextResult -> [TelegramMediaMap] in
guard let contextResult = contextResult else {
return []
}
var list: [TelegramMediaMap] = []
for result in contextResult.results {
switch result.message {
case let .mapLocation(mapMedia, _):
if let _ = mapMedia.venue {
list.append(mapMedia)
}
default:
break
}
}
return list
} |> map(Optional.init))
}
let arguments = CreateGroupArguments(account: context.account, updateEditingName: { editingName in
updateState { current in
var current = current
current.editingName = editingName
current.nameSetFromVenue = false
return current
}
}, done: {
@ -586,10 +677,24 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
clearHighlightImpl?()
})
pushImpl?(controller)
}, updateWithVenue: { venue in
guard let venueData = venue.venue else {
return
}
updateState { current in
var current = current
if current.editingName.isEmpty || current.nameSetFromVenue {
current.editingName = .title(title: venueData.title ?? "", type: .group)
current.nameSetFromVenue = true
}
current.location = PeerGeoLocation(latitude: venue.latitude, longitude: venue.longitude, address: venueData.address ?? "")
return current
}
ensureItemVisibleImpl?(.info, true)
})
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(addressPromise.get()))
|> map { presentationData, state, view, address -> (ItemListControllerState, (ItemListNodeState, Any)) in
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(addressPromise.get()), .single(nil) |> then(venuesPromise.get()))
|> map { presentationData, state, view, address, venues -> (ItemListControllerState, (ItemListNodeState, Any)) in
let rightNavigationButton: ItemListNavigationButton
if state.creating {
@ -601,7 +706,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Compose_NewGroupTitle), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
let listState = ItemListNodeState(entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view, venues: venues), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
return (controllerState, (listState, arguments))
}
@ -634,5 +739,28 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
clearHighlightImpl = { [weak controller] in
controller?.clearItemNodesHighlight(animated: true)
}
ensureItemVisibleImpl = { [weak controller] targetTag, animated in
controller?.afterLayout({
guard let controller = controller else {
return
}
var resultItemNode: ListViewItemNode?
let state = stateValue.with({ $0 })
let _ = controller.frameForItemNode({ itemNode in
if let itemNode = itemNode as? ItemListItemNode {
if let tag = itemNode.tag, tag.isEqual(to: targetTag) {
resultItemNode = itemNode as? ListViewItemNode
return true
}
}
return false
})
if let resultItemNode = resultItemNode {
controller.ensureItemNodeVisible(resultItemNode, animated: animated)
}
})
}
return controller
}

View File

@ -16,6 +16,7 @@ import WebP
import Lottie
import MediaResources
import PhotoResources
import LocationResources
import ImageBlur
import TelegramAnimatedStickerNode
import WallpaperResources

View File

@ -7,6 +7,7 @@ import PassportUI
import OpenInExternalAppUI
import MusicAlbumArtResources
import LocalMediaResources
import LocationResources
public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in
if interfaceState == nil {
@ -25,8 +26,6 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerC
return fetchLocalFileGifMediaResource(resource: resource)
} else if let photoLibraryResource = resource as? PhotoLibraryMediaResource {
return fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier)
} else if let mapSnapshotResource = resource as? MapSnapshotMediaResource {
return .never()
} else if let resource = resource as? ExternalMusicAlbumArtResource {
return fetchExternalMusicAlbumArtResource(account: account, resource: resource)
} else if let resource = resource as? ICloudFileResource {
@ -37,6 +36,8 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerC
return fetchOpenInAppIconResource(resource: resource)
} else if let resource = resource as? EmojiSpriteResource {
return fetchEmojiSpriteResource(postbox: account.postbox, network: account.network, resource: resource)
} else if let resource = resource as? VenueIconResource {
return fetchVenueIconResource(account: account, resource: resource)
}
return nil
}, fetchResourceMediaReferenceHash: { resource in