mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add venues list in location group creation
This commit is contained in:
parent
349e911e24
commit
8ebefb813d
@ -5106,3 +5106,11 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"GroupInfo.ShowMoreMembers_any" = "%@ more";
|
"GroupInfo.ShowMoreMembers_any" = "%@ more";
|
||||||
|
|
||||||
"ContactInfo.Note" = "note";
|
"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?";
|
||||||
|
@ -486,7 +486,7 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
context.scaleBy(x: 1.0, y: -1.0)
|
context.scaleBy(x: 1.0, y: -1.0)
|
||||||
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.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))
|
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 {
|
} else if case .archivedChatsIcon = parameters.icon {
|
||||||
|
268
submodules/Display/Display/ImageCorners.swift
Normal file
268
submodules/Display/Display/ImageCorners.swift
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ static_library(
|
|||||||
"//submodules/MosaicLayout:MosaicLayout",
|
"//submodules/MosaicLayout:MosaicLayout",
|
||||||
"//submodules/LocationUI:LocationUI",
|
"//submodules/LocationUI:LocationUI",
|
||||||
"//submodules/AppBundle:AppBundle",
|
"//submodules/AppBundle:AppBundle",
|
||||||
|
"//submodules/LocationResources:LocationResources",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
@ -11,6 +11,7 @@ import AccountContext
|
|||||||
import RadialStatusNode
|
import RadialStatusNode
|
||||||
import PhotoResources
|
import PhotoResources
|
||||||
import MediaResources
|
import MediaResources
|
||||||
|
import LocationResources
|
||||||
import LiveLocationPositionNode
|
import LiveLocationPositionNode
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
|
||||||
@ -230,7 +231,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
let makePinLayout = self.pinNode.asyncLayout()
|
let makePinLayout = self.pinNode.asyncLayout()
|
||||||
let theme = self.context.sharedContext.currentPresentationData.with { $0 }.theme
|
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)
|
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()
|
pinApply()
|
||||||
} else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) {
|
} else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) {
|
||||||
|
@ -807,12 +807,12 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
|||||||
strongSelf.addSubnode(inputSeparator)
|
strongSelf.addSubnode(inputSeparator)
|
||||||
strongSelf.inputSeparator = inputSeparator
|
strongSelf.inputSeparator = inputSeparator
|
||||||
}
|
}
|
||||||
strongSelf.inputSeparator?.backgroundColor = itemSeparatorColor
|
strongSelf.inputSeparator?.backgroundColor = .clear
|
||||||
|
|
||||||
if strongSelf.inputFirstField == nil {
|
if strongSelf.inputFirstField == nil {
|
||||||
let inputFirstField = TextFieldNodeView()
|
let inputFirstField = TextFieldNodeView()
|
||||||
inputFirstField.delegate = self
|
inputFirstField.delegate = self
|
||||||
inputFirstField.font = Font.regular(17.0)
|
inputFirstField.font = Font.regular(19.0)
|
||||||
inputFirstField.autocorrectionType = .no
|
inputFirstField.autocorrectionType = .no
|
||||||
inputFirstField.attributedText = NSAttributedString(string: title, font: Font.regular(19.0), textColor: item.theme.list.itemPrimaryTextColor)
|
inputFirstField.attributedText = NSAttributedString(string: title, font: Font.regular(19.0), textColor: item.theme.list.itemPrimaryTextColor)
|
||||||
strongSelf.inputFirstField = inputFirstField
|
strongSelf.inputFirstField = inputFirstField
|
||||||
@ -845,8 +845,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
|||||||
strongSelf.addSubnode(strongSelf.inputFirstClearButton!)
|
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.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: 26.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 35.0))
|
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 {
|
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)
|
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)
|
||||||
|
25
submodules/ItemListVenueItem/BUCK
Normal file
25
submodules/ItemListVenueItem/BUCK
Normal 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",
|
||||||
|
],
|
||||||
|
)
|
22
submodules/ItemListVenueItem/Info.plist
Normal file
22
submodules/ItemListVenueItem/Info.plist
Normal 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>
|
309
submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift
Normal file
309
submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -178,16 +178,16 @@ func importLegacyPreferences(accountManager: AccountManager, account: TemporaryA
|
|||||||
//themeSpecificAccentColors: current.themeSpecificAccentColors
|
//themeSpecificAccentColors: current.themeSpecificAccentColors
|
||||||
//settings.themeAccentColor = presentationState.userInfo
|
//settings.themeAccentColor = presentationState.userInfo
|
||||||
}
|
}
|
||||||
settings.chatWallpaper = .color(0xffffff)
|
settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0xffffff)
|
||||||
case 2:
|
case 2:
|
||||||
settings.theme = .builtin(.night)
|
settings.theme = .builtin(.night)
|
||||||
settings.chatWallpaper = .color(0x00000)
|
settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0x000000)
|
||||||
case 3:
|
case 3:
|
||||||
settings.theme = .builtin(.nightAccent)
|
settings.theme = .builtin(.nightAccent)
|
||||||
settings.chatWallpaper = .color(0x18222D)
|
settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0x18222d)
|
||||||
default:
|
default:
|
||||||
settings.theme = .builtin(.dayClassic)
|
settings.theme = .builtin(.dayClassic)
|
||||||
settings.chatWallpaper = .builtin(WallpaperSettings())
|
settings.themeSpecificChatWallpapers[settings.theme.index] = .builtin(WallpaperSettings())
|
||||||
}
|
}
|
||||||
let fontSizeMap: [Int32: PresentationFontSize] = [
|
let fontSizeMap: [Int32: PresentationFontSize] = [
|
||||||
14: .extraSmall,
|
14: .extraSmall,
|
||||||
|
@ -14,6 +14,7 @@ static_library(
|
|||||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
"//submodules/AvatarNode:AvatarNode",
|
"//submodules/AvatarNode:AvatarNode",
|
||||||
"//submodules/AppBundle:AppBundle",
|
"//submodules/AppBundle:AppBundle",
|
||||||
|
"//submodules/LocationResources:LocationResources",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
@ -7,6 +7,7 @@ import SyncCore
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
|
import LocationResources
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
|
||||||
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
||||||
@ -36,12 +37,31 @@ private func removePulseAnimations(layer: CALayer) {
|
|||||||
layer.removeAnimation(forKey: "pulse-opacity")
|
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 final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||||
|
public enum Mode {
|
||||||
|
case liveLocation(Peer, Bool)
|
||||||
|
case location(TelegramMediaMap?)
|
||||||
|
}
|
||||||
|
|
||||||
private let backgroundNode: ASImageNode
|
private let backgroundNode: ASImageNode
|
||||||
|
private let iconNode: TransformImageNode
|
||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
private let pulseNode: ASImageNode
|
private let pulseNode: ASImageNode
|
||||||
|
|
||||||
private var pulseImage: UIImage?
|
private var pulseImage: UIImage?
|
||||||
|
private var venueType: String?
|
||||||
|
|
||||||
override public init() {
|
override public init() {
|
||||||
let isLayerBacked = !smartInvertColorsEnabled()
|
let isLayerBacked = !smartInvertColorsEnabled()
|
||||||
@ -51,6 +71,9 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
|||||||
self.backgroundNode.displaysAsynchronously = false
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
self.backgroundNode.displayWithoutProcessing = true
|
self.backgroundNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
|
self.iconNode = TransformImageNode()
|
||||||
|
self.iconNode.isLayerBacked = true
|
||||||
|
|
||||||
self.avatarNode = AvatarNode(font: avatarFont)
|
self.avatarNode = AvatarNode(font: avatarFont)
|
||||||
self.avatarNode.isLayerBacked = isLayerBacked
|
self.avatarNode.isLayerBacked = isLayerBacked
|
||||||
|
|
||||||
@ -66,23 +89,32 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.addSubnode(self.pulseNode)
|
self.addSubnode(self.pulseNode)
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
|
self.addSubnode(self.iconNode)
|
||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func asyncLayout() -> (_ account: Account, _ theme: PresentationTheme, _ peer: Peer?, _ liveActive: Bool?) -> (CGSize, () -> Void) {
|
public func asyncLayout() -> (_ account: Account, _ theme: PresentationTheme, _ mode: Mode) -> (CGSize, () -> Void) {
|
||||||
let currentPulseImage = self.pulseImage
|
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?
|
let backgroundImage: UIImage?
|
||||||
var hasPulse = false
|
var hasPulse = false
|
||||||
if let _ = peer {
|
switch mode {
|
||||||
backgroundImage = avatarBackgroundImage
|
case let .liveLocation(_, active):
|
||||||
|
backgroundImage = avatarBackgroundImage
|
||||||
if let liveActive = liveActive {
|
hasPulse = active
|
||||||
hasPulse = liveActive
|
case let .location(location):
|
||||||
}
|
let venueType = location?.venue?.type ?? ""
|
||||||
} else {
|
let color = venueType.isEmpty ? theme.list.itemAccentColor : venueIconColor(type: venueType)
|
||||||
backgroundImage = PresentationResourcesChat.chatBubbleMapPinImage(theme)
|
backgroundImage = chatBubbleMapPinImage(theme, color: color)
|
||||||
|
if currentVenueType != venueType {
|
||||||
|
updatedVenueType = venueType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pulseImage: UIImage?
|
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.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))
|
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
|
||||||
if let peer = peer {
|
switch mode {
|
||||||
strongSelf.avatarNode.setPeer(account: account, theme: theme, peer: peer)
|
case let .liveLocation(peer, active):
|
||||||
strongSelf.avatarNode.isHidden = false
|
strongSelf.avatarNode.setPeer(account: account, theme: theme, peer: peer)
|
||||||
|
strongSelf.avatarNode.isHidden = false
|
||||||
if let liveActive = liveActive {
|
strongSelf.iconNode.isHidden = true
|
||||||
strongSelf.avatarNode.alpha = liveActive ? 1.0 : 0.6
|
strongSelf.avatarNode.alpha = active ? 1.0 : 0.6
|
||||||
} else {
|
case let .location(location):
|
||||||
strongSelf.avatarNode.alpha = 1.0
|
strongSelf.iconNode.isHidden = false
|
||||||
}
|
strongSelf.avatarNode.isHidden = true
|
||||||
} else {
|
|
||||||
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.pulseImage = pulseImage
|
||||||
strongSelf.pulseNode.image = 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))
|
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))
|
||||||
|
21
submodules/LocationResources/BUCK
Normal file
21
submodules/LocationResources/BUCK
Normal 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",
|
||||||
|
],
|
||||||
|
)
|
22
submodules/LocationResources/Info.plist
Normal file
22
submodules/LocationResources/Info.plist
Normal 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>
|
@ -1,12 +0,0 @@
|
|||||||
//
|
|
||||||
// LocationIconResources.swift
|
|
||||||
// LocationResources
|
|
||||||
//
|
|
||||||
// Created by Ilya Laktyushin on 13.11.2019.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
class LocationIconResources: NSObject {
|
|
||||||
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SyncCore
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
submodules/LocationResources/Sources/MediaResources.h
Normal file
19
submodules/LocationResources/Sources/MediaResources.h
Normal 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>
|
||||||
|
|
||||||
|
|
156
submodules/LocationResources/Sources/VenueIconResources.swift
Normal file
156
submodules/LocationResources/Sources/VenueIconResources.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,5 @@ static_library(
|
|||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/MapKit.framework",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -41,6 +41,7 @@ static_library(
|
|||||||
"//submodules/TelegramNotices:TelegramNotices",
|
"//submodules/TelegramNotices:TelegramNotices",
|
||||||
"//submodules/PhotoResources:PhotoResources",
|
"//submodules/PhotoResources:PhotoResources",
|
||||||
"//submodules/MediaResources:MediaResources",
|
"//submodules/MediaResources:MediaResources",
|
||||||
|
"//submodules/LocationResources:LocationResources",
|
||||||
"//submodules/Geocoding:Geocoding",
|
"//submodules/Geocoding:Geocoding",
|
||||||
"//submodules/LegacyComponents:LegacyComponents",
|
"//submodules/LegacyComponents:LegacyComponents",
|
||||||
"//submodules/LocationUI:LocationUI",
|
"//submodules/LocationUI:LocationUI",
|
||||||
|
@ -15,6 +15,7 @@ import AlertUI
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import PhotoResources
|
import PhotoResources
|
||||||
import MediaResources
|
import MediaResources
|
||||||
|
import LocationResources
|
||||||
import ItemListAvatarAndNameInfoItem
|
import ItemListAvatarAndNameInfoItem
|
||||||
import Geocoding
|
import Geocoding
|
||||||
import ItemListAddressItem
|
import ItemListAddressItem
|
||||||
|
@ -21,6 +21,7 @@ import AlertUI
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import MediaResources
|
import MediaResources
|
||||||
import PhotoResources
|
import PhotoResources
|
||||||
|
import LocationResources
|
||||||
import GalleryUI
|
import GalleryUI
|
||||||
import LegacyUI
|
import LegacyUI
|
||||||
import LocationUI
|
import LocationUI
|
||||||
|
@ -403,270 +403,6 @@ private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaRef
|
|||||||
return signal
|
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> {
|
public func rawMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference) -> Signal<UIImage?, NoError> {
|
||||||
return chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, autoFetchFullSize: true)
|
return chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, autoFetchFullSize: true)
|
||||||
|> map { value -> UIImage? in
|
|> 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> {
|
public func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||||
return account.postbox.mediaBox.resourceData(file.resource)
|
return account.postbox.mediaBox.resourceData(file.resource)
|
||||||
|> map { fullSizeData in
|
|> map { fullSizeData in
|
||||||
|
@ -69,7 +69,7 @@ enum PrivacyIntroControllerMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final public class PrivacyIntroControllerPresentationArguments {
|
public final class PrivacyIntroControllerPresentationArguments {
|
||||||
let fadeIn: Bool
|
let fadeIn: Bool
|
||||||
let animateIn: Bool
|
let animateIn: Bool
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ private enum CallFeedbackControllerEntryTag: ItemListItemTag {
|
|||||||
case comment
|
case comment
|
||||||
|
|
||||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||||
if let other = other as? CallFeedbackControllerEntryTagv {
|
if let other = other as? CallFeedbackControllerEntryTag {
|
||||||
return self == other
|
return self == other
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -322,7 +322,7 @@ public func callFeedbackController(sharedContext: SharedAccountContext, account:
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
if let resultItemNode = resultItemNode {
|
if let resultItemNode = resultItemNode {
|
||||||
controller.ensureItemNodeVisible(resultItemNode, animated: animated)
|
controller.ensureItemNodeVisible(resultItemNode, animated: animated)
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -203,9 +203,7 @@ public enum PresentationResourceKey: Int32 {
|
|||||||
|
|
||||||
case chatBubbleIncomingCallButtonImage
|
case chatBubbleIncomingCallButtonImage
|
||||||
case chatBubbleOutgoingCallButtonImage
|
case chatBubbleOutgoingCallButtonImage
|
||||||
|
|
||||||
case chatBubbleMapPinImage
|
|
||||||
|
|
||||||
case callListOutgoingIcon
|
case callListOutgoingIcon
|
||||||
case callListInfoButton
|
case callListInfoButton
|
||||||
|
|
||||||
|
@ -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? {
|
public static func chatInputSearchPanelUpImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputSearchPanelUpImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputSearchPanelUpImage.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/UpButton"), color: theme.chat.inputPanel.panelControlAccentColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/UpButton"), color: theme.chat.inputPanel.panelControlAccentColor)
|
||||||
|
@ -198,6 +198,8 @@ framework(
|
|||||||
"//submodules/NotificationsPresentationData:NotificationsPresentationData",
|
"//submodules/NotificationsPresentationData:NotificationsPresentationData",
|
||||||
"//submodules/UrlWhitelist:UrlWhitelist",
|
"//submodules/UrlWhitelist:UrlWhitelist",
|
||||||
"//submodules/AppIntents:AppIntents",
|
"//submodules/AppIntents:AppIntents",
|
||||||
|
"//submodules/LocationResources:LocationResources",
|
||||||
|
"//submodules/ItemListVenueItem:ItemListVenueItem",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
@ -9,6 +9,7 @@ import SyncCore
|
|||||||
import LiveLocationTimerNode
|
import LiveLocationTimerNode
|
||||||
import PhotoResources
|
import PhotoResources
|
||||||
import MediaResources
|
import MediaResources
|
||||||
|
import LocationResources
|
||||||
import LiveLocationPositionNode
|
import LiveLocationPositionNode
|
||||||
|
|
||||||
private let titleFont = Font.medium(14.0)
|
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)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 5.0, hidesBackground: (activeLiveBroadcastingTimeout == nil && selectedMedia?.venue == nil) ? .emptyWallpaper : .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
var pinPeer: Peer?
|
var mode: ChatMessageLiveLocationPositionNode.Mode = .location(selectedMedia)
|
||||||
var pinLiveLocationActive: Bool?
|
if let selectedMedia = selectedMedia, let peer = item.message.author {
|
||||||
if let selectedMedia = selectedMedia {
|
|
||||||
if selectedMedia.liveBroadcastingTimeout != nil {
|
if selectedMedia.liveBroadcastingTimeout != nil {
|
||||||
pinPeer = item.message.author
|
mode = .liveLocation(peer, activeLiveBroadcastingTimeout != nil)
|
||||||
pinLiveLocationActive = 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
|
return (contentProperties, nil, maximumWidth, { constrainedSize, position in
|
||||||
let imageCorners: ImageCorners
|
let imageCorners: ImageCorners
|
||||||
|
@ -15,6 +15,7 @@ import AlertUI
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import MediaResources
|
import MediaResources
|
||||||
import PhotoResources
|
import PhotoResources
|
||||||
|
import LocationResources
|
||||||
import LegacyUI
|
import LegacyUI
|
||||||
import LocationUI
|
import LocationUI
|
||||||
import ItemListPeerItem
|
import ItemListPeerItem
|
||||||
@ -24,6 +25,7 @@ import Geocoding
|
|||||||
import PeerInfoUI
|
import PeerInfoUI
|
||||||
import MapResourceToAvatarSizes
|
import MapResourceToAvatarSizes
|
||||||
import ItemListAddressItem
|
import ItemListAddressItem
|
||||||
|
import ItemListVenueItem
|
||||||
|
|
||||||
private struct CreateGroupArguments {
|
private struct CreateGroupArguments {
|
||||||
let account: Account
|
let account: Account
|
||||||
@ -32,12 +34,14 @@ private struct CreateGroupArguments {
|
|||||||
let done: () -> Void
|
let done: () -> Void
|
||||||
let changeProfilePhoto: () -> Void
|
let changeProfilePhoto: () -> Void
|
||||||
let changeLocation: () -> Void
|
let changeLocation: () -> Void
|
||||||
|
let updateWithVenue: (TelegramMediaMap) -> Void
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CreateGroupSection: Int32 {
|
private enum CreateGroupSection: Int32 {
|
||||||
case info
|
case info
|
||||||
case members
|
case members
|
||||||
case location
|
case location
|
||||||
|
case venues
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CreateGroupEntryTag: ItemListItemTag {
|
private enum CreateGroupEntryTag: ItemListItemTag {
|
||||||
@ -67,6 +71,8 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
case location(PresentationTheme, PeerGeoLocation)
|
case location(PresentationTheme, PeerGeoLocation)
|
||||||
case changeLocation(PresentationTheme, String)
|
case changeLocation(PresentationTheme, String)
|
||||||
case locationInfo(PresentationTheme, String)
|
case locationInfo(PresentationTheme, String)
|
||||||
|
case venueHeader(PresentationTheme, String)
|
||||||
|
case venue(Int32, PresentationTheme, TelegramMediaMap)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -76,6 +82,8 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
return CreateGroupSection.members.rawValue
|
return CreateGroupSection.members.rawValue
|
||||||
case .locationHeader, .location, .changeLocation, .locationInfo:
|
case .locationHeader, .location, .changeLocation, .locationInfo:
|
||||||
return CreateGroupSection.location.rawValue
|
return CreateGroupSection.location.rawValue
|
||||||
|
case .venueHeader, .venue:
|
||||||
|
return CreateGroupSection.venues.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +103,10 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
return 10002
|
return 10002
|
||||||
case .locationInfo:
|
case .locationInfo:
|
||||||
return 10003
|
return 10003
|
||||||
|
case .venueHeader:
|
||||||
|
return 10004
|
||||||
|
case let .venue(index, _, _):
|
||||||
|
return 10005 + index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,6 +201,27 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
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
|
let arguments = arguments as! CreateGroupArguments
|
||||||
switch self {
|
switch self {
|
||||||
case let .groupInfo(theme, strings, dateTimeFormat, peer, state, avatar):
|
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)
|
arguments.updateEditingName(editingName)
|
||||||
}, avatarTapped: {
|
}, avatarTapped: {
|
||||||
|
arguments.changeProfilePhoto()
|
||||||
}, updatingImage: avatar, tag: CreateGroupEntryTag.info)
|
}, updatingImage: avatar, tag: CreateGroupEntryTag.info)
|
||||||
case let .setProfilePhoto(theme, text):
|
case let .setProfilePhoto(theme, text):
|
||||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
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)
|
}, clearHighlightAutomatically: false)
|
||||||
case let .locationInfo(theme, text):
|
case let .locationInfo(theme, text):
|
||||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
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 {
|
private struct CreateGroupState: Equatable {
|
||||||
var creating: Bool
|
var creating: Bool
|
||||||
var editingName: ItemListAvatarAndNameInfoItemName
|
var editingName: ItemListAvatarAndNameInfoItemName
|
||||||
|
var nameSetFromVenue: Bool
|
||||||
var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
||||||
var location: PeerGeoLocation?
|
var location: PeerGeoLocation?
|
||||||
|
|
||||||
@ -238,6 +279,9 @@ private struct CreateGroupState: Equatable {
|
|||||||
if lhs.editingName != rhs.editingName {
|
if lhs.editingName != rhs.editingName {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.nameSetFromVenue != rhs.nameSetFromVenue {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.avatar != rhs.avatar {
|
if lhs.avatar != rhs.avatar {
|
||||||
return false
|
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] = []
|
var entries: [CreateGroupEntry] = []
|
||||||
|
|
||||||
let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil)
|
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)
|
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(.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] = []
|
var peers: [Peer] = []
|
||||||
for peerId in peerIds {
|
for peerId in peerIds {
|
||||||
@ -294,6 +338,21 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
|
|||||||
entries.append(.location(presentationData.theme, location))
|
entries.append(.location(presentationData.theme, location))
|
||||||
entries.append(.changeLocation(presentationData.theme, presentationData.strings.Group_Location_ChangeLocation))
|
entries.append(.changeLocation(presentationData.theme, presentationData.strings.Group_Location_ChangeLocation))
|
||||||
entries.append(.locationInfo(presentationData.theme, presentationData.strings.Group_Location_Info))
|
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
|
return entries
|
||||||
@ -305,7 +364,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
location = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address ?? "")
|
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 statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in
|
let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in
|
||||||
@ -318,6 +377,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
var pushImpl: ((ViewController) -> Void)?
|
var pushImpl: ((ViewController) -> Void)?
|
||||||
var endEditingImpl: (() -> Void)?
|
var endEditingImpl: (() -> Void)?
|
||||||
var clearHighlightImpl: (() -> Void)?
|
var clearHighlightImpl: (() -> Void)?
|
||||||
|
var ensureItemVisibleImpl: ((CreateGroupEntryTag, Bool) -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
@ -326,6 +386,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
|
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
|
||||||
|
|
||||||
let addressPromise = Promise<String?>(nil)
|
let addressPromise = Promise<String?>(nil)
|
||||||
|
let venuesPromise = Promise<[TelegramMediaMap]?>(nil)
|
||||||
if case let .locatedGroup(latitude, longitude, address) = mode {
|
if case let .locatedGroup(latitude, longitude, address) = mode {
|
||||||
if let address = address {
|
if let address = address {
|
||||||
addressPromise.set(.single(address))
|
addressPromise.set(.single(address))
|
||||||
@ -335,12 +396,42 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
return placemark?.fullAddress ?? "\(latitude), \(longitude)"
|
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
|
let arguments = CreateGroupArguments(account: context.account, updateEditingName: { editingName in
|
||||||
updateState { current in
|
updateState { current in
|
||||||
var current = current
|
var current = current
|
||||||
current.editingName = editingName
|
current.editingName = editingName
|
||||||
|
current.nameSetFromVenue = false
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
}, done: {
|
}, done: {
|
||||||
@ -586,10 +677,24 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
clearHighlightImpl?()
|
clearHighlightImpl?()
|
||||||
})
|
})
|
||||||
pushImpl?(controller)
|
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()))
|
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 -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { presentationData, state, view, address, venues -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
|
|
||||||
let rightNavigationButton: ItemListNavigationButton
|
let rightNavigationButton: ItemListNavigationButton
|
||||||
if state.creating {
|
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 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))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
@ -634,5 +739,28 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
clearHighlightImpl = { [weak controller] in
|
clearHighlightImpl = { [weak controller] in
|
||||||
controller?.clearItemNodesHighlight(animated: true)
|
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
|
return controller
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import WebP
|
|||||||
import Lottie
|
import Lottie
|
||||||
import MediaResources
|
import MediaResources
|
||||||
import PhotoResources
|
import PhotoResources
|
||||||
|
import LocationResources
|
||||||
import ImageBlur
|
import ImageBlur
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import WallpaperResources
|
import WallpaperResources
|
||||||
|
Binary file not shown.
@ -7,6 +7,7 @@ import PassportUI
|
|||||||
import OpenInExternalAppUI
|
import OpenInExternalAppUI
|
||||||
import MusicAlbumArtResources
|
import MusicAlbumArtResources
|
||||||
import LocalMediaResources
|
import LocalMediaResources
|
||||||
|
import LocationResources
|
||||||
|
|
||||||
public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in
|
public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in
|
||||||
if interfaceState == nil {
|
if interfaceState == nil {
|
||||||
@ -25,8 +26,6 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerC
|
|||||||
return fetchLocalFileGifMediaResource(resource: resource)
|
return fetchLocalFileGifMediaResource(resource: resource)
|
||||||
} else if let photoLibraryResource = resource as? PhotoLibraryMediaResource {
|
} else if let photoLibraryResource = resource as? PhotoLibraryMediaResource {
|
||||||
return fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier)
|
return fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier)
|
||||||
} else if let mapSnapshotResource = resource as? MapSnapshotMediaResource {
|
|
||||||
return .never()
|
|
||||||
} else if let resource = resource as? ExternalMusicAlbumArtResource {
|
} else if let resource = resource as? ExternalMusicAlbumArtResource {
|
||||||
return fetchExternalMusicAlbumArtResource(account: account, resource: resource)
|
return fetchExternalMusicAlbumArtResource(account: account, resource: resource)
|
||||||
} else if let resource = resource as? ICloudFileResource {
|
} else if let resource = resource as? ICloudFileResource {
|
||||||
@ -37,6 +36,8 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerC
|
|||||||
return fetchOpenInAppIconResource(resource: resource)
|
return fetchOpenInAppIconResource(resource: resource)
|
||||||
} else if let resource = resource as? EmojiSpriteResource {
|
} else if let resource = resource as? EmojiSpriteResource {
|
||||||
return fetchEmojiSpriteResource(postbox: account.postbox, network: account.network, resource: resource)
|
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
|
return nil
|
||||||
}, fetchResourceMediaReferenceHash: { resource in
|
}, fetchResourceMediaReferenceHash: { resource in
|
||||||
|
Loading…
x
Reference in New Issue
Block a user