Various improvements and bug fixes

This commit is contained in:
Ilya Laktyushin 2019-03-17 15:31:10 +03:00
parent 826e1291d6
commit 756e7b836a
58 changed files with 3191 additions and 2860 deletions

View File

@ -130,6 +130,8 @@
09DD88FA21BFD70B000766BC /* ThemedTextAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */; };
09E4A801223AE1B30038140F /* PeerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A800223AE1B30038140F /* PeerType.swift */; };
09E4A803223B833B0038140F /* ForwardPrivacyChatPreviewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A802223B833B0038140F /* ForwardPrivacyChatPreviewItem.swift */; };
09E4A805223D4A5A0038140F /* OpenSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A804223D4A5A0038140F /* OpenSettings.swift */; };
09E4A807223D4B860038140F /* AccountUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A806223D4B860038140F /* AccountUtils.swift */; };
09EDAD26220D30980012A50B /* AutodownloadConnectionTypeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD25220D30980012A50B /* AutodownloadConnectionTypeController.swift */; };
09EDAD2A220DA6A40012A50B /* VolumeButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD29220DA6A40012A50B /* VolumeButtons.swift */; };
09EDAD2C2211552F0012A50B /* AutodownloadMediaCategoryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD2B2211552F0012A50B /* AutodownloadMediaCategoryController.swift */; };
@ -1289,6 +1291,8 @@
09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedTextAlertController.swift; sourceTree = "<group>"; };
09E4A800223AE1B30038140F /* PeerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerType.swift; sourceTree = "<group>"; };
09E4A802223B833B0038140F /* ForwardPrivacyChatPreviewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardPrivacyChatPreviewItem.swift; sourceTree = "<group>"; };
09E4A804223D4A5A0038140F /* OpenSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSettings.swift; sourceTree = "<group>"; };
09E4A806223D4B860038140F /* AccountUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUtils.swift; sourceTree = "<group>"; };
09EDAD25220D30980012A50B /* AutodownloadConnectionTypeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadConnectionTypeController.swift; sourceTree = "<group>"; };
09EDAD29220DA6A40012A50B /* VolumeButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtons.swift; sourceTree = "<group>"; };
09EDAD2B2211552F0012A50B /* AutodownloadMediaCategoryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadMediaCategoryController.swift; sourceTree = "<group>"; };
@ -2640,6 +2644,7 @@
D04ECD711FFBF22B00DE9029 /* OpenUrl.swift */,
D0FC194C201F82A000FEDBB2 /* OpenResolvedUrl.swift */,
D023836F1DDF0462004018B6 /* UrlHandling.swift */,
09E4A804223D4A5A0038140F /* OpenSettings.swift */,
);
name = Routing;
sourceTree = "<group>";
@ -4861,6 +4866,7 @@
090B48C72200BCA8005083FA /* WallpaperUploadManager.swift */,
09D96898221DE92600B1458A /* ID3ArtworkReader.swift */,
09E4A800223AE1B30038140F /* PeerType.swift */,
09E4A806223D4B860038140F /* AccountUtils.swift */,
);
name = Utils;
sourceTree = "<group>";
@ -5415,6 +5421,7 @@
D093D7DF2062F3F000BC3599 /* SecureIdDocumentFormController.swift in Sources */,
D0E9BA371F05585000F079A4 /* STPPhoneNumberValidator.m in Sources */,
0910B0EF21FA532D00F8F87D /* WallpaperResources.swift in Sources */,
09E4A807223D4B860038140F /* AccountUtils.swift in Sources */,
D069F5D0212700B90000565A /* StickerPanePeerSpecificSetupGridItem.swift in Sources */,
D0EC6D041EB9F58800EBF1C3 /* opusenc.m in Sources */,
D0A8998D217A294100759EE6 /* SaveIncomingMediaController.swift in Sources */,
@ -5634,6 +5641,7 @@
D0EC6D681EB9F58800EBF1C3 /* AuthorizationSequenceController.swift in Sources */,
09F664C021EAAFAF00AB7E26 /* ThemeColorsGridController.swift in Sources */,
D0EC6D691EB9F58800EBF1C3 /* AuthorizationSequenceSplashController.swift in Sources */,
09E4A805223D4A5A0038140F /* OpenSettings.swift in Sources */,
D0EC6D6A1EB9F58800EBF1C3 /* AuthorizationSequenceSplashControllerNode.swift in Sources */,
D0C683FC21AD797F00A6CAD5 /* ChatListSelection.swift in Sources */,
D0EC6D6B1EB9F58800EBF1C3 /* AuthorizationSequenceCountrySelectionController.swift in Sources */,

View File

@ -0,0 +1,40 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramCore
func activeAccountsAndPeers(context: AccountContext) -> Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError> {
let sharedContext = context.sharedContext
return context.sharedContext.activeAccounts
|> mapToSignal { primary, activeAccounts, _ -> Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError> in
var accounts: [Signal<(Account, Peer, Int32)?, NoError>] = []
func accountWithPeer(_ account: Account) -> Signal<(Account, Peer, Int32)?, NoError> {
return combineLatest(account.postbox.peerView(id: account.peerId), renderedTotalUnreadCount(accountManager: sharedContext.accountManager, postbox: account.postbox))
|> map { view, totalUnreadCount -> (Peer?, Int32) in
return (view.peers[view.peerId], totalUnreadCount.0)
}
|> distinctUntilChanged { lhs, rhs in
return arePeersEqual(lhs.0, rhs.0) && lhs.1 == rhs.1
}
|> map { peer, totalUnreadCount -> (Account, Peer, Int32)? in
if let peer = peer {
return (account, peer, totalUnreadCount)
} else {
return nil
}
}
}
for (_, account, _) in activeAccounts {
accounts.append(accountWithPeer(account))
}
return combineLatest(accounts)
|> map { accounts -> ((Account, Peer)?, [(Account, Peer, Int32)]) in
var primaryRecord: (Account, Peer)?
if let first = accounts.filter({ $0?.0.id == primary?.id }).first, let (account, peer, _) = first {
primaryRecord = (account, peer)
}
return (primaryRecord, accounts.filter({ $0?.0.id != primary?.id }).compactMap({ $0 }))
}
}
}

View File

