mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Long audio playback improvements: 2x playback, position storing
Various UI fixes
This commit is contained in:
parent
0bbf8bf0fb
commit
1bebfdaf53
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -731,6 +731,9 @@ public class GalleryController: ViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.isOpaqueWhenInOverlay = true
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user