Various fixes

This commit is contained in:
Peter Iakovlev 2018-12-02 04:43:56 +04:00
parent d3d3932595
commit a1c3217083
40 changed files with 2504 additions and 2410 deletions

View File

@ -6,16 +6,26 @@ import SwiftSignalKit
import AsyncDisplayKit
import TelegramCore
enum AvatarGalleryEntry: Equatable {
case topImage([TelegramMediaImageRepresentation], GalleryItemIndexData?)
case image(TelegramMediaImage, Peer, Int32, GalleryItemIndexData?)
public struct ImageRepresentationWithReference: Equatable {
public let representation: TelegramMediaImageRepresentation
public let reference: MediaResourceReference
var representations: [TelegramMediaImageRepresentation] {
public init(representation: TelegramMediaImageRepresentation, reference: MediaResourceReference) {
self.representation = representation
self.reference = reference
}
}
enum AvatarGalleryEntry: Equatable {
case topImage([ImageRepresentationWithReference], GalleryItemIndexData?)
case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], Peer, Int32, GalleryItemIndexData?)
var representations: [ImageRepresentationWithReference] {
switch self {
case let .topImage(representations, _):
return representations
case let .image(image, _, _, _):
return image.representations
case let .image(_, representations, _, _, _):
return representations
}
}
@ -23,7 +33,7 @@ enum AvatarGalleryEntry: Equatable {
switch self {
case let .topImage(_, indexData):
return indexData
case let .image(_, _, _, indexData):
case let .image(_, _, _, _, indexData):
return indexData
}
}
@ -36,8 +46,8 @@ enum AvatarGalleryEntry: Equatable {
} else {
return false
}
case let .image(lhsImage, lhsPeer, lhsDate, lhsIndexData):
if case let .image(rhsImage, rhsPeer, rhsDate, rhsIndexData) = rhs, lhsImage.isEqual(to: rhsImage), arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsIndexData == rhsIndexData {
case let .image(lhsImageReference, lhsRepresentations, lhsPeer, lhsDate, lhsIndexData):
if case let .image(rhsImageReference, rhsRepresentations, rhsPeer, rhsDate, rhsIndexData) = rhs, lhsImageReference == rhsImageReference, lhsRepresentations == rhsRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsIndexData == rhsIndexData {
return true
} else {
return false
@ -58,12 +68,8 @@ final class AvatarGalleryControllerPresentationArguments {
private func initialAvatarGalleryEntries(peer: Peer) -> [AvatarGalleryEntry]{
var initialEntries: [AvatarGalleryEntry] = []
if let user = peer as? TelegramUser, !user.photo.isEmpty {
initialEntries.append(.topImage(user.photo, nil))
} else if let group = peer as? TelegramGroup {
initialEntries.append(.topImage(group.photo, nil))
} else if let channel = peer as? TelegramChannel {
initialEntries.append(.topImage(channel.photo, nil))
if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer) {
initialEntries.append(.topImage(peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), nil))
}
return initialEntries
}
@ -79,10 +85,9 @@ func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<[Avatar
for photo in photos {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
if result.isEmpty, let first = initialEntries.first {
let image = TelegramMediaImage(imageId: photo.image.imageId, representations: first.representations, reference: photo.reference, partialReference: nil)
result.append(.image(image, peer, photo.date, indexData))
result.append(.image(photo.image.reference, first.representations, peer, photo.date, indexData))
} else {
result.append(.image(photo.image, peer, photo.date, indexData))
result.append(.image(photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), peer, photo.date, indexData))
}
index += 1
}
@ -387,8 +392,8 @@ class AvatarGalleryController: ViewController {
switch entry {
case .topImage:
break
case let .image(image, _, _, _):
if let reference = image.reference {
case let .image(reference, _, _, _, _):
if let reference = reference {
let _ = removeAccountPhoto(network: self.account.network, reference: reference).start()
}
if entry == self.entries.first {

View File

@ -74,7 +74,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
var nameText: String?
var dateText: String?
switch entry {
case let .image(_, peer, date, _):
case let .image(_, _, peer, date, _):
nameText = peer.displayTitle
dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date)
default:

View File

@ -151,7 +151,7 @@ final class CallControllerNode: ASDisplayNode {
if !arePeersEqual(self.peer, peer) {
self.peer = peer
if let peerReference = PeerReference(peer), !peer.profileImageRepresentations.isEmpty {
let representations: [(TelegramMediaImageRepresentation, MediaResourceReference)] = peer.profileImageRepresentations.map({ ($0, .avatar(peer: peerReference, resource: $0.resource)) })
let representations: [ImageRepresentationWithReference] = peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: .avatar(peer: peerReference, resource: $0.resource)) })
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.account, representations: representations, autoFetchFullSize: true))
self.dimNode.isHidden = false
} else {

View File

@ -620,7 +620,7 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
})
hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first?.representation
updateHiddenAvatarImpl?()
}))
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in

View File