@ -21,6 +21,8 @@ private func extractAnchor(string: String) -> (String, String?) {
return (trimmedUrl, anchorValue)
}
private let refreshTimeout: Int32 = 60 * 60 * 12
func cachedFaqInstantPage(context: AccountContext) -> Signal<ResolvedUrl, NoError> {
var faqUrl = context.sharedContext.currentPresentationData.with { $0 }.strings.Settings_FAQ_URL
if faqUrl == "Settings.FAQ_URL" || faqUrl.isEmpty {
@ -31,27 +33,36 @@ func cachedFaqInstantPage(context: AccountContext) -> Signal<ResolvedUrl, NoErro
return cachedInstantPage(postbox: context.account.postbox, url: cachedUrl)
|> mapToSignal { cachedInstantPage -> Signal<ResolvedUrl, NoError> in
if let webPage = cachedInstantPage?.webPage, case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, instantPage.isComplete {
return .single(.instantView(webPage, anchor))
} else {
return resolveInstantViewUrl(account: context.account, url: faqUrl)
|> afterNext { result in
if case let .instantView(webPage, _) = result, case let .Loaded(content) = webPage.content, let instantPage = content.instantPage {
if instantPage.isComplete {
let _ = updateCachedInstantPage(postbox: context.account.postbox, url: cachedUrl, webPage: webPage).start()
} else {
let _ = (actualizedWebpage(postbox: context.account.postbox, network: context.account.network, webpage: webPage)
|> mapToSignal { webPage -> Signal<Void, NoError> in
if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, instantPage.isComplete {
return updateCachedInstantPage(postbox: context.account.postbox, url: cachedUrl, webPage: webPage)
} else {
return .complete()
}
}).start()
}
let updated = resolveInstantViewUrl(account: context.account, url: faqUrl)
|> afterNext { result in
if case let .instantView(webPage, _) = result, case let .Loaded(content) = webPage.content, let instantPage = content.instantPage {
if instantPage.isComplete {
let _ = updateCachedInstantPage(postbox: context.account.postbox, url: cachedUrl, webPage: webPage).start()
} else {
let _ = (actualizedWebpage(postbox: context.account.postbox, network: context.account.network, webpage: webPage)
|> mapToSignal { webPage -> Signal<Void, NoError> in
if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, instantPage.isComplete {
return updateCachedInstantPage(postbox: context.account.postbox, url: cachedUrl, webPage: webPage)
} else {
return .complete()
}
}).start()
}
}
}
let now = Int32(CFAbsoluteTimeGetCurrent())
if let cachedInstantPage = cachedInstantPage, case let .Loaded(content) = cachedInstantPage.webPage.content, let instantPage = content.instantPage, instantPage.isComplete {
let current: Signal<ResolvedUrl, NoError> = .single(.instantView(cachedInstantPage.webPage, anchor))
if now > cachedInstantPage.timestamp + refreshTimeout {
return current
|> then(updated)
} else {
return current
}
} else {
return updated
}
}
}
@ -76,7 +87,7 @@ func faqSearchableItems(context: AccountContext) -> Signal<[SettingsSearchableIt
currentAnchor = anchor
case let .header(text):
if let anchor = currentAnchor {
results.append(SettingsSearchableItem(id: .faq(results.count + 1), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ], present: { context, present in
results.append(SettingsSearchableItem(id: .faq(results.count + 1), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ], present: { context, _, present in
present(.push, InstantPageController(context: context, webPage: webPage, sourcePeerType: .channel, anchor: anchor))
}))
}
@ -96,7 +107,7 @@ func faqSearchableItems(context: AccountContext) -> Signal<[SettingsSearchableIt
for item in items {
if case let .text(itemText, _) = item, case let .url(text, url, _) = itemText {
let (_, anchor) = extractAnchor(string: url)
results.append(SettingsSearchableItem(id: .faq(results.count + 1), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ, currentSection], present: { context, present in
results.append(SettingsSearchableItem(id: .faq(results.count + 1), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ, currentSection], present: { context, _, present in
present(.push, InstantPageController(context: context, webPage: webPage, sourcePeerType: .channel, anchor: anchor))
}))
}

View File

@ -5,17 +5,21 @@ import TelegramCore
final class CachedInstantPage: PostboxCoding {
let webPage: TelegramMediaWebpage
let timestamp: Int32
init(webPage: TelegramMediaWebpage) {
init(webPage: TelegramMediaWebpage, timestamp: Int32) {
self.webPage = webPage
self.timestamp = timestamp
}
init(decoder: PostboxDecoder) {
self.webPage = decoder.decodeObjectForKey("webpage", decoder: { TelegramMediaWebpage(decoder: $0) }) as! TelegramMediaWebpage
self.timestamp = decoder.decodeInt32ForKey("timestamp", orElse: 0)
}
func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.webPage, forKey: "webpage")
encoder.encodeInt32(self.timestamp, forKey: "timestamp")
}
}
@ -39,7 +43,7 @@ func updateCachedInstantPage(postbox: Postbox, url: String, webPage: TelegramMed
key.setInt64(0, value: Int64(url.hashValue))
let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.cachedInstantPages, key: key)
if let webPage = webPage {
transaction.putItemCacheEntry(id: id, entry: CachedInstantPage(webPage: webPage), collectionSpec: collectionSpec)
transaction.putItemCacheEntry(id: id, entry: CachedInstantPage(webPage: webPage, timestamp: Int32(CFAbsoluteTimeGetCurrent())), collectionSpec: collectionSpec)
} else {
transaction.removeItemCacheEntry(id: id)
}

View File

@ -296,6 +296,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
return false
}
strongSelf.commitPurposefulAction()
strongSelf.videoUnmuteTooltipController?.dismiss()
var openMessageByAction: Bool = false
@ -1101,6 +1102,10 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
if let strongSelf = self {
openAddContact(context: strongSelf.context, phoneNumber: phoneNumber, present: { [weak self] controller, arguments in
self?.present(controller, in: .window(.root), with: arguments)
}, pushController: { [weak self] controller in
if let strongSelf = self {
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
}
})
}
}, rateCall: { [weak self] message, callId in
@ -2232,6 +2237,8 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}
if isAction && (actions.options == .deleteGlobally || actions.options == .deleteLocally) {
let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: Array(messageIds), type: actions.options == .deleteLocally ? .forLocalPeer : .forEveryone).start()
} else if actions.options == .cancelSending {
let _ = deleteMessagesInteractively(postbox: strongSelf.context.account.postbox, messageIds: Array(messageIds), type: .forEveryone).start()
} else {
var options = actions.options
if messages.first?.flags.isSending ?? false {
@ -2699,7 +2706,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}
if let location = location, let icon = icon {
strongSelf.mediaRestrictedTooltipController?.dismiss()
let tooltipController = TooltipController(content: .iconAndText(icon, strongSelf.presentationInterfaceState.strings.Conversation_PressVolumeButtonForSound), timeout: 3.5, dismissImmediatelyOnLayoutUpdate: true)
let tooltipController = TooltipController(content: .iconAndText(icon, strongSelf.presentationInterfaceState.strings.Conversation_PressVolumeButtonForSound), timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
strongSelf.videoUnmuteTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.videoUnmuteTooltipController === tooltipController {
@ -3240,8 +3247,6 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
guard let strongSelf = self, strongSelf.traceVisibility() && isTopmostChatController(strongSelf) else {
return
}
ApplicationSpecificNotice.setVolumeButtonToUnmute(accountManager: strongSelf.context.sharedContext.accountManager)
strongSelf.videoUnmuteTooltipController?.dismiss()
strongSelf.chatDisplayNode.playFirstMediaWithSound()
})
@ -6104,9 +6109,6 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
})
}
}),
KeyShortcut(input: "W", modifiers: [.command], action: { [weak self] in
})
]
return inputShortcuts + otherShortcuts

View File

@ -597,6 +597,8 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
strongSelf.view.endEditing(true)
openAddContact(context: strongSelf.context, phoneNumber: phoneNumber, present: { [weak self] controller, arguments in
self?.present(controller, in: .window(.root), with: arguments)
}, pushController: { [weak self] controller in
(self?.navigationController as? NavigationController)?.pushViewController(controller)
}, completed: {
self?.deactivateSearch(animated: false)
})

View File

@ -116,6 +116,10 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
}
}
override var isEmpty: Bool {
return self.multiplexedNode?.files.isEmpty ?? true
}
override func willEnterHierarchy() {
super.willEnterHierarchy()

View File

@ -871,9 +871,9 @@ final class ChatMediaInputNode: ChatInputNode {
strongSelf.controllerInteraction.sendGif(file)
}
}),
PeekControllerMenuItem(title: strongSelf.strings.Common_Delete, color: .destructive, action: {
PeekControllerMenuItem(title: strongSelf.strings.Preview_SaveGif, color: .accent, action: {
if let strongSelf = self {
let _ = removeSavedGif(postbox: strongSelf.context.account.postbox, mediaId: file.media.fileId).start()
let _ = addSavedGif(postbox: strongSelf.context.account.postbox, fileReference: file).start()
}
})
])))
@ -1375,11 +1375,13 @@ final class ChatMediaInputNode: ChatInputNode {
self.searchContainerNode = nil
self.searchContainerNodeLoadedDisposable.set(nil)
var paneIsEmpty = false
var placeholderNode: PaneSearchBarPlaceholderNode?
if let searchMode = searchMode {
switch searchMode {
case .gif:
placeholderNode = self.gifPane.searchPlaceholderNode
paneIsEmpty = self.gifPane.isEmpty
case .sticker:
self.stickerPane.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? PaneSearchBarPlaceholderNode {
@ -1389,7 +1391,7 @@ final class ChatMediaInputNode: ChatInputNode {
}
}
if let placeholderNode = placeholderNode {
searchContainerNode.animateOut(to: placeholderNode, transition: transition, completion: { [weak searchContainerNode] in
searchContainerNode.animateOut(to: placeholderNode, animateOutSearchBar: !paneIsEmpty, transition: transition, completion: { [weak searchContainerNode] in
searchContainerNode?.removeFromSupernode()
})
} else {

View File

@ -10,6 +10,9 @@ struct ChatMediaInputPaneScrollState {
class ChatMediaInputPane: ASDisplayNode {
var inputNodeInteraction: ChatMediaInputNodeInteraction?
var collectionListPanelOffset: CGFloat = 0.0
var isEmpty: Bool {
return false
}
func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, transition: ContainedViewLayoutTransition) {
}

View File

@ -150,6 +150,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
private var currentSwipeToReplyTranslation: CGFloat = 0.0
private var appliedItem: ChatMessageItem?
private var appliedForwardInfo: (Peer?, String?)?
override var visibility: ListViewItemNodeVisibility {
didSet {
@ -314,6 +315,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
let layoutConstants = self.layoutConstants
let currentItem = self.appliedItem
let currentForwardInfo = self.appliedForwardInfo
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
let accessibilityData = ChatMessageAccessibilityData(item: item)
@ -746,6 +748,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
var forwardInfoOriginY: CGFloat = 0.0
var forwardInfoSizeApply: (CGSize, () -> ChatMessageForwardInfoNode?) = (CGSize(), { nil })
var forwardSource: Peer?
var forwardAuthorSignature: String?
if displayHeader {
if authorNameString != nil || inlineBotNameString != nil {
if headerSize.height.isZero {
@ -790,13 +795,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
headerSize.width = max(headerSize.width, nameNodeSizeApply.0.width + adminBadgeSizeAndApply.0.size.width + layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right)
headerSize.height += nameNodeSizeApply.0.height
}
if !ignoreForward, let forwardInfo = firstMessage.forwardInfo {
if headerSize.height.isZero {
headerSize.height += 5.0
}
let forwardSource: Peer?
let forwardAuthorSignature: String?
if let source = forwardInfo.source {
forwardSource = source
@ -808,8 +811,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
forwardAuthorSignature = nil
}
} else {
forwardSource = forwardInfo.author
forwardAuthorSignature = forwardInfo.authorSignature
if let currentForwardInfo = currentForwardInfo, forwardInfo.author == nil && currentForwardInfo.0 != nil {
forwardSource = currentForwardInfo.0
forwardAuthorSignature = currentForwardInfo.1
} else {
forwardSource = forwardInfo.author
forwardAuthorSignature = forwardInfo.authorSignature
}
}
let sizeAndApply = forwardInfoLayout(item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude))
forwardInfoSizeApply = (sizeAndApply.0, { sizeAndApply.1() })
@ -1148,6 +1156,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
return (layout, { [weak self] animation, synchronousLoads in
if let strongSelf = self {
strongSelf.appliedItem = item
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
strongSelf.accessibilityLabel = accessibilityData.label
strongSelf.accessibilityValue = accessibilityData.value
@ -1597,7 +1606,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
} else if let id = forwardInfo.source?.id ?? forwardInfo.author?.id {
item.controllerInteraction.openPeer(id, .info, nil)
} else if let authorSignature = forwardInfo.authorSignature {
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode)
}
return

View File

@ -20,6 +20,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
private var swipeToReplyFeedback: HapticFeedback?
private var appliedItem: ChatMessageItem?
private var appliedForwardInfo: (Peer?, String?)?
private var forwardInfoNode: ChatMessageForwardInfoNode?
private var forwardBackgroundNode: ASImageNode?
@ -95,9 +96,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode)
let currentItem = self.appliedItem
let currentForwardInfo = self.appliedForwardInfo
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
let baseWidth = params.width - params.leftInset - params.rightInset
let incoming = item.message.effectivelyIncoming(item.context.account.peerId)
let avatarInset: CGFloat
@ -268,13 +269,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
let availableContentWidth = params.width - params.leftInset - params.rightInset - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left
var forwardSource: Peer?
var forwardAuthorSignature: String?
var forwardInfoSizeApply: (CGSize, () -> ChatMessageForwardInfoNode)?
var updatedForwardBackgroundNode: ASImageNode?
var forwardBackgroundImage: UIImage?
if let forwardInfo = item.message.forwardInfo {
let forwardSource: Peer?
let forwardAuthorSignature: String?
if let source = forwardInfo.source {
forwardSource = source
if let authorSignature = forwardInfo.authorSignature {
@ -285,8 +286,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
forwardAuthorSignature = nil
}
} else {
forwardSource = forwardInfo.author
forwardAuthorSignature = forwardInfo.authorSignature
if let currentForwardInfo = currentForwardInfo, forwardInfo.author == nil && currentForwardInfo.0 != nil {
forwardSource = currentForwardInfo.0
forwardAuthorSignature = currentForwardInfo.1
} else {
forwardSource = forwardInfo.author
forwardAuthorSignature = forwardInfo.authorSignature
}
}
let availableWidth = max(60.0, availableContentWidth - videoLayout.contentSize.width + 6.0)
forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
@ -322,7 +328,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _ in
if let strongSelf = self {
strongSelf.appliedItem = item
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
let transition: ContainedViewLayoutTransition
if animation.isAnimated {
transition = .animated(duration: 0.2, curve: .spring)
@ -508,7 +515,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
} else if let id = forwardInfo.source?.id ?? forwardInfo.author?.id {
item.controllerInteraction.openPeer(id, .chat(textInputState: nil, messageId: nil), nil)
} else if let authorSignature = forwardInfo.authorSignature {
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode)
}
return

View File

@ -747,7 +747,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
statusForegroundColor = presentationData.wallpaper.isEmpty ? bubbleTheme.outgoing.withoutWallpaper.fill : bubbleTheme.outgoing.withWallpaper.fill
}
switch resourceStatus.mediaStatus {
case let .fetchStatus(fetchStatus):
case var .fetchStatus(fetchStatus):
if self.message?.forwardInfo != nil {
fetchStatus = resourceStatus.fetchStatus
}
self.waveformScrubbingNode?.enableScrubbing = false
switch fetchStatus {
case let .Fetching(_, progress):
@ -822,8 +825,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
})
} else {
streamingStatusNode.transitionToState(streamingState, completion: {
})
streamingStatusNode.transitionToState(streamingState)
}
}

View File

@ -45,8 +45,16 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
private let infoBackgroundNode: ASImageNode
private let muteIconNode: ASImageNode
private var status: FileMediaResourceMediaStatus?
private var status: FileMediaResourceStatus?
private var playerStatus: MediaPlayerStatus? {
didSet {
if self.playerStatus != oldValue {
self.updateStatus()
}
}
}
private let playbackStatusDisposable = MetaDisposable()
private let playerStatusDisposable = MetaDisposable()
private let fetchedThumbnailDisposable = MetaDisposable()
private var shouldAcquireVideoContext: Bool {
@ -98,6 +106,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
deinit {
self.fetchDisposable.dispose()
self.playbackStatusDisposable.dispose()
self.playerStatusDisposable.dispose()
self.fetchedThumbnailDisposable.dispose()
}
@ -293,17 +302,6 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
transition.updateFrame(node: strongSelf.muteIconNode, frame: muteIconFrame)
}
if let updatedPlaybackStatus = updatedPlaybackStatus {
strongSelf.playbackStatusDisposable.set((updatedPlaybackStatus
|> deliverOnMainQueue).start(next: { status in
guard let strongSelf = self else {
return
}
strongSelf.status = status.mediaStatus
strongSelf.updateStatus()
}))
}
if let updatedFile = updatedFile, updatedMedia {
if let resource = updatedFile.previewRepresentations.first?.resource {
strongSelf.fetchedThumbnailDisposable.set(fetchedMediaResource(postbox: item.context.account.postbox, reference: FileMediaReference.message(message: MessageReference(item.message), media: updatedFile).resourceReference(resource)).start())
@ -326,6 +324,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floor(videoFrame.midX) + 55.0, videoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: videoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize)
}
var updatedPlayerStatusSignal: Signal<MediaPlayerStatus?, NoError>?
if let telegramFile = updatedFile, updatedMedia {
let durationTextColor: UIColor
let durationFillColor: UIColor
@ -353,7 +352,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
durationNode.defaultDuration = telegramFile.duration.flatMap(Double.init)
let streamVideo = isMediaStreamable(message: item.message, media: telegramFile)
let streamVideo = automaticDownload && isMediaStreamable(message: item.message, media: telegramFile)
if let videoNode = strongSelf.videoNode {
videoNode.layer.allowsGroupOpacity = true
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.5, delay: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
@ -371,7 +370,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
}
}
}), content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, telegramFile.fileId), fileReference: .message(message: MessageReference(item.message), media: telegramFile), streamVideo: streamVideo, enableSound: false, fetchAutomatically: false), priority: .embedded, autoplay: true)
}), content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, telegramFile.fileId), fileReference: .message(message: MessageReference(item.message), media: telegramFile), streamVideo: streamVideo ? .earlierStart : .none, enableSound: false, fetchAutomatically: false), priority: .embedded, autoplay: true)
let previousVideoNode = strongSelf.videoNode
strongSelf.videoNode = videoNode
strongSelf.insertSubnode(videoNode, belowSubnode: previousVideoNode ?? strongSelf.dateAndStatusNode)
@ -385,6 +384,36 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
strongSelf.insertSubnode(strongSelf.secretVideoPlaceholder, belowSubnode: videoNode)
}
}
updatedPlayerStatusSignal = videoNode.status
|> mapToSignal { status -> Signal<MediaPlayerStatus?, NoError> in
if let status = status, case .buffering = status.status {
return .single(status) |> delay(1.0, queue: Queue.mainQueue())
} else {
return .single(status)
}
}
}
if let updatedPlaybackStatus = updatedPlaybackStatus {
strongSelf.playbackStatusDisposable.set((updatedPlaybackStatus
|> deliverOnMainQueue).start(next: { status in
if let strongSelf = self {
strongSelf.status = status
strongSelf.updateStatus()
}
}))
}
if let updatedPlayerStatusSignal = updatedPlayerStatusSignal {
strongSelf.playerStatusDisposable.set((updatedPlayerStatusSignal
|> deliverOnMainQueue).start(next: { [weak self] status in
displayLinkDispatcher.dispatch {
if let strongSelf = self {
strongSelf.playerStatus = status
}
}
}))
}
if let durationNode = strongSelf.durationNode {
@ -448,7 +477,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
let displayMute: Bool
switch status {
switch status.mediaStatus {
case let .fetchStatus(fetchStatus):
switch fetchStatus {
case .Local:
@ -472,7 +501,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
var progressRequired = false
if case let .fetchStatus(fetchStatus) = status {
if case let .fetchStatus(fetchStatus) = status.mediaStatus {
if case .Local = fetchStatus {
if file.isVideo {
progressRequired = true
@ -484,10 +513,6 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
}
if item.message.flags.isSending && item.message.forwardInfo != nil {
progressRequired = false
}
if progressRequired {
if self.statusNode == nil {
let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.bubble.mediaOverlayControlBackgroundColor)
@ -505,13 +530,34 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
}
var isBuffering: Bool?
if let message = self.item?.message, let media = self.media, let size = media.size, (isMediaStreamable(message: message, media: media) || size <= 256 * 1024) && (self.automaticDownload ?? false) {
if let playerStatus = self.playerStatus, case .buffering = playerStatus.status {
isBuffering = true
} else {
isBuffering = false
}
}
var state: RadialStatusNodeState
switch status {
case let .fetchStatus(fetchStatus):
switch status.mediaStatus {
case var .fetchStatus(fetchStatus):
if item.message.forwardInfo != nil {
fetchStatus = status.fetchStatus
}
switch fetchStatus {
case let .Fetching(_, progress):
let adjustedProgress = max(progress, 0.027)
state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true)
if let isBuffering = isBuffering {
if isBuffering {
state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: nil, cancelEnabled: false)
} else {
state = .none
}
} else {
let adjustedProgress = max(progress, 0.027)
state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true)
}
case .Local:
if isSecretMedia && self.secretProgressIcon != nil {
if let (beginTime, timeout) = secretBeginTimeAndTimeout {
@ -526,7 +572,11 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
state = .download(bubbleTheme.mediaOverlayControlForegroundColor)
}
default:
state = .none
if isBuffering ?? false {
state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: nil, cancelEnabled: false)
} else {
state = .none
}
}
if let statusNode = self.statusNode {
if state == .none {
@ -539,7 +589,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
})
}
if case .playbackStatus = status {
if case .playbackStatus = status.mediaStatus {
let playbackStatusNode: InstantVideoRadialStatusNode
if let current = self.playbackStatusNode {
playbackStatusNode = current
@ -608,7 +658,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
return
}
if self.infoBackgroundNode.alpha.isZero {
if let status = self.status, case let .fetchStatus(fetchStatus) = status, case .Remote = fetchStatus {
if let status = self.status, case let .fetchStatus(fetchStatus) = status.mediaStatus, case .Remote = fetchStatus {
item.context.sharedContext.mediaManager.playlistControl(.playback(.pause), type: .voice)
self.videoNode?.fetchControl(.fetch)
} else {
@ -638,7 +688,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
return
}
if let status = self.status {
switch status {
switch status.mediaStatus {
case let .fetchStatus(fetchStatus):
switch fetchStatus {
case .Fetching:

View File

@ -82,7 +82,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
return self.measureNode.measure(CGSize(width: 240.0, height: 160.0)).width
}
func update(theme: PresentationTheme, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool) {
func update(theme: PresentationTheme, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool, badgeAnimated: Bool = true) {
var transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
let previousContentSize = self.previousContentSize
@ -285,9 +285,9 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
mediaStatusFrame = CGRect(origin: CGPoint(x: 7.0 + originX, y: originY), size: CGSize(width: 28.0, height: 28.0))
}
mediaDownloadStatusNode.frame = mediaStatusFrame
mediaDownloadStatusNode.transitionToState(state, animated: true, completion: {})
mediaDownloadStatusNode.transitionToState(state, animated: badgeAnimated, completion: {})
} else if let mediaDownloadStatusNode = self.mediaDownloadStatusNode {
mediaDownloadStatusNode.transitionToState(.none, animated: true, completion: {})
mediaDownloadStatusNode.transitionToState(.none, animated: badgeAnimated, completion: {})
}
}
}

View File

@ -41,6 +41,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
private let imageNode: TransformImageNode
private var currentImageArguments: TransformImageArguments?
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private var statusNode: RadialStatusNode?
var videoNodeDecoration: ChatBubbleVideoDecoration?
private var badgeNode: ChatMessageInteractiveMediaBadge?
@ -73,7 +74,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
} else {
self.stopTimer()
}
self.updateFetchStatus()
self.updateStatus()
}
}
}
@ -176,7 +177,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
if case .ended = recognizer.state {
let point = recognizer.location(in: self.imageNode.view)
if let fetchStatus = self.fetchStatus, case .Local = fetchStatus {
self.activateLocalContent((self.automaticPlayback ?? false) ? .automaticPlayback : .default)
var videoContentMatch = true
if let content = self.videoContent, case let .message(id, _, _) = content.nativeId {
videoContentMatch = self.message?.id == id
}
self.activateLocalContent((self.automaticPlayback ?? false) && videoContentMatch ? .automaticPlayback : .default)
} else {
if let message = self.message, message.flags.isSending {
if let statusNode = self.statusNode, statusNode.frame.contains(point) {
@ -594,13 +599,15 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
let mediaManager = context.sharedContext.mediaManager
let streamVideo = isMediaStreamable(message: message, media: updatedVideoFile)
let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: decoration, content: NativeVideoContent(id: .message(message.id, message.stableId, updatedVideoFile.fileId), fileReference: .message(message: MessageReference(message), media: updatedVideoFile), streamVideo: streamVideo, enableSound: false, fetchAutomatically: false, onlyFullSizeThumbnail: (onlyFullSizeVideoThumbnail ?? false), continuePlayingWithoutSoundOnLostAudioSession: isInlinePlayableVideo, placeholderColor: emptyColor), priority: .embedded)
let videoContent = NativeVideoContent(id: .message(message.id, message.stableId, updatedVideoFile.fileId), fileReference: .message(message: MessageReference(message), media: updatedVideoFile), streamVideo: streamVideo ? .earlierStart : .none, enableSound: false, fetchAutomatically: false, onlyFullSizeThumbnail: (onlyFullSizeVideoThumbnail ?? false), continuePlayingWithoutSoundOnLostAudioSession: isInlinePlayableVideo, placeholderColor: emptyColor)
let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded)
videoNode.isUserInteractionEnabled = false
videoNode.ownsContentNodeUpdated = { [weak self] owns in
if let strongSelf = self {
strongSelf.videoNode?.isHidden = !owns
}
}
strongSelf.videoContent = videoContent
strongSelf.videoNode = videoNode
updatedVideoNodeReadySignal = videoNode.ready
@ -649,19 +656,21 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
}
if let updatedStatusSignal = updatedStatusSignal {
strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status, actualFetchStatus in
strongSelf.statusDisposable.set((updatedStatusSignal
|> deliverOnMainQueue).start(next: { [weak strongSelf] status, actualFetchStatus in
displayLinkDispatcher.dispatch {
if let strongSelf = strongSelf {
strongSelf.fetchStatus = status
strongSelf.actualFetchStatus = actualFetchStatus
strongSelf.updateFetchStatus()
strongSelf.updateStatus()
}
}
}))
}
if let updatedVideoNodeReadySignal = updatedVideoNodeReadySignal {
strongSelf.videoNodeReadyDisposable.set((updatedVideoNodeReadySignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
strongSelf.videoNodeReadyDisposable.set((updatedVideoNodeReadySignal
|> deliverOnMainQueue).start(next: { [weak strongSelf] status in
displayLinkDispatcher.dispatch {
if let strongSelf = strongSelf, let videoNode = strongSelf.videoNode {
strongSelf.insertSubnode(videoNode, aboveSubnode: strongSelf.imageNode)
@ -671,7 +680,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
}
if let updatedPlayerStatusSignal = updatedPlayerStatusSignal {
strongSelf.playerStatusDisposable.set((updatedPlayerStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
strongSelf.playerStatusDisposable.set((updatedPlayerStatusSignal
|> deliverOnMainQueue).start(next: { [weak strongSelf] status in
displayLinkDispatcher.dispatch {
if let strongSelf = strongSelf {
strongSelf.playerStatus = status
@ -731,7 +741,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
strongSelf.fetchControls.with({ $0 })?.fetch(false)
}
strongSelf.updateFetchStatus()
strongSelf.updateStatus()
}
})
})
@ -742,7 +752,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
private func ensureHasTimer() {
if self.playerUpdateTimer == nil {
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
self?.updateFetchStatus()
self?.updateStatus()
}, queue: Queue.mainQueue())
self.playerUpdateTimer = timer
timer.start()
@ -754,11 +764,18 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
self.playerUpdateTimer = nil
}
private func updateFetchStatus() {
private func updateStatus() {
guard let (theme, strings, decimalSeparator) = self.themeAndStrings, let sizeCalculation = self.sizeCalculation, let message = self.message, var automaticPlayback = self.automaticPlayback, let wideLayout = self.wideLayout else {
return
}
let automaticDownload: Bool
if let autoDownload = self.automaticDownload, case .full = autoDownload {
automaticDownload = true
} else {
automaticDownload = false
}
var secretBeginTimeAndTimeout: (Double?, Double)?
let isSecretMedia = message.containsSecretMedia
if isSecretMedia {
@ -814,10 +831,6 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
progressRequired = true
}
}
if message.flags.isSending && message.forwardInfo != nil {
progressRequired = false
}
}
let radialStatusSize: CGFloat = wideLayout ? 50.0 : 32.0
@ -893,7 +906,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
automaticPlayback = false
}
if let actualFetchStatus = self.actualFetchStatus, automaticPlayback {
if let actualFetchStatus = self.actualFetchStatus, automaticPlayback || message.forwardInfo != nil {
fetchStatus = actualFetchStatus
}
@ -913,9 +926,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
if let file = self.media as? TelegramMediaFile {
if wideLayout {
if let size = file.size {
if let duration = file.duration, !message.flags.contains(.Unsent) {
let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: decimalSeparator))"
if file.isAnimated && !automaticDownload {
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: "GIF " + sizeString, size: nil, muted: false, active: false)
}
else if let duration = file.duration, !message.flags.contains(.Unsent) {
let durationString = file.isAnimated ? "GIF" : stringForDuration(playerDuration > 0 ? playerDuration : duration, position: playerPosition)
let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: decimalSeparator))"
if isMediaStreamable(message: message, media: file) {
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: active ? sizeString : nil, muted: muted, active: active)
mediaDownloadState = .fetching(progress: automaticPlayback ? nil : adjustedProgress)
@ -1015,24 +1031,29 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
case .Remote:
state = .download(bubbleTheme.mediaOverlayControlForegroundColor)
if let file = self.media as? TelegramMediaFile {
let durationString = file.isAnimated ? "GIF" : stringForDuration(playerDuration > 0 ? playerDuration : (file.duration ?? 0), position: playerPosition)
if wideLayout {
if isMediaStreamable(message: message, media: file) {
state = automaticPlayback ? .none : .play(bubbleTheme.mediaOverlayControlForegroundColor)
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: dataSizeString(file.size ?? 0, decimalSeparator: decimalSeparator), muted: muted, active: true)
mediaDownloadState = .remote
} else {
state = automaticPlayback ? .none : state
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: nil, muted: muted, active: false)
}
if file.isAnimated && !automaticDownload {
let string = "GIF " + dataSizeString(file.size ?? 0, decimalSeparator: decimalSeparator)
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: string, size: nil, muted: false, active: false)
} else {
if isMediaStreamable(message: message, media: file) {
state = automaticPlayback ? .none : .play(bubbleTheme.mediaOverlayControlForegroundColor)
badgeContent = .text(inset: 12.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: durationString))
mediaDownloadState = .compactRemote
let durationString = file.isAnimated ? "GIF" : stringForDuration(playerDuration > 0 ? playerDuration : (file.duration ?? 0), position: playerPosition)
if wideLayout {
if isMediaStreamable(message: message, media: file) {
state = automaticPlayback ? .none : .play(bubbleTheme.mediaOverlayControlForegroundColor)
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: dataSizeString(file.size ?? 0, decimalSeparator: decimalSeparator), muted: muted, active: true)
mediaDownloadState = .remote
} else {
state = automaticPlayback ? .none : state
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: nil, muted: muted, active: false)
}
} else {
state = automaticPlayback ? .none : state
badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: durationString))
if isMediaStreamable(message: message, media: file) {
state = automaticPlayback ? .none : .play(bubbleTheme.mediaOverlayControlForegroundColor)
badgeContent = .text(inset: 12.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: durationString))
mediaDownloadState = .compactRemote
} else {
state = automaticPlayback ? .none : state
badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: durationString))
}
}
}
} else if let webpage = webpage, let automaticDownload = self.automaticDownload, case .full = automaticDownload, case let .Loaded(content) = webpage.content, content.type != "telegram_background" {
@ -1092,7 +1113,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
if isSecretMedia, secretBeginTimeAndTimeout?.0 != nil {
if self.secretTimer == nil {
self.secretTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: true, completion: { [weak self] in
self?.updateFetchStatus()
self?.updateStatus()
}, queue: Queue.mainQueue())
self.secretTimer?.start()
}

View File

@ -111,7 +111,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
}
if let context = self.context {
let mediaManager = context.sharedContext.mediaManager
let mediaPlayer = MediaPlayer(audioSessionManager: mediaManager.audioSession, postbox: context.account.postbox, resourceReference: .standalone(resource: recordedMediaPreview.resource), streamable: false, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true)
let mediaPlayer = MediaPlayer(audioSessionManager: mediaManager.audioSession, postbox: context.account.postbox, resourceReference: .standalone(resource: recordedMediaPreview.resource), streamable: .none, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true)
self.mediaPlayer = mediaPlayer
self.durationLabel.defaultDuration = Double(recordedMediaPreview.duration)
self.durationLabel.status = mediaPlayer.status

View File

@ -27,7 +27,7 @@ private let rootNavigationBar = PresentationThemeRootNavigationBar(
disabledButtonColor: UIColor(rgb: 0x525252),
primaryTextColor: accentColor,
secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5),
controlColor: accentColor,
controlColor: UIColor(rgb: 0x767677),
accentTextColor: accentColor,
backgroundColor: UIColor(rgb: 0x1c1c1d),
separatorColor: UIColor(rgb: 0x000000),

View File

@ -29,6 +29,19 @@ private enum SettingsSection: Int32 {
case logOut
}
public enum EditSettingsEntryTag: ItemListItemTag {
case bio
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? EditSettingsEntryTag, self == other {
return true
} else {
return false
}
}
}
private enum SettingsEntry: ItemListNodeEntry {
case userInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, CachedPeerData?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?)
case userInfoNotice(PresentationTheme, String)
@ -177,8 +190,7 @@ private enum SettingsEntry: ItemListNodeEntry {
case let .bioText(theme, currentText, placeholder):
return ItemListMultilineInputItem(theme: theme, text: currentText, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: 70, display: true), sectionId: self.section, style: .blocks, textUpdated: { updatedText in
arguments.updateBioText(currentText, updatedText)
}, action: {
}, tag: EditSettingsEntryTag.bio, action: {
})
case let .bioInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
@ -282,7 +294,7 @@ private func editSettingsEntries(presentationData: PresentationData, state: Edit
return entries
}
func editSettingsController(context: AccountContext, currentName: ItemListAvatarAndNameInfoItemName, currentBioText: String, accountManager: AccountManager, canAddAccounts: Bool) -> ViewController {
func editSettingsController(context: AccountContext, currentName: ItemListAvatarAndNameInfoItemName, currentBioText: String, accountManager: AccountManager, canAddAccounts: Bool, focusOnItemTag: EditSettingsEntryTag? = nil) -> ViewController {
let initialState = EditSettingsState(editingName: currentName, editingBioText: currentBioText)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
@ -399,7 +411,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.EditProfile_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(entries: editSettingsEntries(presentationData: presentationData, state: state, view: view, canAddAccounts: canAddAccounts), style: .blocks)
let listState = ItemListNodeState(entries: editSettingsEntries(presentationData: presentationData, state: state, view: view, canAddAccounts: canAddAccounts), style: .blocks, ensureVisibleItemTag: focusOnItemTag)
return (controllerState, (listState, arguments))
} |> afterDisposed {

View File

@ -71,6 +71,9 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource {
private let resourceReference: MediaResourceReference
private let tempFilePath: String?
private let streamable: Bool
private let stallDuration: Double
private let lowWaterDuration: Double
private let highWaterDuration: Double
private let video: Bool
private let preferSoftwareDecoding: Bool
private let fetchAutomatically: Bool
@ -95,7 +98,7 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource {
}
}
init(queue: Queue, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, fetchAutomatically: Bool, maximumFetchSize: Int? = nil) {
init(queue: Queue, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, fetchAutomatically: Bool, maximumFetchSize: Int? = nil, stallDuration: Double = 1.0, lowWaterDuration: Double = 2.0, highWaterDuration: Double = 3.0) {
self.queue = queue
self.postbox = postbox
self.resourceReference = resourceReference
@ -105,6 +108,9 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource {
self.preferSoftwareDecoding = preferSoftwareDecoding
self.fetchAutomatically = fetchAutomatically
self.maximumFetchSize = maximumFetchSize
self.stallDuration = stallDuration
self.lowWaterDuration = lowWaterDuration
self.highWaterDuration = highWaterDuration
self.taskQueue = ThreadTaskQueue()
@ -252,12 +258,12 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource {
var videoBuffer: MediaTrackFrameBuffer?
if let audio = streamDescriptions.audio {
audioBuffer = MediaTrackFrameBuffer(frameSource: strongSelf, decoder: audio.decoder, type: .audio, duration: audio.duration, rotationAngle: 0.0, aspect: 1.0)
audioBuffer = MediaTrackFrameBuffer(frameSource: strongSelf, decoder: audio.decoder, type: .audio, duration: audio.duration, rotationAngle: 0.0, aspect: 1.0, stallDuration: strongSelf.stallDuration, lowWaterDuration: strongSelf.lowWaterDuration, highWaterDuration: strongSelf.highWaterDuration)
}
var extraDecodedVideoFrames: [MediaTrackFrame] = []
if let video = streamDescriptions.video {
videoBuffer = MediaTrackFrameBuffer(frameSource: strongSelf, decoder: video.decoder, type: .video, duration: video.duration, rotationAngle: video.rotationAngle, aspect: video.aspect)
videoBuffer = MediaTrackFrameBuffer(frameSource: strongSelf, decoder: video.decoder, type: .video, duration: video.duration, rotationAngle: video.rotationAngle, aspect: video.aspect, stallDuration: strongSelf.stallDuration, lowWaterDuration: strongSelf.lowWaterDuration, highWaterDuration: strongSelf.highWaterDuration)
for videoFrame in streamDescriptions.extraVideoFrames {
if let decodedFrame = video.decoder.decode(frame: videoFrame) {
extraDecodedVideoFrames.append(decodedFrame)

View File

@ -5,7 +5,6 @@ import SwiftSignalKit
import TelegramCore
import Postbox
class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem {
let context: AccountContext
let theme: PresentationTheme
@ -15,10 +14,11 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem {
let wallpaper: TelegramWallpaper
let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder
let peerName: String
let linkEnabled: Bool
let tooltipText: String
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, linkEnabled: Bool, tooltipText: String) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, peerName: String, linkEnabled: Bool, tooltipText: String) {
self.context = context
self.theme = theme
self.strings = strings
@ -27,6 +27,7 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem {
self.wallpaper = wallpaper
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.peerName = peerName
self.linkEnabled = linkEnabled
self.tooltipText = tooltipText
}
@ -140,9 +141,9 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
var peers = SimpleDictionary<PeerId, Peer>()
let messages = SimpleDictionary<MessageId, Message>()
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: item.strings.Privacy_Forwards_PreviewForwardAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: item.peerName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.strings.Privacy_Forwards_PreviewForwardAuthor)
let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName)
let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: item.theme, wallpaper: item.wallpaper), fontSize: item.fontSize, strings: item.strings, dateTimeFormat: item.dateTimeFormat, nameDisplayOrder: item.nameDisplayOrder, disableAnimations: false)
@ -186,7 +187,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
fromString = String(from)
}
}
let authorString = item.strings.Privacy_Forwards_PreviewForwardAuthor
let authorString = item.peerName
if let fromString = fromString {
var attributedMeasureText = NSAttributedString(string: fromString, font: Font.regular(13.0), textColor: .black)

View File

@ -141,10 +141,10 @@ func galleryItemForEntry(context: AccountContext, presentationData: Presentation
if file.isVideo {
let content: UniversalVideoContent
if file.isAnimated {
content = NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: false, loopVideo: true, enableSound: false, tempFilePath: tempFilePath)
content = NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), loopVideo: true, enableSound: false, tempFilePath: tempFilePath)
} else {
if true || (file.mimeType == "video/mpeg4" || file.mimeType == "video/mov" || file.mimeType == "video/mp4") {
content = NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: true, loopVideo: loopVideos, tempFilePath: tempFilePath)
content = NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath)
} else {
content = PlatformVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), streamVideo: streamVideos, loopVideo: loopVideos)
}
@ -180,11 +180,11 @@ func galleryItemForEntry(context: AccountContext, presentationData: Presentation
var content: UniversalVideoContent?
switch websiteType(of: webpageContent) {
case .instagram where webpageContent.file != nil && webpageContent.image != nil && webpageContent.file!.isVideo:
content = NativeVideoContent(id: .message(message.id, message.stableId, webpageContent.file?.id ?? webpage.webpageId), fileReference: .message(message: MessageReference(message), media: webpageContent.file!), imageReference: webpageContent.image.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: true, enableSound: true)
content = NativeVideoContent(id: .message(message.id, message.stableId, webpageContent.file?.id ?? webpage.webpageId), fileReference: .message(message: MessageReference(message), media: webpageContent.file!), imageReference: webpageContent.image.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, enableSound: true)
default:
if let embedUrl = webpageContent.embedUrl, let image = webpageContent.image {
if let file = webpageContent.file, file.isVideo {
content = NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: true, loopVideo: loopVideos, tempFilePath: tempFilePath)
content = NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath)
} else if URL(string: embedUrl)?.pathExtension == "mp4" {
content = SystemVideoContent(url: embedUrl, imageReference: .webPage(webPage: WebpageReference(webpage), media: image), dimensions: webpageContent.embedSize ?? CGSize(width: 640.0, height: 640.0), duration: Int32(webpageContent.duration ?? 0))
}

View File

@ -1021,11 +1021,29 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
return nil
}
private func presentReferenceView(item: InstantPageTextItem) {
private func presentReferenceView(item: InstantPageTextItem, referenceAnchor: String) {
guard let theme = self.theme, let webPage = self.webPage else {
return
}
let controller = InstantPageReferenceController(context: self.context, theme: theme, webPage: webPage, item: item, openUrl: { [weak self] url in
var targetAnchor: InstantPageTextAnchorItem?
for (name, (line, _)) in item.anchors {
if name == referenceAnchor {
let anchors = item.lines[line].anchorItems
for anchor in anchors {
if anchor.name == referenceAnchor {
targetAnchor = anchor
break
}
}
}
}
guard let anchorText = targetAnchor?.anchorText else {
return
}
let controller = InstantPageReferenceController(context: self.context, theme: theme, webPage: webPage, anchorText: anchorText, openUrl: { [weak self] url in
self?.openUrl(url)
}, openUrlIn: { [weak self] url in
self?.openUrlIn(url)
@ -1043,7 +1061,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if !anchor.isEmpty {
if let (item, lineOffset, reference, detailsItems) = findAnchorItem(String(anchor), items: items) {
if let item = item as? InstantPageTextItem, reference {
self.presentReferenceView(item: item)
self.presentReferenceView(item: item, referenceAnchor: anchor)
} else {
var previousDetailsNode: InstantPageDetailsNode?
var containerOffset: CGFloat = 0.0

View File

@ -28,7 +28,7 @@ struct InstantPageGalleryEntry: Equatable {
return lhs.index == rhs.index && lhs.pageId == rhs.pageId && lhs.media == rhs.media && lhs.caption == rhs.caption && lhs.credit == rhs.credit && lhs.location == rhs.location
}
func item(context: AccountContext, webPage: TelegramMediaWebpage, message: Message?, presentationData: PresentationData, fromPlayingVideo: Bool, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlOptions: @escaping (InstantPageUrlItem) -> Void) -> GalleryItem {
func item(context: AccountContext, webPage: TelegramMediaWebpage, message: Message?, presentationData: PresentationData, fromPlayingVideo: Bool, landscape: Bool, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlOptions: @escaping (InstantPageUrlItem) -> Void) -> GalleryItem {
let caption: NSAttributedString
let credit: NSAttributedString
@ -91,10 +91,10 @@ struct InstantPageGalleryEntry: Equatable {
nativeId = .instantPage(self.pageId, file.fileId)
}
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: nativeId, fileReference: .webPage(webPage: WebpageReference(webPage), media: file), streamVideo: isMediaStreamable(media: file)), originData: nil, indexData: indexData, contentInfo: .webPage(webPage, file), caption: caption, credit: credit, fromPlayingVideo: fromPlayingVideo, performAction: { _ in }, openActionOptions: { _ in })
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: nativeId, fileReference: .webPage(webPage: WebpageReference(webPage), media: file), streamVideo: isMediaStreamable(media: file) ? .conservative : .none), originData: nil, indexData: indexData, contentInfo: .webPage(webPage, file), caption: caption, credit: credit, fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in })
} else if let embedWebpage = self.media.media as? TelegramMediaWebpage, case let .Loaded(webpageContent) = embedWebpage.content {
if let content = WebEmbedVideoContent(webPage: embedWebpage, webpageContent: webpageContent) {
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, performAction: { _ in }, openActionOptions: { _ in })
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in })
} else {
preconditionFailure()
}
@ -133,6 +133,7 @@ class InstantPageGalleryController: ViewController {
private var entries: [InstantPageGalleryEntry] = []
private var centralEntryIndex: Int?
private let fromPlayingVideo: Bool
private let landscape: Bool
private let centralItemTitle = Promise<String>()
private let centralItemTitleView = Promise<UIView?>()
@ -152,11 +153,12 @@ class InstantPageGalleryController: ViewController {
private var innerOpenUrl: (InstantPageUrlItem) -> Void
private var openUrlOptions: (InstantPageUrlItem) -> Void
init(context: AccountContext, webPage: TelegramMediaWebpage, message: Message? = nil, entries: [InstantPageGalleryEntry], centralIndex: Int, fromPlayingVideo: Bool = false, replaceRootController: @escaping (ViewController, ValuePromise<Bool>?) -> Void, baseNavigationController: NavigationController?) {
init(context: AccountContext, webPage: TelegramMediaWebpage, message: Message? = nil, entries: [InstantPageGalleryEntry], centralIndex: Int, fromPlayingVideo: Bool = false, landscape: Bool = false, replaceRootController: @escaping (ViewController, ValuePromise<Bool>?) -> Void, baseNavigationController: NavigationController?) {
self.context = context
self.webPage = webPage
self.message = message
self.fromPlayingVideo = fromPlayingVideo
self.landscape = landscape
self.replaceRootController = replaceRootController
self.baseNavigationController = baseNavigationController
@ -186,7 +188,7 @@ class InstantPageGalleryController: ViewController {
strongSelf.centralEntryIndex = centralIndex
if strongSelf.isViewLoaded {
strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({
$0.item(context: context, webPage: webPage, message: message, presentationData: strongSelf.presentationData, fromPlayingVideo: fromPlayingVideo, openUrl: strongSelf.innerOpenUrl, openUrlOptions: strongSelf.openUrlOptions)
$0.item(context: context, webPage: webPage, message: message, presentationData: strongSelf.presentationData, fromPlayingVideo: fromPlayingVideo, landscape: landscape, openUrl: strongSelf.innerOpenUrl, openUrlOptions: strongSelf.openUrlOptions)
}), centralItemIndex: centralIndex, keepFirst: false)
let ready = strongSelf.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak strongSelf] _ in
@ -325,7 +327,7 @@ class InstantPageGalleryController: ViewController {
}
self.galleryNode.pager.replaceItems(self.entries.map({
$0.item(context: self.context, webPage: self.webPage, message: self.message, presentationData: self.presentationData, fromPlayingVideo: self.fromPlayingVideo, openUrl: self.innerOpenUrl, openUrlOptions: self.openUrlOptions)
$0.item(context: self.context, webPage: self.webPage, message: self.message, presentationData: self.presentationData, fromPlayingVideo: self.fromPlayingVideo, landscape: self.landscape, openUrl: self.innerOpenUrl, openUrlOptions: self.openUrlOptions)
}), centralItemIndex: self.centralEntryIndex)
self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in

View File

@ -173,7 +173,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
let styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false)
let backgroundInset: CGFloat = 14.0
let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: 17.0, y: backgroundInset), media: media, webpage: webpage)
let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: 17.0, y: backgroundInset), media: media, webpage: webpage, opaqueBackground: true)
let backgroundItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height + backgroundInset * 2.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height + backgroundInset * 2.0)), shape: .rect, color: theme.codeBlockBackgroundColor)
var allItems: [InstantPageItem] = [backgroundItem]
allItems.append(contentsOf: items)
@ -751,7 +751,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false)
styleStack.push(.bold)
let backgroundInset: CGFloat = 14.0
let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: horizontalInset, y: backgroundInset), media: media, webpage: webpage)
let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: horizontalInset, y: backgroundInset), media: media, webpage: webpage, opaqueBackground: true)
let backgroundItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: textContentSize.height + backgroundInset * 2.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: textContentSize.height + backgroundInset * 2.0)), shape: .rect, color: theme.panelBackgroundColor)
items.append(backgroundItem)
items.append(contentsOf: textItems)

