Long audio playback improvements: 2x playback, position storing

Various UI fixes
This commit is contained in:
Ilya Laktyushin 2019-09-18 02:24:56 +03:00
parent 0bbf8bf0fb
commit 1bebfdaf53
37 changed files with 302 additions and 78 deletions

View File

@ -66,14 +66,14 @@ public struct SharedMediaPlaybackAlbumArt: Equatable {
}
public enum SharedMediaPlaybackDisplayData: Equatable {
case music(title: String?, performer: String?, albumArt: SharedMediaPlaybackAlbumArt?)
case music(title: String?, performer: String?, albumArt: SharedMediaPlaybackAlbumArt?, long: Bool)
case voice(author: Peer?, peer: Peer?)
case instantVideo(author: Peer?, peer: Peer?, timestamp: Int32)
public static func ==(lhs: SharedMediaPlaybackDisplayData, rhs: SharedMediaPlaybackDisplayData) -> Bool {
switch lhs {
case let .music(lhsTitle, lhsPerformer, lhsAlbumArt):
if case let .music(rhsTitle, rhsPerformer, rhsAlbumArt) = rhs, lhsTitle == rhsTitle, lhsPerformer == rhsPerformer, lhsAlbumArt == rhsAlbumArt {
case let .music(lhsTitle, lhsPerformer, lhsAlbumArt, lhsDuration):
if case let .music(rhsTitle, rhsPerformer, rhsAlbumArt, rhsDuration) = rhs, lhsTitle == rhsTitle, lhsPerformer == rhsPerformer, lhsAlbumArt == rhsAlbumArt, lhsDuration == rhsDuration {
return true
} else {
return false

View File

@ -85,6 +85,10 @@ public final class ActivityIndicator: ASDisplayNode {
super.init()
if case let .custom(_, _, _, forceCustom) = self.type, forceCustom {
self.isLayerBacked = true
}
switch type {
case let .navigationAccent(theme):
self.indicatorNode.image = PresentationResourcesRootController.navigationIndefiniteActivityImage(theme)

View File

@ -55,8 +55,7 @@ public final class CallListController: ViewController {
if case .tab = self.mode {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed))
let icon: UIImage? = UIImage(bundleImageName: "Chat List/Tabs/IconCalls")
let icon = UIImage(bundleImageName: "Chat List/Tabs/IconCalls")
self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle
self.tabBarItem.image = icon
self.tabBarItem.selectedImage = icon

View File

@ -120,7 +120,7 @@ private func mappedInsertEntries(account: Account, showSettings: Bool, nodeInter
}), directionHint: entry.directionHint)
case let .displayTabInfo(theme, text):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(theme: theme, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
case let .messageEntry(topMessage, messages, theme, strings, dateTimeFormat, editing, hasActiveRevealControls):
case let .messageEntry(topMessage, messages, theme, strings, dateTimeFormat, editing, hasActiveRevealControls):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListCallItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, account: account, style: showSettings ? .blocks : .plain, topMessage: topMessage, messages: messages, editing: editing, revealed: hasActiveRevealControls, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .holeEntry(_, theme):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListHoleItem(theme: theme), directionHint: entry.directionHint)

View File

@ -521,8 +521,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.item = item
var peer: Peer?
var displayAsMessage = false
switch item.content {
case let .peer(message, peerValue, _, _, _, _, _, _, _, _, displayAsMessage):
case let .peer(message, peerValue, _, _, _, _, _, _, _, _, displayAsMessageValue):
displayAsMessage = displayAsMessageValue
if displayAsMessage, let author = message?.author as? TelegramUser {
peer = author
} else {
@ -538,7 +540,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let peer = peer {
var overrideImage: AvatarNodeImageOverride?
if peer.id == item.context.account.peerId {
if peer.id == item.context.account.peerId && !displayAsMessage {
overrideImage = .savedMessagesIcon
} else if peer.isDeleted {
overrideImage = .deletedIcon

View File

@ -3934,9 +3934,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let scrollDirection: ListViewScrollDirection
switch direction {
case .down:
scrollDirection = .down
scrollDirection = self.rotated ? .up : .down
default:
scrollDirection = .up
scrollDirection = self.rotated ? .down : .up
}
return self.scrollWithDirection(scrollDirection, distance: distance)
}

View File

@ -46,7 +46,9 @@ private final class NavigationButtonItemNode: ASTextNode {
_text = value
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
self.item?.accessibilityLabel = value
if _image == nil {
self.item?.accessibilityLabel = value
}
}
}
@ -133,6 +135,29 @@ private final class NavigationButtonItemNode: ASTextNode {
}
}
override public var accessibilityLabel: String? {
get {
if let item = self.item, let accessibilityLabel = item.accessibilityLabel {
return accessibilityLabel
} else {
return self.attributedText?.string
}
} set(value) {
}
}
override public var accessibilityHint: String? {
get {
if let item = self.item, let accessibilityHint = item.accessibilityHint {
return accessibilityHint
} else {
return nil
}
} set(value) {
}
}
override public init() {
super.init()
@ -306,17 +331,8 @@ final class NavigationButtonNode: ASDisplayNode {
self.addSubnode(node)
}
node.item = nil
node.text = text
/*if isBack {
node.accessibilityHint = "Back button"
node.accessibilityTraits = 0
} else {
node.accessibilityHint = nil
node.accessibilityTraits = UIAccessibilityTraitButton
}*/
node.image = nil
node.text = text
node.bold = false
node.isEnabled = true
node.node = nil
@ -355,8 +371,8 @@ final class NavigationButtonNode: ASDisplayNode {
self.addSubnode(node)
}
node.item = items[i]
node.text = items[i].title ?? ""
node.image = items[i].image
node.text = items[i].title ?? ""
node.bold = items[i].style == .done
node.isEnabled = items[i].isEnabled
node.node = items[i].customDisplayNode

View File

@ -731,6 +731,9 @@ public class GalleryController: ViewController {
}
}
}
self.blocksBackgroundWhenInOverlay = true
self.isOpaqueWhenInOverlay = true
}
required init(coder aDecoder: NSCoder) {

View File

@ -42,6 +42,8 @@ public final class InstantPageController: ViewController {
super.init(navigationBarPresentationData: nil)
self.navigationPresentation = .modal
self.statusBar.statusBarStyle = .White
self.webpageDisposable = (actualizedWebpage(postbox: self.context.account.postbox, network: self.context.account.network, webpage: webPage) |> deliverOnMainQueue).start(next: { [weak self] result in

View File

@ -86,7 +86,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem {
if (title ?? "").isEmpty && (performer ?? "").isEmpty {
updatedTitle = file.fileName ?? ""
}
return SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: updatedTitle ?? "", performer: updatedPerformer ?? "", isThumbnail: false)))
return SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: updatedTitle ?? "", performer: updatedPerformer ?? "", isThumbnail: false)), long: false)
}
case let .Video(_, _, flags):
if flags.contains(.instantRoundVideo) {
@ -99,7 +99,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem {
}
}
return SharedMediaPlaybackDisplayData.music(title: file.fileName ?? "", performer: "", albumArt: nil)
return SharedMediaPlaybackDisplayData.music(title: file.fileName ?? "", performer: "", albumArt: nil, long: false)
}
return nil
}

View File

@ -153,7 +153,16 @@ final class InstantPageTextItem: InstantPageItem {
if self.opaqueBackground {
context.setBlendMode(.normal)
}
CTLineDraw(line.line, context)
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
if glyphRuns.count != 0 {
for run in glyphRuns {
let run = run as! CTRun
let glyphCount = CTRunGetGlyphCount(run)
CTRunDraw(run, context, CFRangeMake(0, glyphCount))
}
}
if self.opaqueBackground {
context.setBlendMode(.copy)
}

View File

@ -44,13 +44,16 @@ final class ImageBasedPasscodeBackground: PasscodeBackground {
init(image: UIImage, size: CGSize) {
self.size = size
let contextSize = size.fitted(CGSize(width: 320.0, height: 320.0))
let contextSize = size.aspectFilled(CGSize(width: 320.0, height: 320.0))
let foregroundContext = DrawingContext(size: contextSize, scale: 1.0)
let bounds = CGRect(origin: CGPoint(), size: contextSize)
let filledImageSize = image.size.aspectFilled(contextSize)
let filledImageRect = CGRect(origin: CGPoint(x: (contextSize.width - filledImageSize.width) / 2.0, y: (contextSize.height - filledImageSize.height) / 2.0), size: filledImageSize)
foregroundContext.withFlippedContext { c in
c.interpolationQuality = .medium
c.draw(image.cgImage!, in: bounds)
c.draw(image.cgImage!, in: filledImageRect)
}
telegramFastBlurMore(Int32(contextSize.width), Int32(contextSize.height), Int32(foregroundContext.bytesPerRow), foregroundContext.bytes)
telegramFastBlurMore(Int32(contextSize.width), Int32(contextSize.height), Int32(foregroundContext.bytesPerRow), foregroundContext.bytes)
@ -66,7 +69,7 @@ final class ImageBasedPasscodeBackground: PasscodeBackground {
let backgroundContext = DrawingContext(size: contextSize, scale: 1.0)
backgroundContext.withFlippedContext { c in
c.interpolationQuality = .medium
c.draw(image.cgImage!, in: bounds)
c.draw(image.cgImage!, in: filledImageRect)
}
telegramFastBlurMore(Int32(contextSize.width), Int32(contextSize.height), Int32(backgroundContext.bytesPerRow), backgroundContext.bytes)
telegramFastBlurMore(Int32(contextSize.width), Int32(contextSize.height), Int32(backgroundContext.bytesPerRow), backgroundContext.bytes)

View File

@ -347,6 +347,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.iconNode.displayWithoutProcessing = true
self.textField = SearchBarTextField()
self.textField.accessibilityTraits = .searchField
self.textField.autocorrectionType = .no
self.textField.returnKeyType = .search
self.textField.font = self.fieldStyle.font

View File

@ -74,6 +74,9 @@ public struct SegmentedControlItem: Equatable {
}
}
private class SegmentedControlItemNode: HighlightTrackingButtonNode {
}
public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDelegate {
private var theme: SegmentedControlTheme
private var _items: [SegmentedControlItem]
@ -82,7 +85,7 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg
private var validLayout: SegmentedControlLayout?
private let selectionNode: ASImageNode
private var itemNodes: [HighlightTrackingButtonNode]
private var itemNodes: [SegmentedControlItemNode]
private var dividerNodes: [ASDisplayNode]
private var gestureRecognizer: UIPanGestureRecognizer?
@ -101,7 +104,7 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg
self.itemNodes.forEach { $0.removeFromSupernode() }
self.itemNodes = self._items.map { item in
let itemNode = HighlightTrackingButtonNode()
let itemNode = SegmentedControlItemNode()
itemNode.setTitle(item.title, with: textFont, with: self.theme.textColor, for: .normal)
itemNode.setTitle(item.title, with: selectedTextFont, with: self.theme.textColor, for: .selected)
itemNode.setTitle(item.title, with: selectedTextFont, with: self.theme.textColor, for: [.selected, .highlighted])
@ -149,7 +152,7 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg
self.selectionNode.displayWithoutProcessing = true
self.itemNodes = items.map { item in
let itemNode = HighlightTrackingButtonNode()
let itemNode = SegmentedControlItemNode()
itemNode.setTitle(item.title, with: textFont, with: theme.textColor, for: .normal)
itemNode.setTitle(item.title, with: selectedTextFont, with: theme.textColor, for: .selected)
itemNode.setTitle(item.title, with: selectedTextFont, with: theme.textColor, for: [.selected, .highlighted])
@ -304,6 +307,11 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg
} else {
itemNode.isSelected = isSelected
}
if isSelected {
itemNode.accessibilityTraits.insert(.selected)
} else {
itemNode.accessibilityTraits.remove(.selected)
}
}
}
}
@ -328,7 +336,7 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg
return size
}
@objc private func buttonPressed(_ button: HighlightTrackingButtonNode) {
@objc private func buttonPressed(_ button: SegmentedControlItemNode) {
guard let index = self.itemNodes.firstIndex(of: button) else {
return
}
@ -372,8 +380,8 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg
self.selectedIndexChanged(self._selectedIndex)
}
self.gestureSelectedIndex = nil
self.updateButtonsHighlights(highlightedIndex: nil, gestureSelectedIndex: nil)
}
self.updateButtonsHighlights(highlightedIndex: nil, gestureSelectedIndex: nil)
default:
break
}

View File

@ -637,7 +637,9 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
}
let controller = ItemListController(context: context, state: signal)
if case .modal = mode {
controller.navigationPresentation = .modal
}
controller.reorderEntry = { fromIndex, toIndex, entries in
let fromEntry = entries[fromIndex]
guard case let .pack(_, _, _, fromPackInfo, _, _, _, _, _) = fromEntry else {

View File

@ -561,6 +561,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll
}
let controller = ItemListController(context: context, state: signal)
controller.navigationPresentation = .modal
presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}

