Refactoring [skip ci]

This commit is contained in:
Ali 2021-09-24 22:56:48 +03:00
parent b4c5ca2b4d
commit 00b6826303
22 changed files with 346 additions and 280 deletions

View File

@ -130,7 +130,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
}
}
let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(dimensions.width), height: Int32(dimensions.height))
self.imageNode.setSignal(chatMapSnapshotImage(account: context.account, resource: resource))
self.imageNode.setSignal(chatMapSnapshotImage(engine: context.engine, resource: resource))
} else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image {
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image)
self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, photoReference: imageReference))

View File

@ -11,7 +11,7 @@ import ShimmerEffect
public final class ItemListVenueItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let account: Account
let engine: TelegramEngine
let venue: TelegramMediaMap?
let title: String?
let subtitle: String?
@ -22,9 +22,9 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem {
public let sectionId: ItemListSectionId
let header: ListViewItemHeader?
public init(presentationData: ItemListPresentationData, account: Account, venue: TelegramMediaMap?, title: String? = nil, subtitle: String? = nil, sectionId: ItemListSectionId = 0, style: ItemListStyle, action: (() -> Void)?, infoAction: (() -> Void)? = nil, header: ListViewItemHeader? = nil) {
public init(presentationData: ItemListPresentationData, engine: TelegramEngine, venue: TelegramMediaMap?, title: String? = nil, subtitle: String? = nil, sectionId: ItemListSectionId = 0, style: ItemListStyle, action: (() -> Void)?, infoAction: (() -> Void)? = nil, header: ListViewItemHeader? = nil) {
self.presentationData = presentationData
self.account = account
self.engine = engine
self.venue = venue
self.title = title
self.subtitle = subtitle
@ -281,7 +281,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
let _ = addressApply()
if let updatedVenueType = updatedVenueType {
strongSelf.iconNode.setSignal(venueIcon(postbox: item.account.postbox, type: updatedVenueType, background: true))
strongSelf.iconNode.setSignal(venueIcon(engine: item.engine, type: updatedVenueType, background: true))
}
let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets()))

View File

@ -217,7 +217,7 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
if let updatedVenueType = updatedVenueType {
strongSelf.venueType = updatedVenueType
strongSelf.iconNode.setSignal(venueIcon(postbox: context.account.postbox, type: updatedVenueType, background: false))
strongSelf.iconNode.setSignal(venueIcon(engine: context.engine, type: updatedVenueType, background: false))
}
let arguments = VenueIconArguments(defaultBackgroundColor: theme.chat.inputPanel.actionControlFillColor, defaultForegroundColor: theme.chat.inputPanel.actionControlForegroundColor)

View File

@ -11,10 +11,10 @@ swift_library(
],
deps = [
"//submodules/TelegramCore:TelegramCore",
"//submodules/Postbox:Postbox",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Display:Display",
"//submodules/AppBundle:AppBundle",
"//submodules/PersistentStringHash:PersistentStringHash",
],
visibility = [
"//visibility:public",

View File

@ -1,7 +1,6 @@
import Foundation
import UIKit
import Display
import Postbox
import TelegramCore
import MapKit
import SwiftSignalKit
@ -21,7 +20,7 @@ public struct MapSnapshotMediaResourceId {
}
}
public class MapSnapshotMediaResource: TelegramMediaResource {
public class MapSnapshotMediaResource {
public let latitude: Double
public let longitude: Double
public let width: Int32
@ -34,49 +33,8 @@ public class MapSnapshotMediaResource: TelegramMediaResource {
self.height = height
}
public required init(decoder: PostboxDecoder) {
self.latitude = decoder.decodeDoubleForKey("lt", orElse: 0.0)
self.longitude = decoder.decodeDoubleForKey("ln", orElse: 0.0)
self.width = decoder.decodeInt32ForKey("w", orElse: 0)
self.height = decoder.decodeInt32ForKey("h", orElse: 0)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeDouble(self.latitude, forKey: "lt")
encoder.encodeDouble(self.longitude, forKey: "ln")
encoder.encodeInt32(self.width, forKey: "w")
encoder.encodeInt32(self.height, forKey: "h")
}
public var id: MediaResourceId {
return MediaResourceId(MapSnapshotMediaResourceId(latitude: self.latitude, longitude: self.longitude, width: self.width, height: self.height).uniqueId)
}
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? MapSnapshotMediaResource {
return self.latitude == to.latitude && self.longitude == to.longitude && self.width == to.width && self.height == to.height
} else {
return false
}
}
}
public final class MapSnapshotMediaResourceRepresentation: CachedMediaResourceRepresentation {
public let keepDuration: CachedMediaRepresentationKeepDuration = .shortLived
public var uniqueId: String {
return "cached"
}
public init() {
}
public func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
if to is MapSnapshotMediaResourceRepresentation {
return true
} else {
return false
}
public var id: EngineMediaResource.Id {
return EngineMediaResource.Id(MapSnapshotMediaResourceId(latitude: self.latitude, longitude: self.longitude, width: self.width, height: self.height).uniqueId)
}
}
@ -96,7 +54,7 @@ private func adjustGMapLatitude(_ latitude: Double, offset: Int, zoom: Int) -> D
return yToLatitude(latitudeToY(latitude) + t)
}
public func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
private func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Signal<EngineMediaResource.Fetch.Result, EngineMediaResource.Fetch.Error> {
return Signal { subscriber in
let disposable = MetaDisposable()
@ -113,9 +71,9 @@ public func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Sign
snapshotter.start(with: DispatchQueue.global(), completionHandler: { result, error in
if let image = result?.image {
if let data = image.jpegData(compressionQuality: 0.9) {
let tempFile = TempBox.shared.tempFile(fileName: "image.jpg")
let tempFile = EngineTempBox.shared.tempFile(fileName: "image.jpg")
if let _ = try? data.write(to: URL(fileURLWithPath: tempFile.path), options: .atomic) {
subscriber.putNext(.tempFile(tempFile))
subscriber.putNext(.moveTempFile(file: tempFile))
subscriber.putCompletion()
}
}
@ -130,11 +88,17 @@ public func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Sign
}
}
public func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaResource) -> Signal<Data?, NoError> {
public func chatMapSnapshotData(engine: TelegramEngine, 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: []))
let dataDisposable = engine.resources.custom(
id: resource.id.stringRepresentation,
fetch: EngineMediaResource.Fetch {
return fetchMapSnapshotResource(resource: resource)
},
cacheTimeout: .shortLived
).start(next: { next in
if next.availableSize != 0 {
subscriber.putNext(next.availableSize == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}
}, error: subscriber.putError, completed: subscriber.putCompletion)
@ -144,8 +108,8 @@ public func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaReso
}
}
public func chatMapSnapshotImage(account: Account, resource: MapSnapshotMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = chatMapSnapshotData(account: account, resource: resource)
public func chatMapSnapshotImage(engine: TelegramEngine, resource: MapSnapshotMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = chatMapSnapshotData(engine: engine, resource: resource)
return signal |> map { fullSizeData in
return { arguments in

View File

@ -1,10 +1,10 @@
import Foundation
import UIKit
import Display
import Postbox
import TelegramCore
import SwiftSignalKit
import AppBundle
import PersistentStringHash
public struct VenueIconResourceId {
public let type: String
@ -22,79 +22,51 @@ public struct VenueIconResourceId {
}
}
public class VenueIconResource: TelegramMediaResource {
public class VenueIconResource {
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 MediaResourceId(VenueIconResourceId(type: self.type).uniqueId)
}
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? VenueIconResource {
return self.type == to.type
} else {
return false
}
public var id: EngineMediaResource.Id {
return EngineMediaResource.Id(VenueIconResourceId(type: self.type).uniqueId)
}
}
public func fetchVenueIconResource(account: Account, resource: VenueIconResource) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
private func fetchVenueIconResource(engine: TelegramEngine, resource: VenueIconResource) -> Signal<EngineMediaResource.Fetch.Result, EngineMediaResource.Fetch.Error> {
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)
return engine.resources.httpData(url: url).start(next: { data in
let file = EngineTempBox.shared.tempFile(fileName: "file.png")
let _ = try? data.write(to: URL(fileURLWithPath: file.path))
subscriber.putNext(.moveTempFile(file: file))
}, completed: {
subscriber.putCompletion()
}))
return ActionDisposable {
fetchDisposable.dispose()
}
})
}
}
private func venueIconData(postbox: Postbox, resource: MediaResource) -> Signal<Data?, NoError> {
let resourceData = postbox.mediaBox.resourceData(resource)
private func venueIconData(engine: TelegramEngine, resource: VenueIconResource) -> Signal<Data?, NoError> {
let resourceData = engine.resources.custom(
id: resource.id.stringRepresentation,
fetch: EngineMediaResource.Fetch {
return fetchVenueIconResource(engine: engine, resource: resource)
},
cacheTimeout: .shortLived
)
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))
if maybeData.isComplete {
return .single(try? Data(contentsOf: URL(fileURLWithPath: maybeData.path)))
} 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
return .single(nil)
}
} |> distinctUntilChanged(isEqual: { lhs, rhs in
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs == nil && rhs == nil {
return true
} else {
@ -141,7 +113,7 @@ public func venueIconColor(type: String) -> UIColor {
return color
}
let index = Int(abs(persistentHash32(type)) % Int32(randomColors.count))
let index = Int(abs(Int32(bitPattern: UInt32(clamping: type.persistentHashValue))) % Int32(randomColors.count))
return randomColors[index]
}
@ -162,9 +134,9 @@ public struct VenueIconArguments: TransformImageCustomArguments {
}
}
public func venueIcon(postbox: Postbox, type: String, background: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
public func venueIcon(engine: TelegramEngine, type: String, background: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let isBuiltinIcon = ["", "home", "work"].contains(type)
let data: Signal<Data?, NoError> = isBuiltinIcon ? .single(nil) : venueIconData(postbox: postbox, resource: VenueIconResource(type: type))
let data: Signal<Data?, NoError> = isBuiltinIcon ? .single(nil) : venueIconData(engine: engine, resource: VenueIconResource(type: type))
return data |> map { data in
return { arguments in
let context = DrawingContext(size: arguments.drawingSize, clear: true)

View File

@ -81,7 +81,7 @@ private func generateLiveLocationIcon(theme: PresentationTheme, stop: Bool) -> U
final class LocationActionListItem: ListViewItem {
let presentationData: ItemListPresentationData
let account: Account
let engine: TelegramEngine
let title: String
let subtitle: String
let icon: LocationActionListItemIcon
@ -89,9 +89,9 @@ final class LocationActionListItem: ListViewItem {
let action: () -> Void
let highlighted: (Bool) -> Void
public init(presentationData: ItemListPresentationData, account: Account, title: String, subtitle: String, icon: LocationActionListItemIcon, beginTimeAndTimeout: (Double, Double)?, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) {
public init(presentationData: ItemListPresentationData, engine: TelegramEngine, title: String, subtitle: String, icon: LocationActionListItemIcon, beginTimeAndTimeout: (Double, Double)?, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) {
self.presentationData = presentationData
self.account = account
self.engine = engine
self.title = title
self.subtitle = subtitle
self.icon = icon
@ -279,7 +279,7 @@ final class LocationActionListItemNode: ListViewItemNode {
case let .venue(venue):
strongSelf.iconNode.isHidden = true
strongSelf.venueIconNode.isHidden = false
strongSelf.venueIconNode.setSignal(venueIcon(postbox: item.account.postbox, type: venue.venue?.type ?? "", background: true))
strongSelf.venueIconNode.setSignal(venueIcon(engine: item.engine, type: venue.venue?.type ?? "", background: true))
}
if updatedIcon == .stopLiveLocation {

View File

@ -307,8 +307,8 @@ class LocationPinAnnotationView: MKAnnotationView {
let venueType = location.venue?.type ?? ""
let color = venueType.isEmpty ? annotation.theme.list.itemAccentColor : venueIconColor(type: venueType)
self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Location/PinBackground"), color: color)
self.iconNode.setSignal(venueIcon(postbox: annotation.context.account.postbox, type: venueType, background: false))
self.smallIconNode.setSignal(venueIcon(postbox: annotation.context.account.postbox, type: venueType, background: false))
self.iconNode.setSignal(venueIcon(engine: annotation.context.engine, type: venueType, background: false))
self.smallIconNode.setSignal(venueIcon(engine: annotation.context.engine, type: venueType, background: false))
self.smallNode.image = generateSmallBackgroundImage(color: color)
self.dotNode.image = generateFilledCircleImage(diameter: 6.0, color: color)
@ -619,7 +619,7 @@ class LocationPinAnnotationView: MKAnnotationView {
func setCustom(_ custom: Bool, animated: Bool) {
if let annotation = self.annotation as? LocationPinAnnotation {
self.iconNode.setSignal(venueIcon(postbox: annotation.context.account.postbox, type: "", background: false))
self.iconNode.setSignal(venueIcon(engine: annotation.context.engine, type: "", background: false))
}
if let avatarNode = self.avatarNode {

View File

@ -13,7 +13,7 @@ import SolidRoundedButtonNode
final class LocationInfoListItem: ListViewItem {
let presentationData: ItemListPresentationData
let account: Account
let engine: TelegramEngine
let location: TelegramMediaMap
let address: String?
let distance: String?
@ -21,9 +21,9 @@ final class LocationInfoListItem: ListViewItem {
let action: () -> Void
let getDirections: () -> Void
public init(presentationData: ItemListPresentationData, account: Account, location: TelegramMediaMap, address: String?, distance: String?, eta: String?, action: @escaping () -> Void, getDirections: @escaping () -> Void) {
public init(presentationData: ItemListPresentationData, engine: TelegramEngine, location: TelegramMediaMap, address: String?, distance: String?, eta: String?, action: @escaping () -> Void, getDirections: @escaping () -> Void) {
self.presentationData = presentationData
self.account = account
self.engine = engine
self.location = location
self.address = address
self.distance = distance
@ -192,7 +192,7 @@ final class LocationInfoListItemNode: ListViewItemNode {
let arguments = VenueIconArguments(defaultBackgroundColor: item.presentationData.theme.chat.inputPanel.actionControlFillColor, defaultForegroundColor: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor)
if let updatedLocation = updatedLocation {
strongSelf.venueIconNode.setSignal(venueIcon(postbox: item.account.postbox, type: updatedLocation.venue?.type ?? "", background: true))
strongSelf.venueIconNode.setSignal(venueIcon(engine: item.engine, type: updatedLocation.venue?.type ?? "", background: true))
}
let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets(), custom: arguments))

View File

@ -129,7 +129,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
}
}
func item(account: Account, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> ListViewItem {
func item(engine: TelegramEngine, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> ListViewItem {
switch self {
case let .location(_, title, subtitle, venue, coordinate):
let icon: LocationActionListItemIcon
@ -138,7 +138,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
} else {
icon = .location
}
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: {
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: {
if let venue = venue {
interaction?.sendVenue(venue)
} else if let coordinate = coordinate {
@ -148,7 +148,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
interaction?.updateSendActionHighlight(highlighted)
})
case let .liveLocation(_, title, subtitle, coordinate):
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: .liveLocation, beginTimeAndTimeout: nil, action: {
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: .liveLocation, beginTimeAndTimeout: nil, action: {
if let coordinate = coordinate {
interaction?.sendLiveLocation(coordinate)
}
@ -157,7 +157,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
return LocationSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title)
case let .venue(_, venue, _):
let venueType = venue?.venue?.type ?? ""
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), account: account, venue: venue, style: .plain, action: venue.flatMap { venue in
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), engine: engine, venue: venue, style: .plain, action: venue.flatMap { venue in
return { interaction?.sendVenue(venue) }
}, infoAction: ["home", "work"].contains(venueType) ? {
interaction?.openHomeWorkInfo()
@ -168,12 +168,12 @@ private enum LocationPickerEntry: Comparable, Identifiable {
}
}
private func preparedTransition(from fromEntries: [LocationPickerEntry], to toEntries: [LocationPickerEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, account: Account, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> LocationPickerTransaction {
private func preparedTransition(from fromEntries: [LocationPickerEntry], to toEntries: [LocationPickerEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, engine: TelegramEngine, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> LocationPickerTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, presentationData: presentationData, interaction: interaction), directionHint: nil) }
return LocationPickerTransaction(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade)
}
@ -527,7 +527,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
crossFade = true
}
let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: displayedVenues == nil, isEmpty: displayedVenues?.isEmpty ?? false, crossFade: crossFade, account: context.account, presentationData: presentationData, interaction: strongSelf.interaction)
let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: displayedVenues == nil, isEmpty: displayedVenues?.isEmpty ?? false, crossFade: crossFade, engine: context.engine, presentationData: presentationData, interaction: strongSelf.interaction)
strongSelf.enqueueTransition(transition)
var displayingPlacesButton = false

View File

@ -50,7 +50,7 @@ private struct LocationSearchEntry: Identifiable, Comparable {
return lhs.index < rhs.index
}
func item(account: Account, presentationData: PresentationData, sendVenue: @escaping (TelegramMediaMap) -> Void) -> ListViewItem {
func item(engine: TelegramEngine, presentationData: PresentationData, sendVenue: @escaping (TelegramMediaMap) -> Void) -> ListViewItem {
let venue = self.location
let header: ChatListSearchItemHeader
let subtitle: String?
@ -61,7 +61,7 @@ private struct LocationSearchEntry: Identifiable, Comparable {
header = ChatListSearchItemHeader(type: .mapAddress, theme: presentationData.theme, strings: presentationData.strings)
subtitle = presentationData.strings.Map_DistanceAway(stringForDistance(strings: presentationData.strings, distance: self.distance)).string
}
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), account: account, venue: self.location, title: self.title, subtitle: subtitle, style: .plain, action: {
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), engine: engine, venue: self.location, title: self.title, subtitle: subtitle, style: .plain, action: {
sendVenue(venue)
}, header: header)
}
@ -76,12 +76,12 @@ struct LocationSearchContainerTransition {
let isEmpty: Bool
}
private func locationSearchContainerPreparedTransition(from fromEntries: [LocationSearchEntry], to toEntries: [LocationSearchEntry], query: String, isSearching: Bool, isEmpty: Bool, account: Account, presentationData: PresentationData, sendVenue: @escaping (TelegramMediaMap) -> Void) -> LocationSearchContainerTransition {
private func locationSearchContainerPreparedTransition(from fromEntries: [LocationSearchEntry], to toEntries: [LocationSearchEntry], query: String, isSearching: Bool, isEmpty: Bool, engine: TelegramEngine, presentationData: PresentationData, sendVenue: @escaping (TelegramMediaMap) -> Void) -> LocationSearchContainerTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, sendVenue: sendVenue), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, sendVenue: sendVenue), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, presentationData: presentationData, sendVenue: sendVenue), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, presentationData: presentationData, sendVenue: sendVenue), directionHint: nil) }
return LocationSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, query: query, isSearching: isSearching, isEmpty: isEmpty)
}
@ -211,7 +211,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
if let strongSelf = self {
let (items, query) = itemsAndQuery ?? (nil, "")
let previousItems = previousSearchItems.swap(items ?? [])
let transition = locationSearchContainerPreparedTransition(from: previousItems, to: items ?? [], query: query, isSearching: items != nil, isEmpty: items?.isEmpty ?? false, account: context.account, presentationData: strongSelf.presentationData, sendVenue: { venue in self?.listNode.clearHighlightAnimated(true)
let transition = locationSearchContainerPreparedTransition(from: previousItems, to: items ?? [], query: query, isSearching: items != nil, isEmpty: items?.isEmpty ?? false, engine: context.engine, presentationData: strongSelf.presentationData, sendVenue: { venue in self?.listNode.clearHighlightAnimated(true)
if let _ = venue.venue {
self?.interaction.sendVenue(venue)
} else {

View File

@ -126,7 +126,7 @@ private enum LocationViewEntry: Comparable, Identifiable {
distanceString = nil
}
let eta = time.flatMap { stringForEstimatedDuration(strings: presentationData.strings, eta: $0) }
return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), account: context.account, location: location, address: addressString, distance: distanceString, eta: eta, action: {
return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, eta: eta, action: {
interaction?.goToCoordinate(location.coordinate)
}, getDirections: {
interaction?.requestDirections()
@ -138,7 +138,7 @@ private enum LocationViewEntry: Comparable, Identifiable {
} else {
beginTimeAndTimeout = nil
}
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: context.account, title: title, subtitle: subtitle, icon: beginTimeAndTimeout != nil ? .stopLiveLocation : .liveLocation, beginTimeAndTimeout: beginTimeAndTimeout, action: {
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: beginTimeAndTimeout != nil ? .stopLiveLocation : .liveLocation, beginTimeAndTimeout: beginTimeAndTimeout, action: {
if beginTimeAndTimeout != nil {
interaction?.stopLiveLocation()
} else {

View File

@ -614,7 +614,7 @@ private func filteredContactData(contactData: DeviceContactExtendedData, exclude
return DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumbers: phoneNumbers), middleName: contactData.middleName, prefix: contactData.prefix, suffix: contactData.suffix, organization: includeJob ? contactData.organization : "", jobTitle: includeJob ? contactData.jobTitle : "", department: includeJob ? contactData.department : "", emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: includeBirthday ? contactData.birthdayDate : nil, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles, note: includeNote ? contactData.note : "")
}
private func deviceContactInfoEntries(account: Account, presentationData: PresentationData, peer: Peer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool) -> [DeviceContactInfoEntry] {
private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, presentationData: PresentationData, peer: Peer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool) -> [DeviceContactInfoEntry] {
var entries: [DeviceContactInfoEntry] = []
var editingName: ItemListAvatarAndNameInfoItemName?
@ -731,7 +731,7 @@ private func deviceContactInfoEntries(account: Account, presentationData: Presen
|> mapToSignal { coordinates -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
if let (latitude, longitude) = coordinates {
let resource = MapSnapshotMediaResource(latitude: latitude, longitude: longitude, width: 90, height: 90)
return chatMapSnapshotImage(account: account, resource: resource)
return chatMapSnapshotImage(engine: engine, resource: resource)
} else {
return .single({ _ in return nil })
}
@ -1220,7 +1220,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
focusItemTag = DeviceContactInfoEntryTag.editingPhone(insertedPhoneId)
}
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(account: context.account, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(account: context.account, engine: context.engine, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag)
return (controllerState, (listState, arguments))
}

View File

@ -98,17 +98,8 @@ public enum CachedMediaRepresentationKeepDuration {
}
private struct CachedMediaResourceRepresentationKey: Hashable {
let resourceId: MediaResourceId
let representation: CachedMediaResourceRepresentation
static func ==(lhs: CachedMediaResourceRepresentationKey, rhs: CachedMediaResourceRepresentationKey) -> Bool {
return lhs.resourceId == rhs.resourceId && lhs.representation.isEqual(to: rhs.representation)
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.resourceId.hashValue)
hasher.combine(self.representation.uniqueId)
}
let resourceId: String?
let representation: String
}
private final class CachedMediaResourceRepresentationSubscriber {
@ -157,7 +148,7 @@ public final class MediaBox {
private var keepResourceContexts: [MediaResourceId: MediaBoxKeepResourceContext] = [:]
private var wrappedFetchResource = Promise<(MediaResource, Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>>()
public var preFetchedResourcePath: (MediaResource) -> String? = { _ in return nil }
public var fetchResource: ((MediaResource, Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>)? {
didSet {
if let fetchResource = self.fetchResource {
@ -222,15 +213,15 @@ public final class MediaBox {
return ResourceStorePaths(partial: "\(fileNameForId(id))_partial", complete: "\(fileNameForId(id))")
}
private func cachedRepresentationPathsForId(_ id: MediaResourceId, representation: CachedMediaResourceRepresentation) -> ResourceStorePaths {
private func cachedRepresentationPathsForId(_ id: MediaResourceId, representationId: String, keepDuration: CachedMediaRepresentationKeepDuration) -> ResourceStorePaths {
let cacheString: String
switch representation.keepDuration {
switch keepDuration {
case .general:
cacheString = "cache"
case .shortLived:
cacheString = "short-cache"
}
return ResourceStorePaths(partial: "\(self.basePath)/\(cacheString)/\(fileNameForId(id))_partial:\(representation.uniqueId)", complete: "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representation.uniqueId)")
return ResourceStorePaths(partial: "\(self.basePath)/\(cacheString)/\(fileNameForId(id))_partial:\(representationId)", complete: "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representationId)")
}
public func cachedRepresentationPathForId(_ id: String, representationId: String, keepDuration: CachedMediaRepresentationKeepDuration) -> String {
@ -317,12 +308,6 @@ public final class MediaBox {
}
}
private func maybeCopiedPreFetchedResource(completePath: String, resource: MediaResource) {
if let path = self.preFetchedResourcePath(resource) {
let _ = try? FileManager.default.copyItem(atPath: path, toPath: completePath)
}
}
public func resourceStatus(_ resource: MediaResource, approximateSynchronousValue: Bool = false) -> Signal<MediaResourceStatus, NoError> {
let signal = Signal<MediaResourceStatus, NoError> { subscriber in
let disposable = MetaDisposable()
@ -337,16 +322,6 @@ public final class MediaBox {
subscriber.putNext(.Local)
subscriber.putCompletion()
} else {
self.maybeCopiedPreFetchedResource(completePath: paths.complete, resource: resource)
if let _ = fileSize(paths.complete) {
self.timeBasedCleanup.touch(paths: [
paths.complete
])
subscriber.putNext(.Local)
subscriber.putCompletion()
return
}
self.statusQueue.async {
let resourceId = resource.id
let statusContext: ResourceStatusContext
@ -371,7 +346,7 @@ public final class MediaBox {
if let statusUpdateDisposable = statusUpdateDisposable {
let statusQueue = self.statusQueue
self.dataQueue.async {
if let (fileContext, releaseContext) = self.fileContext(for: resource) {
if let (fileContext, releaseContext) = self.fileContext(for: resource.id) {
let statusDisposable = fileContext.status(next: { [weak statusContext] value in
statusQueue.async {
if let current = self.statusContexts[resourceId], current === statusContext, current.status != value {
@ -457,19 +432,17 @@ public final class MediaBox {
return nil
}
}
public func resourceData(_ resource: MediaResource, pathExtension: String? = nil, option: ResourceDataRequestOption = .complete(waitUntilFetchStatus: false), attemptSynchronously: Bool = false) -> Signal<MediaResourceData, NoError> {
return self.resourceData(id: resource.id, pathExtension: pathExtension, option: option, attemptSynchronously: attemptSynchronously)
}
public func resourceData(id: MediaResourceId, pathExtension: String? = nil, option: ResourceDataRequestOption = .complete(waitUntilFetchStatus: false), attemptSynchronously: Bool = false) -> Signal<MediaResourceData, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
let begin: () -> Void = {
let paths = self.storePathsForId(resource.id)
var completeSize = fileSize(paths.complete)
if completeSize == nil {
self.maybeCopiedPreFetchedResource(completePath: paths.complete, resource: resource)
completeSize = fileSize(paths.complete)
}
let paths = self.storePathsForId(id)
if let completeSize = fileSize(paths.complete) {
self.timeBasedCleanup.touch(paths: [
@ -491,7 +464,7 @@ public final class MediaBox {
subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: fileSize(paths.partial) ?? 0, complete: false))
}
self.dataQueue.async {
if let (fileContext, releaseContext) = self.fileContext(for: resource) {
if let (fileContext, releaseContext) = self.fileContext(for: id) {
let waitUntilAfterInitialFetch: Bool
switch option {
case let .complete(waitUntilFetchStatus):
@ -535,16 +508,16 @@ public final class MediaBox {
}
}
private func fileContext(for resource: MediaResource) -> (MediaBoxFileContext, () -> Void)? {
private func fileContext(for id: MediaResourceId) -> (MediaBoxFileContext, () -> Void)? {
assert(self.dataQueue.isCurrent())
let resourceId = resource.id
let resourceId = id
var context: MediaBoxFileContext?
if let current = self.fileContexts[resourceId] {
context = current
} else {
let paths = self.storePathsForId(resource.id)
let paths = self.storePathsForId(id)
self.timeBasedCleanup.touch(paths: [
paths.complete,
paths.partial,
@ -581,7 +554,7 @@ public final class MediaBox {
let disposable = MetaDisposable()
self.dataQueue.async {
guard let (fileContext, releaseContext) = self.fileContext(for: resource) else {
guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else {
subscriber.putCompletion()
return
}
@ -613,13 +586,17 @@ public final class MediaBox {
return disposable
}
}
public func resourceData(_ resource: MediaResource, size: Int, in range: Range<Int>, mode: ResourceDataRangeMode = .complete, notifyAboutIncomplete: Bool = false, attemptSynchronously: Bool = false) -> Signal<(Data, Bool), NoError> {
return self.resourceData(id: resource.id, size: size, in: range, mode: mode, notifyAboutIncomplete: notifyAboutIncomplete, attemptSynchronously: attemptSynchronously)
}
public func resourceData(id: MediaResourceId, size: Int, in range: Range<Int>, mode: ResourceDataRangeMode = .complete, notifyAboutIncomplete: Bool = false, attemptSynchronously: Bool = false) -> Signal<(Data, Bool), NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
if attemptSynchronously {
let paths = self.storePathsForId(resource.id)
let paths = self.storePathsForId(id)
if let completeSize = fileSize(paths.complete) {
self.timeBasedCleanup.touch(paths: [
@ -649,7 +626,7 @@ public final class MediaBox {
}
self.dataQueue.async {
guard let (fileContext, releaseContext) = self.fileContext(for: resource) else {
guard let (fileContext, releaseContext) = self.fileContext(for: id) else {
subscriber.putCompletion()
return
}
@ -700,7 +677,7 @@ public final class MediaBox {
let disposable = MetaDisposable()
self.dataQueue.async {
guard let (fileContext, releaseContext) = self.fileContext(for: resource) else {
guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else {
subscriber.putCompletion()
return
}
@ -734,7 +711,7 @@ public final class MediaBox {
}
subscriber.putCompletion()
} else {
if let (fileContext, releaseContext) = self.fileContext(for: resource) {
if let (fileContext, releaseContext) = self.fileContext(for: resource.id) {
let fetchResource = self.wrappedFetchResource.get()
let fetchedDisposable = fileContext.fetchedFullRange(fetch: { ranges in
return fetchResource
@ -796,7 +773,7 @@ public final class MediaBox {
public func cancelInteractiveResourceFetch(_ resource: MediaResource) {
self.dataQueue.async {
if let (fileContext, releaseContext) = self.fileContext(for: resource) {
if let (fileContext, releaseContext) = self.fileContext(for: resource.id) {
fileContext.cancelFullRangeFetches()
releaseContext()
}
@ -805,7 +782,7 @@ public final class MediaBox {
public func storeCachedResourceRepresentation(_ resource: MediaResource, representation: CachedMediaResourceRepresentation, data: Data) {
self.dataQueue.async {
let path = self.cachedRepresentationPathsForId(resource.id, representation: representation).complete
let path = self.cachedRepresentationPathsForId(resource.id, representationId: representation.uniqueId, keepDuration: representation.keepDuration).complete
let _ = try? data.write(to: URL(fileURLWithPath: path))
}
}
@ -815,7 +792,7 @@ public final class MediaBox {
let disposable = MetaDisposable()
let begin: () -> Void = {
let paths = self.cachedRepresentationPathsForId(resource.id, representation: representation)
let paths = self.cachedRepresentationPathsForId(resource.id, representationId: representation.uniqueId, keepDuration: representation.keepDuration)
if let size = fileSize(paths.complete) {
self.timeBasedCleanup.touch(paths: [
paths.complete
@ -837,7 +814,7 @@ public final class MediaBox {
subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false))
}
self.dataQueue.async {
let key = CachedMediaResourceRepresentationKey(resourceId: resource.id, representation: representation)
let key = CachedMediaResourceRepresentationKey(resourceId: resource.id.stringRepresentation, representation: representation.uniqueId)
let context: CachedMediaResourceRepresentationContext
if let currentContext = self.cachedRepresentationContexts[key] {
context = currentContext
@ -976,6 +953,161 @@ public final class MediaBox {
}
}
}
public func customResourceData(id: String, baseResourceId: String?, pathExtension: String?, complete: Bool, fetch: (() -> Signal<CachedMediaResourceRepresentationResult, NoError>)?, keepDuration: CachedMediaRepresentationKeepDuration, attemptSynchronously: Bool) -> Signal<MediaResourceData, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
let begin: () -> Void = {
let paths: ResourceStorePaths
if let baseResourceId = baseResourceId {
paths = self.cachedRepresentationPathsForId(MediaResourceId(baseResourceId), representationId: id, keepDuration: keepDuration)
} else {
paths = self.storePathsForId(MediaResourceId(id))
}
if let size = fileSize(paths.complete) {
self.timeBasedCleanup.touch(paths: [
paths.complete
])
if let pathExtension = pathExtension {
let symlinkPath = paths.complete + ".\(pathExtension)"
if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.linkItem(atPath: paths.complete, toPath: symlinkPath)
}
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: size, complete: true))
subscriber.putCompletion()
} else {
subscriber.putNext(MediaResourceData(path: paths.complete, offset: 0, size: size, complete: true))
subscriber.putCompletion()
}
} else if let fetch = fetch {
if attemptSynchronously && complete {
subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false))
}
self.dataQueue.async {
let key = CachedMediaResourceRepresentationKey(resourceId: baseResourceId, representation: id)
let context: CachedMediaResourceRepresentationContext
if let currentContext = self.cachedRepresentationContexts[key] {
context = currentContext
} else {
context = CachedMediaResourceRepresentationContext()
self.cachedRepresentationContexts[key] = context
}
let index = context.dataSubscribers.add(CachedMediaResourceRepresentationSubscriber(update: { data in
if !complete || data.complete {
if let pathExtension = pathExtension, data.complete {
let symlinkPath = data.path + ".\(pathExtension)"
if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
}
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: data.offset, size: data.size, complete: data.complete))
} else {
subscriber.putNext(data)
}
}
if data.complete {
subscriber.putCompletion()
}
}, onlyComplete: complete))
if let currentData = context.currentData {
if !complete || currentData.complete {
subscriber.putNext(currentData)
}
if currentData.complete {
subscriber.putCompletion()
}
} else if !complete {
subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false))
}
disposable.set(ActionDisposable { [weak context] in
self.dataQueue.async {
if let currentContext = self.cachedRepresentationContexts[key], currentContext === context {
currentContext.dataSubscribers.remove(index)
if currentContext.dataSubscribers.isEmpty {
currentContext.disposable.dispose()
self.cachedRepresentationContexts.removeValue(forKey: key)
}
}
}
})
if !context.initialized {
context.initialized = true
let signal = fetch()
|> deliverOn(self.dataQueue)
context.disposable.set(signal.start(next: { [weak self, weak context] next in
guard let strongSelf = self else {
return
}
var isDone = false
switch next {
case let .temporaryPath(temporaryPath):
rename(temporaryPath, paths.complete)
isDone = true
case let .tempFile(tempFile):
rename(tempFile.path, paths.complete)
TempBox.shared.dispose(tempFile)
isDone = true
case .reset:
let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .readwrite)
file?.truncate(count: 0)
unlink(paths.complete)
case let .data(dataPart):
let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .append)
let dataCount = dataPart.count
dataPart.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
let _ = file?.write(bytes, count: dataCount)
}
case .done:
link(paths.partial, paths.complete)
isDone = true
}
if let strongSelf = self, let currentContext = strongSelf.cachedRepresentationContexts[key], currentContext === context {
if isDone {
currentContext.disposable.dispose()
strongSelf.cachedRepresentationContexts.removeValue(forKey: key)
}
if let size = fileSize(paths.complete) {
let data = MediaResourceData(path: paths.complete, offset: 0, size: size, complete: isDone)
currentContext.currentData = data
for subscriber in currentContext.dataSubscribers.copyItems() {
if !subscriber.onlyComplete || isDone {
subscriber.update(data)
}
}
} else if let size = fileSize(paths.partial) {
let data = MediaResourceData(path: paths.partial, offset: 0, size: size, complete: isDone)
currentContext.currentData = data
for subscriber in currentContext.dataSubscribers.copyItems() {
if !subscriber.onlyComplete || isDone {
subscriber.update(data)
}
}
}
}
}))
}
}
} else {
subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false))
subscriber.putCompletion()
}
}
if attemptSynchronously {
begin()
} else {
self.concurrentQueue.async(begin)
}
return ActionDisposable {
disposable.dispose()
}
}
}
public func collectResourceCacheUsage(_ ids: [MediaResourceId]) -> Signal<[MediaResourceId: Int64], NoError> {
return Signal { subscriber in
@ -1160,7 +1292,7 @@ public final class MediaBox {
return EmptyDisposable
}
}
public func allFileContexts() -> Signal<[(partial: String, complete: String)], NoError> {
return Signal { subscriber in
self.dataQueue.async {

View File

@ -1222,8 +1222,7 @@ public func updateAccountNetworkUsageStats(account: Account, category: MediaReso
public typealias FetchCachedResourceRepresentation = (_ account: Account, _ resource: MediaResource, _ representation: CachedMediaResourceRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError>
public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: AnyMediaReference, _ userInteractive: Bool) -> Signal<AnyMediaReference?, NoError>
public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil, preFetchedResourcePath: @escaping (MediaResource) -> String? = { _ in return nil }) {
account.postbox.mediaBox.preFetchedResourcePath = preFetchedResourcePath
public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil) {
account.postbox.mediaBox.fetchResource = { [weak account] resource, intervals, parameters -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> in
if let strongAccount = account {
if let result = strongAccount.auxiliaryMethods.fetchResource(strongAccount, resource, intervals, parameters) {

View File

@ -2,7 +2,15 @@ import Foundation
import SwiftSignalKit
import Postbox
public typealias EngineTempBox = TempBox
public typealias EngineTempBoxFile = TempBoxFile
public final class EngineMediaResource: Equatable {
public enum CacheTimeout {
case `default`
case shortLived
}
public struct ByteRange {
public enum Priority {
case `default`
@ -21,27 +29,16 @@ public final class EngineMediaResource: Equatable {
public final class Fetch {
public enum Result {
case dataPart(resourceOffset: Int, data: Data, range: Range<Int>, complete: Bool)
case resourceSizeUpdated(Int)
case progressUpdated(Float)
case replaceHeader(data: Data, range: Range<Int>)
case moveLocalFile(path: String)
case moveTempFile(file: TempBoxFile)
case copyLocalItem(MediaResourceDataFetchCopyLocalItem)
case reset
}
public enum Error {
case generic
}
public let signal: (
Signal<[EngineMediaResource.ByteRange], NoError>
) -> Signal<Result, Error>
public let signal: () -> Signal<Result, Error>
public init(_ signal: @escaping (
Signal<[EngineMediaResource.ByteRange], NoError>
) -> Signal<Result, Error>) {
public init(_ signal: @escaping () -> Signal<Result, Error>) {
self.signal = signal
}
}
@ -93,55 +90,9 @@ public final class EngineMediaResource: Equatable {
}
}
public extension MediaResource {
func fetch(engine: TelegramEngine, parameters: MediaResourceFetchParameters?) -> EngineMediaResource.Fetch {
return EngineMediaResource.Fetch { ranges in
return Signal { subscriber in
return engine.account.postbox.mediaBox.fetchResource!(
self,
ranges |> map { ranges -> [(Range<Int>, MediaBoxFetchPriority)] in
return ranges.map { range -> (Range<Int>, MediaBoxFetchPriority) in
let mappedPriority: MediaBoxFetchPriority
switch range.priority {
case .default:
mappedPriority = .default
case .elevated:
mappedPriority = .elevated
case .maximum:
mappedPriority = .maximum
}
return (range.range, mappedPriority)
}
},
parameters
).start(next: { result in
let mappedResult: EngineMediaResource.Fetch.Result
switch result {
case let .dataPart(resourceOffset, data, range, complete):
mappedResult = .dataPart(resourceOffset: resourceOffset, data: data, range: range, complete: complete)
case let .resourceSizeUpdated(size):
mappedResult = .resourceSizeUpdated(size)
case let .progressUpdated(progress):
mappedResult = .progressUpdated(progress)
case let .replaceHeader(data, range):
mappedResult = .replaceHeader(data: data, range: range)
case let .moveLocalFile(path):
mappedResult = .moveLocalFile(path: path)
case let .moveTempFile(file):
mappedResult = .moveTempFile(file: file)
case let .copyLocalItem(item):
mappedResult = .copyLocalItem(item)
case .reset:
mappedResult = .reset
}
subscriber.putNext(mappedResult)
}, error: { _ in
subscriber.putError(.generic)
}, completed: {
subscriber.putCompletion()
})
}
}
public extension EngineMediaResource.ResourceData {
convenience init(_ data: MediaResourceData) {
self.init(path: data.path, availableSize: data.size, isComplete: data.complete)
}
}
@ -165,12 +116,66 @@ public extension TelegramEngine {
return _internal_clearCachedMediaResources(account: self.account, mediaResourceIds: mediaResourceIds)
}
public func data(id: String) -> Signal<EngineMediaResource.ResourceData, NoError> {
preconditionFailure()
public func data(id: EngineMediaResource.Id, attemptSynchronously: Bool = false) -> Signal<EngineMediaResource.ResourceData, NoError> {
return self.account.postbox.mediaBox.resourceData(
id: MediaResourceId(id.stringRepresentation),
pathExtension: nil,
option: .complete(waitUntilFetchStatus: false),
attemptSynchronously: attemptSynchronously
)
|> map { data in
return EngineMediaResource.ResourceData(data)
}
}
public func fetch(id: String, fetch: EngineMediaResource.Fetch) -> Signal<Never, NoError> {
preconditionFailure()
public func custom(id: String, fetch: EngineMediaResource.Fetch, cacheTimeout: EngineMediaResource.CacheTimeout = .default, attemptSynchronously: Bool = false) -> Signal<EngineMediaResource.ResourceData, NoError> {
let mappedKeepDuration: CachedMediaRepresentationKeepDuration
switch cacheTimeout {
case .default:
mappedKeepDuration = .general
case .shortLived:
mappedKeepDuration = .shortLived
}
return self.account.postbox.mediaBox.customResourceData(
id: id,
baseResourceId: nil,
pathExtension: nil,
complete: true,
fetch: {
return Signal { subscriber in
return fetch.signal().start(next: { result in
let mappedResult: CachedMediaResourceRepresentationResult
switch result {
case let .moveTempFile(file):
mappedResult = .tempFile(file)
}
subscriber.putNext(mappedResult)
}, completed: {
subscriber.putCompletion()
})
}
},
keepDuration: mappedKeepDuration,
attemptSynchronously: attemptSynchronously
)
|> map { data in
return EngineMediaResource.ResourceData(data)
}
}
public func httpData(url: String) -> Signal<Data, EngineMediaResource.Fetch.Error> {
return fetchHttpResource(url: url)
|> mapError { _ -> EngineMediaResource.Fetch.Error in
return .generic
}
|> mapToSignal { value -> Signal<Data, EngineMediaResource.Fetch.Error> in
switch value {
case let .dataPart(_, data, _, _):
return .single(data)
default:
return .complete()
}
}
}
public func cancelAllFetches(id: String) {

View File

@ -135,7 +135,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
}
}
if updated {
updateImageSignal = chatMapSnapshotImage(account: item.context.account, resource: MapSnapshotMediaResource(latitude: selectedMedia.latitude, longitude: selectedMedia.longitude, width: Int32(imageSize.width), height: Int32(imageSize.height)))
updateImageSignal = chatMapSnapshotImage(engine: item.context.engine, resource: MapSnapshotMediaResource(latitude: selectedMedia.latitude, longitude: selectedMedia.longitude, width: Int32(imageSize.width), height: Int32(imageSize.height)))
}
}

View File

@ -247,7 +247,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
case let .locationHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .location(theme, location):
let imageSignal = chatMapSnapshotImage(account: arguments.context.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
let imageSignal = chatMapSnapshotImage(engine: arguments.context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil)
case let .changeLocation(_, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
@ -258,7 +258,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
case let .venueHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .venue(_, _, venue):
return ItemListVenueItem(presentationData: presentationData, account: arguments.context.account, venue: venue, sectionId: self.section, style: .blocks, action: {
return ItemListVenueItem(presentationData: presentationData, engine: arguments.context.engine, venue: venue, sectionId: self.section, style: .blocks, action: {
arguments.updateWithVenue(venue)
})
}

View File

@ -119,8 +119,6 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
}
return fetchAnimatedStickerFirstFrameRepresentation(account: account, resource: resource, resourceData: data, representation: representation)
}
} else if let resource = resource as? MapSnapshotMediaResource, let _ = representation as? MapSnapshotMediaResourceRepresentation {
return fetchMapSnapshotResource(resource: resource)
} else if let resource = resource as? YoutubeEmbedStoryboardMediaResource, let _ = representation as? YoutubeEmbedStoryboardMediaResourceRepresentation {
return fetchYoutubeEmbedStoryboardResource(resource: resource)
} else if let representation = representation as? CachedPreparedPatternWallpaperRepresentation {

View File

@ -1041,7 +1041,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
if let location = (data.cachedData as? CachedChannelData)?.peerGeoLocation {
items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased()))
let imageSignal = chatMapSnapshotImage(account: context.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
items[.groupLocation]!.append(PeerInfoScreenAddressItem(
id: ItemLocation,
label: "",
@ -1271,7 +1271,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
if isCreator, let location = cachedData.peerGeoLocation {
items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased()))
let imageSignal = chatMapSnapshotImage(account: context.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
items[.groupLocation]!.append(PeerInfoScreenAddressItem(
id: ItemLocation,
label: "",

View File

@ -408,9 +408,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|> mapToSignal { result -> Signal<AddedAccountResult, NoError> in
switch result {
case let .authorized(account):
setupAccount(account, fetchCachedResourceRepresentation: fetchCachedResourceRepresentation, transformOutgoingMessageMedia: transformOutgoingMessageMedia, preFetchedResourcePath: { resource in
return nil
})
setupAccount(account, fetchCachedResourceRepresentation: fetchCachedResourceRepresentation, transformOutgoingMessageMedia: transformOutgoingMessageMedia)
return account.postbox.transaction { transaction -> AddedAccountResult in
let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration)?.get(LimitsConfiguration.self) ?? LimitsConfiguration.defaultValue
let contentSettings = getContentSettings(transaction: transaction)

View File

@ -31,8 +31,6 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(fetchResour
return fetchOpenInAppIconResource(resource: resource)
} else if let resource = resource as? EmojiSpriteResource {
return fetchEmojiSpriteResource(account: account, resource: resource)
} else if let resource = resource as? VenueIconResource {
return fetchVenueIconResource(account: account, resource: resource)
} else if let resource = resource as? BundleResource {
return Signal { subscriber in
subscriber.putNext(.reset)