View File

@ -45,7 +45,7 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode {
streamVideo = isMediaStreamable(media: file)
}
self.videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), fileReference: .webPage(webPage: WebpageReference(webPage), media: media.media as! TelegramMediaFile), imageReference: imageReference, streamVideo: streamVideo, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor), priority: .embedded, autoplay: true)
self.videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), fileReference: .webPage(webPage: WebpageReference(webPage), media: media.media as! TelegramMediaFile), imageReference: imageReference, streamVideo: streamVideo ? .earlierStart : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor), priority: .embedded, autoplay: true)
self.videoNode.isUserInteractionEnabled = false
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6))

View File

@ -15,16 +15,16 @@ final class InstantPageReferenceController: ViewController {
private let context: AccountContext
private let theme: InstantPageTheme
private let webPage: TelegramMediaWebpage
private let item: InstantPageTextItem
private let anchorText: NSAttributedString
private let openUrl: (InstantPageUrlItem) -> Void
private let openUrlIn: (InstantPageUrlItem) -> Void
private let present: (ViewController, Any?) -> Void
init(context: AccountContext, theme: InstantPageTheme, webPage: TelegramMediaWebpage, item: InstantPageTextItem, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
init(context: AccountContext, theme: InstantPageTheme, webPage: TelegramMediaWebpage, anchorText: NSAttributedString, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context
self.theme = theme
self.webPage = webPage
self.item = item
self.anchorText = anchorText
self.openUrl = openUrl
self.openUrlIn = openUrlIn
self.present = present
@ -39,7 +39,7 @@ final class InstantPageReferenceController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = InstantPageReferenceControllerNode(context: self.context, theme: self.theme, webPage: self.webPage, item: self.item, openUrl: self.openUrl, openUrlIn: self.openUrlIn, present: self.present)
self.displayNode = InstantPageReferenceControllerNode(context: self.context, theme: self.theme, webPage: self.webPage, anchorText: self.anchorText, openUrl: self.openUrl, openUrlIn: self.openUrlIn, present: self.present)
self.controllerNode.dismiss = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}

View File

@ -10,7 +10,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
private let theme: InstantPageTheme
private var presentationData: PresentationData
private let webPage: TelegramMediaWebpage
private let item: InstantPageTextItem
private let anchorText: NSAttributedString
private let dimNode: ASDisplayNode
private let wrappingScrollNode: ASScrollNode
@ -32,12 +32,12 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
var dismiss: (() -> Void)?
var close: (() -> Void)?
init(context: AccountContext, theme: InstantPageTheme, webPage: TelegramMediaWebpage, item: InstantPageTextItem, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
init(context: AccountContext, theme: InstantPageTheme, webPage: TelegramMediaWebpage, anchorText: NSAttributedString, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.theme = theme
self.webPage = webPage
self.item = item
self.anchorText = anchorText
self.openUrl = openUrl
self.openUrlIn = openUrlIn
self.present = present
@ -195,7 +195,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
}
let sideInset: CGFloat = 16.0
let (_, items, contentSize) = layoutTextItemWithString(self.item.attributedString, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset), media: media, webpage: self.webPage)
let (_, items, contentSize) = layoutTextItemWithString(self.anchorText, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset), media: media, webpage: self.webPage)
let contentNode = InstantPageContentNode(context: self.context, strings: self.presentationData.strings, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in })
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height)))
self.contentContainerNode.insertSubnode(contentNode, at: 0)