@ -154,6 +154,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
private var audioRecorderValue: ManagedAudioRecorder?
private var audioRecorder = Promise<ManagedAudioRecorder?>()
private var audioRecorderDisposable: Disposable?
private var audioRecorderStatusDisposable: Disposable?
private var videoRecorderValue: InstantVideoController?
private var tempVideoRecorderValue: InstantVideoController?
@ -207,6 +208,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
private let chatAdditionalDataDisposable = MetaDisposable()
private var beginMediaRecordingRequestId: Int = 0
private var lockMediaRecordingRequestId: Int?
var purposefulAction: (() -> Void)?
@ -1319,7 +1321,8 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
}
})
self.audioRecorderDisposable = (self.audioRecorder.get() |> deliverOnMainQueue).start(next: { [weak self] audioRecorder in
self.audioRecorderDisposable = (self.audioRecorder.get()
|> deliverOnMainQueue).start(next: { [weak self] audioRecorder in
if let strongSelf = self {
if strongSelf.audioRecorderValue !== audioRecorder {
strongSelf.audioRecorderValue = audioRecorder
@ -1329,7 +1332,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
$0.updatedInputTextPanelState { panelState in
if let audioRecorder = audioRecorder {
if panelState.mediaRecordingState == nil {
return panelState.withUpdatedMediaRecordingState(.audio(recorder: audioRecorder, isLocked: false))
return panelState.withUpdatedMediaRecordingState(.audio(recorder: audioRecorder, isLocked: strongSelf.lockMediaRecordingRequestId == strongSelf.beginMediaRecordingRequestId))
}
} else {
return panelState.withUpdatedMediaRecordingState(nil)
@ -1337,12 +1340,21 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
return panelState
}
})
strongSelf.audioRecorderStatusDisposable?.dispose()
if let audioRecorder = audioRecorder {
if !audioRecorder.beginWithTone {
strongSelf.recorderFeedback?.impact(.light)
}
audioRecorder.start()
strongSelf.audioRecorderStatusDisposable = (audioRecorder.recordingState
|> deliverOnMainQueue).start(next: { value in
if case .stopped = value {
self?.stopMediaRecorder()
}
})
} else {
strongSelf.audioRecorderStatusDisposable = nil
}
}
}
@ -1386,6 +1398,10 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
}
}
strongSelf.present(videoRecorder, in: .window(.root))
if strongSelf.lockMediaRecordingRequestId == strongSelf.beginMediaRecordingRequestId {
videoRecorder.lockVideo()
}
}
if let previousVideoRecorderValue = previousVideoRecorderValue {
@ -1513,6 +1529,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
}
self.urlPreviewQueryState?.1.dispose()
self.audioRecorderDisposable?.dispose()
self.audioRecorderStatusDisposable?.dispose()
self.videoRecorderDisposable?.dispose()
self.buttonKeyboardMessageDisposable?.dispose()
self.cachedDataDisposable?.dispose()
@ -2449,9 +2466,14 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
return
}
strongSelf.beginMediaRecordingRequestId += 1
self?.stopMediaRecorder()
strongSelf.lockMediaRecordingRequestId = nil
strongSelf.stopMediaRecorder()
}, lockMediaRecording: { [weak self] in
self?.lockMediaRecorder()
guard let strongSelf = self else {
return
}
strongSelf.lockMediaRecordingRequestId = strongSelf.beginMediaRecordingRequestId
strongSelf.lockMediaRecorder()
}, deleteRecordedMedia: { [weak self] in
self?.deleteMediaRecording()
}, sendRecordedMedia: { [weak self] in

View File

@ -92,6 +92,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var overlayContextPanelNode: ChatInputContextPanelNode?
private var inputNode: ChatInputNode?
private var disappearingNode: ChatInputNode?
private var textInputPanelNode: ChatTextInputPanelNode?
private var inputMediaNode: ChatMediaInputNode?
@ -1222,7 +1223,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
})
}
if let disappearingNode = self.disappearingNode {
let targetY: CGFloat
if cleanInsets.bottom.isLess(than: insets.bottom) {
targetY = layout.size.height - insets.bottom
} else {
targetY = layout.size.height
}
transition.updateFrame(node: disappearingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: targetY), size: CGSize(width: layout.size.width, height: max(insets.bottom, disappearingNode.bounds.size.height))))
}
if let dismissedInputNode = dismissedInputNode {
self.disappearingNode = dismissedInputNode
let targetY: CGFloat
if cleanInsets.bottom.isLess(than: insets.bottom) {
targetY = layout.size.height - insets.bottom
@ -1230,8 +1241,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
targetY = layout.size.height
}
transition.updateFrame(node: dismissedInputNode, frame: CGRect(origin: CGPoint(x: 0.0, y: targetY), size: CGSize(width: layout.size.width, height: max(insets.bottom, dismissedInputNode.bounds.size.height))), force: true, completion: { [weak self, weak dismissedInputNode] completed in
if completed, let dismissedInputNode = dismissedInputNode {
if let dismissedInputNode = dismissedInputNode {
if let strongSelf = self {
if strongSelf.disappearingNode === dismissedInputNode {
strongSelf.disappearingNode = nil
}
if strongSelf.inputNode !== dismissedInputNode {
dismissedInputNode.alpha = 0.0
dismissedInputNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak dismissedInputNode] completed in

View File

@ -702,7 +702,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
var subject: ShareControllerSubject = ShareControllerSubject.messages(messages)
for m in messages[0].media {
if let image = m as? TelegramMediaImage {
subject = .image(image.representations)
subject = .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(messages[0]), media: m), resource: $0.resource)) }))
} else if let webpage = m as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if content.embedType == "iframe" {
let item = OpenInItem.url(url: content.url)

View File

@ -328,7 +328,10 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
actionSheet?.dismissAnimated()
if let strongSelf = self {
let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId, reportChatSpam: false).start()
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId, reportChatSpam: false).start(completed: {
self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
})
}
}))
@ -337,7 +340,10 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
actionSheet?.dismissAnimated()
if let strongSelf = self {
let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId, reportChatSpam: false).start()
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId, reportChatSpam: false).start(completed: {
self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
})
let _ = requestUpdatePeerIsBlocked(account: strongSelf.account, peerId: peer.id, isBlocked: true).start()
}
}))

View File

@ -336,6 +336,11 @@ final class ChatListNode: ListView {
var isEmptyUpdated: ((Bool) -> Void)?
private var wasEmpty: Bool?
private let currentRemovingPeerId = Atomic<PeerId?>(value: nil)
func setCurrentRemovingPeerId(_ peerId: PeerId?) {
let _ = self.currentRemovingPeerId.swap(peerId)
}
init(account: Account, groupId: PeerGroupId?, controlsHistoryPreload: Bool, mode: ChatListNodeMode, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
self.account = account
self.controlsHistoryPreload = controlsHistoryPreload
@ -431,13 +436,14 @@ final class ChatListNode: ListView {
let viewProcessingQueue = self.viewProcessingQueue
let chatListViewUpdate = self.chatListLocation.get()
|> distinctUntilChanged
|> mapToSignal { location in
return chatListViewForLocation(groupId: groupId, location: location, account: account)
}
|> distinctUntilChanged
|> mapToSignal { location in
return chatListViewForLocation(groupId: groupId, location: location, account: account)
}
let previousState = Atomic<ChatListNodeState>(value: self.currentState)
let previousView = Atomic<ChatListNodeView?>(value: nil)
let currentRemovingPeerId = self.currentRemovingPeerId
let savedMessagesPeer: Signal<Peer?, NoError>
if case let .peers(filter) = mode, filter == [.onlyWriteable] {
@ -453,39 +459,38 @@ final class ChatListNode: ListView {
let entries = chatListNodeEntriesForView(update.view, state: state, savedMessagesPeer: savedMessagesPeer, mode: mode).filter { entry in
switch entry {
case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _):
//ChatListNodePeersFilter
switch mode {
case .chatList:
return true
case let .peers(filter):
guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false }
guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false }
guard !filter.contains(.onlyPrivateChats) || peer.peerId.namespace == Namespaces.Peer.CloudUser else { return false }
case .chatList:
return true
case let .peers(filter):
guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false }
guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false }
guard !filter.contains(.onlyPrivateChats) || peer.peerId.namespace == Namespaces.Peer.CloudUser else { return false }
if filter.contains(.onlyGroups) {
var isGroup: Bool = false
if let peer = peer.chatMainPeer as? TelegramChannel, case .group = peer.info {
isGroup = true
} else if peer.peerId.namespace == Namespaces.Peer.CloudGroup {
isGroup = true
if filter.contains(.onlyGroups) {
var isGroup: Bool = false
if let peer = peer.chatMainPeer as? TelegramChannel, case .group = peer.info {
isGroup = true
} else if peer.peerId.namespace == Namespaces.Peer.CloudGroup {
isGroup = true
}
if !isGroup {
return false
}
}
if !isGroup {
return false
if filter.contains(.onlyChannels) {
if let peer = peer.chatMainPeer as? TelegramChannel, case .broadcast = peer.info {
return true
} else {
return false
}
}
return true
}
if filter.contains(.onlyChannels) {
if let peer = peer.chatMainPeer as? TelegramChannel, case .broadcast = peer.info {
return true
} else {
return false
}
}
default:
return true
}
default:
return true
}
}
@ -533,26 +538,36 @@ final class ChatListNode: ListView {
}
}
let removingPeerId = currentRemovingPeerId.with { $0 }
var disableAnimations = state.presentationData.disableAnimations
if previousState.editing != state.editing {
disableAnimations = false
} else {
var previousPinnedCount = 0
var updatedPinnedCount = 0
var didIncludeRemovingPeerId = false
if let previous = previousView {
for entry in previous.filteredEntries {
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
if index.pinningIndex != nil {
previousPinnedCount += 1
}
if index.messageIndex.id.peerId == removingPeerId {
didIncludeRemovingPeerId = true
}
}
}
}
var doesIncludeRemovingPeerId = false
for entry in processedView.filteredEntries {
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
if index.pinningIndex != nil {
updatedPinnedCount += 1
}
if index.messageIndex.id.peerId == removingPeerId {
doesIncludeRemovingPeerId = true
}
}
}
if previousPinnedCount != updatedPinnedCount {
@ -561,11 +576,19 @@ final class ChatListNode: ListView {
if previousState.selectedPeerIds != state.selectedPeerIds {
disableAnimations = false
}
if !doesIncludeRemovingPeerId, didIncludeRemovingPeerId {
disableAnimations = false
}
}
return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, disableAnimations: disableAnimations, account: account, scrollPosition: updatedScrollPosition)
|> map({ mappedChatListNodeViewListTransition(account: account, nodeInteraction: nodeInteraction, peerGroupId: groupId, mode: mode, transition: $0) })
|> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue)
var searchMode = false
if case .chatList = mode {
searchMode = true
}
return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, disableAnimations: disableAnimations, account: account, scrollPosition: updatedScrollPosition, searchMode: searchMode)
|> map({ mappedChatListNodeViewListTransition(account: account, nodeInteraction: nodeInteraction, peerGroupId: groupId, mode: mode, transition: $0) })
|> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue)
}
let appliedTransition = chatListNodeViewTransition |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal<Void, NoError> in

View File

@ -44,7 +44,7 @@ enum ChatListNodeViewScrollPosition {
case index(index: ChatListIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool)
}
func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toView: ChatListNodeView, reason: ChatListNodeViewTransitionReason, disableAnimations: Bool, account: Account, scrollPosition: ChatListNodeViewScrollPosition?) -> Signal<ChatListNodeViewTransition, NoError> {
func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toView: ChatListNodeView, reason: ChatListNodeViewTransitionReason, disableAnimations: Bool, account: Account, scrollPosition: ChatListNodeViewScrollPosition?, searchMode: Bool) -> Signal<ChatListNodeViewTransition, NoError> {
return Signal { subscriber in
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries)
@ -194,7 +194,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV
fromEmptyView = true
}
if fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 2 {
if !searchMode && fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 2 {
if case .SearchEntry = toView.filteredEntries[toView.filteredEntries.count - 1] {
scrollToItem = ListViewScrollToItem(index: 1, position: .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
}

View File

@ -38,6 +38,8 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
strongSelf.timestamp = duration
case let .recording(duration, _):
strongSelf.timestamp = duration
case .stopped:
break
}
}
}))

View File

@ -350,7 +350,7 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
func micButtonInteractionCancelled(_ velocity: CGPoint) {
//print("\(CFAbsoluteTimeGetCurrent()) cancelled")
self.modeTimeoutTimer?.invalidate()
self.endRecording(false)
self.stopRecording()
}
func micButtonInteractionCompleted(_ velocity: CGPoint) {

View File

@ -52,7 +52,7 @@ public class ExternalMusicAlbumArtResource: TelegramMediaResource {
return ExternalMusicAlbumArtResourceId(title: self.title, performer: self.performer, isThumbnail: self.isThumbnail)
}
public func isEqual(to: TelegramMediaResource) -> Bool {
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? ExternalMusicAlbumArtResource {
return self.title == to.title && self.performer == to.performer && self.isThumbnail == to.isThumbnail
} else {

View File

@ -1088,7 +1088,11 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
if case .creator = group.role, state.editingState != nil {
entries.append(.convertToSupergroup(presentationData.theme, presentationData.strings.GroupInfo_ConvertToSupergroup))
}
entries.append(.leave(presentationData.theme, presentationData.strings.GroupInfo_DeleteAndExit))
if case .creator = group.role {
entries.append(.leave(presentationData.theme, presentationData.strings.GroupInfo_DeleteAndExit))
} else {
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
}
}
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
if case .member = channel.participationStatus, let cachedChannelData = view.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount <= 200 {
@ -1192,7 +1196,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
})
hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first?.representation
updateHiddenAvatarImpl?()
}))
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in

View File

