From 1bebfdaf533e390e997efd18f7098eb8c7624b13 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 18 Sep 2019 02:24:56 +0300 Subject: [PATCH] Long audio playback improvements: 2x playback, position storing Various UI fixes --- .../Sources/SharedMediaPlayer.swift | 6 +- .../Sources/ActivityIndicator.swift | 4 ++ .../Sources/CallListController.swift | 3 +- .../Sources/CallListControllerNode.swift | 2 +- .../Sources/Node/ChatListItem.swift | 6 +- submodules/Display/Display/ListView.swift | 4 +- .../Display/NavigationButtonNode.swift | 40 +++++++---- .../GalleryUI/Sources/GalleryController.swift | 3 + .../Sources/InstantPageController.swift | 2 + .../Sources/InstantPageMediaPlaylist.swift | 4 +- .../Sources/InstantPageTextItem.swift | 11 +++- .../Sources/PasscodeBackground.swift | 9 ++- .../SearchBarNode/Sources/SearchBarNode.swift | 1 + .../Sources/SegmentedControlNode.swift | 18 +++-- .../InstalledStickerPacksController.swift | 4 +- .../Sources/Themes/EditThemeController.swift | 1 + .../Themes/ThemeSettingsController.swift | 13 ++-- .../Sources/ShareSearchBarNode.swift | 2 + .../MediaNavigationAccessoryHeaderNode.swift | 20 +++--- .../AccountStateManagementUtils.swift | 4 +- .../TelegramCore/ActiveSessionsContext.swift | 4 +- .../TelegramCore/RecentAccountSessions.swift | 11 +++- .../TelegramUI/ChatController.swift | 1 + .../ChatInterfaceStateNavigationButtons.swift | 2 +- .../ChatScheduleTimeController.swift | 2 + ...ChatSendMessageActionSheetController.swift | 2 + ...SendMessageActionSheetControllerNode.swift | 1 + .../TelegramUI/DeclareEncodables.swift | 1 + .../TelegramUI/TelegramUI/MediaManager.swift | 50 +++++++++++--- .../TelegramUI/MediaPlaybackStoredState.swift | 55 ++++++++++++++++ .../TelegramUI/OpenChatMessage.swift | 1 - .../TelegramUI/OpenResolvedUrl.swift | 2 +- .../OverlayPlayerControlsNode.swift | 66 +++++++++++++++++-- .../TelegramUI/PaneSearchBarNode.swift | 3 +- .../PaneSearchBarPlaceholderItem.swift | 5 +- .../PeerMessagesMediaPlaylist.swift | 15 ++++- .../Sources/PostboxKeys.swift | 2 + 37 files changed, 302 insertions(+), 78 deletions(-) create mode 100644 submodules/TelegramUI/TelegramUI/MediaPlaybackStoredState.swift diff --git a/submodules/AccountContext/Sources/SharedMediaPlayer.swift b/submodules/AccountContext/Sources/SharedMediaPlayer.swift index 1f3bd04142..397d7d0153 100644 --- a/submodules/AccountContext/Sources/SharedMediaPlayer.swift +++ b/submodules/AccountContext/Sources/SharedMediaPlayer.swift @@ -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 diff --git a/submodules/ActivityIndicator/Sources/ActivityIndicator.swift b/submodules/ActivityIndicator/Sources/ActivityIndicator.swift index 477142bfe5..ef3162a27f 100644 --- a/submodules/ActivityIndicator/Sources/ActivityIndicator.swift +++ b/submodules/ActivityIndicator/Sources/ActivityIndicator.swift @@ -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) diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 4e182ea7e4..ca207e1b13 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -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 diff --git a/submodules/CallListUI/Sources/CallListControllerNode.swift b/submodules/CallListUI/Sources/CallListControllerNode.swift index d4a62d6d39..6926bacfd5 100644 --- a/submodules/CallListUI/Sources/CallListControllerNode.swift +++ b/submodules/CallListUI/Sources/CallListControllerNode.swift @@ -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) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 76de59f99a..ece82bcec1 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -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 diff --git a/submodules/Display/Display/ListView.swift b/submodules/Display/Display/ListView.swift index 8e628f723a..e0867b9a5b 100644 --- a/submodules/Display/Display/ListView.swift +++ b/submodules/Display/Display/ListView.swift @@ -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) } diff --git a/submodules/Display/Display/NavigationButtonNode.swift b/submodules/Display/Display/NavigationButtonNode.swift index ea785e7bcd..d246df1c1f 100644 --- a/submodules/Display/Display/NavigationButtonNode.swift +++ b/submodules/Display/Display/NavigationButtonNode.swift @@ -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 diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 9bc6f47595..8a33aa6caf 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -731,6 +731,9 @@ public class GalleryController: ViewController { } } } + + self.blocksBackgroundWhenInOverlay = true + self.isOpaqueWhenInOverlay = true } required init(coder aDecoder: NSCoder) { diff --git a/submodules/InstantPageUI/Sources/InstantPageController.swift b/submodules/InstantPageUI/Sources/InstantPageController.swift index d33f358d9a..b5fce4b5a6 100644 --- a/submodules/InstantPageUI/Sources/InstantPageController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageController.swift @@ -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 diff --git a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift index eb3c995a12..897df8529a 100644 --- a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift +++ b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift @@ -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 } diff --git a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift index 3bd1f5caeb..5c674293f2 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift @@ -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) } diff --git a/submodules/PasscodeUI/Sources/PasscodeBackground.swift b/submodules/PasscodeUI/Sources/PasscodeBackground.swift index 9203e6c1cb..4fa29bc85a 100644 --- a/submodules/PasscodeUI/Sources/PasscodeBackground.swift +++ b/submodules/PasscodeUI/Sources/PasscodeBackground.swift @@ -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) diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift index 0ce38d027b..7c381d91a9 100644 --- a/submodules/SearchBarNode/Sources/SearchBarNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift @@ -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 diff --git a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift index 87223040a1..3e527f9ef2 100644 --- a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift +++ b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift @@ -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 } diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index 9f55c85589..b134929169 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -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 { diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index 6de4440cb1..c95ddb02c7 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -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) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 48897c9f63..22082e697e 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -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 diff --git a/submodules/ShareController/Sources/ShareSearchBarNode.swift b/submodules/ShareController/Sources/ShareSearchBarNode.swift index af15892746..f86602ab93 100644 --- a/submodules/ShareController/Sources/ShareSearchBarNode.swift +++ b/submodules/ShareController/Sources/ShareSearchBarNode.swift @@ -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() diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift index 00f3e3faca..b8da7e768e 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -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))) diff --git a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift index 5596250bc2..9767ecf210 100644 --- a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift @@ -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) diff --git a/submodules/TelegramCore/TelegramCore/ActiveSessionsContext.swift b/submodules/TelegramCore/TelegramCore/ActiveSessionsContext.swift index 7c124feebe..3efaf2cfe0 100644 --- a/submodules/TelegramCore/TelegramCore/ActiveSessionsContext.swift +++ b/submodules/TelegramCore/TelegramCore/ActiveSessionsContext.swift @@ -92,10 +92,10 @@ public final class ActiveSessionsContext { } } - public func removeOther() -> Signal { + public func removeOther() -> Signal { return terminateOtherAccountSessions(account: self.account) |> deliverOnMainQueue - |> mapToSignal { [weak self] _ -> Signal in + |> mapToSignal { [weak self] _ -> Signal in guard let strongSelf = self else { return .complete() } diff --git a/submodules/TelegramCore/TelegramCore/RecentAccountSessions.swift b/submodules/TelegramCore/TelegramCore/RecentAccountSessions.swift index 4758e7a378..5b4b09e90f 100644 --- a/submodules/TelegramCore/TelegramCore/RecentAccountSessions.swift +++ b/submodules/TelegramCore/TelegramCore/RecentAccountSessions.swift @@ -42,10 +42,15 @@ public func terminateAccountSession(account: Account, hash: Int64) -> Signal Signal { +public func terminateOtherAccountSessions(account: Account) -> Signal { return account.network.request(Api.functions.auth.resetAuthorizations()) - |> retryRequest - |> mapToSignal { _ -> Signal in + |> mapError { error -> TerminateSessionError in + if error.errorCode == 406 { + return .freshReset + } + return .generic + } + |> mapToSignal { _ -> Signal in return .single(Void()) } } diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 5528efad1f..ab121d6da2 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -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) } }) diff --git a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateNavigationButtons.swift index c95e116af4..695aa3d006 100644 --- a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateNavigationButtons.swift @@ -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) } } diff --git a/submodules/TelegramUI/TelegramUI/ChatScheduleTimeController.swift b/submodules/TelegramUI/TelegramUI/ChatScheduleTimeController.swift index 869dafb583..36a6aea0d8 100644 --- a/submodules/TelegramUI/TelegramUI/ChatScheduleTimeController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatScheduleTimeController.swift @@ -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 { diff --git a/submodules/TelegramUI/TelegramUI/ChatSendMessageActionSheetController.swift b/submodules/TelegramUI/TelegramUI/ChatSendMessageActionSheetController.swift index 60e1ebfa83..ea44a0a7bc 100644 --- a/submodules/TelegramUI/TelegramUI/ChatSendMessageActionSheetController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatSendMessageActionSheetController.swift @@ -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 { diff --git a/submodules/TelegramUI/TelegramUI/ChatSendMessageActionSheetControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatSendMessageActionSheetControllerNode.swift index 9d994b83d6..69758f4339 100644 --- a/submodules/TelegramUI/TelegramUI/ChatSendMessageActionSheetControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatSendMessageActionSheetControllerNode.swift @@ -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 diff --git a/submodules/TelegramUI/TelegramUI/DeclareEncodables.swift b/submodules/TelegramUI/TelegramUI/DeclareEncodables.swift index 5ba39fe5e9..41329cbaa8 100644 --- a/submodules/TelegramUI/TelegramUI/DeclareEncodables.swift +++ b/submodules/TelegramUI/TelegramUI/DeclareEncodables.swift @@ -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 }() diff --git a/submodules/TelegramUI/TelegramUI/MediaManager.swift b/submodules/TelegramUI/TelegramUI/MediaManager.swift index 7b9284a658..5d0ed83bd9 100644 --- a/submodules/TelegramUI/TelegramUI/MediaManager.swift +++ b/submodules/TelegramUI/TelegramUI/MediaManager.swift @@ -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() + 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 diff --git a/submodules/TelegramUI/TelegramUI/MediaPlaybackStoredState.swift b/submodules/TelegramUI/TelegramUI/MediaPlaybackStoredState.swift new file mode 100644 index 0000000000..c3fb8009c6 --- /dev/null +++ b/submodules/TelegramUI/TelegramUI/MediaPlaybackStoredState.swift @@ -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 { + 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 { + 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) + } + } +} diff --git a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift index 5ac73f87a4..83b224a046 100644 --- a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift +++ b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift @@ -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 diff --git a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift index 36359ca9ad..bed1c4c463 100644 --- a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift @@ -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 diff --git a/submodules/TelegramUI/TelegramUI/OverlayPlayerControlsNode.swift b/submodules/TelegramUI/TelegramUI/OverlayPlayerControlsNode.swift index 57cd6daef7..f688efe8ea 100644 --- a/submodules/TelegramUI/TelegramUI/OverlayPlayerControlsNode.swift +++ b/submodules/TelegramUI/TelegramUI/OverlayPlayerControlsNode.swift @@ -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 { diff --git a/submodules/TelegramUI/TelegramUI/PaneSearchBarNode.swift b/submodules/TelegramUI/TelegramUI/PaneSearchBarNode.swift index c7837b742d..76bd5d7618 100644 --- a/submodules/TelegramUI/TelegramUI/PaneSearchBarNode.swift +++ b/submodules/TelegramUI/TelegramUI/PaneSearchBarNode.swift @@ -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() diff --git a/submodules/TelegramUI/TelegramUI/PaneSearchBarPlaceholderItem.swift b/submodules/TelegramUI/TelegramUI/PaneSearchBarPlaceholderItem.swift index 2f175126c7..e317de449a 100644 --- a/submodules/TelegramUI/TelegramUI/PaneSearchBarPlaceholderItem.swift +++ b/submodules/TelegramUI/TelegramUI/PaneSearchBarPlaceholderItem.swift @@ -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) } } diff --git a/submodules/TelegramUI/TelegramUI/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/TelegramUI/PeerMessagesMediaPlaylist.swift index bfbae5f215..642ecb9bc1 100644 --- a/submodules/TelegramUI/TelegramUI/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/TelegramUI/PeerMessagesMediaPlaylist.swift @@ -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 diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift index 11e37ba283..d5918a2475 100644 --- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift +++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift @@ -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 {