View File

@ -35,6 +35,7 @@ struct InstantPageTextImageItem {
struct InstantPageTextAnchorItem {
let name: String
let anchorText: NSAttributedString?
let empty: Bool
}
@ -76,6 +77,7 @@ final class InstantPageTextItem: InstantPageItem {
let rtlLineIndices: Set<Int>
var frame: CGRect
let alignment: NSTextAlignment
let opaqueBackground: Bool
let medias: [InstantPageMedia] = []
let anchors: [String: (Int, Bool)]
let wantsNode: Bool = false
@ -86,10 +88,11 @@ final class InstantPageTextItem: InstantPageItem {
return !self.rtlLineIndices.isEmpty
}
init(frame: CGRect, attributedString: NSAttributedString, alignment: NSTextAlignment, lines: [InstantPageTextLine]) {
init(frame: CGRect, attributedString: NSAttributedString, alignment: NSTextAlignment, opaqueBackground: Bool, lines: [InstantPageTextLine]) {
self.attributedString = attributedString
self.alignment = alignment
self.frame = frame
self.opaqueBackground = opaqueBackground
self.lines = lines
var index = 0
var rtlLineIndices = Set<Int>()
@ -111,7 +114,7 @@ final class InstantPageTextItem: InstantPageItem {
context.saveGState()
context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0)
context.translateBy(x: self.frame.minX, y: self.frame.minY)
let clipRect = context.boundingBoxOfClipPath
let upperOriginBound = clipRect.minY - 10.0
@ -143,7 +146,13 @@ final class InstantPageTextItem: InstantPageItem {
context.restoreGState()
}
if self.opaqueBackground {
context.setBlendMode(.normal)
}
CTLineDraw(line.line, context)
if self.opaqueBackground {
context.setBlendMode(.copy)
}
if !line.strikethroughItems.isEmpty {
for item in line.strikethroughItems {
@ -494,7 +503,6 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
let descent: CGFloat
let width: CGFloat
}
var dimensions = dimensions
if let boundingWidth = boundingWidth {
dimensions = dimensions.fittedToWidthOrSmaller(boundingWidth)
@ -517,7 +525,6 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
let mutableAttributedString = attributedStringForRichText(.plain(" "), styleStack: styleStack, url: url).mutableCopy() as! NSMutableAttributedString
mutableAttributedString.addAttributes(attrDictionaryDelegate, range: NSMakeRange(0, mutableAttributedString.length))
return mutableAttributedString
//return NSAttributedString(string: " ", attributes: attrDictionaryDelegate)
case let .anchor(text, name):
var empty = false
var text = text
@ -525,14 +532,15 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
empty = true
text = .plain("\u{200b}")
}
styleStack.push(.anchor(name, empty))
let anchorText = !empty ? attributedStringForRichText(text, styleStack: styleStack, url: url) : nil
styleStack.push(.anchor(name, anchorText, empty))
let result = attributedStringForRichText(text, styleStack: styleStack, url: url)
styleStack.pop()
return result
}
}
func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0) -> (InstantPageTextItem?, [InstantPageItem], CGSize) {
func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0, opaqueBackground: Bool = false) -> (InstantPageTextItem?, [InstantPageItem], CGSize) {
if string.length == 0 {
return (nil, [], CGSize())
}
@ -694,7 +702,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
markedItems.append(InstantPageTextMarkedItem(frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y + delta, width: abs(upperX - lowerX), height: lineHeight), color: color))
}
if let item = attributes[NSAttributedStringKey.init(rawValue: InstantPageAnchorAttribute)] as? Dictionary<String, Any>, let name = item["name"] as? String, let empty = item["empty"] as? Bool {
anchorItems.append(InstantPageTextAnchorItem(name: name, empty: empty))
anchorItems.append(InstantPageTextAnchorItem(name: name, anchorText: item["text"] as? NSAttributedString, empty: empty))
}
}
@ -742,7 +750,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
requiresScroll = true
}
let textItem = InstantPageTextItem(frame: CGRect(x: 0.0, y: 0.0, width: textWidth, height: height), attributedString: string, alignment: alignment, lines: lines)
let textItem = InstantPageTextItem(frame: CGRect(x: 0.0, y: 0.0, width: textWidth, height: height), attributedString: string, alignment: alignment, opaqueBackground: opaqueBackground, lines: lines)
if !requiresScroll {
textItem.frame = textItem.frame.offsetBy(dx: offset.x, dy: offset.y)
}

View File

