mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
316 lines
12 KiB
Swift
316 lines
12 KiB
Swift
import Foundation
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramApi
|
|
|
|
import SyncCore
|
|
|
|
private typealias SignalKitTimer = SwiftSignalKit.Timer
|
|
|
|
public enum PeerNearby {
|
|
case selfPeer(expires: Int32)
|
|
case peer(id: PeerId, expires: Int32, distance: Int32)
|
|
|
|
var expires: Int32 {
|
|
switch self {
|
|
case let .selfPeer(expires), let .peer(_, expires, _):
|
|
return expires
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum PeerNearbyVisibilityUpdate {
|
|
case visible(latitude: Double, longitude: Double)
|
|
case location(latitude: Double, longitude: Double)
|
|
case invisible
|
|
}
|
|
|
|
public func updatePeersNearbyVisibility(account: Account, update: PeerNearbyVisibilityUpdate, background: Bool) -> Signal<Void, NoError> {
|
|
var flags: Int32 = 0
|
|
var geoPoint: Api.InputGeoPoint
|
|
var selfExpires: Int32?
|
|
|
|
switch update {
|
|
case let .visible(latitude, longitude):
|
|
flags |= (1 << 0)
|
|
geoPoint = .inputGeoPoint(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil)
|
|
selfExpires = 10800
|
|
case let .location(latitude, longitude):
|
|
geoPoint = .inputGeoPoint(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil)
|
|
case .invisible:
|
|
flags |= (1 << 0)
|
|
geoPoint = .inputGeoPointEmpty
|
|
selfExpires = 0
|
|
}
|
|
|
|
let _ = (account.postbox.transaction { transaction in
|
|
transaction.updatePreferencesEntry(key: PreferencesKeys.peersNearby, { entry in
|
|
var settings = entry as? PeersNearbyState ?? PeersNearbyState.default
|
|
if case .invisible = update {
|
|
settings.visibilityExpires = nil
|
|
} else if let expires = selfExpires {
|
|
settings.visibilityExpires = expires
|
|
}
|
|
return settings
|
|
})
|
|
}).start()
|
|
|
|
if background {
|
|
flags |= (1 << 1)
|
|
}
|
|
|
|
return account.network.request(Api.functions.contacts.getLocated(flags: flags, geoPoint: geoPoint, selfExpires: selfExpires))
|
|
|> map(Optional.init)
|
|
|> `catch` { error -> Signal<Api.Updates?, NoError> in
|
|
if error.errorCode == 406 {
|
|
if error.errorDescription == "USERPIC_PRIVACY_REQUIRED" {
|
|
let _ = (account.postbox.transaction { transaction in
|
|
transaction.updatePreferencesEntry(key: PreferencesKeys.peersNearby, { entry in
|
|
var settings = entry as? PeersNearbyState ?? PeersNearbyState.default
|
|
settings.visibilityExpires = nil
|
|
return settings
|
|
})
|
|
}).start()
|
|
}
|
|
return .single(nil)
|
|
} else {
|
|
return .single(nil)
|
|
}
|
|
}
|
|
|> mapToSignal { updates -> Signal<Void, NoError> in
|
|
if let updates = updates {
|
|
account.stateManager.addUpdates(updates)
|
|
}
|
|
return .complete()
|
|
}
|
|
}
|
|
|
|
public final class PeersNearbyContext {
|
|
private let queue: Queue = Queue.mainQueue()
|
|
private var subscribers = Bag<([PeerNearby]?) -> Void>()
|
|
private let disposable = MetaDisposable()
|
|
private var timer: SignalKitTimer?
|
|
|
|
private var entries: [PeerNearby]?
|
|
|
|
public init(network: Network, stateManager: AccountStateManager, coordinate: (latitude: Double, longitude: Double)) {
|
|
let expiryExtension: Double = 10.0
|
|
|
|
let poll = network.request(Api.functions.contacts.getLocated(flags: 0, geoPoint: .inputGeoPoint(flags: 0, lat: coordinate.latitude, long: coordinate.longitude, accuracyRadius: nil), selfExpires: nil))
|
|
|> map(Optional.init)
|
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
|
return .single(nil)
|
|
}
|
|
|> castError(Void.self)
|
|
|> mapToSignal { updates -> Signal<[PeerNearby], Void> in
|
|
var peersNearby: [PeerNearby] = []
|
|
if let updates = updates {
|
|
switch updates {
|
|
case let .updates(updates, _, _, _, _):
|
|
for update in updates {
|
|
if case let .updatePeerLocated(peers) = update {
|
|
for peer in peers {
|
|
switch peer {
|
|
case let .peerLocated(peer, expires, distance):
|
|
peersNearby.append(.peer(id: peer.peerId, expires: expires, distance: distance))
|
|
case let .peerSelfLocated(expires):
|
|
peersNearby.append(.selfPeer(expires: expires))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
stateManager.addUpdates(updates)
|
|
}
|
|
return .single(peersNearby)
|
|
|> then(
|
|
stateManager.updatedPeersNearby()
|
|
|> castError(Void.self)
|
|
)
|
|
}
|
|
|
|
let error: Signal<Void, Void> = .single(Void()) |> then(Signal.fail(Void()) |> suspendAwareDelay(25.0, queue: self.queue))
|
|
let combined = combineLatest(poll, error)
|
|
|> map { data, _ -> [PeerNearby] in
|
|
return data
|
|
}
|
|
|> restartIfError
|
|
|> `catch` { _ -> Signal<[PeerNearby], NoError> in
|
|
return .single([])
|
|
}
|
|
|
|
self.disposable.set((combined
|
|
|> deliverOn(self.queue)).start(next: { [weak self] updatedEntries in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
|
var entries = strongSelf.entries?.filter { Double($0.expires) + expiryExtension > timestamp } ?? []
|
|
let updatedEntries = updatedEntries.filter { Double($0.expires) + expiryExtension > timestamp }
|
|
|
|
var existingPeerIds: [PeerId: Int] = [:]
|
|
var existingSelfPeer: Int?
|
|
for i in 0 ..< entries.count {
|
|
if case let .peer(id, _, _) = entries[i] {
|
|
existingPeerIds[id] = i
|
|
} else if case .selfPeer = entries[i] {
|
|
existingSelfPeer = i
|
|
}
|
|
}
|
|
|
|
var selfPeer: PeerNearby?
|
|
for entry in updatedEntries {
|
|
switch entry {
|
|
case .selfPeer:
|
|
if let index = existingSelfPeer {
|
|
entries[index] = entry
|
|
} else {
|
|
selfPeer = entry
|
|
}
|
|
case let .peer(id, _, _):
|
|
if let index = existingPeerIds[id] {
|
|
entries[index] = entry
|
|
} else {
|
|
entries.append(entry)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let peer = selfPeer {
|
|
entries.insert(peer, at: 0)
|
|
}
|
|
|
|
strongSelf.entries = entries
|
|
for subscriber in strongSelf.subscribers.copyItems() {
|
|
subscriber(strongSelf.entries)
|
|
}
|
|
}))
|
|
|
|
self.timer = SignalKitTimer(timeout: 2.0, repeat: true, completion: { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
|
strongSelf.entries = strongSelf.entries?.filter { Double($0.expires) + expiryExtension > timestamp }
|
|
for subscriber in strongSelf.subscribers.copyItems() {
|
|
subscriber(strongSelf.entries)
|
|
}
|
|
}, queue: self.queue)
|
|
self.timer?.start()
|
|
}
|
|
|
|
deinit {
|
|
self.disposable.dispose()
|
|
self.timer?.invalidate()
|
|
}
|
|
|
|
public func get() -> Signal<[PeerNearby]?, NoError> {
|
|
let queue = self.queue
|
|
return Signal { [weak self] subscriber in
|
|
if let strongSelf = self {
|
|
subscriber.putNext(strongSelf.entries)
|
|
|
|
let index = strongSelf.subscribers.add({ entries in
|
|
subscriber.putNext(entries)
|
|
})
|
|
|
|
return ActionDisposable {
|
|
queue.async {
|
|
if let strongSelf = self {
|
|
strongSelf.subscribers.remove(index)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return EmptyDisposable
|
|
}
|
|
} |> runOn(queue)
|
|
}
|
|
}
|
|
|
|
public func updateChannelGeoLocation(postbox: Postbox, network: Network, channelId: PeerId, coordinate: (latitude: Double, longitude: Double)?, address: String?) -> Signal<Bool, NoError> {
|
|
return postbox.transaction { transaction -> Peer? in
|
|
return transaction.getPeer(channelId)
|
|
}
|
|
|> mapToSignal { channel -> Signal<Bool, NoError> in
|
|
guard let channel = channel, let apiChannel = apiInputChannel(channel) else {
|
|
return .single(false)
|
|
}
|
|
|
|
let geoPoint: Api.InputGeoPoint
|
|
if let (latitude, longitude) = coordinate, let _ = address {
|
|
geoPoint = .inputGeoPoint(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil)
|
|
} else {
|
|
geoPoint = .inputGeoPointEmpty
|
|
}
|
|
|
|
return network.request(Api.functions.channels.editLocation(channel: apiChannel, geoPoint: geoPoint, address: address ?? ""))
|
|
|> map { result -> Bool in
|
|
switch result {
|
|
case .boolTrue:
|
|
return true
|
|
case .boolFalse:
|
|
return false
|
|
}
|
|
}
|
|
|> `catch` { error -> Signal<Bool, NoError> in
|
|
return .single(false)
|
|
}
|
|
|> mapToSignal { result in
|
|
if result {
|
|
return postbox.transaction { transaction in
|
|
transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { (_, current) -> CachedPeerData? in
|
|
let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData()
|
|
let peerGeoLocation: PeerGeoLocation?
|
|
if let (latitude, longitude) = coordinate, let address = address {
|
|
peerGeoLocation = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address)
|
|
} else {
|
|
peerGeoLocation = nil
|
|
}
|
|
return current.withUpdatedPeerGeoLocation(peerGeoLocation)
|
|
})
|
|
}
|
|
|> map { _ in
|
|
return result
|
|
}
|
|
} else {
|
|
return .single(result)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct PeersNearbyState: PreferencesEntry, Equatable {
|
|
public var visibilityExpires: Int32?
|
|
|
|
public static var `default` = PeersNearbyState(visibilityExpires: nil)
|
|
|
|
public init(visibilityExpires: Int32?) {
|
|
self.visibilityExpires = visibilityExpires
|
|
}
|
|
|
|
public init(decoder: PostboxDecoder) {
|
|
self.visibilityExpires = decoder.decodeOptionalInt32ForKey("expires")
|
|
}
|
|
|
|
public func encode(_ encoder: PostboxEncoder) {
|
|
if let expires = self.visibilityExpires {
|
|
encoder.encodeInt32(expires, forKey: "expires")
|
|
} else {
|
|
encoder.encodeNil(forKey: "expires")
|
|
}
|
|
}
|
|
|
|
public func isEqual(to: PreferencesEntry) -> Bool {
|
|
if let to = to as? PeersNearbyState, self == to {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|