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";
|
||||
|
||||
"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.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
||||
|
||||
if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIcon"), color: theme.list.freeMonoIconColor) {
|
||||
if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIcon"), color: theme.list.itemAccentColor) {
|
||||
context.draw(editAvatarIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - editAvatarIcon.size.width) / 2.0), y: floor((bounds.size.height - editAvatarIcon.size.height) / 2.0)), size: editAvatarIcon.size))
|
||||
}
|
||||
} else if case .archivedChatsIcon = parameters.icon {
|
||||
|
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/LocationUI:LocationUI",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/LocationResources:LocationResources",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
@ -11,6 +11,7 @@ import AccountContext
|
||||
import RadialStatusNode
|
||||
import PhotoResources
|
||||
import MediaResources
|
||||
import LocationResources
|
||||
import LiveLocationPositionNode
|
||||
import AppBundle
|
||||
|
||||
@ -230,7 +231,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
||||
|
||||
let makePinLayout = self.pinNode.asyncLayout()
|
||||
let theme = self.context.sharedContext.currentPresentationData.with { $0 }.theme
|
||||
let (pinSize, pinApply) = makePinLayout(self.context.account, theme, nil, false)
|
||||
let (pinSize, pinApply) = makePinLayout(self.context.account, theme, .location(nil))
|
||||
self.pinNode.frame = CGRect(origin: CGPoint(x: floor((size.width - pinSize.width) / 2.0), y: floor(size.height * 0.5 - 10.0 - pinSize.height / 2.0)), size: pinSize)
|
||||
pinApply()
|
||||
} else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) {
|
||||
|
@ -807,12 +807,12 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
||||
strongSelf.addSubnode(inputSeparator)
|
||||
strongSelf.inputSeparator = inputSeparator
|
||||
}
|
||||
strongSelf.inputSeparator?.backgroundColor = itemSeparatorColor
|
||||
strongSelf.inputSeparator?.backgroundColor = .clear
|
||||
|
||||
if strongSelf.inputFirstField == nil {
|
||||
let inputFirstField = TextFieldNodeView()
|
||||
inputFirstField.delegate = self
|
||||
inputFirstField.font = Font.regular(17.0)
|
||||
inputFirstField.font = Font.regular(19.0)
|
||||
inputFirstField.autocorrectionType = .no
|
||||
inputFirstField.attributedText = NSAttributedString(string: title, font: Font.regular(19.0), textColor: item.theme.list.itemPrimaryTextColor)
|
||||
strongSelf.inputFirstField = inputFirstField
|
||||
@ -845,8 +845,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
||||
strongSelf.addSubnode(strongSelf.inputFirstClearButton!)
|
||||
}
|
||||
|
||||
strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 62.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 100.0, height: separatorHeight))
|
||||
strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 26.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 35.0))
|
||||
strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 64.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 100.0, height: separatorHeight))
|
||||
strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 28.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 35.0))
|
||||
|
||||
if let image = strongSelf.inputFirstClearButton?.image(for: []), let inputFieldFrame = strongSelf.inputFirstField?.frame {
|
||||
strongSelf.inputFirstClearButton?.frame = CGRect(origin: CGPoint(x: inputFieldFrame.maxX, y: inputFieldFrame.minY + floor((inputFieldFrame.size.height - image.size.height) / 2.0) - 1.0 + UIScreenPixel), size: image.size)
|
||||
|
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
|
||||
//settings.themeAccentColor = presentationState.userInfo
|
||||
}
|
||||
settings.chatWallpaper = .color(0xffffff)
|
||||
settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0xffffff)
|
||||
case 2:
|
||||
settings.theme = .builtin(.night)
|
||||
settings.chatWallpaper = .color(0x00000)
|
||||
settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0x000000)
|
||||
case 3:
|
||||
settings.theme = .builtin(.nightAccent)
|
||||
settings.chatWallpaper = .color(0x18222D)
|
||||
settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0x18222d)
|
||||
default:
|
||||
settings.theme = .builtin(.dayClassic)
|
||||
settings.chatWallpaper = .builtin(WallpaperSettings())
|
||||
settings.themeSpecificChatWallpapers[settings.theme.index] = .builtin(WallpaperSettings())
|
||||
}
|
||||
let fontSizeMap: [Int32: PresentationFontSize] = [
|
||||
14: .extraSmall,
|
||||
|
@ -14,6 +14,7 @@ static_library(
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/LocationResources:LocationResources",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
@ -7,6 +7,7 @@ import SyncCore
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import AvatarNode
|
||||
import LocationResources
|
||||
import AppBundle
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
||||
@ -36,12 +37,31 @@ private func removePulseAnimations(layer: CALayer) {
|
||||
layer.removeAnimation(forKey: "pulse-opacity")
|
||||
}
|
||||
|
||||
private func chatBubbleMapPinImage(_ theme: PresentationTheme, color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 62.0, height: 74.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
if let shadowImage = UIImage(bundleImageName: "Chat/Message/LocationPinShadow"), let cgImage = shadowImage.cgImage {
|
||||
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: shadowImage.size))
|
||||
}
|
||||
if let backgroundImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/LocationPinBackground"), color: color), let cgImage = backgroundImage.cgImage {
|
||||
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: backgroundImage.size))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
public enum Mode {
|
||||
case liveLocation(Peer, Bool)
|
||||
case location(TelegramMediaMap?)
|
||||
}
|
||||
|
||||
private let backgroundNode: ASImageNode
|
||||
private let iconNode: TransformImageNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let pulseNode: ASImageNode
|
||||
|
||||
private var pulseImage: UIImage?
|
||||
private var venueType: String?
|
||||
|
||||
override public init() {
|
||||
let isLayerBacked = !smartInvertColorsEnabled()
|
||||
@ -51,6 +71,9 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
|
||||
self.iconNode = TransformImageNode()
|
||||
self.iconNode.isLayerBacked = true
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isLayerBacked = isLayerBacked
|
||||
|
||||
@ -66,23 +89,32 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
|
||||
self.addSubnode(self.pulseNode)
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ account: Account, _ theme: PresentationTheme, _ peer: Peer?, _ liveActive: Bool?) -> (CGSize, () -> Void) {
|
||||
let currentPulseImage = self.pulseImage
|
||||
public func asyncLayout() -> (_ account: Account, _ theme: PresentationTheme, _ mode: Mode) -> (CGSize, () -> Void) {
|
||||
let iconLayout = self.iconNode.asyncLayout()
|
||||
|
||||
return { [weak self] account, theme, peer, liveActive in
|
||||
let currentPulseImage = self.pulseImage
|
||||
let currentVenueType = self.venueType
|
||||
|
||||
return { [weak self] account, theme, mode in
|
||||
var updatedVenueType: String?
|
||||
|
||||
let backgroundImage: UIImage?
|
||||
var hasPulse = false
|
||||
if let _ = peer {
|
||||
backgroundImage = avatarBackgroundImage
|
||||
|
||||
if let liveActive = liveActive {
|
||||
hasPulse = liveActive
|
||||
}
|
||||
} else {
|
||||
backgroundImage = PresentationResourcesChat.chatBubbleMapPinImage(theme)
|
||||
switch mode {
|
||||
case let .liveLocation(_, active):
|
||||
backgroundImage = avatarBackgroundImage
|
||||
hasPulse = active
|
||||
case let .location(location):
|
||||
let venueType = location?.venue?.type ?? ""
|
||||
let color = venueType.isEmpty ? theme.list.itemAccentColor : venueIconColor(type: venueType)
|
||||
backgroundImage = chatBubbleMapPinImage(theme, color: color)
|
||||
if currentVenueType != venueType {
|
||||
updatedVenueType = venueType
|
||||
}
|
||||
}
|
||||
|
||||
let pulseImage: UIImage?
|
||||
@ -99,19 +131,28 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
}
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 74.0))
|
||||
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
|
||||
if let peer = peer {
|
||||
strongSelf.avatarNode.setPeer(account: account, theme: theme, peer: peer)
|
||||
strongSelf.avatarNode.isHidden = false
|
||||
|
||||
if let liveActive = liveActive {
|
||||
strongSelf.avatarNode.alpha = liveActive ? 1.0 : 0.6
|
||||
} else {
|
||||
strongSelf.avatarNode.alpha = 1.0
|
||||
}
|
||||
} else {
|
||||
strongSelf.avatarNode.isHidden = true
|
||||
switch mode {
|
||||
case let .liveLocation(peer, active):
|
||||
strongSelf.avatarNode.setPeer(account: account, theme: theme, peer: peer)
|
||||
strongSelf.avatarNode.isHidden = false
|
||||
strongSelf.iconNode.isHidden = true
|
||||
strongSelf.avatarNode.alpha = active ? 1.0 : 0.6
|
||||
case let .location(location):
|
||||
strongSelf.iconNode.isHidden = false
|
||||
strongSelf.avatarNode.isHidden = true
|
||||
}
|
||||
|
||||
if let updatedVenueType = updatedVenueType {
|
||||
strongSelf.venueType = updatedVenueType
|
||||
strongSelf.iconNode.setSignal(venueIcon(postbox: account.postbox, type: updatedVenueType, background: false))
|
||||
}
|
||||
|
||||
let iconSize = CGSize(width: 44.0, height: 44.0)
|
||||
let apply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()))
|
||||
apply()
|
||||
|
||||
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 14.0), size: iconSize)
|
||||
|
||||
strongSelf.pulseImage = pulseImage
|
||||
strongSelf.pulseNode.image = pulseImage
|
||||
strongSelf.pulseNode.frame = CGRect(origin: CGPoint(x: floor((62.0 - 60.0) / 2.0), y: 34.0), size: CGSize(width: 60.0, height: 60.0))
|
||||
|
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 UIKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
@ -138,3 +139,75 @@ public func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Sign
|
||||
}
|
||||
}
|
||||
|
||||
public func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaResource) -> Signal<Data?, NoError> {
|
||||
return Signal<Data?, NoError> { subscriber in
|
||||
let dataDisposable = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: MapSnapshotMediaResourceRepresentation(), complete: true).start(next: { next in
|
||||
if next.size != 0 {
|
||||
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
|
||||
}
|
||||
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||
|
||||
return ActionDisposable {
|
||||
dataDisposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func chatMapSnapshotImage(account: Account, resource: MapSnapshotMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let signal = chatMapSnapshotData(account: account, resource: resource)
|
||||
|
||||
return signal |> map { fullSizeData in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
||||
|
||||
var fullSizeImage: CGImage?
|
||||
var imageOrientation: UIImage.Orientation = .up
|
||||
if let fullSizeData = fullSizeData {
|
||||
let options = NSMutableDictionary()
|
||||
options[kCGImageSourceShouldCache as NSString] = false as NSNumber
|
||||
if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) {
|
||||
fullSizeImage = image
|
||||
}
|
||||
|
||||
if let fullSizeImage = fullSizeImage {
|
||||
let drawingRect = arguments.drawingRect
|
||||
var fittedSize = CGSize(width: CGFloat(fullSizeImage.width), height: CGFloat(fullSizeImage.height)).aspectFilled(drawingRect.size)
|
||||
if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) {
|
||||
fittedSize.width = arguments.boundingSize.width
|
||||
}
|
||||
if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) {
|
||||
fittedSize.height = arguments.boundingSize.height
|
||||
}
|
||||
|
||||
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
|
||||
|
||||
context.withFlippedContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height {
|
||||
c.fill(arguments.drawingRect)
|
||||
}
|
||||
|
||||
c.setBlendMode(.copy)
|
||||
|
||||
c.interpolationQuality = .medium
|
||||
c.draw(fullSizeImage, in: fittedRect)
|
||||
|
||||
c.setBlendMode(.normal)
|
||||
}
|
||||
} else {
|
||||
context.withFlippedContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor)
|
||||
c.fill(arguments.drawingRect)
|
||||
|
||||
c.setBlendMode(.normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addCorners(context, arguments: arguments)
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
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 = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/MapKit.framework",
|
||||
],
|
||||
)
|
||||
|
@ -41,6 +41,7 @@ static_library(
|
||||
"//submodules/TelegramNotices:TelegramNotices",
|
||||
"//submodules/PhotoResources:PhotoResources",
|
||||
"//submodules/MediaResources:MediaResources",
|
||||
"//submodules/LocationResources:LocationResources",
|
||||
"//submodules/Geocoding:Geocoding",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/LocationUI:LocationUI",
|
||||
|
@ -15,6 +15,7 @@ import AlertUI
|
||||
import PresentationDataUtils
|
||||
import PhotoResources
|
||||
import MediaResources
|
||||
import LocationResources
|
||||
import ItemListAvatarAndNameInfoItem
|
||||
import Geocoding
|
||||
import ItemListAddressItem
|
||||
|
@ -21,6 +21,7 @@ import AlertUI
|
||||
import PresentationDataUtils
|
||||
import MediaResources
|
||||
import PhotoResources
|
||||
import LocationResources
|
||||
import GalleryUI
|
||||
import LegacyUI
|
||||
import LocationUI
|
||||
|
@ -403,270 +403,6 @@ private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaRef
|
||||
return signal
|
||||
}
|
||||
|
||||
private enum Corner: Hashable {
|
||||
case TopLeft(Int), TopRight(Int), BottomLeft(Int), BottomRight(Int)
|
||||
|
||||
var hashValue: Int {
|
||||
switch self {
|
||||
case let .TopLeft(radius):
|
||||
return radius | (1 << 24)
|
||||
case let .TopRight(radius):
|
||||
return radius | (2 << 24)
|
||||
case let .BottomLeft(radius):
|
||||
return radius | (3 << 24)
|
||||
case let .BottomRight(radius):
|
||||
return radius | (4 << 24)
|
||||
}
|
||||
}
|
||||
|
||||
var radius: Int {
|
||||
switch self {
|
||||
case let .TopLeft(radius):
|
||||
return radius
|
||||
case let .TopRight(radius):
|
||||
return radius
|
||||
case let .BottomLeft(radius):
|
||||
return radius
|
||||
case let .BottomRight(radius):
|
||||
return radius
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func ==(lhs: Corner, rhs: Corner) -> Bool {
|
||||
switch lhs {
|
||||
case let .TopLeft(lhsRadius):
|
||||
switch rhs {
|
||||
case let .TopLeft(rhsRadius) where rhsRadius == lhsRadius:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case let .TopRight(lhsRadius):
|
||||
switch rhs {
|
||||
case let .TopRight(rhsRadius) where rhsRadius == lhsRadius:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case let .BottomLeft(lhsRadius):
|
||||
switch rhs {
|
||||
case let .BottomLeft(rhsRadius) where rhsRadius == lhsRadius:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case let .BottomRight(lhsRadius):
|
||||
switch rhs {
|
||||
case let .BottomRight(rhsRadius) where rhsRadius == lhsRadius:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum Tail: Hashable {
|
||||
case BottomLeft(Int)
|
||||
case BottomRight(Int)
|
||||
|
||||
var hashValue: Int {
|
||||
switch self {
|
||||
case let .BottomLeft(radius):
|
||||
return radius | (1 << 24)
|
||||
case let .BottomRight(radius):
|
||||
return radius | (2 << 24)
|
||||
}
|
||||
}
|
||||
|
||||
var radius: Int {
|
||||
switch self {
|
||||
case let .BottomLeft(radius):
|
||||
return radius
|
||||
case let .BottomRight(radius):
|
||||
return radius
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func ==(lhs: Tail, rhs: Tail) -> Bool {
|
||||
switch lhs {
|
||||
case let .BottomLeft(lhsRadius):
|
||||
switch rhs {
|
||||
case let .BottomLeft(rhsRadius) where rhsRadius == lhsRadius:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case let .BottomRight(lhsRadius):
|
||||
switch rhs {
|
||||
case let .BottomRight(rhsRadius) where rhsRadius == lhsRadius:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var cachedCorners = Atomic<[Corner: DrawingContext]>(value: [:])
|
||||
private var cachedTails = Atomic<[Tail: DrawingContext]>(value: [:])
|
||||
|
||||
private func cornerContext(_ corner: Corner) -> DrawingContext {
|
||||
let cached: DrawingContext? = cachedCorners.with {
|
||||
return $0[corner]
|
||||
}
|
||||
|
||||
if let cached = cached {
|
||||
return cached
|
||||
} else {
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), clear: true)
|
||||
|
||||
context.withContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
c.setFillColor(UIColor.black.cgColor)
|
||||
let rect: CGRect
|
||||
switch corner {
|
||||
case let .TopLeft(radius):
|
||||
rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
case let .TopRight(radius):
|
||||
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
case let .BottomLeft(radius):
|
||||
rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
case let .BottomRight(radius):
|
||||
rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
}
|
||||
c.fillEllipse(in: rect)
|
||||
}
|
||||
|
||||
let _ = cachedCorners.modify { current in
|
||||
var current = current
|
||||
current[corner] = context
|
||||
return current
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
private func tailContext(_ tail: Tail) -> DrawingContext {
|
||||
let cached: DrawingContext? = cachedTails.with {
|
||||
return $0[tail]
|
||||
}
|
||||
|
||||
if let cached = cached {
|
||||
return cached
|
||||
} else {
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(tail.radius) + 3.0, height: CGFloat(tail.radius)), clear: true)
|
||||
|
||||
context.withContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
c.setFillColor(UIColor.black.cgColor)
|
||||
let rect: CGRect
|
||||
switch tail {
|
||||
case let .BottomLeft(radius):
|
||||
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
|
||||
c.move(to: CGPoint(x: 3.0, y: 1.0))
|
||||
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
|
||||
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
|
||||
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
|
||||
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
|
||||
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
|
||||
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
|
||||
c.closePath()
|
||||
c.fillPath()
|
||||
case let .BottomRight(radius):
|
||||
rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1)))
|
||||
|
||||
c.translateBy(x: context.size.width / 2.0, y: context.size.height / 2.0)
|
||||
c.scaleBy(x: -1.0, y: 1.0)
|
||||
c.translateBy(x: -context.size.width / 2.0, y: -context.size.height / 2.0)
|
||||
|
||||
c.move(to: CGPoint(x: 3.0, y: 1.0))
|
||||
c.addLine(to: CGPoint(x: 3.0, y: 11.0))
|
||||
c.addLine(to: CGPoint(x: 2.3, y: 13.0))
|
||||
c.addLine(to: CGPoint(x: 0.0, y: 16.6))
|
||||
c.addLine(to: CGPoint(x: 4.5, y: 15.5))
|
||||
c.addLine(to: CGPoint(x: 6.5, y: 14.3))
|
||||
c.addLine(to: CGPoint(x: 9.0, y: 12.5))
|
||||
c.closePath()
|
||||
c.fillPath()
|
||||
}
|
||||
c.fillEllipse(in: rect)
|
||||
}
|
||||
|
||||
let _ = cachedTails.modify { current in
|
||||
var current = current
|
||||
current[tail] = context
|
||||
return current
|
||||
}
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
public func addCorners(_ context: DrawingContext, arguments: TransformImageArguments) {
|
||||
let corners = arguments.corners
|
||||
let drawingRect = arguments.drawingRect
|
||||
if case let .Corner(radius) = corners.topLeft, radius > CGFloat.ulpOfOne {
|
||||
let corner = cornerContext(.TopLeft(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.minY))
|
||||
}
|
||||
|
||||
if case let .Corner(radius) = corners.topRight, radius > CGFloat.ulpOfOne {
|
||||
let corner = cornerContext(.TopRight(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.minY))
|
||||
}
|
||||
|
||||
switch corners.bottomLeft {
|
||||
case let .Corner(radius):
|
||||
if radius > CGFloat.ulpOfOne {
|
||||
let corner = cornerContext(.BottomLeft(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
|
||||
}
|
||||
case let .Tail(radius, enabled):
|
||||
if radius > CGFloat.ulpOfOne {
|
||||
if enabled {
|
||||
let tail = tailContext(.BottomLeft(Int(radius)))
|
||||
let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0))
|
||||
context.withContext { c in
|
||||
c.clear(CGRect(x: drawingRect.minX - 3.0, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
|
||||
c.setFillColor(color.cgColor)
|
||||
c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
|
||||
}
|
||||
context.blt(tail, at: CGPoint(x: drawingRect.minX - 3.0, y: drawingRect.maxY - radius))
|
||||
} else {
|
||||
let corner = cornerContext(.BottomLeft(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch corners.bottomRight {
|
||||
case let .Corner(radius):
|
||||
if radius > CGFloat.ulpOfOne {
|
||||
let corner = cornerContext(.BottomRight(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
|
||||
}
|
||||
case let .Tail(radius, enabled):
|
||||
if radius > CGFloat.ulpOfOne {
|
||||
if enabled {
|
||||
let tail = tailContext(.BottomRight(Int(radius)))
|
||||
let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0))
|
||||
context.withContext { c in
|
||||
c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
|
||||
c.setFillColor(color.cgColor)
|
||||
c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
|
||||
}
|
||||
context.blt(tail, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
|
||||
} else {
|
||||
let corner = cornerContext(.BottomRight(Int(radius)))
|
||||
context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func rawMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference) -> Signal<UIImage?, NoError> {
|
||||
return chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, autoFetchFullSize: true)
|
||||
|> map { value -> UIImage? in
|
||||
@ -2469,90 +2205,6 @@ public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepr
|
||||
}
|
||||
}
|
||||
|
||||
public func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaResource) -> Signal<Data?, NoError> {
|
||||
return Signal<Data?, NoError> { subscriber in
|
||||
let dataDisposable = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: MapSnapshotMediaResourceRepresentation(), complete: true).start(next: { next in
|
||||
if next.size != 0 {
|
||||
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
|
||||
}
|
||||
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||
|
||||
return ActionDisposable {
|
||||
dataDisposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let locationPinImage = UIImage(named: "ModernMessageLocationPin")?.precomposed()
|
||||
|
||||
public func chatMapSnapshotImage(account: Account, resource: MapSnapshotMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let signal = chatMapSnapshotData(account: account, resource: resource)
|
||||
|
||||
return signal |> map { fullSizeData in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
||||
|
||||
var fullSizeImage: CGImage?
|
||||
var imageOrientation: UIImage.Orientation = .up
|
||||
if let fullSizeData = fullSizeData {
|
||||
let options = NSMutableDictionary()
|
||||
options[kCGImageSourceShouldCache as NSString] = false as NSNumber
|
||||
if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) {
|
||||
imageOrientation = imageOrientationFromSource(imageSource)
|
||||
fullSizeImage = image
|
||||
}
|
||||
|
||||
if let fullSizeImage = fullSizeImage {
|
||||
let drawingRect = arguments.drawingRect
|
||||
var fittedSize = CGSize(width: CGFloat(fullSizeImage.width), height: CGFloat(fullSizeImage.height)).aspectFilled(drawingRect.size)
|
||||
if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) {
|
||||
fittedSize.width = arguments.boundingSize.width
|
||||
}
|
||||
if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) {
|
||||
fittedSize.height = arguments.boundingSize.height
|
||||
}
|
||||
|
||||
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
|
||||
|
||||
context.withFlippedContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height {
|
||||
c.fill(arguments.drawingRect)
|
||||
}
|
||||
|
||||
c.setBlendMode(.copy)
|
||||
|
||||
c.interpolationQuality = .medium
|
||||
drawImage(context: c, image: fullSizeImage, orientation: imageOrientation, in: fittedRect)
|
||||
|
||||
c.setBlendMode(.normal)
|
||||
|
||||
if let locationPinImage = locationPinImage {
|
||||
c.draw(locationPinImage.cgImage!, in: CGRect(origin: CGPoint(x: floor((arguments.drawingSize.width - locationPinImage.size.width) / 2.0), y: floor((arguments.drawingSize.height - locationPinImage.size.height) / 2.0) - 5.0), size: locationPinImage.size))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.withFlippedContext { c in
|
||||
c.setBlendMode(.copy)
|
||||
c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor)
|
||||
c.fill(arguments.drawingRect)
|
||||
|
||||
c.setBlendMode(.normal)
|
||||
|
||||
if let locationPinImage = locationPinImage {
|
||||
c.draw(locationPinImage.cgImage!, in: CGRect(origin: CGPoint(x: floor((arguments.drawingSize.width - locationPinImage.size.width) / 2.0), y: floor((arguments.drawingSize.height - locationPinImage.size.height) / 2.0) - 5.0), size: locationPinImage.size))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addCorners(context, arguments: arguments)
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
return account.postbox.mediaBox.resourceData(file.resource)
|
||||
|> map { fullSizeData in
|
||||
|
@ -69,7 +69,7 @@ enum PrivacyIntroControllerMode {
|
||||
}
|
||||
}
|
||||
|
||||
final public class PrivacyIntroControllerPresentationArguments {
|
||||
public final class PrivacyIntroControllerPresentationArguments {
|
||||
let fadeIn: Bool
|
||||
let animateIn: Bool
|
||||
|
||||
|
@ -83,7 +83,7 @@ private enum CallFeedbackControllerEntryTag: ItemListItemTag {
|
||||
case comment
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? CallFeedbackControllerEntryTagv {
|
||||
if let other = other as? CallFeedbackControllerEntryTag {
|
||||
return self == other
|
||||
} else {
|
||||
return false
|
||||
@ -322,7 +322,7 @@ public func callFeedbackController(sharedContext: SharedAccountContext, account:
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
|
||||
if let resultItemNode = resultItemNode {
|
||||
controller.ensureItemNodeVisible(resultItemNode, animated: animated)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -203,9 +203,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
|
||||
case chatBubbleIncomingCallButtonImage
|
||||
case chatBubbleOutgoingCallButtonImage
|
||||
|
||||
case chatBubbleMapPinImage
|
||||
|
||||
|
||||
case callListOutgoingIcon
|
||||
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? {
|
||||
return theme.image(PresentationResourceKey.chatInputSearchPanelUpImage.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/UpButton"), color: theme.chat.inputPanel.panelControlAccentColor)
|
||||
|
@ -198,6 +198,8 @@ framework(
|
||||
"//submodules/NotificationsPresentationData:NotificationsPresentationData",
|
||||
"//submodules/UrlWhitelist:UrlWhitelist",
|
||||
"//submodules/AppIntents:AppIntents",
|
||||
"//submodules/LocationResources:LocationResources",
|
||||
"//submodules/ItemListVenueItem:ItemListVenueItem",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
@ -9,6 +9,7 @@ import SyncCore
|
||||
import LiveLocationTimerNode
|
||||
import PhotoResources
|
||||
import MediaResources
|
||||
import LocationResources
|
||||
import LiveLocationPositionNode
|
||||
|
||||
private let titleFont = Font.medium(14.0)
|
||||
@ -141,15 +142,13 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 5.0, hidesBackground: (activeLiveBroadcastingTimeout == nil && selectedMedia?.venue == nil) ? .emptyWallpaper : .never, forceFullCorners: false, forceAlignment: .none)
|
||||
|
||||
var pinPeer: Peer?
|
||||
var pinLiveLocationActive: Bool?
|
||||
if let selectedMedia = selectedMedia {
|
||||
var mode: ChatMessageLiveLocationPositionNode.Mode = .location(selectedMedia)
|
||||
if let selectedMedia = selectedMedia, let peer = item.message.author {
|
||||
if selectedMedia.liveBroadcastingTimeout != nil {
|
||||
pinPeer = item.message.author
|
||||
pinLiveLocationActive = activeLiveBroadcastingTimeout != nil
|
||||
mode = .liveLocation(peer, activeLiveBroadcastingTimeout != nil)
|
||||
}
|
||||
}
|
||||
let (pinSize, pinApply) = makePinLayout(item.context.account, item.presentationData.theme.theme, pinPeer, pinLiveLocationActive)
|
||||
let (pinSize, pinApply) = makePinLayout(item.context.account, item.presentationData.theme.theme, mode)
|
||||
|
||||
return (contentProperties, nil, maximumWidth, { constrainedSize, position in
|
||||
let imageCorners: ImageCorners
|
||||
|
@ -15,6 +15,7 @@ import AlertUI
|
||||
import PresentationDataUtils
|
||||
import MediaResources
|
||||
import PhotoResources
|
||||
import LocationResources
|
||||
import LegacyUI
|
||||
import LocationUI
|
||||
import ItemListPeerItem
|
||||
@ -24,6 +25,7 @@ import Geocoding
|
||||
import PeerInfoUI
|
||||
import MapResourceToAvatarSizes
|
||||
import ItemListAddressItem
|
||||
import ItemListVenueItem
|
||||
|
||||
private struct CreateGroupArguments {
|
||||
let account: Account
|
||||
@ -32,12 +34,14 @@ private struct CreateGroupArguments {
|
||||
let done: () -> Void
|
||||
let changeProfilePhoto: () -> Void
|
||||
let changeLocation: () -> Void
|
||||
let updateWithVenue: (TelegramMediaMap) -> Void
|
||||
}
|
||||
|
||||
private enum CreateGroupSection: Int32 {
|
||||
case info
|
||||
case members
|
||||
case location
|
||||
case venues
|
||||
}
|
||||
|
||||
private enum CreateGroupEntryTag: ItemListItemTag {
|
||||
@ -67,6 +71,8 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
case location(PresentationTheme, PeerGeoLocation)
|
||||
case changeLocation(PresentationTheme, String)
|
||||
case locationInfo(PresentationTheme, String)
|
||||
case venueHeader(PresentationTheme, String)
|
||||
case venue(Int32, PresentationTheme, TelegramMediaMap)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
@ -76,6 +82,8 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
return CreateGroupSection.members.rawValue
|
||||
case .locationHeader, .location, .changeLocation, .locationInfo:
|
||||
return CreateGroupSection.location.rawValue
|
||||
case .venueHeader, .venue:
|
||||
return CreateGroupSection.venues.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +103,10 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
return 10002
|
||||
case .locationInfo:
|
||||
return 10003
|
||||
case .venueHeader:
|
||||
return 10004
|
||||
case let .venue(index, _, _):
|
||||
return 10005 + index
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,6 +201,27 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .venueHeader(lhsTheme, lhsTitle):
|
||||
if case let .venueHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .venue(lhsIndex, lhsTheme, lhsVenue):
|
||||
if case let .venue(rhsIndex, rhsTheme, rhsVenue) = rhs {
|
||||
if lhsIndex != rhsIndex {
|
||||
return false
|
||||
}
|
||||
if lhsTheme !== rhsTheme {
|
||||
return false
|
||||
}
|
||||
if !lhsVenue.isEqual(to: rhsVenue) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,9 +233,10 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
let arguments = arguments as! CreateGroupArguments
|
||||
switch self {
|
||||
case let .groupInfo(theme, strings, dateTimeFormat, peer, state, avatar):
|
||||
return ItemListAvatarAndNameInfoItem(account: arguments.account, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: nil, cachedData: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
|
||||
return ItemListAvatarAndNameInfoItem(account: arguments.account, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer, presence: nil, cachedData: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
|
||||
arguments.updateEditingName(editingName)
|
||||
}, avatarTapped: {
|
||||
arguments.changeProfilePhoto()
|
||||
}, updatingImage: avatar, tag: CreateGroupEntryTag.info)
|
||||
case let .setProfilePhoto(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
@ -221,6 +255,12 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
}, clearHighlightAutomatically: false)
|
||||
case let .locationInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||
case let .venueHeader(theme, title):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section)
|
||||
case let .venue(_, theme, venue):
|
||||
return ItemListVenueItem(theme: theme, account: arguments.account, venue: venue, sectionId: self.section, action: {
|
||||
arguments.updateWithVenue(venue)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -228,6 +268,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
private struct CreateGroupState: Equatable {
|
||||
var creating: Bool
|
||||
var editingName: ItemListAvatarAndNameInfoItemName
|
||||
var nameSetFromVenue: Bool
|
||||
var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
||||
var location: PeerGeoLocation?
|
||||
|
||||
@ -238,6 +279,9 @@ private struct CreateGroupState: Equatable {
|
||||
if lhs.editingName != rhs.editingName {
|
||||
return false
|
||||
}
|
||||
if lhs.nameSetFromVenue != rhs.nameSetFromVenue {
|
||||
return false
|
||||
}
|
||||
if lhs.avatar != rhs.avatar {
|
||||
return false
|
||||
}
|
||||
@ -248,7 +292,7 @@ private struct CreateGroupState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView) -> [CreateGroupEntry] {
|
||||
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView, venues: [TelegramMediaMap]?) -> [CreateGroupEntry] {
|
||||
var entries: [CreateGroupEntry] = []
|
||||
|
||||
let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil)
|
||||
@ -256,7 +300,7 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
|
||||
let peer = TelegramGroup(id: PeerId(namespace: -1, id: 0), title: state.editingName.composedTitle, photo: [], participantCount: 0, role: .creator(rank: nil), membership: .Member, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
|
||||
|
||||
entries.append(.groupInfo(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, groupInfoState, state.avatar))
|
||||
entries.append(.setProfilePhoto(presentationData.theme, presentationData.strings.GroupInfo_SetGroupPhoto))
|
||||
//entries.append(.setProfilePhoto(presentationData.theme, presentationData.strings.GroupInfo_SetGroupPhoto))
|
||||
|
||||
var peers: [Peer] = []
|
||||
for peerId in peerIds {
|
||||
@ -294,6 +338,21 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
|
||||
entries.append(.location(presentationData.theme, location))
|
||||
entries.append(.changeLocation(presentationData.theme, presentationData.strings.Group_Location_ChangeLocation))
|
||||
entries.append(.locationInfo(presentationData.theme, presentationData.strings.Group_Location_Info))
|
||||
|
||||
entries.append(.venueHeader(presentationData.theme, presentationData.strings.Group_Location_CreateInThisPlace.uppercased()))
|
||||
if let venues = venues {
|
||||
if !venues.isEmpty {
|
||||
var index: Int32 = 0
|
||||
for venue in venues {
|
||||
entries.append(.venue(index, presentationData.theme, venue))
|
||||
index += 1
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
@ -305,7 +364,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
location = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address ?? "")
|
||||
}
|
||||
|
||||
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), avatar: nil, location: location)
|
||||
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), nameSetFromVenue: false, avatar: nil, location: location)
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in
|
||||
@ -318,6 +377,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var endEditingImpl: (() -> Void)?
|
||||
var clearHighlightImpl: (() -> Void)?
|
||||
var ensureItemVisibleImpl: ((CreateGroupEntryTag, Bool) -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
@ -326,6 +386,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
|
||||
|
||||
let addressPromise = Promise<String?>(nil)
|
||||
let venuesPromise = Promise<[TelegramMediaMap]?>(nil)
|
||||
if case let .locatedGroup(latitude, longitude, address) = mode {
|
||||
if let address = address {
|
||||
addressPromise.set(.single(address))
|
||||
@ -335,12 +396,42 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
return placemark?.fullAddress ?? "\(latitude), \(longitude)"
|
||||
})
|
||||
}
|
||||
|
||||
venuesPromise.set(resolvePeerByName(account: context.account, name: "foursquare")
|
||||
|> take(1)
|
||||
|> mapToSignal { peerId -> Signal<ChatContextResultCollection?, NoError> in
|
||||
guard let peerId = peerId else {
|
||||
return .single(nil)
|
||||
}
|
||||
return requestChatContextResults(account: context.account, botId: peerId, peerId: context.account.peerId, query: "", location: .single((latitude, longitude)), offset: "")
|
||||
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> map { contextResult -> [TelegramMediaMap] in
|
||||
guard let contextResult = contextResult else {
|
||||
return []
|
||||
}
|
||||
var list: [TelegramMediaMap] = []
|
||||
for result in contextResult.results {
|
||||
switch result.message {
|
||||
case let .mapLocation(mapMedia, _):
|
||||
if let _ = mapMedia.venue {
|
||||
list.append(mapMedia)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return list
|
||||
} |> map(Optional.init))
|
||||
}
|
||||
|
||||
let arguments = CreateGroupArguments(account: context.account, updateEditingName: { editingName in
|
||||
updateState { current in
|
||||
var current = current
|
||||
current.editingName = editingName
|
||||
current.nameSetFromVenue = false
|
||||
return current
|
||||
}
|
||||
}, done: {
|
||||
@ -586,10 +677,24 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
clearHighlightImpl?()
|
||||
})
|
||||
pushImpl?(controller)
|
||||
}, updateWithVenue: { venue in
|
||||
guard let venueData = venue.venue else {
|
||||
return
|
||||
}
|
||||
updateState { current in
|
||||
var current = current
|
||||
if current.editingName.isEmpty || current.nameSetFromVenue {
|
||||
current.editingName = .title(title: venueData.title ?? "", type: .group)
|
||||
current.nameSetFromVenue = true
|
||||
}
|
||||
current.location = PeerGeoLocation(latitude: venue.latitude, longitude: venue.longitude, address: venueData.address ?? "")
|
||||
return current
|
||||
}
|
||||
ensureItemVisibleImpl?(.info, true)
|
||||
})
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(addressPromise.get()))
|
||||
|> map { presentationData, state, view, address -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(addressPromise.get()), .single(nil) |> then(venuesPromise.get()))
|
||||
|> map { presentationData, state, view, address, venues -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|
||||
let rightNavigationButton: ItemListNavigationButton
|
||||
if state.creating {
|
||||
@ -601,7 +706,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Compose_NewGroupTitle), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
|
||||
let listState = ItemListNodeState(entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view, venues: venues), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
@ -634,5 +739,28 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
clearHighlightImpl = { [weak controller] in
|
||||
controller?.clearItemNodesHighlight(animated: true)
|
||||
}
|
||||
ensureItemVisibleImpl = { [weak controller] targetTag, animated in
|
||||
controller?.afterLayout({
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
|
||||
var resultItemNode: ListViewItemNode?
|
||||
let state = stateValue.with({ $0 })
|
||||
let _ = controller.frameForItemNode({ itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode {
|
||||
if let tag = itemNode.tag, tag.isEqual(to: targetTag) {
|
||||
resultItemNode = itemNode as? ListViewItemNode
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if let resultItemNode = resultItemNode {
|
||||
controller.ensureItemNodeVisible(resultItemNode, animated: animated)
|
||||
}
|
||||
})
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import WebP
|
||||
import Lottie
|
||||
import MediaResources
|
||||
import PhotoResources
|
||||
import LocationResources
|
||||
import ImageBlur
|
||||
import TelegramAnimatedStickerNode
|
||||
import WallpaperResources
|
||||
|
Binary file not shown.
@ -7,6 +7,7 @@ import PassportUI
|
||||
import OpenInExternalAppUI
|
||||
import MusicAlbumArtResources
|
||||
import LocalMediaResources
|
||||
import LocationResources
|
||||
|
||||
public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in
|
||||
if interfaceState == nil {
|
||||
@ -25,8 +26,6 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerC
|
||||
return fetchLocalFileGifMediaResource(resource: resource)
|
||||
} else if let photoLibraryResource = resource as? PhotoLibraryMediaResource {
|
||||
return fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier)
|
||||
} else if let mapSnapshotResource = resource as? MapSnapshotMediaResource {
|
||||
return .never()
|
||||
} else if let resource = resource as? ExternalMusicAlbumArtResource {
|
||||
return fetchExternalMusicAlbumArtResource(account: account, resource: resource)
|
||||
} else if let resource = resource as? ICloudFileResource {
|
||||
@ -37,6 +36,8 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerC
|
||||
return fetchOpenInAppIconResource(resource: resource)
|
||||
} else if let resource = resource as? EmojiSpriteResource {
|
||||
return fetchEmojiSpriteResource(postbox: account.postbox, network: account.network, resource: resource)
|
||||
} else if let resource = resource as? VenueIconResource {
|
||||
return fetchVenueIconResource(account: account, resource: resource)
|
||||
}
|
||||
return nil
|
||||
}, fetchResourceMediaReferenceHash: { resource in
|
||||
|
Loading…
x
Reference in New Issue
Block a user