@ -16,7 +16,7 @@ enum InstantPageTextStyle {
case superscript
case markerColor(UIColor)
case marker
case anchor(String, Bool)
case anchor(String, NSAttributedString?, Bool)
case linkColor(UIColor)
case linkMarkerColor(UIColor)
case link(Bool)
@ -32,12 +32,12 @@ final class InstantPageTextStyleStack {
private var items: [InstantPageTextStyle] = []
func push(_ item: InstantPageTextStyle) {
items.append(item)
self.items.append(item)
}
func pop() {
if !items.isEmpty {
items.removeLast()
if !self.items.isEmpty {
self.items.removeLast()
}
}
@ -114,9 +114,13 @@ final class InstantPageTextStyleStack {
if marker == nil {
marker = true
}
case let .anchor(name, empty):
case let .anchor(name, anchorText, empty):
if anchor == nil {
anchor = ["name": name, "empty": empty]
if let anchorText = anchorText {
anchor = ["name": name, "text": anchorText, "empty": empty]
} else {
anchor = ["name": name, "empty": empty]
}
}
case let .linkColor(color):
if linkColor == nil {

View File

@ -9,19 +9,8 @@ final class InstantPageTile {
self.frame = frame
}
func getRandomColor() -> UIColor {
//Generate between 0 to 1
let red:CGFloat = CGFloat(drand48())
let green:CGFloat = CGFloat(drand48())
let blue:CGFloat = CGFloat(drand48())
return UIColor(red:red, green: green, blue: blue, alpha: 1.0)
}
func draw(context: CGContext) {
context.translateBy(x: -self.frame.minX, y: -self.frame.minY)
//context.setFillColor(getRandomColor().cgColor)
//context.fill(self.frame)
for item in self.items {
item.drawInTile(context: context)
}

View File

@ -12,7 +12,7 @@ func isMediaStreamable(message: Message, media: TelegramMediaFile) -> Bool {
guard let size = media.size else {
return false
}
if size < 1 * 1024 * 1024 {
if size < 256 * 1024 {
return false
}
for attribute in media.attributes {

View File

@ -480,7 +480,7 @@ class ItemListController<Entry: ItemListNodeEntry>: ViewController {
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments, !self.didPlayPresentationAnimation {
self.didPlayPresentationAnimation = true
if case .modalSheet = presentationArguments.presentationAnimation {
(self.displayNode as! ItemListControllerNode<Entry>).animateIn()
(self.displayNode as! ItemListControllerNode<Entry>).animateIn(completion: presentationArguments.completion)
}
}

View File

@ -304,8 +304,10 @@ class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UIScrollV
}
}
func animateIn() {
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
func animateIn(completion: (() -> Void)? = nil) {
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
completion?()
})
}
func animateOut(completion: (() -> Void)? = nil) {

View File

@ -23,12 +23,6 @@ final class MediaNavigationAccessoryContainerNode: ASDisplayNode, UIGestureRecog
self.addSubnode(self.backgroundNode)
self.addSubnode(self.headerNode)
self.headerNode.tapAction = { [weak self] in
if let strongSelf = self {
}
}
}
func updatePresentationData(_ presentationData: PresentationData) {

View File

@ -207,6 +207,18 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
self.actionPauseNode.image = PresentationResourcesRootController.navigationPlayerPauseIcon(self.theme)
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 {
case .x1:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: [])
case .x2:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
}
}
if let (size, leftInset, rightInset) = self.validLayout {
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
}
}
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {

View File

@ -60,6 +60,29 @@ enum MediaPlayerPlayOnceWithSoundSeek {
case automatic
}
enum MediaPlayerStreaming {
case none
case conservative
case earlierStart
var enabled: Bool {
if case .none = self {
return false
} else {
return true
}
}
var parameters: (Double, Double, Double) {
switch self {
case .none, .conservative:
return (1.0, 2.0, 3.0)
case .earlierStart:
return (0.5, 0.5, 1.0)
}
}
}
private final class MediaPlayerAudioRendererContext {
let renderer: MediaPlayerAudioRenderer
var requestedFrames = false
@ -76,7 +99,7 @@ private final class MediaPlayerContext {
private let postbox: Postbox
private let resourceReference: MediaResourceReference
private let tempFilePath: String?
private let streamable: Bool
private let streamable: MediaPlayerStreaming
private let video: Bool
private let preferSoftwareDecoding: Bool
private var enableSound: Bool
@ -102,7 +125,7 @@ private final class MediaPlayerContext {
private var stoppedAtEnd = false
init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: Promise<MediaPlayerStatus>, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, playAndRecord: Bool, keepAudioSessionWhilePaused: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool) {
init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: Promise<MediaPlayerStatus>, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: MediaPlayerStreaming, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, playAndRecord: Bool, keepAudioSessionWhilePaused: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool) {
assert(queue.isCurrent())
self.queue = queue
@ -135,12 +158,6 @@ private final class MediaPlayerContext {
case .paused:
if value {
strongSelf.play()
// if strongSelf.enableSound {
// strongSelf.play()
// //strongSelf.continuePlayingWithoutSound()
// } else {
// strongSelf.play()
// }
}
case .playing:
if !value {
@ -151,11 +168,6 @@ private final class MediaPlayerContext {
case .pause:
if value {
strongSelf.play()
// if strongSelf.enableSound {
// strongSelf.continuePlayingWithoutSound()
// } else {
// strongSelf.play()
// }
}
case .play:
if !value {
@ -280,7 +292,7 @@ private final class MediaPlayerContext {
self.playerStatus.set(.single(status))
}
let frameSource = FFMpegMediaFrameSource(queue: self.queue, postbox: self.postbox, resourceReference: self.resourceReference, tempFilePath: self.tempFilePath, streamable: self.streamable, video: self.video, preferSoftwareDecoding: self.preferSoftwareDecoding, fetchAutomatically: self.fetchAutomatically)
let frameSource = FFMpegMediaFrameSource(queue: self.queue, postbox: self.postbox, resourceReference: self.resourceReference, tempFilePath: self.tempFilePath, streamable: self.streamable.enabled, video: self.video, preferSoftwareDecoding: self.preferSoftwareDecoding, fetchAutomatically: self.fetchAutomatically, stallDuration: self.streamable.parameters.0, lowWaterDuration: self.streamable.parameters.1, highWaterDuration: self.streamable.parameters.2)
let disposable = MetaDisposable()
let updatedSeekState: MediaPlayerSeekState?
if let loadedDuration = loadedDuration {
@ -930,7 +942,7 @@ final class MediaPlayer {
}
}
init(audioSessionManager: ManagedAudioSession, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String? = nil, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool, baseRate: Double = 1.0, fetchAutomatically: Bool, playAndRecord: Bool = false, keepAudioSessionWhilePaused: Bool = true, continuePlayingWithoutSoundOnLostAudioSession: Bool = false) {
init(audioSessionManager: ManagedAudioSession, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String? = nil, streamable: MediaPlayerStreaming, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool, baseRate: Double = 1.0, fetchAutomatically: Bool, playAndRecord: Bool = false, keepAudioSessionWhilePaused: Bool = true, continuePlayingWithoutSoundOnLostAudioSession: Bool = false) {
self.queue.async {
let context = MediaPlayerContext(queue: self.queue, audioSessionManager: audioSessionManager, playerStatus: self.statusValue, postbox: postbox, resourceReference: resourceReference, tempFilePath: tempFilePath, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, playAutomatically: playAutomatically, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically, playAndRecord: playAndRecord, keepAudioSessionWhilePaused: keepAudioSessionWhilePaused, continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession)
self.contextRef = Unmanaged.passRetained(context)

View File

@ -19,9 +19,9 @@ enum MediaTrackFrameResult {
private let traceEvents = false
final class MediaTrackFrameBuffer {
private let stallDuration: Double = 1.0
private let lowWaterDuration: Double = 2.0
private let highWaterDuration: Double = 3.0
private let stallDuration: Double
private let lowWaterDuration: Double
private let highWaterDuration: Double
private let frameSource: MediaFrameSource
private let decoder: MediaTrackFrameDecoder
@ -38,13 +38,16 @@ final class MediaTrackFrameBuffer {
private var endOfStream = false
private var bufferedUntilTime: CMTime?
init(frameSource: MediaFrameSource, decoder: MediaTrackFrameDecoder, type: MediaTrackFrameType, duration: CMTime, rotationAngle: Double, aspect: Double) {
init(frameSource: MediaFrameSource, decoder: MediaTrackFrameDecoder, type: MediaTrackFrameType, duration: CMTime, rotationAngle: Double, aspect: Double, stallDuration: Double = 1.0, lowWaterDuration: Double = 2.0, highWaterDuration: Double = 3.0) {
self.frameSource = frameSource
self.type = type
self.decoder = decoder
self.duration = duration
self.rotationAngle = rotationAngle
self.aspect = aspect
self.stallDuration = stallDuration
self.lowWaterDuration = lowWaterDuration
self.highWaterDuration = highWaterDuration
self.frameSourceSinkIndex = self.frameSource.addEventSink { [weak self] event in
if let strongSelf = self {

View File

@ -52,7 +52,7 @@ final class NativeVideoContent: UniversalVideoContent {
let imageReference: ImageMediaReference?
let dimensions: CGSize
let duration: Int32
let streamVideo: Bool
let streamVideo: MediaPlayerStreaming
let loopVideo: Bool
let enableSound: Bool
let baseRate: Double
@ -62,7 +62,7 @@ final class NativeVideoContent: UniversalVideoContent {
let placeholderColor: UIColor
let tempFilePath: String?
init(id: NativeVideoContentId, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: Bool = false, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil) {
init(id: NativeVideoContentId, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: MediaPlayerStreaming = .none, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil) {
self.id = id
self.nativeId = id
self.fileReference = fileReference
@ -143,7 +143,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
private var validLayout: CGSize?
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?) {
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?) {
self.postbox = postbox
self.fileReference = fileReference
self.placeholderColor = placeholderColor

View File

@ -3,14 +3,22 @@ import SwiftSignalKit
import TelegramCore
import Display
func openAddContact(context: AccountContext, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!<Mobile>!$_", present: @escaping (ViewController, Any?) -> Void, completed: @escaping () -> Void = {}) {
func openAddContact(context: AccountContext, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!<Mobile>!$_", present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void = {}) {
let _ = (DeviceAccess.authorizationStatus(context: context, subject: .contacts)
|> take(1)
|> deliverOnMainQueue).start(next: { value in
switch value {
case .allowed:
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: label, value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [])
present(deviceContactInfoController(context: context, subject: .create(peer: nil, contactData: contactData, completion: { _, _,_ in }), completed: completed), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
present(deviceContactInfoController(context: context, subject: .create(peer: nil, contactData: contactData, completion: { peer, stableId, contactData in
if let peer = peer {
if let infoController = peerInfoController(context: context, peer: peer) {
pushController(infoController)
}
} else {
pushController(deviceContactInfoController(context: context, subject: .vcard(nil, stableId, contactData)))
}
}), completed: completed), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
case .notDetermined:
DeviceAccess.authorizeAccess(to: .contacts)
default:

View File

@ -93,7 +93,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
}
}
let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: fromPlayingVideo, replaceRootController: { [weak navigationController] controller, ready in
let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: fromPlayingVideo, landscape: landscape, replaceRootController: { [weak navigationController] controller, ready in
if let navigationController = navigationController {
navigationController.replaceTopController(controller, animated: false, ready: ready)
}

View File

@ -0,0 +1,54 @@
import Foundation
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
private let maximumNumberOfAccounts = 3
func openEditSettings(context: AccountContext, accountsAndPeers: Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError>, focusOnItemTag: EditSettingsEntryTag? = nil, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void) -> Disposable {
let openEditingDisposable = MetaDisposable()
var cancelImpl: (() -> Void)?
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: {
cancelImpl?()
}))
presentController(controller, nil)
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
let peerKey: PostboxViewKey = .peer(peerId: context.account.peerId, components: [])
let cachedDataKey: PostboxViewKey = .cachedPeerData(peerId: context.account.peerId)
let signal = (combineLatest(accountsAndPeers |> take(1), context.account.postbox.combinedView(keys: [peerKey, cachedDataKey]))
|> mapToSignal { accountsAndPeers, view -> Signal<(TelegramUser, CachedUserData, Bool), NoError> in
guard let cachedDataView = view.views[cachedDataKey] as? CachedPeerDataView, let cachedData = cachedDataView.cachedPeerData as? CachedUserData else {
return .complete()
}
guard let peerView = view.views[peerKey] as? PeerView, let peer = peerView.peers[context.account.peerId] as? TelegramUser else {
return .complete()
}
return .single((peer, cachedData, accountsAndPeers.1.count + 1 < maximumNumberOfAccounts))
}
|> take(1))
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
cancelImpl = {
openEditingDisposable.set(nil)
}
openEditingDisposable.set((signal
|> deliverOnMainQueue).start(next: { peer, cachedData, canAddAccounts in
pushController(editSettingsController(context: context, currentName: .personName(firstName: peer.firstName ?? "", lastName: peer.lastName ?? ""), currentBioText: cachedData.about ?? "", accountManager: context.sharedContext.accountManager, canAddAccounts: canAddAccounts, focusOnItemTag: focusOnItemTag))
}))
return openEditingDisposable
}

View File

@ -53,6 +53,9 @@ final class OverlayUniversalVideoNode: OverlayMediaItemNode {
}
closeImpl = { [weak self] in
if let strongSelf = self {
if strongSelf.videoNode.hasAttachedContext {
strongSelf.videoNode.continuePlayingWithoutSound()
}
strongSelf.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false, completion: { _ in
self?.dismiss()
close()

View File

@ -56,6 +56,8 @@ final class PaneSearchContainerNode: ASDisplayNode {
super.init()
self.clipsToBounds = true
self.addSubnode(self.backgroundNode)
self.addSubnode(self.contentNode)
self.addSubnode(self.searchBar)
@ -133,7 +135,7 @@ final class PaneSearchContainerNode: ASDisplayNode {
}
}
func animateOut(to placeholder: PaneSearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
func animateOut(to placeholder: PaneSearchBarPlaceholderNode, animateOutSearchBar: Bool, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
if case let .animated(duration, curve) = transition {
if let size = self.validLayout {
let placeholderFrame = placeholder.view.convert(placeholder.bounds, to: self.view)
@ -144,8 +146,10 @@ final class PaneSearchContainerNode: ASDisplayNode {
self.searchBar.transitionOut(to: placeholder, transition: transition, completion: {
completion()
})
transition.updateAlpha(node: self.backgroundNode, alpha: 0.0, completion: { _ in
})
transition.updateAlpha(node: self.backgroundNode, alpha: 0.0)
if animateOutSearchBar {
transition.updateAlpha(node: self.searchBar, alpha: 0.0)
}
self.contentNode.animateOut(transition: transition)
self.deactivate()
}

File diff suppressed because it is too large Load Diff

View File

@ -383,7 +383,7 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
return entries
}
public func privacyAndSecurityController(context: AccountContext) -> ViewController {
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil) -> ViewController {
let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: PrivacyAndSecurityControllerState())
let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in
@ -403,7 +403,7 @@ public func privacyAndSecurityController(context: AccountContext) -> ViewControl
actionsDisposable.add(updateAccountTimeoutDisposable)
let privacySettingsPromise = Promise<AccountPrivacySettings?>()
privacySettingsPromise.set(.single(nil) |> then(requestAccountPrivacySettings(account: context.account) |> map(Optional.init)))
privacySettingsPromise.set(.single(initialSettings) |> then(requestAccountPrivacySettings(account: context.account) |> map(Optional.init)))
let arguments = PrivacyAndSecurityControllerArguments(account: context.account, openBlockedUsers: {
pushControllerImpl?(blockedPeersController(context: context))
@ -615,6 +615,11 @@ public func privacyAndSecurityController(context: AccountContext) -> ViewControl
})
actionsDisposable.add(managedUpdatedRecentPeers(accountPeerId: context.account.peerId, postbox: context.account.postbox, network: context.account.network).start())
actionsDisposable.add((privacySettingsPromise.get()
|> deliverOnMainQueue).start(next: { settings in
updatedSettings?(settings)
}))
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), privacySettingsPromise.get(), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.secretChatLinkPreviewsKey()), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), recentPeers(account: context.account))
|> map { presentationData, state, privacySettings, noticeView, sharedData, recentPeers -> (ItemListControllerState, (ItemListNodeState<PrivacyAndSecurityEntry>, PrivacyAndSecurityEntry.ItemGenerationArguments)) in

View File

@ -143,7 +143,7 @@ public final class RadialStatusNode: ASControlNode {
super.init()
}
public func transitionToState(_ state: RadialStatusNodeState, animated: Bool = true, completion: @escaping () -> Void) {
public func transitionToState(_ state: RadialStatusNodeState, animated: Bool = true, completion: @escaping () -> Void = {}) {
if self.state != state {
let fromState = self.state
self.state = state

View File

@ -74,7 +74,7 @@ private func stringForUserCount(_ count: Int, strings: PresentationStrings) -> S
private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
case forwardsPreviewHeader(PresentationTheme, String)
case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Bool, String)
case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String)
case settingHeader(PresentationTheme, String)
case everybody(PresentationTheme, String, Bool)
case contacts(PresentationTheme, String, Bool)
@ -164,8 +164,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
} else {
return false
}
case let .forwardsPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsLinkEnabled, lhsTooltipText):
if case let .forwardsPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsLinkEnabled, rhsTooltipText) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsLinkEnabled == rhsLinkEnabled, lhsTooltipText == rhsTooltipText {
case let .forwardsPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat, lhsNameOrder, lhsPeerName, lhsLinkEnabled, lhsTooltipText):
if case let .forwardsPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat, rhsNameOrder, rhsPeerName, rhsLinkEnabled, rhsTooltipText) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat, lhsNameOrder == rhsNameOrder, lhsPeerName == rhsPeerName, lhsLinkEnabled == rhsLinkEnabled, lhsTooltipText == rhsTooltipText {
return true
} else {
return false
@ -289,8 +289,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
switch self {
case let .forwardsPreviewHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, multiline: true, sectionId: self.section)
case let .forwardsPreview(theme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, linkEnabled, tooltipText):
return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, linkEnabled: linkEnabled, tooltipText: tooltipText)
case let .forwardsPreview(theme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder, peerName, linkEnabled, tooltipText):
return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText)
case let .settingHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, multiline: true, sectionId: self.section)
case let .everybody(theme, text, value):
@ -448,7 +448,7 @@ private struct SelectivePrivacySettingsControllerState: Equatable {
}
}
private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState) -> [SelectivePrivacySettingsEntry] {
private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState, peerName: String) -> [SelectivePrivacySettingsEntry] {
var entries: [SelectivePrivacySettingsEntry] = []
let settingTitle: String
@ -498,7 +498,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
linkEnabled = false
}
entries.append(.forwardsPreviewHeader(presentationData.theme, presentationData.strings.Privacy_Forwards_Preview))
entries.append(.forwardsPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, linkEnabled, tootipText))
entries.append(.forwardsPreview(presentationData.theme, presentationData.chatWallpaper, presentationData.fontSize, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peerName, linkEnabled, tootipText))
}
entries.append(.settingHeader(presentationData.theme, settingTitle))
@ -693,8 +693,12 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
}).start()
})
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get()) |> deliverOnMainQueue
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<SelectivePrivacySettingsEntry>, SelectivePrivacySettingsEntry.ItemGenerationArguments)) in
let peerName = context.account.postbox.transaction { transaction -> String in
return (transaction.getPeer(context.account.peerId) as? TelegramUser)?.displayTitle ?? ""
}
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peerName) |> deliverOnMainQueue
|> map { presentationData, state, peerName -> (ItemListControllerState, (ItemListNodeState<SelectivePrivacySettingsEntry>, SelectivePrivacySettingsEntry.ItemGenerationArguments)) in
let title: String
switch kind {
@ -710,7 +714,7 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
title = presentationData.strings.Privacy_Forwards
}
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(entries: selectivePrivacySettingsControllerEntries(presentationData: presentationData, kind: kind, state: state), style: .blocks, animateChanges: false)
let listState = ItemListNodeState(entries: selectivePrivacySettingsControllerEntries(presentationData: presentationData, kind: kind, state: state, peerName: peerName), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments))
} |> afterDisposed {

View File

@ -556,6 +556,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var dismissInputImpl: (() -> Void)?
var setDisplayNavigationBarImpl: ((Bool) -> Void)?
var getNavigationControllerImpl: (() -> NavigationController?)?
@ -588,47 +589,10 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let archivedPacks = Promise<[ArchivedStickerPackItem]?>()
let contextValue = Promise<AccountContext>()
let networkArguments = context.account.networkArguments
let auxiliaryMethods = context.account.auxiliaryMethods
let rootPath = rootPathForBasePath(context.sharedContext.applicationBindings.containerPath)
let sharedContext = context.sharedContext
let accountsAndPeersSignal: Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError> = context.sharedContext.activeAccounts
|> mapToSignal { primary, activeAccounts, _ -> Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError> in
var accounts: [Signal<(Account, Peer, Int32)?, NoError>] = []
func accountWithPeer(_ account: Account) -> Signal<(Account, Peer, Int32)?, NoError> {
return combineLatest(account.postbox.peerView(id: account.peerId), renderedTotalUnreadCount(accountManager: sharedContext.accountManager, postbox: account.postbox))
|> map { view, totalUnreadCount -> (Peer?, Int32) in
return (view.peers[view.peerId], totalUnreadCount.0)
}
|> distinctUntilChanged { lhs, rhs in
return arePeersEqual(lhs.0, rhs.0) && lhs.1 == rhs.1
}
|> map { peer, totalUnreadCount -> (Account, Peer, Int32)? in
if let peer = peer {
return (account, peer, totalUnreadCount)
} else {
return nil
}
}
}
for (_, account, _) in activeAccounts {
accounts.append(accountWithPeer(account))
}
return combineLatest(accounts)
|> map { accounts -> ((Account, Peer)?, [(Account, Peer, Int32)]) in
var primaryRecord: (Account, Peer)?
if let first = accounts.filter({ $0?.0.id == primary?.id }).first, let (account, peer, _) = first {
primaryRecord = (account, peer)
}
return (primaryRecord, accounts.filter({ $0?.0.id != primary?.id }).compactMap({ $0 }))
}
}
let accountsAndPeers = Promise<((Account, Peer)?, [(Account, Peer, Int32)])>()
accountsAndPeers.set(accountsAndPeersSignal)
accountsAndPeers.set(activeAccountsAndPeers(context: context))
let privacySettings = Promise<[PeerId: AccountPrivacySettings]>([:])
let openFaq: (Promise<ResolvedUrl>) -> Void = { resolvedUrl in
let _ = (contextValue.get()
@ -717,7 +681,19 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let _ = (contextValue.get()
|> deliverOnMainQueue
|> take(1)).start(next: { context in
pushControllerImpl?(privacyAndSecurityController(context: context))
let _ = (privacySettings.get()
|> take(1)
|> deliverOnMainQueue).start(next: { settings in
pushControllerImpl?(privacyAndSecurityController(context: context, initialSettings: settings[context.account.peerId], updatedSettings: { settings in
let _ = ((privacySettings.get()
|> take(1)
|> deliverOnMainQueue).start(next: { currentPrivacySettings in
var updatedPrivacySettings = currentPrivacySettings
updatedPrivacySettings[context.account.peerId] = settings
privacySettings.set(.single(updatedPrivacySettings))
}))
}))
})
})
}, openDataAndStorage: {
let _ = (contextValue.get()
@ -798,48 +774,9 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let _ = (contextValue.get()
|> deliverOnMainQueue
|> take(1)).start(next: { context in
var cancelImpl: (() -> Void)?
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: {
cancelImpl?()
}))
presentControllerImpl?(controller, nil)
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
if let presentControllerImpl = presentControllerImpl, let pushControllerImpl = pushControllerImpl {
openEditingDisposable.set(openEditSettings(context: context, accountsAndPeers: accountsAndPeers.get(), presentController: presentControllerImpl, pushController: pushControllerImpl))
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
let peerKey: PostboxViewKey = .peer(peerId: context.account.peerId, components: [])
let cachedDataKey: PostboxViewKey = .cachedPeerData(peerId: context.account.peerId)
let signal = (combineLatest(accountsAndPeers.get() |> take(1), context.account.postbox.combinedView(keys: [peerKey, cachedDataKey]))
|> mapToSignal { accountsAndPeers, view -> Signal<(TelegramUser, CachedUserData, Bool), NoError> in
guard let cachedDataView = view.views[cachedDataKey] as? CachedPeerDataView, let cachedData = cachedDataView.cachedPeerData as? CachedUserData else {
return .complete()
}
guard let peerView = view.views[peerKey] as? PeerView, let peer = peerView.peers[context.account.peerId] as? TelegramUser else {
return .complete()
}
return .single((peer, cachedData, accountsAndPeers.1.count + 1 < maximumNumberOfAccounts))
}
|> take(1))
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
cancelImpl = {
openEditingDisposable.set(nil)
}
openEditingDisposable.set((signal
|> deliverOnMainQueue).start(next: { peer, cachedData, canAddAccounts in
pushControllerImpl?(editSettingsController(context: context, currentName: .personName(firstName: peer.firstName ?? "", lastName: peer.lastName ?? ""), currentBioText: cachedData.about ?? "", accountManager: accountManager, canAddAccounts: canAddAccounts))
}))
})
}, displayCopyContextMenu: {
let _ = (contextValue.get()
@ -1137,10 +1074,11 @@ public func settingsController(context: AccountContext, accountManager: AccountM
return state
}
}, presentController: { v, a in
dismissInputImpl?()
presentControllerImpl?(v, a)
}, pushController: { v in
pushControllerImpl?(v)
})
}, getNavigationController: getNavigationControllerImpl)
let (hasPassport, hasWatchApp) = hasPassportAndWatch
let listState = ItemListNodeState(entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up))
@ -1243,7 +1181,9 @@ public func settingsController(context: AccountContext, accountManager: AccountM
}
presentControllerImpl = { [weak controller] value, arguments in
controller?.present(value, in: .window(.root), with: arguments ?? ViewControllerPresentationArguments(presentationAnimation: .modalSheet), blockInteraction: true)
}
dismissInputImpl = { [weak controller] in
controller?.view.window?.endEditing(true)
}
getNavigationControllerImpl = { [weak controller] in
return (controller?.navigationController as? NavigationController)
@ -1285,7 +1225,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let _ = (contextValue.get()
|> take(1)
|> deliverOnMainQueue).start(next: { accountContext in
pushControllerImpl?(debugController(sharedContext: sharedContext, context: accountContext))
pushControllerImpl?(debugController(sharedContext: accountContext.sharedContext, context: accountContext))
})
}

View File

@ -19,32 +19,32 @@ extension NavigationBarSearchContentNode: ItemListControllerSearchNavigationCont
extension SettingsSearchableItemIcon {
func image() -> UIImage? {
switch self {
case .proxy:
return PresentationResourcesSettings.proxy
case .savedMessages:
return PresentationResourcesSettings.savedMessages
case .calls:
return PresentationResourcesSettings.recentCalls
case .stickers:
return PresentationResourcesSettings.stickers
case .notifications:
return PresentationResourcesSettings.notifications
case .privacy:
return PresentationResourcesSettings.security
case .data:
return PresentationResourcesSettings.dataAndStorage
case .appearance:
return PresentationResourcesSettings.appearance
case .language:
return PresentationResourcesSettings.language
case .watch:
return PresentationResourcesSettings.watch
case .passport:
return PresentationResourcesSettings.passport
case .support:
return PresentationResourcesSettings.support
case .faq:
return PresentationResourcesSettings.faq
case .proxy:
return PresentationResourcesSettings.proxy
case .savedMessages:
return PresentationResourcesSettings.savedMessages
case .calls:
return PresentationResourcesSettings.recentCalls
case .stickers:
return PresentationResourcesSettings.stickers
case .notifications:
return PresentationResourcesSettings.notifications
case .privacy:
return PresentationResourcesSettings.security
case .data:
return PresentationResourcesSettings.dataAndStorage
case .appearance:
return PresentationResourcesSettings.appearance
case .language:
return PresentationResourcesSettings.language
case .watch:
return PresentationResourcesSettings.watch
case .passport:
return PresentationResourcesSettings.passport
case .support:
return PresentationResourcesSettings.support
case .faq:
return PresentationResourcesSettings.faq
}
}
}
@ -57,12 +57,13 @@ final class SettingsSearchItem: ItemListControllerSearch {
let updateActivated: (Bool) -> Void
let presentController: (ViewController, Any?) -> Void
let pushController: (ViewController) -> Void
let getNavigationController: (() -> NavigationController?)?
private var updateActivity: ((Bool) -> Void)?
private var activity: ValuePromise<Bool> = ValuePromise(ignoreRepeated: false)
private let activityDisposable = MetaDisposable()
init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void) {
init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?) {
self.context = context
self.theme = theme
self.placeholder = placeholder
@ -70,6 +71,7 @@ final class SettingsSearchItem: ItemListControllerSearch {
self.updateActivated = updateActivated
self.presentController = presentController
self.pushController = pushController
self.getNavigationController = getNavigationController
self.activityDisposable.set((activity.get() |> mapToSignal { value -> Signal<Bool, NoError> in
if value {
return .single(value) |> delay(0.2, queue: Queue.mainQueue())
@ -133,7 +135,7 @@ final class SettingsSearchItem: ItemListControllerSearch {
pushController(c)
}, presentController: { c, a in
presentController(c, a)
})
}, getNavigationController: self.getNavigationController)
}
}
}
@ -401,15 +403,16 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode {
let pushController: (ViewController) -> Void
let presentController: (ViewController, Any?) -> Void
let getNavigationController: (() -> NavigationController?)?
var cancel: () -> Void
init(context: AccountContext, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, Any?) -> Void) {
init(context: AccountContext, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: (() -> NavigationController?)?) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.pushController = pushController
self.presentController = presentController
self.getNavigationController = getNavigationController
self.cancel = cancel
super.init()
@ -417,6 +420,7 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode {
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.searchDisplayController?.updatePresentationData(presentationData)
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
@ -426,13 +430,15 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode {
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: SettingsSearchContainerNode(context: self.context, listState: LocalizationListState.defaultSettings, openResult: { [weak self] result in
if let strongSelf = self {
result.present(strongSelf.context, { [weak self] mode, controller in
result.present(strongSelf.context, strongSelf.getNavigationController?(), { [weak self] mode, controller in
if let strongSelf = self {
switch mode {
case .push:
strongSelf.pushController(controller)
case .modal:
strongSelf.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
strongSelf.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet, completion: { [weak self] in
self?.cancel()
}))
case .immediate:
strongSelf.presentController(controller, nil)
}

View File

@ -4,6 +4,8 @@ import SwiftSignalKit
import Postbox
import TelegramCore
private let maximumNumberOfAccounts = 3
enum SettingsSearchableItemIcon {
case proxy
case savedMessages
@ -21,6 +23,7 @@ enum SettingsSearchableItemIcon {
}
enum SettingsSearchableItemId: Hashable {
case profile(Int)
case proxy(Int)
case savedMessages(Int)
case calls(Int)
@ -48,7 +51,57 @@ struct SettingsSearchableItem {
let alternate: [String]
let icon: SettingsSearchableItemIcon
let breadcrumbs: [String]
let present: (AccountContext, @escaping (SettingsSearchableItemPresentation, ViewController) -> Void) -> Void
let present: (AccountContext, NavigationController?, @escaping (SettingsSearchableItemPresentation, ViewController) -> Void) -> Void
}
private func profileSearchableItems(context: AccountContext, canAddAccount: Bool) -> [SettingsSearchableItem] {
let icon: SettingsSearchableItemIcon = .calls
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
let presentProfileSettings: (AccountContext, @escaping (SettingsSearchableItemPresentation, ViewController) -> Void, EditSettingsEntryTag?) -> Void = { context, present, itemTag in
let _ = openEditSettings(context: context, accountsAndPeers: activeAccountsAndPeers(context: context), focusOnItemTag: itemTag, presentController: { controller, _ in
present(.immediate, controller)
}, pushController: { controller in
present(.push, controller)
})
}
var items: [SettingsSearchableItem] = []
items.append(SettingsSearchableItem(id: .profile(0), title: strings.EditProfile_Title, alternate: [], icon: icon, breadcrumbs: [], present: { context, _, present in
presentProfileSettings(context, present, nil)
}))
items.append(SettingsSearchableItem(id: .profile(1), title: strings.UserInfo_About_Placeholder, alternate: [], icon: icon, breadcrumbs: [strings.EditProfile_Title], present: { context, _, present in
presentProfileSettings(context, present, .bio)
}))
items.append(SettingsSearchableItem(id: .profile(2), title: strings.Settings_PhoneNumber, alternate: [], icon: icon, breadcrumbs: [strings.EditProfile_Title], present: { context, _, present in
let _ = (context.account.postbox.transaction { transaction -> String in
return (transaction.getPeer(context.account.peerId) as? TelegramUser)?.phone ?? ""
}
|> deliverOnMainQueue).start(next: { phoneNumber in
present(.push, ChangePhoneNumberIntroController(context: context, phoneNumber: formatPhoneNumber(phoneNumber)))
})
}))
items.append(SettingsSearchableItem(id: .profile(3), title: strings.Settings_Username, alternate: [], icon: icon, breadcrumbs: [strings.EditProfile_Title], present: { context, _, present in
present(.modal, usernameSetupController(context: context))
}))
if canAddAccount {
items.append(SettingsSearchableItem(id: .profile(4), title: strings.Settings_AddAccount, alternate: [], icon: icon, breadcrumbs: [strings.EditProfile_Title], present: { context, _, present in
let isTestingEnvironment = context.account.testingEnvironment
context.sharedContext.beginNewAuth(testingEnvironment: isTestingEnvironment)
}))
}
items.append(SettingsSearchableItem(id: .profile(5), title: strings.Settings_Logout, alternate: [], icon: icon, breadcrumbs: [strings.EditProfile_Title], present: { context, navigationController, present in
let _ = (context.account.postbox.transaction { transaction -> String in
return (transaction.getPeer(context.account.peerId) as? TelegramUser)?.phone ?? ""
}
|> deliverOnMainQueue).start(next: { phoneNumber in
if let navigationController = navigationController {
present(.modal, logoutOptionsController(context: context, navigationController: navigationController, canAddAccounts: canAddAccount, phoneNumber: phoneNumber))
}
})
}))
return items
}
private func callSearchableItems(context: AccountContext) -> [SettingsSearchableItem] {
@ -60,10 +113,10 @@ private func callSearchableItems(context: AccountContext) -> [SettingsSearchable
}
return [
SettingsSearchableItem(id: .calls(0), title: strings.CallSettings_RecentCalls, alternate: [], icon: icon, breadcrumbs: [], present: { context, present in
SettingsSearchableItem(id: .calls(0), title: strings.CallSettings_RecentCalls, alternate: [], icon: icon, breadcrumbs: [], present: { context, _, present in
presentCallSettings(context, present)
}),
SettingsSearchableItem(id: .calls(1), title: strings.CallSettings_TabIcon, alternate: [], icon: icon, breadcrumbs: [strings.CallSettings_RecentCalls], present: { context, present in
SettingsSearchableItem(id: .calls(1), title: strings.CallSettings_TabIcon, alternate: [], icon: icon, breadcrumbs: [strings.CallSettings_RecentCalls], present: { context, _, present in
presentCallSettings(context, present)
})
]
@ -79,23 +132,23 @@ private func stickerSearchableItems(context: AccountContext) -> [SettingsSearcha
}
return [
SettingsSearchableItem(id: .stickers(0), title: strings.ChatSettings_Stickers, alternate: [], icon: icon, breadcrumbs: [], present: { context, present in
SettingsSearchableItem(id: .stickers(0), title: strings.ChatSettings_Stickers, alternate: [], icon: icon, breadcrumbs: [], present: { context, _, present in
presentStickerSettings(context, present, nil)
}),
SettingsSearchableItem(id: .stickers(1), title: strings.Stickers_SuggestStickers, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers], present: { context, present in
SettingsSearchableItem(id: .stickers(1), title: strings.Stickers_SuggestStickers, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers], present: { context, _, present in
presentStickerSettings(context, present, .suggestOptions)
}),
SettingsSearchableItem(id: .stickers(2), title: strings.StickerPacksSettings_FeaturedPacks, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers], present: { context, present in
SettingsSearchableItem(id: .stickers(2), title: strings.StickerPacksSettings_FeaturedPacks, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers], present: { context, _, present in
present(.push, featuredStickerPacksController(context: context))
}),
SettingsSearchableItem(id: .stickers(3), title: strings.StickerPacksSettings_ArchivedPacks, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers], present: { context, present in
SettingsSearchableItem(id: .stickers(3), title: strings.StickerPacksSettings_ArchivedPacks, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers], present: { context, _, present in
present(.push, archivedStickerPacksController(context: context, mode: .stickers, archived: nil, updatedPacks: { _ in
}))
}),
SettingsSearchableItem(id: .stickers(4), title: strings.MaskStickerSettings_Title, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers], present: { context, present in
SettingsSearchableItem(id: .stickers(4), title: strings.MaskStickerSettings_Title, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers], present: { context, _, present in
present(.push, installedStickerPacksController(context: context, mode: .masks, archivedPacks: nil, updatedPacks: { _ in}))
}),
SettingsSearchableItem(id: .stickers(5), title: strings.StickerPacksSettings_ArchivedMasks, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers, strings.MaskStickerSettings_Title], present: { context, present in
SettingsSearchableItem(id: .stickers(5), title: strings.StickerPacksSettings_ArchivedMasks, alternate: [], icon: icon, breadcrumbs: [strings.ChatSettings_Stickers, strings.MaskStickerSettings_Title], present: { context, _, present in
present(.push, archivedStickerPacksController(context: context, mode: .masks, archived: nil, updatedPacks: { _ in
}))
})
@ -111,64 +164,64 @@ private func notificationSearchableItems(context: AccountContext, notifyExceptio
}
return [
SettingsSearchableItem(id: .notifications(0), title: strings.Settings_NotificationsAndSounds, alternate: [], icon: icon, breadcrumbs: [], present: { context, present in
SettingsSearchableItem(id: .notifications(0), title: strings.Settings_NotificationsAndSounds, alternate: [], icon: icon, breadcrumbs: [], present: { context, _, present in
presentNotificationSettings(context, present, nil)
}),
SettingsSearchableItem(id: .notifications(1), title: strings.Notifications_MessageNotificationsAlert, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(1), title: strings.Notifications_MessageNotificationsAlert, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .messageAlerts)
}),
SettingsSearchableItem(id: .notifications(2), title: strings.Notifications_MessageNotificationsPreview, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(2), title: strings.Notifications_MessageNotificationsPreview, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .messagePreviews)
}),
SettingsSearchableItem(id: .notifications(3), title: strings.Notifications_MessageNotificationsSound, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(3), title: strings.Notifications_MessageNotificationsSound, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, nil)
}),
SettingsSearchableItem(id: .notifications(4), title: strings.Notifications_GroupNotificationsAlert, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(4), title: strings.Notifications_GroupNotificationsAlert, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .groupAlerts)
}),
SettingsSearchableItem(id: .notifications(5), title: strings.Notifications_GroupNotificationsPreview, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(5), title: strings.Notifications_GroupNotificationsPreview, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .groupPreviews)
}),
SettingsSearchableItem(id: .notifications(6), title: strings.Notifications_GroupNotificationsSound, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(6), title: strings.Notifications_GroupNotificationsSound, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, nil)
}),
SettingsSearchableItem(id: .notifications(7), title: strings.Notifications_ChannelNotificationsAlert, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(7), title: strings.Notifications_ChannelNotificationsAlert, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .channelAlerts)
}),
SettingsSearchableItem(id: .notifications(8), title: strings.Notifications_ChannelNotificationsPreview, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(8), title: strings.Notifications_ChannelNotificationsPreview, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .channelPreviews)
}),
SettingsSearchableItem(id: .notifications(9), title: strings.Notifications_ChannelNotificationsSound, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(9), title: strings.Notifications_ChannelNotificationsSound, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, nil)
}),
SettingsSearchableItem(id: .notifications(10), title: strings.Notifications_InAppNotificationsSounds, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_InAppNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(10), title: strings.Notifications_InAppNotificationsSounds, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_InAppNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .inAppSounds)
}),
SettingsSearchableItem(id: .notifications(11), title: strings.Notifications_InAppNotificationsVibrate, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_InAppNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(11), title: strings.Notifications_InAppNotificationsVibrate, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_InAppNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .inAppVibrate)
}),
SettingsSearchableItem(id: .notifications(12), title: strings.Notifications_InAppNotificationsPreview, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_InAppNotifications.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(12), title: strings.Notifications_InAppNotificationsPreview, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_InAppNotifications.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .inAppPreviews)
}),
SettingsSearchableItem(id: .notifications(13), title: strings.Notifications_DisplayNamesOnLockScreen, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds], present: { context, present in
SettingsSearchableItem(id: .notifications(13), title: strings.Notifications_DisplayNamesOnLockScreen, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds], present: { context, _, present in
presentNotificationSettings(context, present, .displayNamesOnLockscreen)
}),
SettingsSearchableItem(id: .notifications(14), title: strings.Notifications_Badge_IncludeMutedChats, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(14), title: strings.Notifications_Badge_IncludeMutedChats, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .unreadCountStyle)
}),
SettingsSearchableItem(id: .notifications(15), title: strings.Notifications_Badge_IncludePublicGroups, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(15), title: strings.Notifications_Badge_IncludePublicGroups, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .includePublicGroups)
}),
SettingsSearchableItem(id: .notifications(16), title: strings.Notifications_Badge_IncludeChannels, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(16), title: strings.Notifications_Badge_IncludeChannels, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .includeChannels)
}),
SettingsSearchableItem(id: .notifications(17), title: strings.Notifications_Badge_CountUnreadMessages, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge.capitalized], present: { context, present in
SettingsSearchableItem(id: .notifications(17), title: strings.Notifications_Badge_CountUnreadMessages, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge.capitalized], present: { context, _, present in
presentNotificationSettings(context, present, .unreadCountCategory)
}),
SettingsSearchableItem(id: .notifications(18), title: strings.NotificationSettings_ContactJoined, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds], present: { context, present in
SettingsSearchableItem(id: .notifications(18), title: strings.NotificationSettings_ContactJoined, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds], present: { context, _, present in
presentNotificationSettings(context, present, .joinedNotifications)
}),
SettingsSearchableItem(id: .notifications(19), title: strings.Notifications_ResetAllNotifications, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds], present: { context, present in
SettingsSearchableItem(id: .notifications(19), title: strings.Notifications_ResetAllNotifications, alternate: [], icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds], present: { context, _, present in
presentNotificationSettings(context, present, .reset)
})
]
@ -240,28 +293,28 @@ private func privacySearchableItems(context: AccountContext) -> [SettingsSearcha
}
return [
SettingsSearchableItem(id: .privacy(0), title: strings.Settings_PrivacySettings, alternate: [], icon: icon, breadcrumbs: [], present: { context, present in
SettingsSearchableItem(id: .privacy(0), title: strings.Settings_PrivacySettings, alternate: [], icon: icon, breadcrumbs: [], present: { context, _, present in
presentPrivacySettings(context, present)
}),
SettingsSearchableItem(id: .privacy(1), title: strings.Settings_BlockedUsers, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(1), title: strings.Settings_BlockedUsers, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
present(.push, blockedPeersController(context: context))
}),
SettingsSearchableItem(id: .privacy(2), title: strings.PrivacySettings_LastSeen, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(2), title: strings.PrivacySettings_LastSeen, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
presentSelectivePrivacySettings(context, .presence, present)
}),
SettingsSearchableItem(id: .privacy(3), title: strings.Privacy_ProfilePhoto, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(3), title: strings.Privacy_ProfilePhoto, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
presentSelectivePrivacySettings(context, .profilePhoto, present)
}),
SettingsSearchableItem(id: .privacy(4), title: strings.Privacy_Forwards, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(4), title: strings.Privacy_Forwards, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
presentSelectivePrivacySettings(context, .forwards, present)
}),
SettingsSearchableItem(id: .privacy(5), title: strings.Privacy_Calls, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(5), title: strings.Privacy_Calls, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
presentSelectivePrivacySettings(context, .voiceCalls, present)
}),
SettingsSearchableItem(id: .privacy(6), title: strings.Privacy_GroupsAndChannels, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(6), title: strings.Privacy_GroupsAndChannels, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
presentSelectivePrivacySettings(context, .groupInvitations, present)
}),
SettingsSearchableItem(id: .privacy(7), title: passcodeTitle, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(7), title: passcodeTitle, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
let _ = passcodeOptionsAccessController(context: context, completion: { animated in
let controller = passcodeOptionsController(context: context)
if animated {
@ -275,35 +328,35 @@ private func privacySearchableItems(context: AccountContext) -> [SettingsSearcha
}
})
}),
SettingsSearchableItem(id: .privacy(8), title: strings.PrivacySettings_TwoStepAuth, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(8), title: strings.PrivacySettings_TwoStepAuth, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
present(.modal, twoStepVerificationUnlockSettingsController(context: context, mode: .access))
}),
SettingsSearchableItem(id: .privacy(9), title: strings.PrivacySettings_AuthSessions, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(9), title: strings.PrivacySettings_AuthSessions, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
present(.push, recentSessionsController(context: context))
}),
SettingsSearchableItem(id: .privacy(10), title: strings.PrivacySettings_DeleteAccountTitle.capitalized, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(10), title: strings.PrivacySettings_DeleteAccountTitle.capitalized, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
presentPrivacySettings(context, present)
}),
SettingsSearchableItem(id: .privacy(11), title: strings.PrivacySettings_DataSettings, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, present in
SettingsSearchableItem(id: .privacy(11), title: strings.PrivacySettings_DataSettings, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
presentDataPrivacySettings(context, present)
}),
SettingsSearchableItem(id: .privacy(12), title: strings.Privacy_ContactsReset, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, present in
SettingsSearchableItem(id: .privacy(12), title: strings.Privacy_ContactsReset, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, _, present in
presentDataPrivacySettings(context, present)
}),
SettingsSearchableItem(id: .privacy(13), title: strings.Privacy_ContactsSync, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, present in
SettingsSearchableItem(id: .privacy(13), title: strings.Privacy_ContactsSync, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, _, present in
presentDataPrivacySettings(context, present)
}),
SettingsSearchableItem(id: .privacy(14), title: strings.Privacy_TopPeers, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, present in
SettingsSearchableItem(id: .privacy(14), title: strings.Privacy_TopPeers, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, _, present in
presentDataPrivacySettings(context, present)
}),
SettingsSearchableItem(id: .privacy(15), title: strings.Privacy_DeleteDrafts, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, present in
SettingsSearchableItem(id: .privacy(15), title: strings.Privacy_DeleteDrafts, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, _, present in
presentDataPrivacySettings(context, present)
}),
SettingsSearchableItem(id: .privacy(16), title: strings.Privacy_PaymentsClearInfo, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, present in
SettingsSearchableItem(id: .privacy(16), title: strings.Privacy_PaymentsClearInfo, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings], present: { context, _, present in
presentDataPrivacySettings(context, present)
}),
SettingsSearchableItem(id: .privacy(17), title: strings.Privacy_SecretChatsLinkPreviews, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings, strings.Privacy_SecretChatsTitle], present: { context, present in
SettingsSearchableItem(id: .privacy(17), title: strings.Privacy_SecretChatsLinkPreviews, alternate: [], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings, strings.PrivacySettings_DataSettings, strings.Privacy_SecretChatsTitle], present: { context, _, present in
presentDataPrivacySettings(context, present)
})
]
@ -318,46 +371,46 @@ private func dataSearchableItems(context: AccountContext) -> [SettingsSearchable
}
return [
SettingsSearchableItem(id: .data(0), title: strings.Settings_ChatSettings, alternate: [], icon: icon, breadcrumbs: [], present: { context, present in
SettingsSearchableItem(id: .data(0), title: strings.Settings_ChatSettings, alternate: [], icon: icon, breadcrumbs: [], present: { context, _, present in
presentDataSettings(context, present, nil)
}),
SettingsSearchableItem(id: .data(1), title: strings.ChatSettings_Cache, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, present in
SettingsSearchableItem(id: .data(1), title: strings.ChatSettings_Cache, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, _, present in
present(.push, storageUsageController(context: context))
}),
SettingsSearchableItem(id: .data(2), title: strings.Cache_KeepMedia, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_Cache], present: { context, present in
SettingsSearchableItem(id: .data(2), title: strings.Cache_KeepMedia, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_Cache], present: { context, _, present in
present(.push, storageUsageController(context: context))
}),
SettingsSearchableItem(id: .data(3), title: strings.Cache_ClearCache, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_Cache], present: { context, present in
SettingsSearchableItem(id: .data(3), title: strings.Cache_ClearCache, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_Cache], present: { context, _, present in
present(.push, storageUsageController(context: context))
}),
SettingsSearchableItem(id: .data(4), title: strings.NetworkUsageSettings_Title, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, present in
SettingsSearchableItem(id: .data(4), title: strings.NetworkUsageSettings_Title, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, _, present in
present(.push, networkUsageStatsController(context: context))
}),
SettingsSearchableItem(id: .data(5), title: strings.ChatSettings_AutoDownloadUsingCellular, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_AutoDownloadTitle.capitalized], present: { context, present in
SettingsSearchableItem(id: .data(5), title: strings.ChatSettings_AutoDownloadUsingCellular, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_AutoDownloadTitle.capitalized], present: { context, _, present in
present(.push, autodownloadMediaConnectionTypeController(context: context, connectionType: .cellular))
}),
SettingsSearchableItem(id: .data(6), title: strings.ChatSettings_AutoDownloadUsingWiFi, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_AutoDownloadTitle.capitalized], present: { context, present in
SettingsSearchableItem(id: .data(6), title: strings.ChatSettings_AutoDownloadUsingWiFi, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_AutoDownloadTitle.capitalized], present: { context, _, present in
present(.push, autodownloadMediaConnectionTypeController(context: context, connectionType: .wifi))
}),
SettingsSearchableItem(id: .data(7), title: strings.ChatSettings_AutoDownloadReset, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, present in
SettingsSearchableItem(id: .data(7), title: strings.ChatSettings_AutoDownloadReset, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, _, present in
presentDataSettings(context, present, .automaticDownloadReset)
}),
SettingsSearchableItem(id: .data(8), title: strings.ChatSettings_AutoPlayGifs, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_AutoPlayTitle], present: { context, present in
SettingsSearchableItem(id: .data(8), title: strings.ChatSettings_AutoPlayGifs, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_AutoPlayTitle], present: { context, _, present in
presentDataSettings(context, present, .autoplayGifs)
}),
SettingsSearchableItem(id: .data(9), title: strings.ChatSettings_AutoPlayVideos, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_AutoPlayTitle], present: { context, present in
SettingsSearchableItem(id: .data(9), title: strings.ChatSettings_AutoPlayVideos, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.ChatSettings_AutoPlayTitle], present: { context, _, present in
presentDataSettings(context, present, .autoplayVideos)
}),
SettingsSearchableItem(id: .data(10), title: strings.CallSettings_UseLessData, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.Settings_CallSettings], present: { context, present in
SettingsSearchableItem(id: .data(10), title: strings.CallSettings_UseLessData, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings, strings.Settings_CallSettings], present: { context, _, present in
present(.push, voiceCallDataSavingController(context: context))
}),
SettingsSearchableItem(id: .data(11), title: strings.Settings_SaveIncomingPhotos, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, present in
SettingsSearchableItem(id: .data(11), title: strings.Settings_SaveIncomingPhotos, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, _, present in
present(.push, saveIncomingMediaController(context: context))
}),
SettingsSearchableItem(id: .data(12), title: strings.Settings_SaveEditedPhotos, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, present in
SettingsSearchableItem(id: .data(12), title: strings.Settings_SaveEditedPhotos, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, _, present in
presentDataSettings(context, present, .saveEditedPhotos)
}),
SettingsSearchableItem(id: .data(13), title: strings.ChatSettings_DownloadInBackground, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, present in
SettingsSearchableItem(id: .data(13), title: strings.ChatSettings_DownloadInBackground, alternate: [], icon: icon, breadcrumbs: [strings.Settings_ChatSettings], present: { context, _, present in
presentDataSettings(context, present, .downloadInBackground)
})
]
@ -372,13 +425,13 @@ private func proxySearchableItems(context: AccountContext) -> [SettingsSearchabl
}
return [
SettingsSearchableItem(id: .proxy(0), title: strings.Settings_Proxy, alternate: [], icon: icon, breadcrumbs: [], present: { context, present in
SettingsSearchableItem(id: .proxy(0), title: strings.Settings_Proxy, alternate: [], icon: icon, breadcrumbs: [], present: { context, _, present in
presentProxySettings(context, present)
}),
SettingsSearchableItem(id: .proxy(1), title: strings.SocksProxySetup_AddProxy, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Proxy], present: { context, present in
SettingsSearchableItem(id: .proxy(1), title: strings.SocksProxySetup_AddProxy, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Proxy], present: { context, _, present in
present(.modal, proxyServerSettingsController(context: context))
}),
SettingsSearchableItem(id: .proxy(2), title: strings.SocksProxySetup_UseForCalls, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Proxy], present: { context, present in
SettingsSearchableItem(id: .proxy(2), title: strings.SocksProxySetup_UseForCalls, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Proxy], present: { context, _, present in
presentProxySettings(context, present)
})
]
@ -393,45 +446,53 @@ private func appearanceSearchableItems(context: AccountContext) -> [SettingsSear
}
return [
SettingsSearchableItem(id: .appearance(0), title: strings.Settings_Appearance, alternate: [], icon: icon, breadcrumbs: [], present: { context, present in
SettingsSearchableItem(id: .appearance(0), title: strings.Settings_Appearance, alternate: [], icon: icon, breadcrumbs: [], present: { context, _, present in
presentAppearanceSettings(context, present, nil)
}),
SettingsSearchableItem(id: .appearance(1), title: strings.Appearance_TextSize.capitalized, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance], present: { context, present in
SettingsSearchableItem(id: .appearance(1), title: strings.Appearance_TextSize.capitalized, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance], present: { context, _, present in
presentAppearanceSettings(context, present, .fontSize)
}),
SettingsSearchableItem(id: .appearance(2), title: strings.Settings_ChatBackground, alternate: ["Wallpaper"], icon: icon, breadcrumbs: [strings.Settings_Appearance], present: { context, present in
SettingsSearchableItem(id: .appearance(2), title: strings.Settings_ChatBackground, alternate: ["Wallpaper"], icon: icon, breadcrumbs: [strings.Settings_Appearance], present: { context, _, present in
present(.push, ThemeGridController(context: context))
}),
SettingsSearchableItem(id: .appearance(3), title: strings.Wallpaper_SetColor, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance, strings.Settings_ChatBackground], present: { context, present in
SettingsSearchableItem(id: .appearance(3), title: strings.Wallpaper_SetColor, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance, strings.Settings_ChatBackground], present: { context, _, present in
present(.push, ThemeColorsGridController(context: context))
}),
SettingsSearchableItem(id: .appearance(4), title: strings.Wallpaper_SetCustomBackground, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance, strings.Settings_ChatBackground], present: { context, present in
SettingsSearchableItem(id: .appearance(4), title: strings.Wallpaper_SetCustomBackground, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance, strings.Settings_ChatBackground], present: { context, _, present in
presentCustomWallpaperPicker(context: context, present: { controller in
present(.immediate, controller)
})
}),
SettingsSearchableItem(id: .appearance(5), title: strings.Appearance_AutoNightTheme, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance], present: { context, present in
SettingsSearchableItem(id: .appearance(5), title: strings.Appearance_AutoNightTheme, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance], present: { context, _, present in
present(.push, themeAutoNightSettingsController(context: context))
}),
SettingsSearchableItem(id: .appearance(6), title: strings.Appearance_ColorTheme.capitalized, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance], present: { context, present in
SettingsSearchableItem(id: .appearance(6), title: strings.Appearance_ColorTheme.capitalized, alternate: [], icon: icon, breadcrumbs: [strings.Settings_Appearance], present: { context, _, present in
presentAppearanceSettings(context, present, .accentColor)
}),
SettingsSearchableItem(id: .appearance(7), title: strings.Appearance_ReduceMotion, alternate: ["Animations"], icon: icon, breadcrumbs: [strings.Settings_Appearance, strings.Appearance_Animations.capitalized], present: { context, present in
SettingsSearchableItem(id: .appearance(7), title: strings.Appearance_ReduceMotion, alternate: ["Animations"], icon: icon, breadcrumbs: [strings.Settings_Appearance, strings.Appearance_Animations.capitalized], present: { context, _, present in
presentAppearanceSettings(context, present, .animations)
}),
]
}
func settingsSearchableItems(context: AccountContext) -> Signal<[SettingsSearchableItem], NoError> {
let watchAppInstalled = context.watchManager?.watchAppInstalled ?? .single(false)
return watchAppInstalled
let watchAppInstalled = (context.watchManager?.watchAppInstalled ?? .single(false))
|> take(1)
|> map { watchAppInstalled in
let canAddAccount = activeAccountsAndPeers(context: context)
|> take(1)
|> map { accountsAndPeers -> Bool in
return accountsAndPeers.1.count + 1 < maximumNumberOfAccounts
}
return combineLatest(watchAppInstalled, canAddAccount)
|> map { watchAppInstalled, canAddAccount in
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
var allItems: [SettingsSearchableItem] = []
let savedMessages = SettingsSearchableItem(id: .savedMessages(0), title: strings.Settings_SavedMessages, alternate: [], icon: .savedMessages, breadcrumbs: [], present: { context, present in
let profileItems = profileSearchableItems(context: context, canAddAccount: canAddAccount)
allItems.append(contentsOf: profileItems)
let savedMessages = SettingsSearchableItem(id: .savedMessages(0), title: strings.Settings_SavedMessages, alternate: [], icon: .savedMessages, breadcrumbs: [], present: { context, _, present in
present(.push, ChatController(context: context, chatLocation: .peer(context.account.peerId)))
})
allItems.append(savedMessages)
@ -457,22 +518,29 @@ func settingsSearchableItems(context: AccountContext) -> Signal<[SettingsSearcha
let appearanceItems = appearanceSearchableItems(context: context)
allItems.append(contentsOf: appearanceItems)
let language = SettingsSearchableItem(id: .language(0), title: strings.Settings_AppLanguage, alternate: [], icon: .language, breadcrumbs: [], present: { context, present in
let language = SettingsSearchableItem(id: .language(0), title: strings.Settings_AppLanguage, alternate: [], icon: .language, breadcrumbs: [], present: { context, _, present in
present(.push, LocalizationListController(context: context))
})
allItems.append(language)
let passport = SettingsSearchableItem(id: .passport(0), title: strings.Settings_Passport, alternate: [], icon: .passport, breadcrumbs: [], present: { context, present in
if watchAppInstalled {
let watch = SettingsSearchableItem(id: .watch(0), title: strings.Settings_AppleWatch, alternate: [], icon: .watch, breadcrumbs: [], present: { context, _, present in
present(.push, watchSettingsController(context: context))
})
allItems.append(watch)
}
let passport = SettingsSearchableItem(id: .passport(0), title: strings.Settings_Passport, alternate: [], icon: .passport, breadcrumbs: [], present: { context, _, present in
present(.modal, SecureIdAuthController(context: context, mode: .list))
})
allItems.append(passport)
let support = SettingsSearchableItem(id: .support(0), title: strings.Settings_Support, alternate: ["Support"], icon: .support, breadcrumbs: [], present: { context, present in
let support = SettingsSearchableItem(id: .support(0), title: strings.Settings_Support, alternate: ["Support"], icon: .support, breadcrumbs: [], present: { context, _, present in
//return .push(ChatController(context: context, chatLocation: .peer(context.account.peerId)))
})
allItems.append(support)
let faq = SettingsSearchableItem(id: .faq(0), title: strings.Settings_FAQ, alternate: [], icon: .faq, breadcrumbs: [], present: { context, present in
let faq = SettingsSearchableItem(id: .faq(0), title: strings.Settings_FAQ, alternate: [], icon: .faq, breadcrumbs: [], present: { context, _, present in
//return .push(ChatController(context: context, chatLocation: .peer(context.account.peerId)))
})
allItems.append(faq)
@ -535,7 +603,6 @@ private func matchStringTokens(_ tokens: [ValueBoxKey], with other: [ValueBoxKey
return false
}
func searchSettingsItems(items: [SettingsSearchableItem], query: String) -> [SettingsSearchableItem] {
let queryTokens = stringTokens(query.lowercased())

View File

@ -466,13 +466,13 @@ final class SharedMediaPlayer {
case .voice, .music:
switch playbackData.source {
case let .telegramFile(fileReference):
strongSelf.playbackItem = .audio(MediaPlayer(audioSessionManager: strongSelf.audioSession, postbox: strongSelf.account.postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: playbackData.type == .music, video: false, preferSoftwareDecoding: false, enableSound: true, baseRate: rateValue, fetchAutomatically: true, playAndRecord: controlPlaybackWithProximity))
strongSelf.playbackItem = .audio(MediaPlayer(audioSessionManager: strongSelf.audioSession, postbox: strongSelf.account.postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: playbackData.type == .music ? .conservative : .none, video: false, preferSoftwareDecoding: false, enableSound: true, baseRate: rateValue, fetchAutomatically: true, playAndRecord: controlPlaybackWithProximity))
}
case .instantVideo:
if let mediaManager = strongSelf.mediaManager, let item = item as? MessageMediaPlaylistItem {
switch playbackData.source {
case let .telegramFile(fileReference):
let videoNode = OverlayInstantVideoNode(postbox: strongSelf.account.postbox, audioSession: strongSelf.audioSession, manager: mediaManager.universalVideoManager, content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, fileReference.media.fileId), fileReference: fileReference, streamVideo: false, enableSound: false, baseRate: rateValue), close: { [weak mediaManager] in
let videoNode = OverlayInstantVideoNode(postbox: strongSelf.account.postbox, audioSession: strongSelf.audioSession, manager: mediaManager.universalVideoManager, content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, fileReference.media.fileId), fileReference: fileReference, enableSound: false, baseRate: rateValue), close: { [weak mediaManager] in
mediaManager?.setPlaylist(nil, type: .voice)
})
strongSelf.playbackItem = .instantVideo(videoNode)

View File

@ -189,7 +189,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case let .fontSize(theme, fontSize):
return ThemeSettingsFontSizeItem(theme: theme, fontSize: fontSize, sectionId: self.section, updated: { value in
arguments.selectFontSize(value)
})
}, tag: ThemeSettingsEntryTag.fontSize)
case let .chatPreviewHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, dateTimeFormat, nameDisplayOrder):
@ -201,7 +201,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case let .accentColor(theme, text, color):
return ItemListDisclosureItem(theme: theme, icon: nil, title: text, label: "", labelStyle: .color(UIColor(rgb: UInt32(bitPattern: color))), sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openAccentColor(color)
})
}, tag: ThemeSettingsEntryTag.accentColor)
case let .autoNightTheme(theme, text, value):
return ItemListDisclosureItem(theme: theme, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openAutoNightTheme()
@ -217,7 +217,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case let .animations(theme, title, value):
return ItemListSwitchItem(theme: theme, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.disableAnimations(value)
})
}, tag: ThemeSettingsEntryTag.animations)
case let .animationsInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
}

View File

@ -64,7 +64,7 @@ private func generateKnobImage() -> UIImage? {
})
}
class ThemeSettingsFontSizeItemNode: ListViewItemNode {
class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
@ -76,7 +76,7 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode {
private var item: ThemeSettingsFontSizeItem?
private var layoutParams: ListViewItemLayoutParams?
var tag: Any? {
var tag: ItemListItemTag? {
return self.item?.tag
}

View File

@ -302,6 +302,6 @@ final class UniversalVideoContentManager {
}
}
}
} |> runOn(Queue.mainQueue())
} |> runOn(Queue.mainQueue())
}
}

View File

@ -418,7 +418,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
default:
if let content = item.content as? NativeVideoContent, !content.streamVideo {
if let content = item.content as? NativeVideoContent, !content.streamVideo.enabled {
if !content.enableSound {
isPaused = false
}
@ -557,10 +557,21 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let videoNode = self.videoNode {
if isCentral {
var isAnimated = false
if let item = self.item, let content = item.content as? NativeVideoContent {
isAnimated = content.fileReference.media.isAnimated
}
self.hideStatusNodeUntilCentrality = false
if self.shouldAutoplayOnCentrality() {
self.initiallyActivated = true
videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: .stop)
if videoNode.ownsContentNode {
if isAnimated {
videoNode.seek(0.0)
videoNode.play()
}
else if self.shouldAutoplayOnCentrality() {
self.initiallyActivated = true
videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: .stop)
}
}
} else {
self.dismissOnOrientationChange = false
@ -604,7 +615,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
override func activateAsInitial() {
if let videoNode = self.videoNode, self.isCentral {
self.initiallyActivated = true
var isAnimated = false
if let item = self.item, let content = item.content as? NativeVideoContent {
isAnimated = content.fileReference.media.isAnimated
@ -708,9 +719,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
Queue.mainQueue().after(0.001) {
videoNode.canAttachContent = true
self.updateDisplayPlaceholder(!videoNode.ownsContentNode)
if self.item?.fromPlayingVideo ?? false {
Queue.mainQueue().after(0.001) {
videoNode.canAttachContent = true
self.updateDisplayPlaceholder(!videoNode.ownsContentNode)
}
}
if let pictureInPictureNode = self.pictureInPictureNode {

View File

@ -33,11 +33,11 @@ struct WebSearchGalleryEntry: Equatable {
case let .externalReference(_, _, type, _, _, _, content, thumbnail, _):
if let content = content, type == "gif", let thumbnailResource = thumbnail?.resource, let dimensions = content.dimensions {
let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]))
return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: fileReference, streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true), controllerInteraction: controllerInteraction)
return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: fileReference, loopVideo: true, enableSound: false, fetchAutomatically: true), controllerInteraction: controllerInteraction)
}
case let .internalReference(_, _, _, _, _, _, file, _):
if let file = file {
return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: .standalone(media: file), streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true), controllerInteraction: controllerInteraction)
return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: .standalone(media: file), loopVideo: true, enableSound: false, fetchAutomatically: true), controllerInteraction: controllerInteraction)
}
}
preconditionFailure()

View File

@ -203,7 +203,7 @@ final class WebSearchVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
default:
if let content = item.content as? NativeVideoContent, !content.streamVideo {
if let content = item.content as? NativeVideoContent, !content.streamVideo.enabled {
if !content.enableSound {
isPaused = false
}