View File

@ -462,7 +462,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
pushControllerImpl?(controller)
}, contextAction: { isCurrent, reference, node, gesture in
let _ = (context.account.postbox.transaction { transaction in
return makePresentationTheme(mediaBox: context.sharedContext.accountManager.mediaBox, themeReference: reference, accentColor: nil, serviceBackgroundColor: defaultServiceBackgroundColor, baseColor: .blue)
@ -489,7 +489,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})
c.dismiss(completion: {
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
pushControllerImpl?(controller)
})
})))
}
@ -508,10 +508,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
let _ = (cloudThemes.get() |> delay(0.5, queue: Queue.mainQueue())
|> take(1)
|> deliverOnMainQueue).start(next: { themes in
if isCurrent, let themeIndex = themes.firstIndex(where: { $0.id == theme.theme.id }) {
if isCurrent, let currentThemeIndex = themes.firstIndex(where: { $0.id == theme.theme.id }) {
let previousThemeIndex = themes.prefix(upTo: currentThemeIndex).reversed().firstIndex(where: { $0.file != nil })
let newTheme: PresentationThemeReference
if themeIndex > 0 {
newTheme = .cloud(PresentationCloudTheme(theme: themes[themeIndex - 1], resolvedWallpaper: nil))
if let previousThemeIndex = previousThemeIndex {
newTheme = .cloud(PresentationCloudTheme(theme: themes[themes.index(before: previousThemeIndex.base)], resolvedWallpaper: nil))
} else {
newTheme = .builtin(.nightAccent)
}
@ -647,7 +648,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
}
})
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
pushControllerImpl?(controller)
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in

View File

@ -51,6 +51,8 @@ final class ShareSearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.textInputNode.textField.attributedPlaceholder = NSAttributedString(string: placeholder, font: Font.regular(16.0), textColor: theme.actionSheet.inputPlaceholderColor)
self.textInputNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
self.textInputNode.textField.tintColor = theme.actionSheet.controlAccentColor
self.textInputNode.textField.returnKeyType = .search
self.textInputNode.textField.accessibilityTraits = .searchField
super.init()

View File

@ -39,8 +39,8 @@ private class MediaHeaderItemNode: ASDisplayNode {
var subtitleString: NSAttributedString?
if let playbackItem = playbackItem, let displayData = playbackItem.displayData {
switch displayData {
case let .music(title, performer, _):
rateButtonHidden = true
case let .music(title, performer, _, long):
rateButtonHidden = !long
let titleText: String = title ?? "Unknown Track"
let subtitleText: String = performer ?? "Unknown Artist"
@ -171,12 +171,12 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
var playPrevious: (() -> Void)?
var playNext: (() -> Void)?
var voiceBaseRate: AudioPlaybackRate? = nil {
var playbackBaseRate: AudioPlaybackRate? = nil {
didSet {
guard self.voiceBaseRate != oldValue, let voiceBaseRate = self.voiceBaseRate else {
guard self.playbackBaseRate != oldValue, let playbackBaseRate = self.playbackBaseRate else {
return
}
switch voiceBaseRate {
switch playbackBaseRate {
case .x1:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
@ -315,9 +315,9 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
} else {
baseRate = .x2
}
strongSelf.voiceBaseRate = baseRate
strongSelf.playbackBaseRate = baseRate
} else {
strongSelf.voiceBaseRate = .x1
strongSelf.playbackBaseRate = .x1
}
}
@ -373,8 +373,8 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
self.separatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor
self.scrubbingNode.updateContent(.standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.theme.rootController.navigationBar.accentTextColor))
if let voiceBaseRate = self.voiceBaseRate {
switch voiceBaseRate {
if let playbackBaseRate = self.playbackBaseRate {
switch playbackBaseRate {
case .x1:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
case .x2:
@ -457,7 +457,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 44.0 - rightInset, y: 0.0), size: CGSize(width: 44.0, height: minHeight)))
let rateButtonSize = CGSize(width: 24.0, height: minHeight)
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - 18.0 - rateButtonSize.width - rightInset, y: 0.0), size: rateButtonSize))
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - 17.0 - rateButtonSize.width - rightInset, y: 0.0), size: rateButtonSize))
transition.updateFrame(node: self.actionPlayNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
transition.updateFrame(node: self.actionPauseNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))

View File

@ -2246,8 +2246,8 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
if let file = media as? TelegramMediaFile {
for attribute in file.attributes {
switch attribute {
case .Sticker:
if let index = message.index {
case let .Sticker(_, packReference, _):
if let index = message.index, packReference != nil {
if let (currentIndex, _) = recentlyUsedStickers[file.fileId] {
if currentIndex < index {
recentlyUsedStickers[file.fileId] = (index, file)

View File

@ -92,10 +92,10 @@ public final class ActiveSessionsContext {
}
}
public func removeOther() -> Signal<Never, NoError> {
public func removeOther() -> Signal<Never, TerminateSessionError> {
return terminateOtherAccountSessions(account: self.account)
|> deliverOnMainQueue
|> mapToSignal { [weak self] _ -> Signal<Never, NoError> in
|> mapToSignal { [weak self] _ -> Signal<Never, TerminateSessionError> in
guard let strongSelf = self else {
return .complete()
}

View File

@ -42,10 +42,15 @@ public func terminateAccountSession(account: Account, hash: Int64) -> Signal<Voi
}
}
public func terminateOtherAccountSessions(account: Account) -> Signal<Void, NoError> {
public func terminateOtherAccountSessions(account: Account) -> Signal<Void, TerminateSessionError> {
return account.network.request(Api.functions.auth.resetAuthorizations())
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
|> mapError { error -> TerminateSessionError in
if error.errorCode == 406 {
return .freshReset
}
return .generic
}
|> mapToSignal { _ -> Signal<Void, TerminateSessionError> in
return .single(Void())
}
}

View File

@ -3182,6 +3182,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let controller = ChatSearchResultsController(context: strongSelf.context, searchQuery: searchData.query, messages: searchResult.messages, navigateToMessageIndex: { index in
strongSelf.interfaceInteraction?.navigateMessageSearch(.index(index))
})
strongSelf.chatDisplayNode.dismissInput()
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
}
})

View File

@ -73,7 +73,7 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch
return nil
} else {
let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector)
buttonItem.accessibilityLabel = strings.Conversation_Info
buttonItem.accessibilityLabel = strings.Conversation_Search
return ChatNavigationButton(action: .search, buttonItem: buttonItem)
}
}

View File

@ -38,6 +38,8 @@ final class ChatScheduleTimeController: ViewController {
super.init(navigationBarPresentationData: nil)
self.blocksBackgroundWhenInOverlay = true
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {

View File

@ -36,6 +36,8 @@ final class ChatSendMessageActionSheetController: ViewController {
super.init(navigationBarPresentationData: nil)
self.blocksBackgroundWhenInOverlay = true
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {

View File

@ -202,6 +202,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
self.sendButtonNode = HighlightableButtonNode()
self.sendButtonNode.imageNode.displayWithoutProcessing = false
self.sendButtonNode.imageNode.displaysAsynchronously = false
self.sendButtonNode.accessibilityLabel = self.presentationData.strings.MediaPicker_Send
self.messageClipNode = ASDisplayNode()
self.messageClipNode.clipsToBounds = true

View File

@ -49,6 +49,7 @@ private var telegramUIDeclaredEncodables: Void = {
declareEncodable(RecentSettingsSearchQueryItem.self, f: { RecentSettingsSearchQueryItem(decoder: $0) })
declareEncodable(VoipDerivedState.self, f: { VoipDerivedState(decoder: $0) })
declareEncodable(ChatArchiveSettings.self, f: { ChatArchiveSettings(decoder: $0) })
declareEncodable(MediaPlaybackStoredState.self, f: { MediaPlaybackStoredState(decoder: $0) })
return
}()

View File

@ -40,6 +40,8 @@ private struct GlobalControlOptions: OptionSet {
static let seek = GlobalControlOptions(rawValue: 1 << 5)
}
public var test: Double?
public final class MediaManagerImpl: NSObject, MediaManager {
public static var globalAudioSession: ManagedAudioSession {
return sharedAudioSession
@ -153,6 +155,7 @@ public final class MediaManagerImpl: NSObject, MediaManager {
}
private let setPlaylistByTypeDisposables = DisposableDict<MediaManagerPlayerType>()
private var mediaPlaybackStateDisposable = MetaDisposable()
private let sharedPlayerByGroup: [SharedMediaPlayerGroup: SharedMediaPlayer] = [:]
private var currentOverlayVideoNode: OverlayMediaItemNode?
@ -227,7 +230,7 @@ public final class MediaManagerImpl: NSObject, MediaManager {
var artwork: SharedMediaPlaybackAlbumArt?
switch displayData {
case let .music(title, performer, artworkValue):
case let .music(title, performer, artworkValue, _):
artwork = artworkValue
let titleText: String = title ?? "Unknown Track"
@ -386,6 +389,23 @@ public final class MediaManagerImpl: NSObject, MediaManager {
}
}
let throttledSignal = self.globalMediaPlayerState
|> mapToThrottled { next -> Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> in
return .single(next) |> then(.complete() |> delay(4.0, queue: Queue.concurrentDefaultQueue()))
}
self.mediaPlaybackStateDisposable.set(throttledSignal.start(next: { accountStateAndType in
if let (account, stateOrLoading, type) = accountStateAndType, type == .music, case let .state(state) = stateOrLoading, state.status.duration > 60.0 * 20.0, case .playing = state.status.status {
if let item = state.item as? MessageMediaPlaylistItem {
var storedState: MediaPlaybackStoredState?
if state.status.timestamp > 5.0 && state.status.timestamp < state.status.duration - 5.0 {
storedState = MediaPlaybackStoredState(timestamp: state.status.timestamp, playbackRate: state.status.baseRate > 1.0 ? .x2 : .x1)
}
let _ = updateMediaPlaybackStoredStateInteractively(postbox: account.postbox, messageId: item.message.id, state: storedState).start()
}
}
}))
self.globalAudioSessionForegroundDisposable.set((shouldKeepAudioSession |> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
@ -401,6 +421,7 @@ public final class MediaManagerImpl: NSObject, MediaManager {
self.globalControlsArtworkDisposable.dispose()
self.globalControlsStatusDisposable.dispose()
self.setPlaylistByTypeDisposables.dispose()
self.mediaPlaybackStateDisposable.dispose()
self.globalAudioSessionForegroundDisposable.dispose()
}
@ -417,24 +438,32 @@ public final class MediaManagerImpl: NSObject, MediaManager {
disposable.set(ActionDisposable {
})
}
return disposable
}
}
public func setPlaylist(_ playlist: (Account, SharedMediaPlaylist)?, type: MediaManagerPlayerType, control: SharedMediaPlayerControlAction) {
assert(Queue.mainQueue().isCurrent())
let inputData: Signal<(Account, SharedMediaPlaylist, MusicPlaybackSettings)?, NoError>
let inputData: Signal<(Account, SharedMediaPlaylist, MusicPlaybackSettings, MediaPlaybackStoredState?)?, NoError>
if let (account, playlist) = playlist {
inputData = self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.musicPlaybackSettings])
|> take(1)
|> map { sharedData in
|> mapToSignal { sharedData -> Signal<(Account, SharedMediaPlaylist, MusicPlaybackSettings, MediaPlaybackStoredState?)?, NoError> in
let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.musicPlaybackSettings] as? MusicPlaybackSettings) ?? MusicPlaybackSettings.defaultSettings
return (account, playlist, settings)
if let location = playlist.location as? PeerMessagesPlaylistLocation, let messageId = location.messageId {
return mediaPlaybackStoredState(postbox: account.postbox, messageId: messageId)
|> map { storedState in
return (account, playlist, settings, storedState)
}
} else {
return .single((account, playlist, settings, nil))
}
}
} else {
inputData = .single(nil)
}
self.setPlaylistByTypeDisposables.set((inputData
|> deliverOnMainQueue).start(next: { [weak self] inputData in
if let strongSelf = self {
@ -444,7 +473,7 @@ public final class MediaManagerImpl: NSObject, MediaManager {
case .voice:
strongSelf.musicMediaPlayer?.control(.playback(.pause))
strongSelf.voiceMediaPlayer?.stop()
if let (account, playlist, settings) = inputData {
if let (account, playlist, settings, _) = inputData {
let voiceMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: true)
strongSelf.voiceMediaPlayer = voiceMediaPlayer
voiceMediaPlayer.playedToEnd = { [weak voiceMediaPlayer] in
@ -466,8 +495,8 @@ public final class MediaManagerImpl: NSObject, MediaManager {
case .music:
strongSelf.musicMediaPlayer?.stop()
strongSelf.voiceMediaPlayer?.control(.playback(.pause))
if let (account, playlist, settings) = inputData {
let musicMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, initialPlaybackRate: .x1, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false)
if let (account, playlist, settings, storedState) = inputData {
let musicMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, initialPlaybackRate: storedState?.playbackRate ?? .x1, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false)
strongSelf.musicMediaPlayer = musicMediaPlayer
musicMediaPlayer.cancelled = { [weak musicMediaPlayer] in
if let strongSelf = self, let musicMediaPlayer = musicMediaPlayer, musicMediaPlayer === strongSelf.musicMediaPlayer {
@ -475,6 +504,11 @@ public final class MediaManagerImpl: NSObject, MediaManager {
strongSelf.musicMediaPlayer = nil
}
}
var control = control
if let timestamp = storedState?.timestamp {
control = .seek(timestamp)
}
strongSelf.musicMediaPlayer?.control(control)
} else {
strongSelf.musicMediaPlayer = nil

View File

@ -0,0 +1,55 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramUIPreferences
public final class MediaPlaybackStoredState: PostboxCoding {
public let timestamp: Double
public let playbackRate: AudioPlaybackRate
public init(timestamp: Double, playbackRate: AudioPlaybackRate) {
self.timestamp = timestamp
self.playbackRate = playbackRate
}
public init(decoder: PostboxDecoder) {
self.timestamp = decoder.decodeDoubleForKey("timestamp", orElse: 0.0)
self.playbackRate = AudioPlaybackRate(rawValue: decoder.decodeInt32ForKey("playbackRate", orElse: AudioPlaybackRate.x1.rawValue)) ?? .x1
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeDouble(self.timestamp, forKey: "timestamp")
encoder.encodeInt32(self.playbackRate.rawValue, forKey: "playbackRate")
}
}
public func mediaPlaybackStoredState(postbox: Postbox, messageId: MessageId) -> Signal<MediaPlaybackStoredState?, NoError> {
return postbox.transaction { transaction -> MediaPlaybackStoredState? in
let key = ValueBoxKey(length: 8)
key.setInt32(0, value: messageId.namespace)
key.setInt32(4, value: messageId.id)
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.mediaPlaybackStoredState, key: key)) as? MediaPlaybackStoredState {
return entry
} else {
return nil
}
}
}
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 25, highWaterItemCount: 50)
public func updateMediaPlaybackStoredStateInteractively(postbox: Postbox, messageId: MessageId, state: MediaPlaybackStoredState?) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
let key = ValueBoxKey(length: 8)
key.setInt32(0, value: messageId.namespace)
key.setInt32(4, value: messageId.id)
let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.mediaPlaybackStoredState, key: key)
if let state = state {
transaction.putItemCacheEntry(id: id, entry: state, collectionSpec: collectionSpec)
} else {
transaction.removeItemCacheEntry(id: id)
}
}
}

View File

@ -426,7 +426,6 @@ func openChatInstantPage(context: AccountContext, message: Message, sourcePeerTy
}
let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor)
pageController.navigationPresentation = .modal
navigationController.pushViewController(pageController)
}
break

View File

@ -310,7 +310,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
if let theme = makePresentationTheme(data: dataAndTheme.0) {
let previewController = ThemePreviewController(context: context, previewTheme: theme, source: .theme(dataAndTheme.1))
present(previewController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
navigationController?.pushViewController(previewController)
}
}, error: { [weak controller] error in
let errorText: String

View File

@ -42,7 +42,7 @@ private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, them
let titleText: String
let subtitleText: String
switch data {
case let .music(title, performer, _):
case let .music(title, performer, _, _):
titleText = title ?? "Unknown Track"
subtitleText = performer ?? "Unknown Artist"
case .voice, .instantVideo:
@ -88,6 +88,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private var currentLooping: MusicPlaybackSettingsLooping?
private let loopingButton: IconButtonNode
private var currentRate: AudioPlaybackRate?
private let rateButton: HighlightableButtonNode
let separatorNode: ASDisplayNode
var isExpanded = false
@ -144,6 +147,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.rightDurationLabel.mode = .reversed
self.rightDurationLabel.alignment = .right
self.rateButton = HighlightableButtonNode()
self.rateButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -4.0, bottom: -8.0, right: -4.0)
self.rateButton.displaysAsynchronously = false
self.backwardButton = IconButtonNode()
self.backwardButton.displaysAsynchronously = false
@ -180,6 +187,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.addSubnode(self.scrubberNode)
self.addSubnode(self.leftDurationLabel)
self.addSubnode(self.rightDurationLabel)
self.addSubnode(self.rateButton)
self.addSubnode(self.orderButton)
self.addSubnode(self.loopingButton)
@ -206,7 +214,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
let mappedStatus = combineLatest(delayedStatus, self.scrubberNode.scrubbingTimestamp) |> map { value, scrubbingTimestamp -> MediaPlayerStatus in
if let (_, valueOrLoading) = value, case let .state(value) = valueOrLoading {
return MediaPlayerStatus(generationTimestamp: value.status.generationTimestamp, duration: value.status.duration, dimensions: value.status.dimensions, timestamp: scrubbingTimestamp ?? value.status.timestamp, baseRate: value.status.baseRate, seekId: value.status.seekId, status: value.status.status, soundEnabled: value.status.soundEnabled)
return MediaPlayerStatus(generationTimestamp: scrubbingTimestamp != nil ? 0 : value.status.generationTimestamp, duration: value.status.duration, dimensions: value.status.dimensions, timestamp: scrubbingTimestamp ?? value.status.timestamp, baseRate: value.status.baseRate, seekId: value.status.seekId, status: value.status.status, soundEnabled: value.status.soundEnabled)
} else {
return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true)
}
@ -259,10 +267,28 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
strongSelf.currentLooping = value.looping
strongSelf.updateLoopButton(value.looping)
}
let baseRate: AudioPlaybackRate
if !value.status.baseRate.isEqual(to: 1.0) {
baseRate = .x2
} else {
baseRate = .x1
}
if baseRate != strongSelf.currentRate {
strongSelf.currentRate = baseRate
strongSelf.updateRateButton(baseRate)
}
if let displayData = displayData, case let .music(_, _, _, long) = displayData, long {
strongSelf.rateButton.isHidden = false
} else {
strongSelf.rateButton.isHidden = true
}
} else {
strongSelf.playPauseButton.isEnabled = false
strongSelf.backwardButton.isEnabled = false
strongSelf.forwardButton.isEnabled = false
strongSelf.rateButton.isHidden = true
displayData = nil
}
@ -301,6 +327,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.backwardButton.addTarget(self, action: #selector(self.backwardPressed), forControlEvents: .touchUpInside)
self.forwardButton.addTarget(self, action: #selector(self.forwardPressed), forControlEvents: .touchUpInside)
self.playPauseButton.addTarget(self, action: #selector(self.playPausePressed), forControlEvents: .touchUpInside)
self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside)
}
deinit {
@ -336,6 +363,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
if let looping = self.currentLooping {
self.updateLoopButton(looping)
}
if let rate = self.currentRate {
self.updateRateButton(rate)
}
self.separatorNode.backgroundColor = theme.list.itemPlainSeparatorColor
}
@ -368,7 +398,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
var albumArt: SharedMediaPlaybackAlbumArt?
if let displayData = self.displayData {
switch displayData {
case let .music(_, _, value):
case let .music(_, _, value, _):
albumArt = value
default:
break
@ -416,6 +446,15 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
}
}
private func updateRateButton(_ baseRate: AudioPlaybackRate) {
switch baseRate {
case .x2:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
default:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
}
}
static let basePanelHeight: CGFloat = 220.0
static func heightForLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, isExpanded: Bool) -> CGFloat {
@ -523,8 +562,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
let scrubberVerticalOrigin: CGFloat = infoVerticalOrigin + 64.0
transition.updateFrame(node: self.scrubberNode, frame: CGRect(origin: CGPoint(x: leftInset + sideInset, y: scrubberVerticalOrigin - 8.0), size: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset, height: 10.0 + 8.0 * 2.0)))
transition.updateFrame(node: self.leftDurationLabel, frame: CGRect(origin: CGPoint(x: leftInset + sideInset, y: scrubberVerticalOrigin + 12.0), size: CGSize(width: 100.0, height: 20.0)))
transition.updateFrame(node: self.rightDurationLabel, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 100.0, y: scrubberVerticalOrigin + 12.0), size: CGSize(width: 100.0, height: 20.0)))
transition.updateFrame(node: self.leftDurationLabel, frame: CGRect(origin: CGPoint(x: leftInset + sideInset, y: scrubberVerticalOrigin + 14.0), size: CGSize(width: 100.0, height: 20.0)))
transition.updateFrame(node: self.rightDurationLabel, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 100.0, y: scrubberVerticalOrigin + 14.0), size: CGSize(width: 100.0, height: 20.0)))
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 100.0 + 24.0, y: scrubberVerticalOrigin + 10.0), size: CGSize(width: 24.0, height: 24.0)))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: width, height: panelHeight + 8.0)))
@ -607,6 +648,21 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.control?(.playback(.togglePlayPause))
}
@objc func rateButtonPressed() {
var nextRate: AudioPlaybackRate
if let currentRate = self.currentRate {
switch currentRate {
case .x1:
nextRate = .x2
default:
nextRate = .x1
}
} else {
nextRate = .x2
}
self.control?(.setBaseRate(nextRate))
}
@objc func albumArtTap(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if let supernode = self.supernode {

View File

@ -216,8 +216,9 @@ class PaneSearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.iconNode.displayWithoutProcessing = true
self.textField = PaneSearchBarTextField()
self.textField.accessibilityTraits = .searchField
self.textField.autocorrectionType = .no
self.textField.returnKeyType = .done
self.textField.returnKeyType = .search
self.textField.font = Font.regular(17.0)
self.clearButton = HighlightableButtonNode()

View File

@ -74,6 +74,9 @@ final class PaneSearchBarPlaceholderNode: GridItemNode {
super.init()
self.isAccessibilityElement = true
self.accessibilityTraits = .searchField
self.addSubnode(self.backgroundNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.iconNode)
@ -97,7 +100,7 @@ final class PaneSearchBarPlaceholderNode: GridItemNode {
placeholder = strings.Gif_Search
}
self.labelNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: theme.chat.inputMediaPanel.stickersSearchPlaceholderColor)
self.accessibilityLabel = placeholder
self.currentState = (theme, strings, type)
}
}