@ -42,7 +42,7 @@ public class ICloudFileResource: TelegramMediaResource {
return ICloudFileResourceId(urlData: self.urlData)
}
public func isEqual(to: TelegramMediaResource) -> Bool {
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? ICloudFileResource {
if self.urlData != to.urlData {
return false

View File

@ -135,6 +135,8 @@ final class InstantPageMediaPlaylist: SharedMediaPlaylist {
return InstantPagePlaylistLocation(webpageId: self.webPage.webpageId)
}
var currentItemDisappeared: (() -> Void)?
private var currentItem: InstantPageMedia?
private var playedToEnd: Bool = false
private var order: MusicPlaybackSettingsOrder = .regular

View File

@ -263,7 +263,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let tempFilePath = NSTemporaryDirectory() + "\(randomId).jpeg"
let scaledSize = image.size.aspectFitted(CGSize(width: 1280.0, height: 1280.0))
let scaledSize = image.size.aspectFittedOrSmaller(CGSize(width: 1280.0, height: 1280.0))
if let scaledImage = TGScaleImageToPixelSize(image, scaledSize) {
if let scaledImageData = compressImageToJPEG(scaledImage, quality: 0.6) {
let _ = try? scaledImageData.write(to: URL(fileURLWithPath: tempFilePath))
@ -299,7 +299,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let size = CGSize(width: CGFloat(asset.pixelWidth), height: CGFloat(asset.pixelHeight))
let scaledSize = size.aspectFitted(CGSize(width: 1280.0, height: 1280.0))
let scaledSize = size.aspectFittedOrSmaller(CGSize(width: 1280.0, height: 1280.0))
let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: arc4random64())
representations.append(TelegramMediaImageRepresentation(dimensions: scaledSize, resource: resource))

View File

@ -53,7 +53,7 @@ private enum LanguageListEntry: Comparable, Identifiable {
openSearch()
})
case let .localization(_, info, type, selected, activity, revealed, editing):
return LocalizationListItem(theme: theme, strings: strings, id: info.languageCode, title: info.title, subtitle: info.localizedTitle, checked: selected, activity: activity, editing: LocalizationListItemEditing(editable: !searchMode && !info.isOfficial, editing: editing, revealed: revealed, reorderable: false), sectionId: type == .official ? LanguageListSection.official.rawValue : LanguageListSection.unofficial.rawValue, alwaysPlain: searchMode, action: {
return LocalizationListItem(theme: theme, strings: strings, id: info.languageCode, title: info.title, subtitle: info.localizedTitle, checked: selected, activity: activity, editing: LocalizationListItemEditing(editable: !selected && !searchMode && !info.isOfficial, editing: editing, revealed: !selected && revealed, reorderable: false), sectionId: type == .official ? LanguageListSection.official.rawValue : LanguageListSection.unofficial.rawValue, alwaysPlain: searchMode, action: {
selectLocalization(info)
}, setItemWithRevealedOptions: setItemWithRevealedOptions, removeItem: removeItem)
}

View File

@ -175,9 +175,9 @@ final class ManagedAudioRecorderContext {
private var hasAudioSession = false
private var audioSessionDisposable: Disposable?
private var tonePlayer: TonePlayer?
//private var toneRenderer: MediaPlayerAudioRenderer?
//private var toneRendererAudioSession: MediaPlayerAudioSessionCustomControl?
//private var tonePlayer: TonePlayer?
private var toneRenderer: MediaPlayerAudioRenderer?
private var toneRendererAudioSession: MediaPlayerAudioSessionCustomControl?
private var toneRendererAudioSessionActivated = false
private var processSamples = false
@ -200,7 +200,7 @@ final class ManagedAudioRecorderContext {
self.dataItem = TGDataItem()
self.oggWriter = TGOggOpusWriter()
/*if false, let toneData = audioRecordingToneData {
if beginWithTone, let toneData = audioRecordingToneData {
self.processSamples = false
let toneRenderer = MediaPlayerAudioRenderer(audioSession: .custom({ [weak self] control in
queue.async {
@ -286,9 +286,9 @@ final class ManagedAudioRecorderContext {
toneTimer.start()
} else {
self.processSamples = true
}*/
}
if beginWithTone, let beginToneData = beginToneData {
/*if beginWithTone, let beginToneData = beginToneData {
self.tonePlayer = TonePlayer()
self.tonePlayer?.play(data: beginToneData, completed: { [weak self] in
queue.async {
@ -307,7 +307,7 @@ final class ManagedAudioRecorderContext {
})
} else {
self.processSamples = true
}
}*/
addAudioRecorderContext(self.id, self)
addAudioUnitHolder(self.id, queue, self.audioUnit)
@ -331,7 +331,7 @@ final class ManagedAudioRecorderContext {
self.audioSessionDisposable?.dispose()
//self.toneRenderer?.stop()
self.toneRenderer?.stop()
self.toneTimer?.invalidate()
}
@ -396,7 +396,7 @@ final class ManagedAudioRecorderContext {
if self.audioSessionDisposable == nil {
let queue = self.queue
self.audioSessionDisposable = self.mediaManager.audioSession.push(audioSessionType: .record, activate: { [weak self] state in
self.audioSessionDisposable = self.mediaManager.audioSession.push(audioSessionType: .record(speaker: self.beginWithTone), activate: { [weak self] state in
queue.async {
if let strongSelf = self, !strongSelf.paused {
strongSelf.hasAudioSession = true
@ -409,6 +409,7 @@ final class ManagedAudioRecorderContext {
if let strongSelf = self {
strongSelf.hasAudioSession = false
strongSelf.stop()
strongSelf.recordingState.set(.stopped)
subscriber.putCompletion()
}
}
@ -420,12 +421,13 @@ final class ManagedAudioRecorderContext {
}
func audioSessionAcquired(headset: Bool) {
if let tonePlayer = self.tonePlayer, headset || self.beginWithTone {
if let toneRenderer = self.toneRenderer, headset || self.beginWithTone {
self.beganWithTone(true)
if !self.toneRendererAudioSessionActivated {
self.toneRendererAudioSessionActivated = true
tonePlayer.start()
self.toneRendererAudioSession?.activate()
}
toneRenderer.setRate(1.0)
} else {
self.processSamples = true
self.beganWithTone(false)
@ -463,9 +465,9 @@ final class ManagedAudioRecorderContext {
}
}
if let tonePlayer = self.tonePlayer, self.toneRendererAudioSessionActivated {
if let toneRenderer = self.toneRenderer, self.toneRendererAudioSessionActivated {
self.toneRendererAudioSessionActivated = false
tonePlayer.stop()
toneRenderer.stop()
}
let audioSessionDisposable = self.audioSessionDisposable
@ -647,23 +649,7 @@ final class ManagedAudioRecorderContext {
enum AudioRecordingState: Equatable {
case paused(duration: Double)
case recording(duration: Double, durationMediaTimestamp: Double)
static func ==(lhs: AudioRecordingState, rhs: AudioRecordingState) -> Bool {
switch lhs {
case let .paused(duration):
if case .paused(duration) = rhs {
return true
} else {
return false
}
case let .recording(duration, durationMediaTimestamp):
if case .recording(duration, durationMediaTimestamp) = rhs {
return true
} else {
return false
}
}
}
case stopped
}
final class ManagedAudioRecorder {

View File

@ -3,10 +3,10 @@ import SwiftSignalKit
import AVFoundation
import UIKit
enum ManagedAudioSessionType {
enum ManagedAudioSessionType: Equatable {
case play
case playWithPossiblePortOverride
case record
case record(speaker: Bool)
case voiceCall
}
@ -544,7 +544,17 @@ public final class ManagedAudioSession {
private func applyNoneDelayed() {
self.deactivateTimer?.invalidate()
if self.currentTypeAndOutputMode?.0 == .voiceCall || self.currentTypeAndOutputMode?.0 == .record {
var immediately = false
if let mode = self.currentTypeAndOutputMode?.0 {
switch mode {
case .voiceCall, .record:
immediately = true
default:
break
}
}
if immediately {
self.applyNone()
} else {
let deactivateTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in
@ -696,22 +706,22 @@ public final class ManagedAudioSession {
}
}
if resetToBuiltin {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
switch type {
case .voiceCall, .playWithPossiblePortOverride, .record:
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
if let routes = AVAudioSession.sharedInstance().availableInputs {
for route in routes {
if route.portType == AVAudioSessionPortBuiltInMic {
if type == .record && self.isHeadsetPluggedInValue {
if case .record = type, self.isHeadsetPluggedInValue {
} else {
let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route)
}
break
}
}
}
}
default:
break
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
}
}
}

View File

@ -58,7 +58,7 @@ public class MapSnapshotMediaResource: TelegramMediaResource {
return MapSnapshotMediaResourceId(latitude: self.latitude, longitude: self.longitude, width: self.width, height: self.height)
}
public func isEqual(to: TelegramMediaResource) -> Bool {
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 {

View File

@ -450,6 +450,13 @@ public final class MediaManager: NSObject {
strongSelf.voiceMediaPlayer = voiceMediaPlayer
voiceMediaPlayer.playedToEnd = { [weak voiceMediaPlayer] in
if let strongSelf = self, let voiceMediaPlayer = voiceMediaPlayer, voiceMediaPlayer === strongSelf.voiceMediaPlayer {
voiceMediaPlayer.stop()
strongSelf.voiceMediaPlayer = nil
}
}
voiceMediaPlayer.cancelled = { [weak voiceMediaPlayer] in
if let strongSelf = self, let voiceMediaPlayer = voiceMediaPlayer, voiceMediaPlayer === strongSelf.voiceMediaPlayer {
voiceMediaPlayer.stop()
strongSelf.voiceMediaPlayer = nil
}
}
@ -461,7 +468,14 @@ public final class MediaManager: NSObject {
strongSelf.musicMediaPlayer?.stop()
strongSelf.voiceMediaPlayer?.control(.playback(.pause))
if let playlist = playlist {
strongSelf.musicMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, postbox: strongSelf.postbox, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, initialPlaybackRate: .x1, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false)
let musicMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, postbox: strongSelf.postbox, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, initialPlaybackRate: .x1, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false)
strongSelf.musicMediaPlayer = musicMediaPlayer
musicMediaPlayer.cancelled = { [weak musicMediaPlayer] in
if let strongSelf = self, let musicMediaPlayer = musicMediaPlayer, musicMediaPlayer === strongSelf.musicMediaPlayer {
musicMediaPlayer.stop()
strongSelf.musicMediaPlayer = nil
}
}
strongSelf.musicMediaPlayer?.control(.playback(.play))
} else {
strongSelf.musicMediaPlayer = nil

View File

@ -132,7 +132,7 @@ public final class VideoLibraryMediaResource: TelegramMediaResource {
return VideoLibraryMediaResourceId(localIdentifier: self.localIdentifier, adjustmentsDigest: adjustmentsDigest)
}
public func isEqual(to: TelegramMediaResource) -> Bool {
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? VideoLibraryMediaResource {
return self.localIdentifier == to.localIdentifier && self.conversion == to.conversion
} else {
@ -196,7 +196,7 @@ public final class LocalFileVideoMediaResource: TelegramMediaResource {
return LocalFileVideoMediaResourceId(randomId: self.randomId)
}
public func isEqual(to: TelegramMediaResource) -> Bool {
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? LocalFileVideoMediaResource {
return self.randomId == to.randomId && self.path == to.path && self.adjustments == to.adjustments
} else {
@ -253,7 +253,7 @@ public class PhotoLibraryMediaResource: TelegramMediaResource {
return PhotoLibraryMediaResourceId(localIdentifier: self.localIdentifier, resourceId: self.uniqueId)
}
public func isEqual(to: TelegramMediaResource) -> Bool {
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? PhotoLibraryMediaResource {
return self.localIdentifier == to.localIdentifier && self.uniqueId == to.uniqueId
} else {
@ -309,7 +309,7 @@ public final class LocalFileGifMediaResource: TelegramMediaResource {
return LocalFileGifMediaResourceId(randomId: self.randomId)
}
public func isEqual(to: TelegramMediaResource) -> Bool {
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? LocalFileGifMediaResource {
return self.randomId == to.randomId && self.path == to.path
} else {

View File

@ -29,7 +29,7 @@ private func chatMessageGalleryControllerData(account: Account, message: Message
switch action.action {
case let .photoUpdated(image):
if let peer = messageMainPeer(message), let image = image {
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image, peer, message.timestamp, nil)])
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil)])
let galleryController = AvatarGalleryController(account: account, peer: peer, remoteEntries: promise, replaceRootController: { controller, ready in
})

View File

@ -42,7 +42,7 @@ public class OpenInAppIconResource: TelegramMediaResource {
return OpenInAppIconResourceId(appStoreId: self.appStoreId)
}
public func isEqual(to: TelegramMediaResource) -> Bool {
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? OpenInAppIconResource {
return self.appStoreId == to.appStoreId
} else {

View File

@ -5,39 +5,20 @@ import SwiftSignalKit
import Postbox
import TelegramCore
private enum PeerAvatarImageGalleryThumbnailContent: Equatable {
case avatar(PeerReference, [TelegramMediaImageRepresentation])
case standaloneImage([TelegramMediaImageRepresentation])
var representations: [TelegramMediaImageRepresentation] {
switch self {
case let .avatar(_, representations):
return representations
case let .standaloneImage(representations):
return representations
}
}
}
private struct PeerAvatarImageGalleryThumbnailItem: GalleryThumbnailItem {
let account: Account
let peer: Peer
let content: PeerAvatarImageGalleryThumbnailContent
let content: [ImageRepresentationWithReference]
init(account: Account, peer: Peer, content: PeerAvatarImageGalleryThumbnailContent) {
init(account: Account, peer: Peer, content: [ImageRepresentationWithReference]) {
self.account = account
self.peer = peer
self.content = content
}
var image: (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) {
if let representation = largestImageRepresentation(self.content.representations) {
switch self.content {
case let .avatar(peer, representations):
return (avatarGalleryThumbnailPhoto(account: self.account, representations: representations.map({ ($0, .avatar(peer: peer, resource: $0.resource)) })), representation.dimensions)
case let .standaloneImage(representations):
return (avatarGalleryThumbnailPhoto(account: self.account, representations: representations.map({ ($0, .standalone(resource: $0.resource)) })), representation.dimensions)
}
if let representation = largestImageRepresentation(self.content.map({ $0.representation })) {
return (avatarGalleryThumbnailPhoto(account: self.account, representations: self.content), representation.dimensions)
} else {
return (.single({ _ in return nil }), CGSize(width: 128.0, height: 128.0))
}
@ -92,16 +73,12 @@ class PeerAvatarImageGalleryItem: GalleryItem {
}
func thumbnailItem() -> (Int64, GalleryThumbnailItem)? {
let content: PeerAvatarImageGalleryThumbnailContent
let content: [ImageRepresentationWithReference]
switch self.entry {
case let .topImage(representations, _):
if let peerReference = PeerReference(self.peer) {
content = .avatar(peerReference, representations)
} else {
return nil
}
case let .image(image, _, _, _):
content = .standaloneImage(image.representations)
content = representations
case let .image(_, representations, _, _, _):
content = representations
}
return (0, PeerAvatarImageGalleryThumbnailItem(account: self.account, peer: self.peer, content: content))
@ -182,73 +159,65 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.footerContentNode.setEntry(entry)
if let largestSize = largestImageRepresentation(entry.representations) {
if let largestSize = largestImageRepresentation(entry.representations.map({ $0.representation })) {
let displaySize = largestSize.dimensions.fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))()
let representations: [(TelegramMediaImageRepresentation, MediaResourceReference)]
let representations: [ImageRepresentationWithReference]
switch entry {
case let .topImage(topRepresentations, _):
if let peerReference = PeerReference(self.peer) {
representations = topRepresentations.map { representation in
return (representation, .avatar(peer: peerReference, resource: representation.resource))
}
} else {
representations = []
}
case let .image(image, _, _, _):
representations = image.representations.map { representation in
return (representation, .standalone(resource: representation.resource))
}
representations = topRepresentations
case let .image(_, imageRepresentations, _, _, _):
representations = imageRepresentations
}
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, representations: representations), dispatchOnDisplayLink: false)
self.zoomableContent = (largestSize.dimensions, self.imageNode)
if let largestIndex = representations.index(where: { $0.0 == largestSize }) {
self.fetchDisposable.set(fetchedMediaResource(postbox: self.account.postbox, reference: representations[largestIndex].1).start())
if let largestIndex = representations.index(where: { $0.representation == largestSize }) {
self.fetchDisposable.set(fetchedMediaResource(postbox: self.account.postbox, reference: representations[largestIndex].reference).start())
}
self.statusDisposable.set((account.postbox.mediaBox.resourceStatus(largestSize.resource)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self {
let previousStatus = strongSelf.status
strongSelf.status = status
switch status {
case .Remote:
strongSelf.statusNode.isHidden = false
strongSelf.statusNodeContainer.isUserInteractionEnabled = true
strongSelf.statusNode.transitionToState(.download(.white), completion: {})
case let .Fetching(isActive, progress):
strongSelf.statusNode.isHidden = false
strongSelf.statusNodeContainer.isUserInteractionEnabled = true
var actualProgress = progress
if isActive {
actualProgress = max(actualProgress, 0.027)
}
strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: CGFloat(actualProgress), cancelEnabled: true), completion: {})
case .Local:
if let previousStatus = previousStatus, case .Fetching = previousStatus {
strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: 1.0, cancelEnabled: true), completion: {
if let strongSelf = self {
strongSelf.statusNode.alpha = 0.0
strongSelf.statusNodeContainer.isUserInteractionEnabled = false
strongSelf.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { _ in
if let strongSelf = self {
strongSelf.statusNode.transitionToState(.none, animated: false, completion: {})
}
})
}
})
} else if !strongSelf.statusNode.isHidden && !strongSelf.statusNode.alpha.isZero {
strongSelf.statusNode.alpha = 0.0
strongSelf.statusNodeContainer.isUserInteractionEnabled = false
strongSelf.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { _ in
if let strongSelf = self {
strongSelf.statusNode.transitionToState(.none, animated: false, completion: {})
}
})
}
}
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self {
let previousStatus = strongSelf.status
strongSelf.status = status
switch status {
case .Remote:
strongSelf.statusNode.isHidden = false
strongSelf.statusNodeContainer.isUserInteractionEnabled = true
strongSelf.statusNode.transitionToState(.download(.white), completion: {})
case let .Fetching(isActive, progress):
strongSelf.statusNode.isHidden = false
strongSelf.statusNodeContainer.isUserInteractionEnabled = true
var actualProgress = progress
if isActive {
actualProgress = max(actualProgress, 0.027)
}
strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: CGFloat(actualProgress), cancelEnabled: true), completion: {})
case .Local:
if let previousStatus = previousStatus, case .Fetching = previousStatus {
strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: 1.0, cancelEnabled: true), completion: {
if let strongSelf = self {
strongSelf.statusNode.alpha = 0.0
strongSelf.statusNodeContainer.isUserInteractionEnabled = false
strongSelf.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { _ in
if let strongSelf = self {
strongSelf.statusNode.transitionToState(.none, animated: false, completion: {})
}
})
}
})
} else if !strongSelf.statusNode.isHidden && !strongSelf.statusNode.alpha.isZero {
strongSelf.statusNode.alpha = 0.0
strongSelf.statusNodeContainer.isUserInteractionEnabled = false
strongSelf.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { _ in
if let strongSelf = self {
strongSelf.statusNode.transitionToState(.none, animated: false, completion: {})
}
})
}
}
}))
}
}))
} else {
self._ready.set(.single(Void()))
}
@ -359,29 +328,21 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
}
@objc func statusPressed() {
if let entry = self.entry, let largestSize = largestImageRepresentation(entry.representations), let status = self.status {
if let entry = self.entry, let largestSize = largestImageRepresentation(entry.representations.map({ $0.representation })), let status = self.status {
switch status {
case .Fetching:
self.account.postbox.mediaBox.cancelInteractiveResourceFetch(largestSize.resource)
case .Remote:
let representations: [(TelegramMediaImageRepresentation, MediaResourceReference)]
let representations: [ImageRepresentationWithReference]
switch entry {
case let .topImage(topRepresentations, _):
if let peerReference = PeerReference(self.peer) {
representations = topRepresentations.map { representation in
return (representation, .avatar(peer: peerReference, resource: representation.resource))
}
} else {
representations = []
}
case let .image(image, _, _, _):
representations = image.representations.map { representation in
return (representation, .standalone(resource: representation.resource))
}
representations = topRepresentations
case let .image(_, imageRepresentations, _, _, _):
representations = imageRepresentations
}
if let largestIndex = representations.index(where: { $0.0 == largestSize }) {
self.fetchDisposable.set(fetchedMediaResource(postbox: self.account.postbox, reference: representations[largestIndex].1).start())
if let largestIndex = representations.index(where: { $0.representation == largestSize }) {
self.fetchDisposable.set(fetchedMediaResource(postbox: self.account.postbox, reference: representations[largestIndex].reference).start())
}
default:
break

View File

@ -360,11 +360,15 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
return self.messagesLocation
}
var currentItemDisappeared: (() -> Void)?
private let navigationDisposable = MetaDisposable()
private var playbackStack = PlaybackStack()
private var currentItem: (current: Message, around: [Message])?
private var currentlyObservedMessageId: MessageId?
private let currentlyObservedMessageDisposable = MetaDisposable()
private var loadingItem: Bool = false
private var playedToEnd: Bool = false
private var order: MusicPlaybackSettingsOrder = .regular
@ -400,6 +404,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
deinit {
self.navigationDisposable.dispose()
self.currentlyObservedMessageDisposable.dispose()
}
func control(_ action: SharedMediaPlaylistControlAction) {
@ -481,6 +486,27 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
}
}
self.stateValue.set(.single(SharedMediaPlaylistState(loading: self.loadingItem, playedToEnd: self.playedToEnd, item: item, nextItem: nextItem, previousItem: previousItem, order: self.order, looping: self.looping)))
if item?.message.id != self.currentlyObservedMessageId {
self.currentlyObservedMessageId = item?.message.id
if let id = item?.message.id {
let key: PostboxViewKey = .messages(Set([id]))
self.currentlyObservedMessageDisposable.set((self.postbox.combinedView(keys: [key])
|> filter { views in
if let view = views.views[key] as? MessagesView {
if !view.messages.isEmpty {
return false
}
}
return true
}
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
self?.currentItemDisappeared?()
}))
} else {
self.currentlyObservedMessageDisposable.set(nil)
}
}
}
private func loadItem(anchor: PeerMessagesMediaPlaylistLoadAnchor, navigation: PeerMessagesMediaPlaylistNavigation) {

View File

@ -1110,18 +1110,19 @@ func chatSecretPhoto(account: Account, photoReference: ImageMediaReference) -> S
}
}
private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [(TelegramMediaImageRepresentation, MediaResourceReference)], fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.0 })), let largestRepresentation = imageRepresentationLargerThan(representations.map({ $0.0 }), size: fullRepresentationSize), let smallestIndex = representations.index(where: { $0.0 == smallestRepresentation }), let largestIndex = representations.index(where: { $0.0 == largestRepresentation }) {
private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [ImageRepresentationWithReference], fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = imageRepresentationLargerThan(representations.map({ $0.representation }), size: fullRepresentationSize), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) {
let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource)
let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in
let signal = maybeFullSize
|> take(1)
|> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in
if maybeData.complete {
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
return .single((nil, loadedData, true))
} else {
let fetchedThumbnail = fetchedMediaResource(postbox: postbox, reference: representations[smallestIndex].1, statsCategory: .image)
let fetchedFullSize = fetchedMediaResource(postbox: postbox, reference: representations[largestIndex].1, statsCategory: .image)
let fetchedThumbnail = fetchedMediaResource(postbox: postbox, reference: representations[smallestIndex].reference, statsCategory: .image)
let fetchedFullSize = fetchedMediaResource(postbox: postbox, reference: representations[largestIndex].reference, statsCategory: .image)
let thumbnail = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
@ -1177,7 +1178,7 @@ private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [(Te
}
}
func avatarGalleryThumbnailPhoto(account: Account, representations: [(TelegramMediaImageRepresentation, MediaResourceReference)]) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
func avatarGalleryThumbnailPhoto(account: Account, representations: [ImageRepresentationWithReference]) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = avatarGalleryThumbnailDatas(postbox: account.postbox, representations: representations, fullRepresentationSize: CGSize(width: 127.0, height: 127.0), autoFetchFullSize: true)
return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in
@ -2129,8 +2130,8 @@ func instantPageImageFile(account: Account, fileReference: FileMediaReference, f
}
}
private func avatarGalleryPhotoDatas(account: Account, representations: [(TelegramMediaImageRepresentation, MediaResourceReference)], autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.0 })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.0 })), let smallestIndex = representations.index(where: { $0.0 == smallestRepresentation }), let largestIndex = representations.index(where: { $0.0 == largestRepresentation }) {
private func avatarGalleryPhotoDatas(account: Account, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) {
let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource)
let signal = maybeFullSize
@ -2140,8 +2141,8 @@ private func avatarGalleryPhotoDatas(account: Account, representations: [(Telegr
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
return .single((nil, loadedData, true))
} else {
let fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: representations[smallestIndex].1)
let fetchedFullSize = fetchedMediaResource(postbox: account.postbox, reference: representations[largestIndex].1)
let fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: representations[smallestIndex].reference)
let fetchedFullSize = fetchedMediaResource(postbox: account.postbox, reference: representations[largestIndex].reference)
let thumbnail = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
@ -2191,7 +2192,7 @@ private func avatarGalleryPhotoDatas(account: Account, representations: [(Telegr
}
}
func chatAvatarGalleryPhoto(account: Account, representations: [(TelegramMediaImageRepresentation, MediaResourceReference)], autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = avatarGalleryPhotoDatas(account: account, representations: representations, autoFetchFullSize: autoFetchFullSize)
return signal

File diff suppressed because it is too large Load Diff

View File

@ -270,8 +270,12 @@ public final class SecretMediaPreviewController: ViewController {
}
}
if let _ = media as? TelegramMediaFile {
strongSelf.title = strongSelf.presentationData.strings.Message_Video
if let file = media as? TelegramMediaFile {
if file.isAnimated {
strongSelf.title = strongSelf.presentationData.strings.Message_Animation
} else {
strongSelf.title = strongSelf.presentationData.strings.Message_Video
}
} else {
strongSelf.title = strongSelf.presentationData.strings.Message_Photo
}
@ -289,8 +293,12 @@ public final class SecretMediaPreviewController: ViewController {
let contentNode = SecretMediaPreviewFooterContentNode()
let peerTitle = messageMainPeer(message)?.compactDisplayTitle ?? ""
let text: String
if let _ = media as? TelegramMediaFile {
text = strongSelf.presentationData.strings.SecretVideo_NotViewedYet(peerTitle).0
if let file = media as? TelegramMediaFile {
if file.isAnimated {
text = strongSelf.presentationData.strings.SecretGIF_NotViewedYet(peerTitle).0
} else {
text = strongSelf.presentationData.strings.SecretVideo_NotViewedYet(peerTitle).0
}
} else {
text = strongSelf.presentationData.strings.SecretImage_NotViewedYet(peerTitle).0
}

View File

@ -47,7 +47,7 @@ public class SecureIdLocalImageResource: TelegramMediaResource {
return SecureIdLocalImageResourceId(id: self.localId)
}
public func isEqual(to: TelegramMediaResource) -> Bool {
public func isEqual(to: MediaResource) -> Bool {
if let to = to as? SecureIdLocalImageResource {
return self.localId == to.localId && self.source.isEqual(to:to.source)
} else {

View File

@ -497,7 +497,7 @@ public func settingsController(account: Account, accountManager: AccountManager)
})
hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first?.representation
updateHiddenAvatarImpl?()
}))
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in

View File

@ -47,7 +47,7 @@ final class SettingsThemeWallpaperNode: ASDisplayNode {
self.imageNode.isHidden = false
self.backgroundNode.isHidden = true
let convertedRepresentations: [(TelegramMediaImageRepresentation, MediaResourceReference)] = representations.map({ ($0, .wallpaper(resource: $0.resource)) })
let convertedRepresentations: [ImageRepresentationWithReference] = representations.map({ ImageRepresentationWithReference(representation: $0, reference: .wallpaper(resource: $0.resource)) })
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, representations: convertedRepresentations, autoFetchFullSize: true))
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: largestImageRepresentation(representations)!.dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
apply()

View File

@ -27,7 +27,7 @@ public enum ShareControllerSubject {
case text(String)
case quote(text: String, url: String)
case messages([Message])
case image([TelegramMediaImageRepresentation])
case image([ImageRepresentationWithReference])
case media(AnyMediaReference)
case mapMedia(TelegramMediaMap)
case fromExternal(([PeerId], String) -> Signal<ShareControllerExternalStatus, NoError>)
@ -220,7 +220,7 @@ public final class ShareController: ViewController {
case let .image(representations):
if case .saveToCameraRoll = preferredAction {
self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Preview_SaveToCameraRoll, action: { [weak self] in
self?.saveToCameraRoll(image: representations)
self?.saveToCameraRoll(representations: representations)
})
}
case let .media(mediaReference):
@ -358,7 +358,7 @@ public final class ShareController: ViewController {
if !text.isEmpty {
messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil))
}
messages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()), representations: representations, reference: nil, partialReference: nil)), replyToMessageId: nil, localGroupingKey: nil))
messages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()), representations: representations.map({ $0.representation }), reference: nil, partialReference: nil)), replyToMessageId: nil, localGroupingKey: nil))
let _ = enqueueMessages(account: strongSelf.account, peerId: peerId, messages: messages).start()
}
return .complete()
@ -421,7 +421,7 @@ public final class ShareController: ViewController {
case let .quote(text, url):
collectableItems.append(CollectableExternalShareItem(url: "", text: "\"\(text)\"\n\n\(url)", mediaReference: nil))
case let .image(representations):
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()), representations: representations, reference: nil, partialReference: nil)
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()), representations: representations.map({ $0.representation }), reference: nil, partialReference: nil)
collectableItems.append(CollectableExternalShareItem(url: "", text: "", mediaReference: .standalone(media: media)))
case let .media(mediaReference):
collectableItems.append(CollectableExternalShareItem(url: "", text: "", mediaReference: mediaReference))
@ -543,8 +543,8 @@ public final class ShareController: ViewController {
}
}
private func saveToCameraRoll(image: [TelegramMediaImageRepresentation]) {
let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image, reference: nil, partialReference: nil)
private func saveToCameraRoll(representations: [ImageRepresentationWithReference]) {
let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations.map({ $0.representation }), reference: nil, partialReference: nil)
self.controllerNode.transitionToProgress(signal: TelegramUI.saveToCameraRoll(applicationContext: self.account.telegramApplicationContext, postbox: self.account.postbox, mediaReference: .standalone(media: media)))
}

View File

@ -189,11 +189,12 @@ protocol SharedMediaPlaylistLocation {
func isEqual(to: SharedMediaPlaylistLocation) -> Bool
}
protocol SharedMediaPlaylist {
protocol SharedMediaPlaylist: class {
var id: SharedMediaPlaylistId { get }
var location: SharedMediaPlaylistLocation { get }
var state: Signal<SharedMediaPlaylistState, NoError> { get }
var looping: MusicPlaybackSettingsLooping { get }
var currentItemDisappeared: (() -> Void)? { get set }
func control(_ action: SharedMediaPlaylistControlAction)
func setOrder(_ order: MusicPlaybackSettingsOrder)
@ -405,6 +406,7 @@ final class SharedMediaPlayer {
private let markItemAsPlayedDisposable = MetaDisposable()
var playedToEnd: (() -> Void)?
var cancelled: (() -> Void)?
private var inForegroundDisposable: Disposable?
@ -427,6 +429,10 @@ final class SharedMediaPlayer {
self.forceAudioToSpeaker = !DeviceProximityManager.shared().currentValue()
}
playlist.currentItemDisappeared = { [weak self] in
self?.cancelled?()
}
self.stateDisposable = (playlist.state
|> deliverOnMainQueue).start(next: { [weak self] state in
if let strongSelf = self {

View File

@ -219,7 +219,7 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone
let convertedType: ManagedAudioSessionType
switch type {
case TGAudioSessionTypePlayAndRecord, TGAudioSessionTypePlayAndRecordHeadphones:
convertedType = .record
convertedType = .record(speaker: false)
default:
convertedType = .play
}

View File

@ -95,12 +95,12 @@ final class ThemeGalleryItemNode: ZoomableContentGalleryItemNode {
let displaySize = largestSize.dimensions.fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))()
let convertedRepresentations: [(TelegramMediaImageRepresentation, MediaResourceReference)] = representations.map({ ($0, .wallpaper(resource: $0.resource)) })
let convertedRepresentations: [ImageRepresentationWithReference] = representations.map({ ImageRepresentationWithReference(representation: $0, reference: .wallpaper(resource: $0.resource)) })
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: account, representations: convertedRepresentations), dispatchOnDisplayLink: false)
self.zoomableContent = (largestSize.dimensions, self.imageNode)
if let largestIndex = convertedRepresentations.index(where: { $0.0 == largestSize }) {
self.fetchDisposable.set(fetchedMediaResource(postbox: self.account.postbox, reference: convertedRepresentations[largestIndex].1).start())
if let largestIndex = convertedRepresentations.index(where: { $0.representation == largestSize }) {
self.fetchDisposable.set(fetchedMediaResource(postbox: self.account.postbox, reference: convertedRepresentations[largestIndex].reference).start())
}
} else {
self._ready.set(.single(Void()))

View File

@ -426,7 +426,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
strongSelf.fetchStatus = fetchStatus
if !item.hideControls {
strongSelf.statusButtonNode.isHidden = !initialBuffering && (strongSelf.didPause || !isPaused || value == nil) && !fetching
strongSelf.statusButtonNode.isHidden = !initialBuffering && (strongSelf.didPause || !isPaused) && !fetching
}
if isAnimated || disablePlayerControls {

View File

@ -840,7 +840,7 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
let galleryController = AvatarGalleryController(account: account, peer: peer, remoteEntries: cachedAvatarEntries.with { $0 }, replaceRootController: { controller, ready in
})
hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first?.representation
updateHiddenAvatarImpl?()
}))
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in