View File

@ -105,7 +105,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem {
if let file = extractFileMedia(self.message) {
for attribute in file.attributes {
switch attribute {
case let .Audio(isVoice, _, title, performer, _):
case let .Audio(isVoice, duration, title, performer, _):
if isVoice {
return SharedMediaPlaybackDisplayData.voice(author: self.message.effectiveAuthor, peer: self.message.peers[self.message.id.peerId])
} else {
@ -114,7 +114,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem {
if (title ?? "").isEmpty && (performer ?? "").isEmpty {
updatedTitle = file.fileName ?? ""
}
return SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: updatedTitle ?? "", performer: updatedPerformer ?? "", isThumbnail: false)))
return SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: updatedTitle ?? "", performer: updatedPerformer ?? "", isThumbnail: false)), long: duration > 60 * 20)
}
case let .Video(_, _, flags):
if flags.contains(.instantRoundVideo) {
@ -127,7 +127,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem {
}
}
return SharedMediaPlaybackDisplayData.music(title: file.fileName ?? "", performer: self.message.effectiveAuthor?.displayTitle ?? "", albumArt: nil)
return SharedMediaPlaybackDisplayData.music(title: file.fileName ?? "", performer: self.message.effectiveAuthor?.displayTitle ?? "", albumArt: nil, long: false)
}
return nil
}
@ -214,6 +214,15 @@ enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation {
}
}
var messageId: MessageId? {
switch self {
case let .messages(_, _, messageId), let .singleMessage(messageId):
return messageId
default:
return nil
}
}
func isEqual(to: SharedMediaPlaylistLocation) -> Bool {
if let to = to as? PeerMessagesPlaylistLocation {
return self == to

View File

@ -54,12 +54,14 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
case instantPageStoredState = 0
case cachedInstantPages = 1
case cachedWallpapers = 2
case mediaPlaybackStoredState = 3
}
public struct ApplicationSpecificItemCacheCollectionId {
public static let instantPageStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.instantPageStoredState.rawValue)
public static let cachedInstantPages = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedInstantPages.rawValue)
public static let cachedWallpapers = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedWallpapers.rawValue)
public static let mediaPlaybackStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaPlaybackStoredState.rawValue)
}
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {