mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'b6d84038f17cc37618016d2dca66cd05265e0bf3' into beta
This commit is contained in:
commit
c0652152c2
Binary file not shown.
@ -6038,7 +6038,6 @@ Sorry for the inconvenience.";
|
||||
"Conversation.ForwardTooltip.TwoChats.Many" = "Messages forwarded to **%@** and **%@**";
|
||||
"Conversation.ForwardTooltip.ManyChats.One" = "Message forwarded to **%@** and %@ others";
|
||||
"Conversation.ForwardTooltip.ManyChats.Many" = "Messages forwarded to **%@** and %@ others";
|
||||
|
||||
"Conversation.ForwardTooltip.SavedMessages.One" = "Message forwarded to **Saved Messages**";
|
||||
"Conversation.ForwardTooltip.SavedMessages.Many" = "Messages forwarded to **Saved Messages**";
|
||||
|
||||
@ -6299,3 +6298,13 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.ForwardTooltip.ManyChats" = "Invite link forwarded to **%@** and %@ others";
|
||||
|
||||
"GroupRemoved.ViewChannelInfo" = "View Channel";
|
||||
|
||||
"UserInfo.ContactForwardTooltip.Chat.One" = "Contact forwarded to **%@**";
|
||||
"UserInfo.ContactForwardTooltip.TwoChats.One" = "Contact forwarded to **%@** and **%@**";
|
||||
"UserInfo.ContactForwardTooltip.ManyChats.One" = "Contact forwarded to **%@** and %@ others";
|
||||
"UserInfo.ContactForwardTooltip.SavedMessages.One" = "Contact forwarded to **Saved Messages**";
|
||||
|
||||
"UserInfo.LinkForwardTooltip.Chat.One" = "Link forwarded to **%@**";
|
||||
"UserInfo.LinkForwardTooltip.TwoChats.One" = "Link forwarded to **%@** and **%@**";
|
||||
"UserInfo.LinkForwardTooltip.ManyChats.One" = "Link forwarded to **%@** and %@ others";
|
||||
"UserInfo.LinkForwardTooltip.SavedMessages.One" = "Link forwarded to **Saved Messages**";
|
||||
|
@ -52,8 +52,9 @@ public final class TelegramApplicationBindings {
|
||||
public let getAvailableAlternateIcons: () -> [PresentationAppIcon]
|
||||
public let getAlternateIconName: () -> String?
|
||||
public let requestSetAlternateIconName: (String?, @escaping (Bool) -> Void) -> Void
|
||||
public let forceOrientation: (UIInterfaceOrientation) -> Void
|
||||
|
||||
public init(isMainApp: Bool, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void) {
|
||||
public init(isMainApp: Bool, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) {
|
||||
self.isMainApp = isMainApp
|
||||
self.containerPath = containerPath
|
||||
self.appSpecificScheme = appSpecificScheme
|
||||
@ -77,6 +78,7 @@ public final class TelegramApplicationBindings {
|
||||
self.getAvailableAlternateIcons = getAvailableAlternateIcons
|
||||
self.getAlternateIconName = getAlternateIconName
|
||||
self.requestSetAlternateIconName = requestSetAlternateIconName
|
||||
self.forceOrientation = forceOrientation
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,15 +473,17 @@ public final class ContactSelectionControllerParams {
|
||||
public let options: [ContactListAdditionalOption]
|
||||
public let displayDeviceContacts: Bool
|
||||
public let displayCallIcons: Bool
|
||||
public let multipleSelection: Bool
|
||||
public let confirmation: (ContactListPeer) -> Signal<Bool, NoError>
|
||||
|
||||
public init(context: AccountContext, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: [ContactListAdditionalOption] = [], displayDeviceContacts: Bool = false, displayCallIcons: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal<Bool, NoError> = { _ in .single(true) }) {
|
||||
public init(context: AccountContext, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: [ContactListAdditionalOption] = [], displayDeviceContacts: Bool = false, displayCallIcons: Bool = false, multipleSelection: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal<Bool, NoError> = { _ in .single(true) }) {
|
||||
self.context = context
|
||||
self.autoDismiss = autoDismiss
|
||||
self.title = title
|
||||
self.options = options
|
||||
self.displayDeviceContacts = displayDeviceContacts
|
||||
self.displayCallIcons = displayCallIcons
|
||||
self.multipleSelection = multipleSelection
|
||||
self.confirmation = confirmation
|
||||
}
|
||||
}
|
||||
@ -604,7 +608,7 @@ public protocol SharedAccountContext: class {
|
||||
func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void)
|
||||
func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messageIds: Set<MessageId>) -> Signal<ChatAvailableMessageActions, NoError>
|
||||
func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messageIds: Set<MessageId>, messages: [MessageId: Message], peers: [PeerId: Peer]) -> Signal<ChatAvailableMessageActions, NoError>
|
||||
func resolveUrl(account: Account, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError>
|
||||
func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError>
|
||||
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?)
|
||||
func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
|
||||
func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
|
||||
|
@ -3,7 +3,7 @@ import Display
|
||||
import SwiftSignalKit
|
||||
|
||||
public protocol ContactSelectionController: ViewController {
|
||||
var result: Signal<(ContactListPeer, ContactListAction)?, NoError> { get }
|
||||
var result: Signal<([ContactListPeer], ContactListAction)?, NoError> { get }
|
||||
var displayProgress: Bool { get set }
|
||||
var dismissed: (() -> Void)? { get set }
|
||||
|
||||
|
@ -39,8 +39,9 @@ public final class PeerSelectionControllerParams {
|
||||
public let attemptSelection: ((Peer) -> Void)?
|
||||
public let createNewGroup: (() -> Void)?
|
||||
public let pretendPresentedInModal: Bool
|
||||
public let multipleSelection: Bool
|
||||
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false) {
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false) {
|
||||
self.context = context
|
||||
self.filter = filter
|
||||
self.hasChatListSelector = hasChatListSelector
|
||||
@ -50,6 +51,7 @@ public final class PeerSelectionControllerParams {
|
||||
self.attemptSelection = attemptSelection
|
||||
self.createNewGroup = createNewGroup
|
||||
self.pretendPresentedInModal = pretendPresentedInModal
|
||||
self.multipleSelection = multipleSelection
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,12 +231,12 @@ public final class CallListController: TelegramBaseController {
|
||||
switch strongSelf.mode {
|
||||
case .tab:
|
||||
if strongSelf.editingMode {
|
||||
strongSelf.navigationItem.leftBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed))
|
||||
strongSelf.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed)), animated: true)
|
||||
var pressedImpl: (() -> Void)?
|
||||
let buttonNode = DeleteAllButtonNode(presentationData: strongSelf.presentationData, pressed: {
|
||||
pressedImpl?()
|
||||
})
|
||||
strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: buttonNode)
|
||||
strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(customDisplayNode: buttonNode), animated: true)
|
||||
strongSelf.navigationItem.rightBarButtonItem?.setCustomAction({
|
||||
pressedImpl?()
|
||||
})
|
||||
@ -249,14 +249,14 @@ public final class CallListController: TelegramBaseController {
|
||||
|
||||
//strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Notification_Exceptions_DeleteAll, style: .plain, target: strongSelf, action: #selector(strongSelf.deleteAllPressed))
|
||||
} else {
|
||||
strongSelf.navigationItem.leftBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed))
|
||||
strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(strongSelf.presentationData.theme), style: .plain, target: self, action: #selector(strongSelf.callPressed))
|
||||
strongSelf.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed)), animated: true)
|
||||
strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(strongSelf.presentationData.theme), style: .plain, target: self, action: #selector(strongSelf.callPressed)), animated: true)
|
||||
}
|
||||
case .navigation:
|
||||
if strongSelf.editingMode {
|
||||
strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed))
|
||||
strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed)), animated: true)
|
||||
} else {
|
||||
strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed))
|
||||
strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed)), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -365,9 +365,9 @@ public final class CallListController: TelegramBaseController {
|
||||
controller.navigationPresentation = .modal
|
||||
self.createActionDisposable.set((controller.result
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak controller, weak self] peer in
|
||||
|> deliverOnMainQueue).start(next: { [weak controller, weak self] result in
|
||||
controller?.dismissSearch()
|
||||
if let strongSelf = self, let (contactPeer, action) = peer, case let .peer(peer, _, _) = contactPeer {
|
||||
if let strongSelf = self, let (contactPeers, action) = result, let contactPeer = contactPeers.first, case let .peer(peer, _, _) = contactPeer {
|
||||
strongSelf.call(peer.id, isVideo: action == .videoCall, began: {
|
||||
if let strongSelf = self {
|
||||
let _ = (strongSelf.context.sharedContext.hasOngoingCall.get()
|
||||
@ -394,12 +394,12 @@ public final class CallListController: TelegramBaseController {
|
||||
|
||||
switch self.mode {
|
||||
case .tab:
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)), animated: true)
|
||||
var pressedImpl: (() -> Void)?
|
||||
let buttonNode = DeleteAllButtonNode(presentationData: self.presentationData, pressed: {
|
||||
pressedImpl?()
|
||||
})
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: buttonNode)
|
||||
self.navigationItem.setRightBarButton(UIBarButtonItem(customDisplayNode: buttonNode), animated: true)
|
||||
self.navigationItem.rightBarButtonItem?.setCustomAction({
|
||||
pressedImpl?()
|
||||
})
|
||||
@ -411,7 +411,7 @@ public final class CallListController: TelegramBaseController {
|
||||
}
|
||||
//self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Notification_Exceptions_DeleteAll, style: .plain, target: self, action: #selector(self.deleteAllPressed))
|
||||
case .navigation:
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||
self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)), animated: true)
|
||||
}
|
||||
|
||||
self.controllerNode.updateState { state in
|
||||
@ -423,10 +423,10 @@ public final class CallListController: TelegramBaseController {
|
||||
self.editingMode = false
|
||||
switch self.mode {
|
||||
case .tab:
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed))
|
||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)), animated: true)
|
||||
self.navigationItem.setRightBarButton(UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed)), animated: true)
|
||||
case .navigation:
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
|
||||
self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)), animated: true)
|
||||
}
|
||||
|
||||
self.controllerNode.updateState { state in
|
||||
|
@ -534,7 +534,7 @@ public final class ChatImportActivityScreen: ViewController {
|
||||
self.radialStatusText.attributedText = NSAttributedString(string: "\(Int(effectiveProgress * 100.0))%", font: Font.with(size: floor(36.0 * maxK), design: .round, weight: .semibold), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let radialStatusTextSize = self.radialStatusText.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
self.progressText.attributedText = NSAttributedString(string: "\(dataSizeString(Int(effectiveProgress * CGFloat(self.totalBytes)))) of \(dataSizeString(Int(1.0 * CGFloat(self.totalBytes))))", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
self.progressText.attributedText = NSAttributedString(string: "\(dataSizeString(Int(effectiveProgress * CGFloat(self.totalBytes)), formatting: DataSizeStringFormatting(presentationData: self.presentationData))) of \(dataSizeString(Int(1.0 * CGFloat(self.totalBytes)), formatting: DataSizeStringFormatting(presentationData: self.presentationData)))", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let progressTextSize = self.progressText.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
switch self.state {
|
||||
|
@ -283,6 +283,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return (data.isLockable, false)
|
||||
}
|
||||
|
||||
let previousEditingAndNetworkStateValue = Atomic<(Bool, AccountNetworkState)?>(value: nil)
|
||||
if !self.hideNetworkActivityStatus {
|
||||
self.titleDisposable = combineLatest(queue: .mainQueue(),
|
||||
context.account.networkState,
|
||||
@ -298,13 +299,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
} else {
|
||||
defaultTitle = strongSelf.presentationData.strings.ChatList_ArchivedChatsTitle
|
||||
}
|
||||
let previousEditingAndNetworkState = previousEditingAndNetworkStateValue.swap((stateAndFilterId.state.editing, networkState))
|
||||
if stateAndFilterId.state.editing {
|
||||
if strongSelf.groupId == .root {
|
||||
strongSelf.navigationItem.rightBarButtonItem = nil
|
||||
}
|
||||
|
||||
let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? strongSelf.presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle
|
||||
strongSelf.titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false)
|
||||
|
||||
var animated = false
|
||||
if let (previousEditing, previousNetworkState) = previousEditingAndNetworkState {
|
||||
if previousEditing != stateAndFilterId.state.editing, previousNetworkState == networkState, case .online = networkState {
|
||||
animated = true
|
||||
}
|
||||
}
|
||||
strongSelf.titleView.setTitle(NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false), animated: animated)
|
||||
} else if isReorderingTabs {
|
||||
if strongSelf.groupId == .root {
|
||||
strongSelf.navigationItem.rightBarButtonItem = nil
|
||||
@ -374,7 +382,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
case .updating:
|
||||
strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked)
|
||||
case .online:
|
||||
strongSelf.titleView.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked)
|
||||
strongSelf.titleView.setTitle(NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked), animated: (previousEditingAndNetworkState?.0 ?? false) != stateAndFilterId.state.editing)
|
||||
}
|
||||
if groupId == .root && filter == nil && checkProxy {
|
||||
if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil && strongSelf.navigationController?.topViewController === self {
|
||||
@ -1413,10 +1421,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||
editItem.accessibilityLabel = self.presentationData.strings.Common_Done
|
||||
if case .root = self.groupId, self.filter == nil {
|
||||
self.navigationItem.leftBarButtonItem = editItem
|
||||
self.navigationItem.setLeftBarButton(editItem, animated: true)
|
||||
(self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(.details, transition: .animated(duration: 0.5, curve: .spring))
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = editItem
|
||||
self.navigationItem.setRightBarButton(editItem, animated: true)
|
||||
(self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(.master, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
self.searchContentNode?.setIsEnabled(false, animated: true)
|
||||
@ -1440,9 +1448,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
|
||||
editItem.accessibilityLabel = self.presentationData.strings.Common_Edit
|
||||
if case .root = self.groupId, self.filter == nil {
|
||||
self.navigationItem.leftBarButtonItem = editItem
|
||||
self.navigationItem.setLeftBarButton(editItem, animated: true)
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = editItem
|
||||
self.navigationItem.setRightBarButton(editItem, animated: true)
|
||||
}
|
||||
(self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(nil, transition: .animated(duration: 0.4, curve: .spring))
|
||||
self.searchContentNode?.setIsEnabled(true, animated: true)
|
||||
|
@ -160,11 +160,11 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
addAppLogEvent(postbox: context.account.postbox, type: "search_global_open_message", peerId: peer.id, data: .dictionary(["msg_id": .number(Double(messageId.id))]))
|
||||
}
|
||||
}, openUrl: { [weak self] url in
|
||||
openUserGeneratedUrl(context: context, url: url, concealed: false, present: { c in
|
||||
openUserGeneratedUrl(context: context, peerId: nil, url: url, concealed: false, present: { c in
|
||||
present(c, nil)
|
||||
}, openResolved: { [weak self] resolved in
|
||||
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peerId, navigation in
|
||||
// self?.openPeer(peerId: peerId, navigation: navigation)
|
||||
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
|
@ -965,7 +965,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
|
||||
let resolvedMessage = .single(nil)
|
||||
|> then(context.sharedContext.resolveUrl(account: context.account, url: finalQuery, skipUrlAuth: true)
|
||||
|> then(context.sharedContext.resolveUrl(context: context, peerId: nil, url: finalQuery, skipUrlAuth: true)
|
||||
|> mapToSignal { resolvedUrl -> Signal<Message?, NoError> in
|
||||
if case let .channelMessage(_, messageId) = resolvedUrl {
|
||||
return downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: messageId)
|
||||
|
@ -17,6 +17,7 @@ struct NetworkStatusTitle: Equatable {
|
||||
final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitleTransitionNode {
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let lockView: ChatListTitleLockView
|
||||
private weak var lockSnapshotView: UIView?
|
||||
private let activityIndicator: ActivityIndicator
|
||||
private let buttonView: HighlightTrackingButton
|
||||
private let proxyNode: ChatTitleProxyNode
|
||||
@ -24,30 +25,71 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
|
||||
|
||||
private var validLayout: (CGSize, CGRect)?
|
||||
|
||||
var title: NetworkStatusTitle = NetworkStatusTitle(text: "", activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false) {
|
||||
didSet {
|
||||
if self.title != oldValue {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title.text, font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
||||
self.buttonView.accessibilityLabel = self.title.text
|
||||
self.activityIndicator.isHidden = !self.title.activity
|
||||
if self.title.connectsViaProxy {
|
||||
self.proxyNode.status = self.title.activity ? .connecting : .connected
|
||||
} else {
|
||||
self.proxyNode.status = .available
|
||||
}
|
||||
self.proxyNode.isHidden = !self.title.hasProxy
|
||||
self.proxyButton.isHidden = !self.title.hasProxy
|
||||
|
||||
self.buttonView.isHidden = !self.title.isPasscodeSet
|
||||
if self.title.isPasscodeSet && !self.title.activity {
|
||||
self.lockView.isHidden = false
|
||||
} else {
|
||||
self.lockView.isHidden = true
|
||||
}
|
||||
self.lockView.updateTheme(self.theme)
|
||||
|
||||
self.setNeedsLayout()
|
||||
private var _title: NetworkStatusTitle = NetworkStatusTitle(text: "", activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false)
|
||||
var title: NetworkStatusTitle {
|
||||
get {
|
||||
return self._title
|
||||
}
|
||||
set {
|
||||
self.setTitle(newValue, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
func setTitle(_ title: NetworkStatusTitle, animated: Bool) {
|
||||
let oldValue = self._title
|
||||
self._title = title
|
||||
|
||||
if self._title != oldValue {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title.text, font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
||||
self.buttonView.accessibilityLabel = self.title.text
|
||||
self.activityIndicator.isHidden = !self.title.activity
|
||||
|
||||
self.proxyButton.isHidden = !self.title.hasProxy
|
||||
if self.title.connectsViaProxy {
|
||||
self.proxyNode.status = self.title.activity ? .connecting : .connected
|
||||
} else {
|
||||
self.proxyNode.status = .available
|
||||
}
|
||||
|
||||
let proxyIsHidden = !self.title.hasProxy
|
||||
let previousProxyIsHidden = self.proxyNode.isHidden
|
||||
if proxyIsHidden != previousProxyIsHidden {
|
||||
if proxyIsHidden {
|
||||
if let snapshotView = self.proxyNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.proxyNode.frame
|
||||
self.proxyNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.proxyNode.view)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
self.proxyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
}
|
||||
self.proxyNode.isHidden = !self.title.hasProxy
|
||||
|
||||
self.buttonView.isHidden = !self.title.isPasscodeSet
|
||||
if self.title.isPasscodeSet && !self.title.activity {
|
||||
if self.lockView.isHidden && animated {
|
||||
self.lockView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
self.lockView.isHidden = false
|
||||
} else {
|
||||
if !self.lockView.isHidden && animated {
|
||||
if let snapshotView = self.lockView.snapshotContentTree() {
|
||||
self.lockSnapshotView = snapshotView
|
||||
snapshotView.frame = self.lockView.frame
|
||||
self.lockView.superview?.insertSubview(snapshotView, aboveSubview: self.lockView)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
self.lockView.isHidden = true
|
||||
}
|
||||
self.lockView.updateTheme(self.theme)
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,11 +220,9 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
|
||||
indicatorPadding = indicatorSize.width + 6.0
|
||||
}
|
||||
var maxTitleWidth = clearBounds.size.width - indicatorPadding
|
||||
var alignedTitleWidth = size.width - indicatorPadding
|
||||
var proxyPadding: CGFloat = 0.0
|
||||
if !self.proxyNode.isHidden {
|
||||
maxTitleWidth -= 25.0
|
||||
alignedTitleWidth -= 20.0
|
||||
proxyPadding += 39.0
|
||||
}
|
||||
if !self.lockView.isHidden {
|
||||
@ -197,18 +237,24 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
|
||||
titleContentRect.origin.x = min(titleContentRect.origin.x, clearBounds.maxX - proxyPadding - titleContentRect.width)
|
||||
|
||||
let titleFrame = titleContentRect
|
||||
self.titleNode.frame = titleFrame
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
|
||||
let proxyFrame = CGRect(origin: CGPoint(x: clearBounds.maxX - 9.0 - self.proxyNode.bounds.width, y: floor((size.height - proxyNode.bounds.height) / 2.0)), size: proxyNode.bounds.size)
|
||||
let proxyFrame = CGRect(origin: CGPoint(x: clearBounds.maxX - 9.0 - self.proxyNode.bounds.width, y: floor((size.height - self.proxyNode.bounds.height) / 2.0)), size: self.proxyNode.bounds.size)
|
||||
self.proxyNode.frame = proxyFrame
|
||||
|
||||
self.proxyButton.frame = proxyFrame.insetBy(dx: -2.0, dy: -2.0)
|
||||
|
||||
let buttonX = max(0.0, titleFrame.minX - 10.0)
|
||||
self.buttonView.frame = CGRect(origin: CGPoint(x: buttonX, y: 0.0), size: CGSize(width: min(titleFrame.maxX + 28.0, size.width) - buttonX, height: size.height))
|
||||
|
||||
self.lockView.frame = CGRect(x: titleFrame.maxX + 6.0, y: titleFrame.minY + 2.0, width: 2.0, height: 2.0)
|
||||
let lockFrame = CGRect(x: titleFrame.maxX + 6.0, y: titleFrame.minY + 2.0, width: 2.0, height: 2.0)
|
||||
transition.updateFrame(view: self.lockView, frame: lockFrame)
|
||||
if let lockSnapshotView = self.lockSnapshotView {
|
||||
transition.updateFrame(view: lockSnapshotView, frame: lockFrame)
|
||||
}
|
||||
|
||||
self.activityIndicator.frame = CGRect(origin: CGPoint(x: titleFrame.minX - indicatorSize.width - 4.0, y: titleFrame.minY - 1.0), size: indicatorSize)
|
||||
let activityIndicatorFrame = CGRect(origin: CGPoint(x: titleFrame.minX - indicatorSize.width - 4.0, y: titleFrame.minY - 1.0), size: indicatorSize)
|
||||
transition.updateFrame(node: self.activityIndicator, frame: activityIndicatorFrame)
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
|
@ -7,8 +7,8 @@ import TextFormat
|
||||
import RadialStatusNode
|
||||
import AppBundle
|
||||
|
||||
private let font = Font.regular(11.0)
|
||||
private let boldFont = Font.semibold(11.0)
|
||||
private let font = Font.with(size: 11.0, design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
||||
private let boldFont = Font.with(size: 11.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers])
|
||||
|
||||
public enum ChatMessageInteractiveMediaDownloadState: Equatable {
|
||||
case remote
|
||||
@ -188,7 +188,7 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
|
||||
transition.updateAlpha(node: statusNode, alpha: active ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
let durationFrame = CGRect(x: active ? 42.0 : 7.0, y: active ? 6.0 : 3.0, width: durationSize.width, height: durationSize.height)
|
||||
let durationFrame = CGRect(x: active ? 42.0 : 7.0, y: active ? 6.0 : 2.0 + UIScreenPixel, width: durationSize.width, height: durationSize.height)
|
||||
self.durationNode.bounds = CGRect(origin: CGPoint(), size: durationFrame.size)
|
||||
textTransition.updatePosition(node: self.durationNode, position: durationFrame.center)
|
||||
|
||||
|
@ -240,7 +240,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
})]
|
||||
}
|
||||
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch : .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch : .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
|
||||
interaction.openPeer(peer, .generic)
|
||||
}, itemHighlighting: interaction.itemHighlighting, contextAction: itemContextAction)
|
||||
}
|
||||
@ -594,10 +594,46 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var index: Int = 0
|
||||
|
||||
var existingPeerIds = Set<ContactListPeerId>()
|
||||
if let selectionState = selectionState {
|
||||
for peer in selectionState.foundPeers {
|
||||
if existingPeerIds.contains(peer.id) {
|
||||
continue
|
||||
}
|
||||
existingPeerIds.insert(peer.id)
|
||||
|
||||
let selection: ContactsPeerItemSelection = .selectable(selected: selectionState.selectedPeerIndices[peer.id] != nil)
|
||||
|
||||
var presence: PeerPresence?
|
||||
if case let .peer(peer, _, _) = peer {
|
||||
presence = presences[peer.id]
|
||||
}
|
||||
let enabled: Bool
|
||||
switch peer {
|
||||
case let .peer(peer, _, _):
|
||||
enabled = !disabledPeerIds.contains(peer.id)
|
||||
default:
|
||||
enabled = true
|
||||
}
|
||||
|
||||
entries.append(.peer(index, peer, presence, nil, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, enabled))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 ..< orderedPeers.count {
|
||||
let peer = orderedPeers[i]
|
||||
if existingPeerIds.contains(peer.id) {
|
||||
continue
|
||||
}
|
||||
existingPeerIds.insert(peer.id)
|
||||
|
||||
let selection: ContactsPeerItemSelection
|
||||
if let selectionState = selectionState {
|
||||
selection = .selectable(selected: selectionState.selectedPeerIndices[orderedPeers[i].id] != nil)
|
||||
selection = .selectable(selected: selectionState.selectedPeerIndices[peer.id] != nil)
|
||||
} else {
|
||||
selection = .none
|
||||
}
|
||||
@ -606,20 +642,21 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
|
||||
case .orderedByPresence:
|
||||
header = commonHeader
|
||||
default:
|
||||
header = headers[orderedPeers[i].id]
|
||||
header = headers[peer.id]
|
||||
}
|
||||
var presence: PeerPresence?
|
||||
if case let .peer(peer, _, _) = orderedPeers[i] {
|
||||
if case let .peer(peer, _, _) = peer {
|
||||
presence = presences[peer.id]
|
||||
}
|
||||
let enabled: Bool
|
||||
switch orderedPeers[i] {
|
||||
switch peer {
|
||||
case let .peer(peer, _, _):
|
||||
enabled = !disabledPeerIds.contains(peer.id)
|
||||
default:
|
||||
enabled = true
|
||||
}
|
||||
entries.append(.peer(i, orderedPeers[i], presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, enabled))
|
||||
entries.append(.peer(index, peer, presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, enabled))
|
||||
index += 1
|
||||
}
|
||||
return entries
|
||||
}
|
||||
@ -706,15 +743,21 @@ public enum ContactListPresentation {
|
||||
|
||||
public struct ContactListNodeGroupSelectionState: Equatable {
|
||||
public let selectedPeerIndices: [ContactListPeerId: Int]
|
||||
public let foundPeers: [ContactListPeer]
|
||||
public let selectedPeerMap: [ContactListPeerId: ContactListPeer]
|
||||
public let nextSelectionIndex: Int
|
||||
|
||||
private init(selectedPeerIndices: [ContactListPeerId: Int], nextSelectionIndex: Int) {
|
||||
private init(selectedPeerIndices: [ContactListPeerId: Int], foundPeers: [ContactListPeer], selectedPeerMap: [ContactListPeerId: ContactListPeer], nextSelectionIndex: Int) {
|
||||
self.selectedPeerIndices = selectedPeerIndices
|
||||
self.foundPeers = foundPeers
|
||||
self.selectedPeerMap = selectedPeerMap
|
||||
self.nextSelectionIndex = nextSelectionIndex
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.selectedPeerIndices = [:]
|
||||
self.foundPeers = []
|
||||
self.selectedPeerMap = [:]
|
||||
self.nextSelectionIndex = 0
|
||||
}
|
||||
|
||||
@ -722,13 +765,21 @@ public struct ContactListNodeGroupSelectionState: Equatable {
|
||||
var updatedIndices = self.selectedPeerIndices
|
||||
if let _ = updatedIndices[peerId] {
|
||||
updatedIndices.removeValue(forKey: peerId)
|
||||
return ContactListNodeGroupSelectionState(selectedPeerIndices: updatedIndices, nextSelectionIndex: self.nextSelectionIndex)
|
||||
return ContactListNodeGroupSelectionState(selectedPeerIndices: updatedIndices, foundPeers: self.foundPeers, selectedPeerMap: self.selectedPeerMap, nextSelectionIndex: self.nextSelectionIndex)
|
||||
} else {
|
||||
updatedIndices[peerId] = self.nextSelectionIndex
|
||||
return ContactListNodeGroupSelectionState(selectedPeerIndices: updatedIndices, nextSelectionIndex: self.nextSelectionIndex + 1)
|
||||
return ContactListNodeGroupSelectionState(selectedPeerIndices: updatedIndices, foundPeers: self.foundPeers, selectedPeerMap: self.selectedPeerMap, nextSelectionIndex: self.nextSelectionIndex + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func withFoundPeers(_ foundPeers: [ContactListPeer]) -> ContactListNodeGroupSelectionState {
|
||||
return ContactListNodeGroupSelectionState(selectedPeerIndices: self.selectedPeerIndices, foundPeers: foundPeers, selectedPeerMap: self.selectedPeerMap, nextSelectionIndex: self.nextSelectionIndex)
|
||||
}
|
||||
|
||||
public func withSelectedPeerMap(_ selectedPeerMap: [ContactListPeerId: ContactListPeer]) -> ContactListNodeGroupSelectionState {
|
||||
return ContactListNodeGroupSelectionState(selectedPeerIndices: self.selectedPeerIndices, foundPeers: self.foundPeers, selectedPeerMap: selectedPeerMap, nextSelectionIndex: self.nextSelectionIndex)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ContactListNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
@ -754,11 +805,35 @@ public final class ContactListNode: ASDisplayNode {
|
||||
private var selectionStateValue: ContactListNodeGroupSelectionState? {
|
||||
didSet {
|
||||
self.selectionStatePromise.set(.single(self.selectionStateValue))
|
||||
self.selectionStateUpdated?(self.selectionStateValue)
|
||||
}
|
||||
}
|
||||
public var selectionState: ContactListNodeGroupSelectionState? {
|
||||
return self.selectionStateValue
|
||||
}
|
||||
public var selectionStateUpdated: ((ContactListNodeGroupSelectionState?) -> Void)?
|
||||
|
||||
public var selectedPeers: [ContactListPeer] {
|
||||
if let selectionState = self.selectionState {
|
||||
var selectedPeers: [ContactListPeer] = []
|
||||
var selectedIndices: [(Int, ContactListPeerId)] = []
|
||||
for (id, index) in selectionState.selectedPeerIndices {
|
||||
selectedIndices.append((index, id))
|
||||
}
|
||||
selectedIndices.sort(by: { lhs, rhs in
|
||||
return lhs.0 < rhs.0
|
||||
})
|
||||
for (_, id) in selectedIndices {
|
||||
if let peer = selectionState.selectedPeerMap[id] {
|
||||
selectedPeers.append(peer)
|
||||
}
|
||||
}
|
||||
return selectedPeers
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private var interaction: ContactListNodeInteraction?
|
||||
|
||||
private var enableUpdatesValue = false
|
||||
@ -864,6 +939,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
|
||||
let processingQueue = Queue()
|
||||
let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil)
|
||||
let previousSelectionState = Atomic<ContactListNodeGroupSelectionState?>(value: nil)
|
||||
|
||||
let interaction = ContactListNodeInteraction(activateSearch: { [weak self] in
|
||||
self?.activateSearch?()
|
||||
@ -874,7 +950,21 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}, suppressWarning: { [weak self] in
|
||||
self?.suppressPermissionWarning?()
|
||||
}, openPeer: { [weak self] peer, action in
|
||||
self?.openPeer?(peer, action)
|
||||
if let strongSelf = self {
|
||||
if let _ = strongSelf.selectionStateValue {
|
||||
strongSelf.updateSelectionState({ state in
|
||||
if let state = state {
|
||||
var selectedPeerMap = state.selectedPeerMap
|
||||
selectedPeerMap[peer.id] = peer
|
||||
return state.withToggledPeerId(peer.id).withSelectedPeerMap(selectedPeerMap)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
} else {
|
||||
strongSelf.openPeer?(peer, action)
|
||||
}
|
||||
}
|
||||
}, contextAction: contextAction)
|
||||
|
||||
self.indexNode.indexSelected = { [weak self] section in
|
||||
@ -1037,6 +1127,15 @@ public final class ContactListNode: ASDisplayNode {
|
||||
|
||||
var peers: [ContactListPeer] = []
|
||||
|
||||
if let selectionState = selectionState {
|
||||
for peer in selectionState.foundPeers {
|
||||
if case let .peer(peer, _, _) = peer {
|
||||
existingPeerIds.insert(peer.id)
|
||||
}
|
||||
peers.append(peer)
|
||||
}
|
||||
}
|
||||
|
||||
if !excludeSelf && !existingPeerIds.contains(accountPeer.id) {
|
||||
let lowercasedQuery = query.lowercased()
|
||||
if presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) {
|
||||
@ -1210,6 +1309,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: peers, presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons)
|
||||
let previous = previousEntries.swap(entries)
|
||||
let previousSelection = previousSelectionState.swap(selectionState)
|
||||
|
||||
var hadPermissionInfo = false
|
||||
if let previous = previous {
|
||||
@ -1229,10 +1329,11 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
let animation: ContactListAnimation
|
||||
if hadPermissionInfo != hasPermissionInfo {
|
||||
if (previousSelection == nil) != (selectionState == nil) {
|
||||
animation = .insertion
|
||||
}
|
||||
else if let previous = previous, !presentationData.disableAnimations, (entries.count - previous.count) < 20 {
|
||||
} else if hadPermissionInfo != hasPermissionInfo {
|
||||
animation = .insertion
|
||||
} else if let previous = previous, !presentationData.disableAnimations, (entries.count - previous.count) < 20 {
|
||||
animation = .default
|
||||
} else {
|
||||
animation = .none
|
||||
@ -1337,7 +1438,6 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func updateSelectedChatLocation(_ chatLocation: ChatLocation?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
||||
self.interaction?.itemHighlighting.chatLocation = chatLocation
|
||||
self.interaction?.itemHighlighting.progress = progress
|
||||
|
||||
@ -1352,7 +1452,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
self.disposable.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
|
||||
public func updateSelectionState(_ f: (ContactListNodeGroupSelectionState?) -> ContactListNodeGroupSelectionState?) {
|
||||
let updatedSelectionState = f(self.selectionStateValue)
|
||||
if updatedSelectionState != self.selectionStateValue {
|
||||
|
@ -40,6 +40,11 @@ public enum ContactsPeerItemSelection: Equatable {
|
||||
case selectable(selected: Bool)
|
||||
}
|
||||
|
||||
public enum ContactsPeerItemSelectionPosition: Equatable {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
public struct ContactsPeerItemEditing: Equatable {
|
||||
public var editable: Bool
|
||||
public var editing: Bool
|
||||
@ -130,6 +135,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
let badge: ContactsPeerItemBadge?
|
||||
let enabled: Bool
|
||||
let selection: ContactsPeerItemSelection
|
||||
let selectionPosition: ContactsPeerItemSelectionPosition
|
||||
let editing: ContactsPeerItemEditing
|
||||
let options: [ItemListPeerItemRevealOption]
|
||||
let additionalActions: [ContactsPeerItemAction]
|
||||
@ -148,7 +154,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
|
||||
public let header: ListViewItemHeader?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, arrowAction: (() -> Void)? = nil) {
|
||||
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, selectionPosition: ContactsPeerItemSelectionPosition = .right, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, arrowAction: (() -> Void)? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.style = style
|
||||
self.sectionId = sectionId
|
||||
@ -161,6 +167,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
self.badge = badge
|
||||
self.enabled = enabled
|
||||
self.selection = selection
|
||||
self.selectionPosition = selectionPosition
|
||||
self.editing = editing
|
||||
self.options = options
|
||||
self.additionalActions = additionalActions
|
||||
@ -518,7 +525,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
}
|
||||
let leftInset: CGFloat = 65.0 + params.leftInset
|
||||
var leftInset: CGFloat = 65.0 + params.leftInset
|
||||
var rightInset: CGFloat = 10.0 + params.rightInset
|
||||
|
||||
let updatedSelectionNode: CheckNode?
|
||||
@ -527,7 +534,12 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
case .none:
|
||||
updatedSelectionNode = nil
|
||||
case let .selectable(selected):
|
||||
rightInset += 38.0
|
||||
switch item.selectionPosition {
|
||||
case .left:
|
||||
leftInset += 38.0
|
||||
case .right:
|
||||
rightInset += 38.0
|
||||
}
|
||||
isSelected = selected
|
||||
|
||||
let selectionNode: CheckNode
|
||||
@ -999,14 +1011,29 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
if let updatedSelectionNode = updatedSelectionNode {
|
||||
let hadSelectionNode = strongSelf.selectionNode != nil
|
||||
if strongSelf.selectionNode !== updatedSelectionNode {
|
||||
strongSelf.selectionNode?.removeFromSupernode()
|
||||
strongSelf.selectionNode = updatedSelectionNode
|
||||
strongSelf.addSubnode(updatedSelectionNode)
|
||||
}
|
||||
updatedSelectionNode.setSelected(isSelected, animated: animated)
|
||||
updatedSelectionNode.setSelected(isSelected, animated: true)
|
||||
|
||||
updatedSelectionNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 22.0 - 17.0, y: floor((nodeLayout.contentSize.height - 22.0) / 2.0)), size: CGSize(width: 22.0, height: 22.0))
|
||||
switch item.selectionPosition {
|
||||
case .left:
|
||||
updatedSelectionNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 17.0, y: floor((nodeLayout.contentSize.height - 22.0) / 2.0)), size: CGSize(width: 22.0, height: 22.0))
|
||||
case .right:
|
||||
updatedSelectionNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 22.0 - 17.0, y: floor((nodeLayout.contentSize.height - 22.0) / 2.0)), size: CGSize(width: 22.0, height: 22.0))
|
||||
}
|
||||
|
||||
if !hadSelectionNode {
|
||||
switch item.selectionPosition {
|
||||
case .left:
|
||||
transition.animateFrame(node: updatedSelectionNode, from: updatedSelectionNode.frame.offsetBy(dx: -38.0, dy: 0.0))
|
||||
case .right:
|
||||
transition.animateFrame(node: updatedSelectionNode, from: updatedSelectionNode.frame.offsetBy(dx: 38.0, dy: 0.0))
|
||||
}
|
||||
}
|
||||
} else if let selectionNode = strongSelf.selectionNode {
|
||||
selectionNode.removeFromSupernode()
|
||||
strongSelf.selectionNode = nil
|
||||
|
@ -433,7 +433,32 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updateFrame(view: UIView, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
func updateFrame(view: UIView, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
|
||||
if frame.origin.x.isNaN {
|
||||
return
|
||||
}
|
||||
if frame.origin.y.isNaN {
|
||||
return
|
||||
}
|
||||
if frame.size.width.isNaN {
|
||||
return
|
||||
}
|
||||
if frame.size.width < 0.0 {
|
||||
return
|
||||
}
|
||||
if frame.size.height.isNaN {
|
||||
return
|
||||
}
|
||||
if frame.size.height < 0.0 {
|
||||
return
|
||||
}
|
||||
if !ASIsCGRectValidForLayout(CGRect(origin: CGPoint(), size: frame.size)) {
|
||||
return
|
||||
}
|
||||
if !ASIsCGPositionValidForLayout(frame.origin) {
|
||||
return
|
||||
}
|
||||
|
||||
if view.frame.equalTo(frame) && !force {
|
||||
completion?(true)
|
||||
} else {
|
||||
@ -444,9 +469,14 @@ public extension ContainedViewLayoutTransition {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
let previousFrame = view.frame
|
||||
let previousFrame: CGRect
|
||||
if beginWithCurrentState, let presentation = view.layer.presentation() {
|
||||
previousFrame = presentation.frame
|
||||
} else {
|
||||
previousFrame = view.frame
|
||||
}
|
||||
view.frame = frame
|
||||
view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
|
||||
view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
@ -454,7 +484,7 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.frame.equalTo(frame) {
|
||||
completion?(true)
|
||||
|
@ -518,15 +518,13 @@ open class NavigationBar: ASDisplayNode {
|
||||
self.leftButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
} else {
|
||||
if animated {
|
||||
if self.leftButtonNode.view.superview != nil {
|
||||
if let snapshotView = self.leftButtonNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.leftButtonNode.frame
|
||||
self.leftButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.leftButtonNode.view)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
if animated, self.leftButtonNode.view.superview != nil {
|
||||
if let snapshotView = self.leftButtonNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.leftButtonNode.frame
|
||||
self.leftButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.leftButtonNode.view)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
self.leftButtonNode.removeFromSupernode()
|
||||
@ -606,9 +604,27 @@ open class NavigationBar: ASDisplayNode {
|
||||
self.rightButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
} else {
|
||||
if animated, self.rightButtonNode.view.superview != nil {
|
||||
if let snapshotView = self.rightButtonNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.rightButtonNode.frame
|
||||
self.rightButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.rightButtonNode.view)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
self.rightButtonNode.removeFromSupernode()
|
||||
}
|
||||
} else {
|
||||
if animated, self.rightButtonNode.view.superview != nil {
|
||||
if let snapshotView = self.rightButtonNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.rightButtonNode.frame
|
||||
self.rightButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.rightButtonNode.view)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
self.rightButtonNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
|
@ -225,7 +225,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
|
||||
return size
|
||||
} else if let imageNode = self.imageNode {
|
||||
let nodeSize = imageNode.image?.size ?? CGSize()
|
||||
let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height))
|
||||
let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(44.0, max(nodeSize.height, superSize.height)))
|
||||
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeSize.width) / 2.0) + 5.0, y: floorToScreenPixels((size.height - nodeSize.height) / 2.0)), size: nodeSize)
|
||||
imageNode.frame = imageFrame
|
||||
self.imageRippleNode.frame = imageFrame
|
||||
|
@ -107,6 +107,8 @@ class CaptionScrollWrapperNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScrollViewDelegate {
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
@ -126,8 +128,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
private let textNode: ImmediateTextNode
|
||||
private let authorNameNode: ASTextNode
|
||||
private let dateNode: ASTextNode
|
||||
private let backwardButton: HighlightableButtonNode
|
||||
private let forwardButton: HighlightableButtonNode
|
||||
private let backwardButton: PlaybackButtonNode
|
||||
private let forwardButton: PlaybackButtonNode
|
||||
private let playbackControlButton: HighlightableButtonNode
|
||||
private let playPauseIconNode: PlayPauseIconNode
|
||||
|
||||
@ -188,7 +190,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
var statusState: RadialStatusNodeState = .none
|
||||
switch status {
|
||||
case let .Fetching(isActive, progress):
|
||||
case let .Fetching(_, progress):
|
||||
let adjustedProgress = max(progress, 0.027)
|
||||
statusState = .cloudProgress(color: UIColor.white, strokeBackgroundColor: UIColor.white.withAlphaComponent(0.5), lineWidth: 2.0, value: CGFloat(adjustedProgress))
|
||||
case .Local:
|
||||
@ -207,7 +209,14 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.backwardButton.isHidden = !seekable
|
||||
self.forwardButton.isHidden = !seekable
|
||||
self.playbackControlButton.isHidden = false
|
||||
self.playPauseIconNode.enqueueState(paused && !self.wasPlaying ? .play : .pause, animated: true)
|
||||
|
||||
let icon: PlayPauseIconNodeState
|
||||
if let wasPlaying = self.wasPlaying {
|
||||
icon = wasPlaying ? .pause : .play
|
||||
} else {
|
||||
icon = paused ? .play : .pause
|
||||
}
|
||||
self.playPauseIconNode.enqueueState(icon, animated: true)
|
||||
self.statusButtonNode.isHidden = true
|
||||
self.statusNode.isHidden = true
|
||||
}
|
||||
@ -303,13 +312,14 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.dateNode.isUserInteractionEnabled = false
|
||||
self.dateNode.displaysAsynchronously = false
|
||||
|
||||
self.backwardButton = HighlightableButtonNode()
|
||||
self.backwardButton = PlaybackButtonNode()
|
||||
self.backwardButton.isHidden = true
|
||||
self.backwardButton.setImage(backwardImage, for: [])
|
||||
self.backwardButton.backgroundIconNode.image = backwardImage
|
||||
|
||||
self.forwardButton = HighlightableButtonNode()
|
||||
self.forwardButton = PlaybackButtonNode()
|
||||
self.forwardButton.isHidden = true
|
||||
self.forwardButton.setImage(forwardImage, for: [])
|
||||
self.forwardButton.forward = true
|
||||
self.forwardButton.backgroundIconNode.image = forwardImage
|
||||
|
||||
self.playbackControlButton = HighlightableButtonNode()
|
||||
self.playbackControlButton.isHidden = true
|
||||
@ -408,12 +418,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.forwardButton.view.addGestureRecognizer(forwardLongPressGestureRecognizer)
|
||||
}
|
||||
|
||||
private var wasPlaying = false
|
||||
private var wasPlaying: Bool?
|
||||
@objc private func seekBackwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.backwardButton.isPressing = true
|
||||
self.wasPlaying = !self.currentIsPaused
|
||||
if self.wasPlaying {
|
||||
if self.wasPlaying == true {
|
||||
self.playbackControl?()
|
||||
}
|
||||
|
||||
@ -434,12 +445,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.seekTimer = seekTimer
|
||||
seekTimer.start()
|
||||
case .ended, .cancelled:
|
||||
self.backwardButton.isPressing = false
|
||||
self.seekTimer?.invalidate()
|
||||
self.seekTimer = nil
|
||||
if self.wasPlaying {
|
||||
if self.wasPlaying == true {
|
||||
self.playbackControl?()
|
||||
self.wasPlaying = false
|
||||
}
|
||||
self.wasPlaying = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -448,8 +460,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
@objc private func seekForwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.forwardButton.isPressing = true
|
||||
self.wasPlaying = !self.currentIsPaused
|
||||
if !self.wasPlaying {
|
||||
if self.wasPlaying == false {
|
||||
self.playbackControl?()
|
||||
}
|
||||
|
||||
@ -470,13 +483,15 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.seekTimer = seekTimer
|
||||
seekTimer.start()
|
||||
case .ended, .cancelled:
|
||||
self.forwardButton.isPressing = false
|
||||
self.setPlayRate?(1.0)
|
||||
self.seekTimer?.invalidate()
|
||||
self.seekTimer = nil
|
||||
|
||||
if !self.wasPlaying {
|
||||
if self.wasPlaying == false {
|
||||
self.playbackControl?()
|
||||
}
|
||||
self.wasPlaying = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -762,10 +777,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.deleteButton.frame = deleteFrame
|
||||
self.editButton.frame = editFrame
|
||||
|
||||
if let image = self.backwardButton.image(for: .normal) {
|
||||
if let image = self.backwardButton.backgroundIconNode.image {
|
||||
self.backwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) - 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
|
||||
}
|
||||
if let image = self.forwardButton.image(for: .normal) {
|
||||
if let image = self.forwardButton.backgroundIconNode.image {
|
||||
self.forwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) + 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
|
||||
}
|
||||
|
||||
@ -1392,7 +1407,7 @@ private enum PlayPauseIconNodeState: Equatable {
|
||||
}
|
||||
|
||||
private final class PlayPauseIconNode: ManagedAnimationNode {
|
||||
private let duration: Double = 0.4
|
||||
private let duration: Double = 0.35
|
||||
private var iconState: PlayPauseIconNodeState = .pause
|
||||
|
||||
init() {
|
||||
@ -1435,3 +1450,69 @@ private final class PlayPauseIconNode: ManagedAnimationNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let circleDiameter: CGFloat = 80.0
|
||||
|
||||
private final class PlaybackButtonNode: HighlightTrackingButtonNode {
|
||||
let backgroundIconNode: ASImageNode
|
||||
let textNode: ImmediateTextNode
|
||||
|
||||
var forward: Bool = false
|
||||
|
||||
var isPressing = false {
|
||||
didSet {
|
||||
if self.isPressing != oldValue && !self.isPressing {
|
||||
self.highligthedChanged(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundIconNode = ASImageNode()
|
||||
self.backgroundIconNode.isLayerBacked = true
|
||||
self.backgroundIconNode.displaysAsynchronously = false
|
||||
self.backgroundIconNode.displayWithoutProcessing = true
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.attributedText = NSAttributedString(string: "15", font: Font.with(size: 11.0, design: .round, weight: .semibold, traits: []), textColor: .white)
|
||||
|
||||
super.init(pointerStyle: .circle)
|
||||
|
||||
self.addSubnode(self.backgroundIconNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.backgroundIconNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.backgroundIconNode.alpha = 0.4
|
||||
|
||||
strongSelf.textNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.textNode.alpha = 0.4
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.18, curve: .linear)
|
||||
let angle = CGFloat.pi / 4.0 + 0.226
|
||||
transition.updateTransformRotation(node: strongSelf.backgroundIconNode, angle: strongSelf.forward ? angle : -angle)
|
||||
} else if !strongSelf.isPressing {
|
||||
strongSelf.backgroundIconNode.alpha = 1.0
|
||||
strongSelf.backgroundIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
|
||||
strongSelf.textNode.alpha = 1.0
|
||||
strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .linear)
|
||||
transition.updateTransformRotation(node: strongSelf.backgroundIconNode, angle: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
self.backgroundIconNode.frame = self.bounds
|
||||
|
||||
let size = self.bounds.size
|
||||
let textSize = self.textNode.updateLayout(size)
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0) + UIScreenPixel), size: textSize)
|
||||
}
|
||||
}
|
||||
|
@ -226,6 +226,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
}
|
||||
|
||||
func setFetchStatusSignal(_ fetchStatus: Signal<MediaResourceStatus, NoError>?, strings: PresentationStrings, decimalSeparator: String, fileSize: Int?) {
|
||||
let formatting = DataSizeStringFormatting(strings: strings, decimalSeparator: decimalSeparator)
|
||||
if let fileSize = fileSize {
|
||||
if let fetchStatus = fetchStatus {
|
||||
self.fetchStatusDisposable.set((fetchStatus
|
||||
@ -234,9 +235,9 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
var text: String
|
||||
switch status {
|
||||
case .Remote:
|
||||
text = dataSizeString(fileSize, forceDecimal: true, decimalSeparator: decimalSeparator)
|
||||
text = dataSizeString(fileSize, forceDecimal: true, formatting: formatting)
|
||||
case let .Fetching(_, progress):
|
||||
text = strings.DownloadingStatus(dataSizeString(Int64(Float(fileSize) * progress), forceDecimal: true, decimalSeparator: decimalSeparator), dataSizeString(fileSize, forceDecimal: true, decimalSeparator: decimalSeparator)).0
|
||||
text = strings.DownloadingStatus(dataSizeString(Int64(Float(fileSize) * progress), forceDecimal: true, formatting: formatting), dataSizeString(fileSize, forceDecimal: true, formatting: formatting)).0
|
||||
default:
|
||||
text = ""
|
||||
}
|
||||
@ -248,7 +249,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.infoNode.attributedText = NSAttributedString(string: dataSizeString(fileSize, forceDecimal: true, decimalSeparator: decimalSeparator), font: textFont, textColor: .white)
|
||||
self.infoNode.attributedText = NSAttributedString(string: dataSizeString(fileSize, forceDecimal: true, formatting: formatting), font: textFont, textColor: .white)
|
||||
}
|
||||
} else {
|
||||
self.infoNode.attributedText = nil
|
||||
|
@ -54,12 +54,14 @@ public final class GalleryFooterNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
var animateOverlayIn = false
|
||||
var dismissedCurrentOverlayContentNode: GalleryOverlayContentNode?
|
||||
if self.currentOverlayContentNode !== overlayContentNode {
|
||||
if let currentOverlayContentNode = self.currentOverlayContentNode {
|
||||
dismissedCurrentOverlayContentNode = currentOverlayContentNode
|
||||
}
|
||||
self.currentOverlayContentNode = overlayContentNode
|
||||
animateOverlayIn = true
|
||||
if let overlayContentNode = overlayContentNode {
|
||||
overlayContentNode.setVisibilityAlpha(self.visibilityAlpha)
|
||||
self.addSubnode(overlayContentNode)
|
||||
@ -96,7 +98,9 @@ public final class GalleryFooterNode: ASDisplayNode {
|
||||
overlayContentNode.updateLayout(size: layout.size, metrics: layout.metrics, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: backgroundHeight, transition: transition)
|
||||
transition.updateFrame(node: overlayContentNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
overlayContentNode.animateIn(previousContentNode: dismissedCurrentOverlayContentNode, transition: contentTransition)
|
||||
if animateOverlayIn {
|
||||
overlayContentNode.animateIn(previousContentNode: dismissedCurrentOverlayContentNode, transition: contentTransition)
|
||||
}
|
||||
if let dismissedCurrentOverlayContentNode = dismissedCurrentOverlayContentNode {
|
||||
dismissedCurrentOverlayContentNode.animateOut(nextContentNode: overlayContentNode, transition: contentTransition, completion: { [weak self, weak dismissedCurrentOverlayContentNode] in
|
||||
if let strongSelf = self, let dismissedCurrentOverlayContentNode = dismissedCurrentOverlayContentNode, dismissedCurrentOverlayContentNode !== strongSelf.currentOverlayContentNode {
|
||||
|
@ -171,7 +171,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self.setupStatus(resource: fileReference.media.resource)
|
||||
|
||||
|
||||
self._title.set(.single("\(fileReference.media.fileName ?? "") - \(dataSizeString(fileReference.media.size ?? 0, forceDecimal: false, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))"))
|
||||
self._title.set(.single("\(fileReference.media.fileName ?? "") - \(dataSizeString(fileReference.media.size ?? 0, forceDecimal: false, formatting: DataSizeStringFormatting(presentationData: self.presentationData)))"))
|
||||
|
||||
let speedItem = UIBarButtonItem(image: UIImage(bundleImageName: "Media Gallery/SlowDown"), style: .plain, target: self, action: #selector(self.toggleSpeedButtonPressed))
|
||||
let backgroundItem = UIBarButtonItem(image: backgroundButtonIcon, style: .plain, target: self, action: #selector(self.toggleBackgroundButtonPressed))
|
||||
|
@ -164,68 +164,61 @@ private final class UniversalVideoGalleryItemPictureInPictureNode: ASDisplayNode
|
||||
}
|
||||
}
|
||||
|
||||
private let soundOnImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/SoundOn"), color: .white)
|
||||
private let soundOffImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/SoundOff"), color: .white)
|
||||
private var roundButtonBackgroundImage = {
|
||||
return generateImage(CGSize(width: 42.0, height: 42), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
context.setFillColor(UIColor(white: 0.0, alpha: 0.5).cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
})
|
||||
}()
|
||||
private let fullscreenImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Fullscreen"), color: .white)
|
||||
private let minimizeImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Minimize"), color: .white)
|
||||
|
||||
private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentNode {
|
||||
private let soundButtonNode: HighlightableButtonNode
|
||||
private let wrapperNode: ASDisplayNode
|
||||
private let fullscreenNode: HighlightableButtonNode
|
||||
private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat)?
|
||||
|
||||
var action: ((Bool) -> Void)?
|
||||
|
||||
override init() {
|
||||
self.soundButtonNode = HighlightableButtonNode()
|
||||
self.soundButtonNode.alpha = 0.0
|
||||
self.soundButtonNode.setBackgroundImage(roundButtonBackgroundImage, for: .normal)
|
||||
self.soundButtonNode.setImage(soundOffImage, for: .normal)
|
||||
self.soundButtonNode.setImage(soundOnImage, for: .selected)
|
||||
self.soundButtonNode.setImage(soundOnImage, for: [.selected, .highlighted])
|
||||
self.wrapperNode = ASDisplayNode()
|
||||
self.wrapperNode.alpha = 0.0
|
||||
|
||||
self.fullscreenNode = HighlightableButtonNode()
|
||||
self.fullscreenNode.setImage(fullscreenImage, for: .normal)
|
||||
self.fullscreenNode.setImage(minimizeImage, for: .selected)
|
||||
self.fullscreenNode.setImage(minimizeImage, for: [.selected, .highlighted])
|
||||
|
||||
super.init()
|
||||
|
||||
self.soundButtonNode.addTarget(self, action: #selector(self.soundButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.addSubnode(self.soundButtonNode)
|
||||
}
|
||||
|
||||
func hide() {
|
||||
self.soundButtonNode.isHidden = true
|
||||
self.addSubnode(self.wrapperNode)
|
||||
self.wrapperNode.addSubnode(self.fullscreenNode)
|
||||
|
||||
self.fullscreenNode.addTarget(self, action: #selector(self.soundButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (size, metrics, leftInset, rightInset, bottomInset)
|
||||
|
||||
let soundButtonDiameter: CGFloat = 42.0
|
||||
let inset: CGFloat = 12.0
|
||||
let effectiveBottomInset = self.visibilityAlpha < 1.0 ? 0.0 : bottomInset
|
||||
let soundButtonFrame = CGRect(origin: CGPoint(x: size.width - soundButtonDiameter - inset - rightInset, y: size.height - soundButtonDiameter - inset - effectiveBottomInset), size: CGSize(width: soundButtonDiameter, height: soundButtonDiameter))
|
||||
transition.updateFrame(node: self.soundButtonNode, frame: soundButtonFrame)
|
||||
let isLandscape = size.width > size.height
|
||||
self.fullscreenNode.isSelected = isLandscape
|
||||
|
||||
let iconSize: CGFloat = 42.0
|
||||
let inset: CGFloat = 4.0
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: size.width - iconSize - inset - rightInset, y: size.height - iconSize - inset - bottomInset), size: CGSize(width: iconSize, height: iconSize))
|
||||
transition.updateFrame(node: self.wrapperNode, frame: buttonFrame)
|
||||
transition.updateFrame(node: self.fullscreenNode, frame: CGRect(origin: CGPoint(), size: buttonFrame.size))
|
||||
}
|
||||
|
||||
override func animateIn(previousContentNode: GalleryOverlayContentNode?, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.soundButtonNode, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.wrapperNode, alpha: 1.0)
|
||||
}
|
||||
|
||||
override func animateOut(nextContentNode: GalleryOverlayContentNode?, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
transition.updateAlpha(node: self.soundButtonNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.wrapperNode, alpha: 0.0)
|
||||
}
|
||||
|
||||
override func setVisibilityAlpha(_ alpha: CGFloat) {
|
||||
super.setVisibilityAlpha(alpha)
|
||||
self.updateSoundButtonVisibility()
|
||||
self.updateFullscreenButtonVisibility()
|
||||
}
|
||||
|
||||
func updateSoundButtonVisibility() {
|
||||
if self.soundButtonNode.isSelected {
|
||||
self.soundButtonNode.alpha = self.visibilityAlpha
|
||||
} else {
|
||||
self.soundButtonNode.alpha = 1.0
|
||||
}
|
||||
func updateFullscreenButtonVisibility() {
|
||||
self.wrapperNode.alpha = self.visibilityAlpha
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.updateLayout(size: validLayout.0, metrics: validLayout.1, leftInset: validLayout.2, rightInset: validLayout.3, bottomInset: validLayout.4, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
@ -233,12 +226,18 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
||||
}
|
||||
|
||||
@objc func soundButtonPressed() {
|
||||
self.soundButtonNode.isSelected = !self.soundButtonNode.isSelected
|
||||
self.updateSoundButtonVisibility()
|
||||
var toLandscape = false
|
||||
if let (size, _, _, _ ,_) = self.validLayout, size.width < size.height {
|
||||
toLandscape = true
|
||||
}
|
||||
if toLandscape {
|
||||
self.wrapperNode.alpha = 0.0
|
||||
}
|
||||
self.action?(toLandscape)
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if !self.soundButtonNode.frame.contains(point) {
|
||||
if !self.wrapperNode.frame.contains(point) {
|
||||
return nil
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
@ -324,6 +323,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.overlayContentNode.action = { [weak self] toLandscape in
|
||||
self?.updateControlsVisibility(!toLandscape)
|
||||
context.sharedContext.applicationBindings.forceOrientation(toLandscape ? .landscapeRight : .portrait)
|
||||
}
|
||||
|
||||
self.scrubberView.seek = { [weak self] timecode in
|
||||
self?.videoNode?.seek(timecode)
|
||||
}
|
||||
@ -484,6 +488,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private var previousPlaying: Bool?
|
||||
|
||||
private func setupControlsTimer() {
|
||||
return
|
||||
let timer = SwiftSignalKit.Timer(timeout: 3.0, repeat: false, completion: { [weak self] in
|
||||
self?.updateControlsVisibility(false)
|
||||
self?.controlsTimer = nil
|
||||
@ -500,6 +505,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self.statusButtonNode.isHidden = true
|
||||
}
|
||||
|
||||
let dimensions = item.content.dimensions
|
||||
if dimensions.height > 0.0 {
|
||||
if dimensions.width / dimensions.height < 1.33 {
|
||||
self.overlayContentNode.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
self.dismissOnOrientationChange = item.landscape
|
||||
|
||||
var hasLinkedStickers = false
|
||||
@ -576,7 +588,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335)
|
||||
if item.fromPlayingVideo {
|
||||
videoNode.canAttachContent = false
|
||||
self.overlayContentNode.hide()
|
||||
} else {
|
||||
self.updateDisplayPlaceholder(!videoNode.ownsContentNode)
|
||||
}
|
||||
@ -1630,6 +1641,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> {
|
||||
return .single((self.footerContentNode, nil))
|
||||
return .single((self.footerContentNode, self.overlayContentNode))
|
||||
}
|
||||
}
|
||||
|
@ -1147,7 +1147,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.loadProgress.set(0.02)
|
||||
|
||||
self.loadWebpageDisposable.set(nil)
|
||||
self.resolveUrlDisposable.set((self.context.sharedContext.resolveUrl(account: self.context.account, url: url.url, skipUrlAuth: true)
|
||||
self.resolveUrlDisposable.set((self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url.url, skipUrlAuth: true)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
strongSelf.loadProgress.set(0.07)
|
||||
|
@ -31,6 +31,7 @@ swift_library(
|
||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
"//submodules/FileMediaResourceStatus:FileMediaResourceStatus",
|
||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -19,6 +19,7 @@ import MusicAlbumArtResources
|
||||
import UniversalMediaPlayer
|
||||
import ContextUI
|
||||
import FileMediaResourceStatus
|
||||
import ManagedAnimationNode
|
||||
|
||||
private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:])
|
||||
|
||||
@ -184,7 +185,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
private let playbackStatusDisposable = MetaDisposable()
|
||||
private let playbackStatus = Promise<MediaPlayerStatus>()
|
||||
|
||||
private var downloadStatusIconNode: ASImageNode
|
||||
private var downloadStatusIconNode: DownloadIconNode
|
||||
private var linearProgressNode: LinearProgressNode?
|
||||
|
||||
private var context: AccountContext?
|
||||
@ -246,10 +247,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
self.iconStatusNode = SemanticStatusNode(backgroundNodeColor: .clear, foregroundNodeColor: .white)
|
||||
self.iconStatusNode.isUserInteractionEnabled = false
|
||||
|
||||
self.downloadStatusIconNode = ASImageNode()
|
||||
self.downloadStatusIconNode.isLayerBacked = true
|
||||
self.downloadStatusIconNode.displaysAsynchronously = false
|
||||
self.downloadStatusIconNode.displayWithoutProcessing = true
|
||||
self.downloadStatusIconNode = DownloadIconNode()
|
||||
|
||||
self.restrictionNode = ASDisplayNode()
|
||||
self.restrictionNode.isHidden = true
|
||||
@ -422,7 +420,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
descriptionString = "\(stringForDuration(Int32(duration))) • \(performer)"
|
||||
}
|
||||
} else if let size = file.size {
|
||||
descriptionString = dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
|
||||
descriptionString = dataSizeString(size, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))
|
||||
} else {
|
||||
descriptionString = ""
|
||||
}
|
||||
@ -514,9 +512,9 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
var descriptionString: String = ""
|
||||
if let size = file.size {
|
||||
if item.isGlobalSearchResult {
|
||||
descriptionString = (dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator))
|
||||
descriptionString = dataSizeString(size, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))
|
||||
} else {
|
||||
descriptionString = "\(dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)) • \(dateString)"
|
||||
descriptionString = "\(dataSizeString(size, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))) • \(dateString)"
|
||||
}
|
||||
} else {
|
||||
if !item.isGlobalSearchResult {
|
||||
@ -738,6 +736,8 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
strongSelf.linearProgressNode?.updateTheme(theme: item.presentationData.theme.theme)
|
||||
|
||||
strongSelf.restrictionNode.backgroundColor = item.presentationData.theme.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.6)
|
||||
|
||||
strongSelf.downloadStatusIconNode.customColor = item.presentationData.theme.theme.list.itemAccentColor
|
||||
}
|
||||
|
||||
if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply {
|
||||
@ -849,7 +849,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
}))
|
||||
}
|
||||
|
||||
transition.updateFrame(node: strongSelf.downloadStatusIconNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset, y: strongSelf.descriptionNode.frame.minY + floor((strongSelf.descriptionNode.frame.height - 12.0) / 2.0)), size: CGSize(width: 12.0, height: 12.0)))
|
||||
transition.updateFrame(node: strongSelf.downloadStatusIconNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset - 3.0, y: strongSelf.descriptionNode.frame.minY + floor((strongSelf.descriptionNode.frame.height - 18.0) / 2.0)), size: CGSize(width: 18.0, height: 18.0)))
|
||||
|
||||
if let updatedFetchControls = updatedFetchControls {
|
||||
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
|
||||
@ -990,7 +990,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
switch fetchStatus {
|
||||
case let .Fetching(_, progress):
|
||||
if let file = self.currentMedia as? TelegramMediaFile, let size = file.size {
|
||||
downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator))"
|
||||
downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))) / \(dataSizeString(size, forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData)))"
|
||||
}
|
||||
descriptionOffset = 14.0
|
||||
case .Remote:
|
||||
@ -1015,10 +1015,12 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
transition.updateFrame(node: linearProgressNode, frame: progressFrame)
|
||||
linearProgressNode.updateProgress(value: CGFloat(progress), completion: {})
|
||||
|
||||
var animated = true
|
||||
if self.downloadStatusIconNode.supernode == nil {
|
||||
animated = false
|
||||
self.offsetContainerNode.addSubnode(self.downloadStatusIconNode)
|
||||
}
|
||||
self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadPauseIcon(item.presentationData.theme.theme)
|
||||
self.downloadStatusIconNode.enqueueState(.pause, animated: animated)
|
||||
case .Local:
|
||||
if let linearProgressNode = self.linearProgressNode {
|
||||
self.linearProgressNode = nil
|
||||
@ -1031,7 +1033,6 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
if self.downloadStatusIconNode.supernode != nil {
|
||||
self.downloadStatusIconNode.removeFromSupernode()
|
||||
}
|
||||
self.downloadStatusIconNode.image = nil
|
||||
case .Remote:
|
||||
if let linearProgressNode = self.linearProgressNode {
|
||||
self.linearProgressNode = nil
|
||||
@ -1039,10 +1040,12 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
linearProgressNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
var animated = true
|
||||
if self.downloadStatusIconNode.supernode == nil {
|
||||
animated = false
|
||||
self.offsetContainerNode.addSubnode(self.downloadStatusIconNode)
|
||||
}
|
||||
self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(item.presentationData.theme.theme)
|
||||
self.downloadStatusIconNode.enqueueState(.download, animated: animated)
|
||||
}
|
||||
} else {
|
||||
if let linearProgressNode = self.linearProgressNode {
|
||||
@ -1063,18 +1066,19 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
transition.updateFrame(node: self.descriptionNode, frame: descriptionFrame)
|
||||
}
|
||||
|
||||
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
|
||||
if downloadingString != nil {
|
||||
self.descriptionProgressNode.isHidden = false
|
||||
self.descriptionNode.isHidden = true
|
||||
alphaTransition.updateAlpha(node: self.descriptionProgressNode, alpha: 1.0)
|
||||
alphaTransition.updateAlpha(node: self.descriptionNode, alpha: 0.0)
|
||||
} else {
|
||||
self.descriptionProgressNode.isHidden = true
|
||||
self.descriptionNode.isHidden = false
|
||||
alphaTransition.updateAlpha(node: self.descriptionProgressNode, alpha: 0.0)
|
||||
alphaTransition.updateAlpha(node: self.descriptionNode, alpha: 1.0)
|
||||
}
|
||||
let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 13.0 / 17.0))
|
||||
|
||||
let descriptionFont = Font.with(size: floor(item.presentationData.fontSize.baseDisplaySize * 13.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
||||
self.descriptionProgressNode.attributedText = NSAttributedString(string: downloadingString ?? "", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
|
||||
let descriptionSize = self.descriptionProgressNode.updateLayout(CGSize(width: size.width - 14.0, height: size.height))
|
||||
transition.updateFrame(node: self.descriptionProgressNode, frame: CGRect(origin: self.descriptionNode.frame.origin, size: descriptionSize))
|
||||
|
||||
}
|
||||
|
||||
func activateMedia() {
|
||||
@ -1269,3 +1273,53 @@ private final class LinearProgressNode: ASDisplayNode {
|
||||
self.shimmerNode.frame = CGRect(origin: CGPoint(x: shimmerOffset - shimmerWidth / 2.0, y: 0.0), size: CGSize(width: shimmerWidth, height: 3.0))
|
||||
}
|
||||
}
|
||||
|
||||
private enum DownloadIconNodeState: Equatable {
|
||||
case download
|
||||
case pause
|
||||
}
|
||||
|
||||
private final class DownloadIconNode: ManagedAnimationNode {
|
||||
private let duration: Double = 0.3
|
||||
private var iconState: DownloadIconNodeState = .download
|
||||
|
||||
init() {
|
||||
super.init(size: CGSize(width: 18.0, height: 18.0))
|
||||
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_shareddownload"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01))
|
||||
}
|
||||
|
||||
func enqueueState(_ state: DownloadIconNodeState, animated: Bool) {
|
||||
guard self.iconState != state else {
|
||||
return
|
||||
}
|
||||
|
||||
let previousState = self.iconState
|
||||
self.iconState = state
|
||||
|
||||
switch previousState {
|
||||
case .pause:
|
||||
switch state {
|
||||
case .download:
|
||||
if animated {
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_shareddownload"), frames: .range(startFrame: 100, endFrame: 120), duration: self.duration))
|
||||
} else {
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_shareddownload"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01))
|
||||
}
|
||||
case .pause:
|
||||
break
|
||||
}
|
||||
case .download:
|
||||
switch state {
|
||||
case .pause:
|
||||
if animated {
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_shareddownload"), frames: .range(startFrame: 0, endFrame: 20), duration: self.duration))
|
||||
} else {
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("anim_shareddownload"), frames: .range(startFrame: 60, endFrame: 60), duration: 0.01))
|
||||
}
|
||||
case .download:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1322,8 +1322,8 @@ private func addContactToExisting(context: AccountContext, parentController: Vie
|
||||
contactsController.navigationPresentation = .modal
|
||||
(parentController.navigationController as? NavigationController)?.pushViewController(contactsController)
|
||||
let _ = (contactsController.result
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if let (peer, _) = peer {
|
||||
|> deliverOnMainQueue).start(next: { result in
|
||||
if let (peers, _) = result, let peer = peers.first {
|
||||
let dataSignal: Signal<(Peer?, DeviceContactStableId?), NoError>
|
||||
switch peer {
|
||||
case let .peer(contact, _, _):
|
||||
|
@ -1,11 +1,12 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import AccountContext
|
||||
import OverlayStatusController
|
||||
import UrlWhitelist
|
||||
|
||||
public func openUserGeneratedUrl(context: AccountContext, url: String, concealed: Bool, skipUrlAuth: Bool = false, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void) {
|
||||
public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: String, concealed: Bool, skipUrlAuth: Bool = false, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void) {
|
||||
var concealed = concealed
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -31,7 +32,7 @@ public func openUserGeneratedUrl(context: AccountContext, url: String, concealed
|
||||
cancelImpl = {
|
||||
disposable.dispose()
|
||||
}
|
||||
disposable.set((context.sharedContext.resolveUrl(account: context.account, url: url, skipUrlAuth: skipUrlAuth)
|
||||
disposable.set((context.sharedContext.resolveUrl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth)
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
|
@ -138,6 +138,7 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
|
||||
let isFirstTime = self.peer == nil
|
||||
self.peer = peer
|
||||
guard let mainPeer = peer.chatMainPeer else {
|
||||
return
|
||||
@ -165,7 +166,7 @@ public final class SelectablePeerNode: ASDisplayNode {
|
||||
|
||||
let onlineLayout = self.onlineNode.asyncLayout()
|
||||
let (onlineSize, onlineApply) = onlineLayout(online, false)
|
||||
let _ = onlineApply(false)
|
||||
let _ = onlineApply(!isFirstTime)
|
||||
|
||||
self.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(theme, state: .panel), color: nil, transition: .immediate)
|
||||
self.onlineNode.frame = CGRect(origin: CGPoint(), size: onlineSize)
|
||||
|
@ -10,6 +10,9 @@ swift_library(
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/GZip:GZip",
|
||||
"//submodules/rlottie:RLottieBinding",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -3,6 +3,9 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import RLottieBinding
|
||||
import GZip
|
||||
import AppBundle
|
||||
|
||||
public enum SemanticStatusNodeState: Equatable {
|
||||
public struct ProgressAppearance: Equatable {
|
||||
@ -88,10 +91,22 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
|
||||
let transitionFraction: CGFloat
|
||||
let icon: SemanticStatusNodeIcon
|
||||
|
||||
private let instance: LottieInstance?
|
||||
private let renderContext: DrawingContext?
|
||||
|
||||
init(transitionFraction: CGFloat, icon: SemanticStatusNodeIcon) {
|
||||
self.transitionFraction = transitionFraction
|
||||
self.icon = icon
|
||||
|
||||
let displaySize = CGSize(width: 44.0, height: 44.0)
|
||||
if let path = getAppBundle().path(forResource: "anim_playpause", ofType: "tgs"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024), let instance = LottieInstance(data: unpackedData, cacheKey: "anim_playpause") {
|
||||
self.instance = instance
|
||||
self.renderContext = DrawingContext(size: displaySize, scale: UIScreenScale, premultiplied: true, clear: true)
|
||||
} else {
|
||||
self.instance = nil
|
||||
self.renderContext = nil
|
||||
}
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
|
||||
case peerChannels(PresentationTheme, String, Bool)
|
||||
|
||||
case sizeHeader(PresentationTheme, String)
|
||||
case sizeItem(PresentationTheme, String, String, Int32)
|
||||
case sizeItem(PresentationTheme, PresentationStrings, String, String, Int32)
|
||||
case sizePreload(PresentationTheme, String, Bool, Bool)
|
||||
case sizePreloadInfo(PresentationTheme, String)
|
||||
|
||||
@ -145,8 +145,8 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .sizeItem(lhsTheme, lhsDecimalSeparator, lhsText, lhsValue):
|
||||
if case let .sizeItem(rhsTheme, rhsDecimalSeparator, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsDecimalSeparator == rhsDecimalSeparator, lhsText == rhsText, lhsValue == rhsValue {
|
||||
case let .sizeItem(lhsTheme, lhsStrings, lhsDecimalSeparator, lhsText, lhsValue):
|
||||
if case let .sizeItem(rhsTheme, rhsStrings, rhsDecimalSeparator, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDecimalSeparator == rhsDecimalSeparator, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -173,35 +173,35 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry {
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! AutodownloadMediaCategoryControllerArguments
|
||||
switch self {
|
||||
case let .peerHeader(theme, text):
|
||||
case let .peerHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .peerContacts(theme, text, value):
|
||||
case let .peerContacts(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.togglePeer(.contact)
|
||||
})
|
||||
case let .peerOtherPrivate(theme, text, value):
|
||||
case let .peerOtherPrivate(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.togglePeer(.otherPrivate)
|
||||
})
|
||||
case let .peerGroups(theme, text, value):
|
||||
case let .peerGroups(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.togglePeer(.group)
|
||||
})
|
||||
case let .peerChannels(theme, text, value):
|
||||
case let .peerChannels(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.togglePeer(.channel)
|
||||
})
|
||||
case let .sizeHeader(theme, text):
|
||||
case let .sizeHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .sizeItem(theme, decimalSeparator, text, value):
|
||||
return AutodownloadSizeLimitItem(theme: theme, decimalSeparator: decimalSeparator, text: text, value: value, sectionId: self.section, updated: { value in
|
||||
case let .sizeItem(theme, strings, decimalSeparator, text, value):
|
||||
return AutodownloadSizeLimitItem(theme: theme, strings: strings, decimalSeparator: decimalSeparator, text: text, value: value, sectionId: self.section, updated: { value in
|
||||
arguments.adjustSize(value)
|
||||
})
|
||||
case let .sizePreload(theme, text, value, enabled):
|
||||
case let .sizePreload(_, text, value, enabled):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value && enabled, enableInteractiveChanges: true, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleVideoPreload()
|
||||
})
|
||||
case let .sizePreloadInfo(theme, text):
|
||||
case let .sizePreloadInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
@ -277,7 +277,7 @@ private func autodownloadMediaCategoryControllerEntries(presentationData: Presen
|
||||
sizeText = autodownloadDataSizeString(Int64(size), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
}
|
||||
let text = presentationData.strings.AutoDownloadSettings_UpTo(sizeText).0
|
||||
entries.append(.sizeItem(presentationData.theme, presentationData.dateTimeFormat.decimalSeparator, text, size))
|
||||
entries.append(.sizeItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat.decimalSeparator, text, size))
|
||||
if #available(iOSApplicationExtension 10.3, *), category == .video {
|
||||
entries.append(.sizePreload(presentationData.theme, presentationData.strings.AutoDownloadSettings_PreloadVideo, predownload, size > 2 * 1024 * 1024))
|
||||
entries.append(.sizePreloadInfo(presentationData.theme, presentationData.strings.AutoDownloadSettings_PreloadVideoInfo(sizeText).0))
|
||||
|
@ -51,14 +51,16 @@ private func sizeValue(for sliderValue: CGFloat) -> Int32 {
|
||||
|
||||
final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let decimalSeparator: String
|
||||
let text: String
|
||||
let value: Int32
|
||||
let sectionId: ItemListSectionId
|
||||
let updated: (Int32) -> Void
|
||||
|
||||
init(theme: PresentationTheme, decimalSeparator: String, text: String, value: Int32, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, decimalSeparator: String, text: String, value: Int32, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.decimalSeparator = decimalSeparator
|
||||
self.text = text
|
||||
self.value = value
|
||||
@ -201,9 +203,11 @@ private final class AutodownloadSizeLimitItemNode: ListViewItemNode {
|
||||
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (minTextLayout, minTextApply) = makeMinTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: dataSizeString(512 * 1024, decimalSeparator: item.decimalSeparator), font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
let formatting = DataSizeStringFormatting(strings: item.strings, decimalSeparator: item.decimalSeparator)
|
||||
|
||||
let (maxTextLayout, maxTextApply) = makeMaxTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: dataSizeString(1536 * 1024 * 1024, decimalSeparator: item.decimalSeparator), font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (minTextLayout, minTextApply) = makeMinTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: dataSizeString(512 * 1024, formatting: formatting), font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (maxTextLayout, maxTextApply) = makeMaxTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: dataSizeString(1536 * 1024 * 1024, formatting: formatting), font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
contentSize = CGSize(width: params.width, height: 88.0)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
|
@ -24,7 +24,7 @@ private func stringForCacheSize(strings: PresentationStrings, size: Int32) -> St
|
||||
if size > 100 {
|
||||
return strings.Cache_NoLimit
|
||||
} else {
|
||||
return dataSizeString(Int64(size) * 1024 * 1024 * 1024)
|
||||
return dataSizeString(Int64(size) * 1024 * 1024 * 1024, formatting: DataSizeStringFormatting(strings: strings, decimalSeparator: "."))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,47 +258,47 @@ private enum NetworkUsageStatsEntry: ItemListNodeEntry {
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! NetworkUsageStatsControllerArguments
|
||||
switch self {
|
||||
case let .messagesHeader(theme, text):
|
||||
case let .messagesHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .messagesSent(theme, text, value):
|
||||
case let .messagesSent(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .messagesReceived(theme, text, value):
|
||||
case let .messagesReceived(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .imageHeader(theme, text):
|
||||
case let .imageHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .imageSent(theme, text, value):
|
||||
case let .imageSent(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .imageReceived(theme, text, value):
|
||||
case let .imageReceived(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .videoHeader(theme, text):
|
||||
case let .videoHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .videoSent(theme, text, value):
|
||||
case let .videoSent(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .videoReceived(theme, text, value):
|
||||
case let .videoReceived(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .audioHeader(theme, text):
|
||||
case let .audioHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .audioSent(theme, text, value):
|
||||
case let .audioSent(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .audioReceived(theme, text, value):
|
||||
case let .audioReceived(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .fileHeader(theme, text):
|
||||
case let .fileHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .fileSent(theme, text, value):
|
||||
case let .fileSent(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .fileReceived(theme, text, value):
|
||||
case let .fileReceived(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .callHeader(theme, text):
|
||||
case let .callHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .callSent(theme, text, value):
|
||||
case let .callSent(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .callReceived(theme, text, value):
|
||||
case let .callReceived(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none , action: nil)
|
||||
case let .reset(theme, section, text):
|
||||
case let .reset(_, section, text):
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.resetStatistics(section)
|
||||
})
|
||||
case let .resetTimestamp(theme, text):
|
||||
case let .resetTimestamp(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
@ -307,31 +307,32 @@ private enum NetworkUsageStatsEntry: ItemListNodeEntry {
|
||||
private func networkUsageStatsControllerEntries(presentationData: PresentationData, section: NetworkUsageControllerSection, stats: NetworkUsageStats) -> [NetworkUsageStatsEntry] {
|
||||
var entries: [NetworkUsageStatsEntry] = []
|
||||
|
||||
let formatting = DataSizeStringFormatting(presentationData: presentationData)
|
||||
switch section {
|
||||
case .cellular:
|
||||
entries.append(.messagesHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_GeneralDataSection))
|
||||
entries.append(.messagesSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.generic.cellular.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.messagesReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.generic.cellular.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.messagesSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.generic.cellular.outgoing, formatting: formatting)))
|
||||
entries.append(.messagesReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.generic.cellular.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.imageHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_MediaImageDataSection))
|
||||
entries.append(.imageSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.image.cellular.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.imageReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.image.cellular.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.imageSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.image.cellular.outgoing, formatting: formatting)))
|
||||
entries.append(.imageReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.image.cellular.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.videoHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_MediaVideoDataSection))
|
||||
entries.append(.videoSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.video.cellular.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.videoReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.video.cellular.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.videoSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.video.cellular.outgoing, formatting: formatting)))
|
||||
entries.append(.videoReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.video.cellular.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.audioHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_MediaAudioDataSection))
|
||||
entries.append(.audioSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.audio.cellular.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.audioReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.audio.cellular.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.audioSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.audio.cellular.outgoing, formatting: formatting)))
|
||||
entries.append(.audioReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.audio.cellular.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.fileHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_MediaDocumentDataSection))
|
||||
entries.append(.fileSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.file.cellular.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.fileReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.file.cellular.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.fileSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.file.cellular.outgoing, formatting: formatting)))
|
||||
entries.append(.fileReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.file.cellular.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.callHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_CallDataSection))
|
||||
entries.append(.callSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.call.cellular.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.callReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.call.cellular.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.callSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.call.cellular.outgoing, formatting: formatting)))
|
||||
entries.append(.callReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.call.cellular.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.reset(presentationData.theme, section, presentationData.strings.NetworkUsageSettings_ResetStats))
|
||||
|
||||
@ -344,28 +345,28 @@ private func networkUsageStatsControllerEntries(presentationData: PresentationDa
|
||||
}
|
||||
case .wifi:
|
||||
entries.append(.messagesHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_GeneralDataSection))
|
||||
entries.append(.messagesSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.generic.wifi.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.messagesReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.generic.wifi.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.messagesSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.generic.wifi.outgoing, formatting: formatting)))
|
||||
entries.append(.messagesReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.generic.wifi.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.imageHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_MediaImageDataSection))
|
||||
entries.append(.imageSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.image.wifi.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.imageReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.image.wifi.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.imageSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.image.wifi.outgoing, formatting: formatting)))
|
||||
entries.append(.imageReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.image.wifi.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.videoHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_MediaVideoDataSection))
|
||||
entries.append(.videoSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.video.wifi.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.videoReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.video.wifi.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.videoSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.video.wifi.outgoing, formatting: formatting)))
|
||||
entries.append(.videoReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.video.wifi.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.audioHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_MediaAudioDataSection))
|
||||
entries.append(.audioSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.audio.wifi.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.audioReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.audio.wifi.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.audioSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.audio.wifi.outgoing, formatting: formatting)))
|
||||
entries.append(.audioReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.audio.wifi.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.fileHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_MediaDocumentDataSection))
|
||||
entries.append(.fileSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.file.wifi.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.fileReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.file.wifi.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.fileSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.file.wifi.outgoing, formatting: formatting)))
|
||||
entries.append(.fileReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.file.wifi.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.callHeader(presentationData.theme, presentationData.strings.NetworkUsageSettings_CallDataSection))
|
||||
entries.append(.callSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.call.wifi.outgoing, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.callReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.call.wifi.incoming, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.callSent(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesSent, dataSizeString(stats.call.wifi.outgoing, formatting: formatting)))
|
||||
entries.append(.callReceived(presentationData.theme, presentationData.strings.NetworkUsageSettings_BytesReceived, dataSizeString(stats.call.wifi.incoming, formatting: formatting)))
|
||||
|
||||
entries.append(.reset(presentationData.theme, section, presentationData.strings.NetworkUsageSettings_ResetStats))
|
||||
if stats.resetWifiTimestamp != 0 {
|
||||
|
@ -71,7 +71,7 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
case maximumSizeInfo(PresentationTheme, String)
|
||||
|
||||
case storageHeader(PresentationTheme, String)
|
||||
case storageUsage(PresentationTheme, PresentationDateTimeFormat, [StorageUsageCategory])
|
||||
case storageUsage(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, [StorageUsageCategory])
|
||||
case collecting(PresentationTheme, String)
|
||||
case clearAll(PresentationTheme, String, Bool)
|
||||
|
||||
@ -164,8 +164,8 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .storageUsage(lhsTheme, lhsDateTimeFormat, lhsCategories):
|
||||
if case let .storageUsage(rhsTheme, rhsDateTimeFormat, rhsCategories) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsCategories == rhsCategories {
|
||||
case let .storageUsage(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsCategories):
|
||||
if case let .storageUsage(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsCategories) = rhs, lhsTheme === rhsTheme, lhsStrings == rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsCategories == rhsCategories {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -249,8 +249,8 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .storageHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .storageUsage(theme, dateTimeFormat, categories):
|
||||
return StorageUsageItem(theme: theme, dateTimeFormat: dateTimeFormat, categories: categories, sectionId: self.section)
|
||||
case let .storageUsage(theme, strings, dateTimeFormat, categories):
|
||||
return StorageUsageItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, categories: categories, sectionId: self.section)
|
||||
case let .collecting(theme, text):
|
||||
return CalculatingCacheSizeItem(theme: theme, title: text, sectionId: self.section, style: .blocks)
|
||||
case let .clearAll(_, text, enabled):
|
||||
@ -262,7 +262,7 @@ private enum StorageUsageEntry: ItemListNodeEntry {
|
||||
case let .peersHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .peer(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, chatPeer, value, revealed):
|
||||
var options: [ItemListPeerItemRevealOption] = [ItemListPeerItemRevealOption(type: .destructive, title: strings.ClearCache_Clear, action: {
|
||||
let options: [ItemListPeerItemRevealOption] = [ItemListPeerItemRevealOption(type: .destructive, title: strings.ClearCache_Clear, action: {
|
||||
arguments.clearPeerMedia(peer.id)
|
||||
})]
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, aliasHandling: .threatSelfAsSaved, nameColor: chatPeer == nil ? .primary : .secret, presence: nil, text: .none, label: .disclosure(value), editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
@ -340,7 +340,7 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c
|
||||
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageOtherApps, size: otherAppsSpace, fraction: CGFloat(otherAppsSpace) / totalSpaceValue, color: presentationData.theme.list.itemBarChart.color2))
|
||||
categories.append(StorageUsageCategory(title: presentationData.strings.ClearCache_StorageFree, size: freeSpace, fraction: CGFloat(freeSpace) / totalSpaceValue, color: presentationData.theme.list.itemBarChart.color3))
|
||||
|
||||
entries.append(.storageUsage(presentationData.theme, presentationData.dateTimeFormat, categories))
|
||||
entries.append(.storageUsage(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, categories))
|
||||
|
||||
entries.append(.clearAll(presentationData.theme, presentationData.strings.ClearCache_ClearCache, telegramCacheSize > 0))
|
||||
|
||||
@ -358,7 +358,7 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c
|
||||
chatPeer = mainPeer
|
||||
mainPeer = associatedPeer
|
||||
}
|
||||
entries.append(.peer(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, mainPeer, chatPeer, dataSizeString(size, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator), state.peerIdWithRevealedOptions == peer.id))
|
||||
entries.append(.peer(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, mainPeer, chatPeer, dataSizeString(size, formatting: DataSizeStringFormatting(presentationData: presentationData)), state.peerIdWithRevealedOptions == peer.id))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
@ -490,7 +490,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
if filteredSize == 0 {
|
||||
title = presentationData.strings.Cache_ClearNone
|
||||
} else {
|
||||
title = presentationData.strings.Cache_Clear("\(dataSizeString(filteredSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))").0
|
||||
title = presentationData.strings.Cache_Clear("\(dataSizeString(filteredSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").0
|
||||
}
|
||||
|
||||
if let item = item as? ActionSheetButtonItem {
|
||||
@ -527,7 +527,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
let categorySize: Int64 = size
|
||||
totalSize += categorySize
|
||||
let index = itemIndex
|
||||
items.append(ActionSheetCheckboxItem(title: stringForCategory(strings: presentationData.strings, category: categoryId), label: dataSizeString(categorySize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator), value: true, action: { value in
|
||||
items.append(ActionSheetCheckboxItem(title: stringForCategory(strings: presentationData.strings, category: categoryId), label: dataSizeString(categorySize, formatting: DataSizeStringFormatting(presentationData: presentationData)), value: true, action: { value in
|
||||
toggleCheck(categoryId, index)
|
||||
}))
|
||||
itemIndex += 1
|
||||
@ -537,7 +537,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
if otherSize.1 != 0 {
|
||||
totalSize += otherSize.1
|
||||
let index = itemIndex
|
||||
items.append(ActionSheetCheckboxItem(title: presentationData.strings.Localization_LanguageOther, label: dataSizeString(otherSize.1, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator), value: true, action: { value in
|
||||
items.append(ActionSheetCheckboxItem(title: presentationData.strings.Localization_LanguageOther, label: dataSizeString(otherSize.1, formatting: DataSizeStringFormatting(presentationData: presentationData)), value: true, action: { value in
|
||||
toggleCheck(nil, index)
|
||||
}))
|
||||
itemIndex += 1
|
||||
@ -545,7 +545,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
selectedSize = totalSize
|
||||
|
||||
if !items.isEmpty {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))").0, action: {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").0, action: {
|
||||
if let statsPromise = statsPromise {
|
||||
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
||||
|
||||
@ -637,7 +637,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
statsPromise.set(.single(.result(resultStats)))
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
||||
}))
|
||||
}
|
||||
|
||||
@ -692,7 +692,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
if filteredSize == 0 {
|
||||
title = presentationData.strings.Cache_ClearNone
|
||||
} else {
|
||||
title = presentationData.strings.Cache_Clear("\(dataSizeString(filteredSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))").0
|
||||
title = presentationData.strings.Cache_Clear("\(dataSizeString(filteredSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").0
|
||||
}
|
||||
|
||||
if let item = item as? ActionSheetButtonItem {
|
||||
@ -732,7 +732,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
totalSize += categorySize
|
||||
if categorySize > 1024 {
|
||||
let index = itemIndex
|
||||
items.append(ActionSheetCheckboxItem(title: stringForCategory(strings: presentationData.strings, category: categoryId), label: dataSizeString(categorySize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator), value: true, action: { value in
|
||||
items.append(ActionSheetCheckboxItem(title: stringForCategory(strings: presentationData.strings, category: categoryId), label: dataSizeString(categorySize, formatting: DataSizeStringFormatting(presentationData: presentationData)), value: true, action: { value in
|
||||
toggleCheck(categoryId, index)
|
||||
}))
|
||||
itemIndex += 1
|
||||
@ -742,7 +742,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
selectedSize = totalSize
|
||||
|
||||
if !items.isEmpty {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))").0, action: {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").0, action: {
|
||||
if let statsPromise = statsPromise {
|
||||
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
||||
var clearMediaIds = Set<MediaId>()
|
||||
@ -818,7 +818,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
statsPromise.set(.single(.result(resultStats)))
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
||||
}))
|
||||
}
|
||||
|
||||
@ -945,7 +945,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
||||
clearDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
statsPromise.set(.single(.result(resultStats)))
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(totalSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), .current, nil)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,14 @@ struct StorageUsageCategory: Equatable {
|
||||
|
||||
final class StorageUsageItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let categories: [StorageUsageCategory]
|
||||
let sectionId: ItemListSectionId
|
||||
|
||||
init(theme: PresentationTheme, dateTimeFormat: PresentationDateTimeFormat, categories: [StorageUsageCategory], sectionId: ItemListSectionId) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, categories: [StorageUsageCategory], sectionId: ItemListSectionId) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.categories = categories
|
||||
self.sectionId = sectionId
|
||||
@ -187,7 +189,7 @@ private final class StorageUsageItemNode: ListViewItemNode {
|
||||
let category = item.categories[i]
|
||||
|
||||
let attributedString = NSMutableAttributedString(string: category.title, font: Font.regular(14.0), textColor: item.theme.list.itemPrimaryTextColor, paragraphAlignment: .natural)
|
||||
attributedString.append(NSAttributedString(string: " • \(dataSizeString(category.size, forceDecimal: true, decimalSeparator: item.dateTimeFormat.decimalSeparator))", font: Font.bold(14.0), textColor: item.theme.list.itemPrimaryTextColor))
|
||||
attributedString.append(NSAttributedString(string: " • \(dataSizeString(category.size, forceDecimal: true, formatting: DataSizeStringFormatting(strings: item.strings, decimalSeparator: item.dateTimeFormat.decimalSeparator)))", font: Font.bold(14.0), textColor: item.theme.list.itemPrimaryTextColor))
|
||||
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 60.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
|
@ -238,7 +238,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
case .builtin:
|
||||
displaySize = CGSize(width: 1308.0, height: 2688.0).fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor
|
||||
contentSize = displaySize
|
||||
signal = settingsBuiltinWallpaperImage(account: context.account)
|
||||
signal = settingsBuiltinWallpaperImage(account: self.context.account)
|
||||
fetchSignal = .complete()
|
||||
statusSignal = .single(.Local)
|
||||
subtitleSignal = .single(nil)
|
||||
@ -316,8 +316,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
|
||||
self.colorPreview = self.arguments.colorPreview
|
||||
|
||||
signal = patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true)
|
||||
colorSignal = chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: context.account.postbox.mediaBox)
|
||||
signal = patternWallpaperImage(account: self.context.account, accountManager: self.context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true)
|
||||
colorSignal = chatServiceBackgroundColor(wallpaper: wallpaper, mediaBox: self.context.account.postbox.mediaBox)
|
||||
|
||||
isBlurrable = false
|
||||
} else {
|
||||
@ -341,7 +341,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
}
|
||||
}
|
||||
if let fileSize = file.file.size {
|
||||
subtitleSignal = .single(dataSizeString(fileSize))
|
||||
subtitleSignal = .single(dataSizeString(fileSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData)))
|
||||
} else {
|
||||
subtitleSignal = .single(nil)
|
||||
}
|
||||
@ -374,7 +374,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
||||
}
|
||||
}
|
||||
if let fileSize = largestSize.resource.size {
|
||||
subtitleSignal = .single(dataSizeString(fileSize))
|
||||
subtitleSignal = .single(dataSizeString(fileSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData)))
|
||||
} else {
|
||||
subtitleSignal = .single(nil)
|
||||
}
|
||||
|
@ -496,7 +496,7 @@ private enum PlayPauseIconNodeState: Equatable {
|
||||
}
|
||||
|
||||
private final class PlayPauseIconNode: ManagedAnimationNode {
|
||||
private let duration: Double = 0.4
|
||||
private let duration: Double = 0.35
|
||||
private var iconState: PlayPauseIconNodeState = .pause
|
||||
|
||||
init() {
|
||||
|
@ -1920,7 +1920,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
for participant in membersValue.participants {
|
||||
if participant.peer.id == self.joinAsPeerId {
|
||||
if participant.raiseHandRating != nil {
|
||||
if participant.hasRaiseHand {
|
||||
return
|
||||
}
|
||||
break
|
||||
@ -1936,7 +1936,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
for participant in membersValue.participants {
|
||||
if participant.peer.id == self.joinAsPeerId {
|
||||
if participant.raiseHandRating == nil {
|
||||
if !participant.hasRaiseHand {
|
||||
return
|
||||
}
|
||||
break
|
||||
|
@ -140,7 +140,7 @@ private final class VoiceChatControllerTitleNode: ASDisplayNode {
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: UIColor(rgb: 0xffffff))
|
||||
self.infoNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.5))
|
||||
|
||||
let constrainedSize = CGSize(width: size.width - 120.0, height: size.height)
|
||||
let constrainedSize = CGSize(width: size.width - 140.0, height: size.height)
|
||||
let titleSize = self.titleNode.measure(constrainedSize)
|
||||
let infoSize = self.infoNode.measure(constrainedSize)
|
||||
let titleInfoSpacing: CGFloat = 0.0
|
||||
@ -1278,11 +1278,28 @@ public final class VoiceChatController: ViewController {
|
||||
let context = strongSelf.context
|
||||
strongSelf.controller?.dismiss(completion: {
|
||||
Queue.mainQueue().justDispatch {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer.id), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||
if peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
var expandAvatar = true
|
||||
if peer.smallProfileImage == nil {
|
||||
expandAvatar = false
|
||||
}
|
||||
if let (validLayout, _) = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet {
|
||||
expandAvatar = false
|
||||
}
|
||||
if let strongSelf = self, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: expandAvatar, fromChat: false) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer.id), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
f(.default)
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
|
||||
if let callState = strongSelf.callState, (callState.canManageCall && !callState.adminIds.contains(peer.id)) {
|
||||
@ -2105,18 +2122,30 @@ public final class VoiceChatController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (strongSelf.call.leave(terminateIfPossible: true)
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
strongSelf.leaveDisposable.set((strongSelf.call.leave(terminateIfPossible: true)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
self?.controller?.dismiss()
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData.withUpdated(theme: self.darkTheme))
|
||||
var items: [ActionSheetItem] = []
|
||||
|
||||
items.append(ActionSheetTextItem(title: self.presentationData.strings.VoiceChat_LeaveConfirmation))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.VoiceChat_LeaveAndEndVoiceChat, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
if let strongSelf = self {
|
||||
if let (members, _) = strongSelf.currentCallMembers, members.count >= 10 || true {
|
||||
let alertController = textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: strongSelf.presentationData.strings.VoiceChat_EndConfirmationTitle, text: strongSelf.presentationData.strings.VoiceChat_EndConfirmationText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.VoiceChat_EndConfirmationEnd, action: {
|
||||
action()
|
||||
})])
|
||||
strongSelf.controller?.present(alertController, in: .window(.root))
|
||||
} else {
|
||||
action()
|
||||
}
|
||||
}
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.VoiceChat_LeaveVoiceChat, color: .accent, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
@ -2130,12 +2159,6 @@ public final class VoiceChatController: ViewController {
|
||||
}))
|
||||
}))
|
||||
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.VoiceChat_LeaveAndEndVoiceChat, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
action()
|
||||
}))
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
|
@ -1524,6 +1524,11 @@ public final class GroupCallParticipantsContext {
|
||||
strongSelf.isLoadingMore = false
|
||||
strongSelf.shouldResetStateFromServer = false
|
||||
var state = state
|
||||
state.adminIds = strongSelf.stateValue.state.adminIds
|
||||
state.isCreator = strongSelf.stateValue.state.isCreator
|
||||
state.defaultParticipantsAreMuted = strongSelf.stateValue.state.defaultParticipantsAreMuted
|
||||
state.title = strongSelf.stateValue.state.title
|
||||
state.recordingStartTimestamp = strongSelf.stateValue.state.recordingStartTimestamp
|
||||
state.mergeActivity(from: strongSelf.stateValue.state, myPeerId: nil, previousMyPeerId: nil, mergeActivityTimestamps: false)
|
||||
strongSelf.stateValue.state = state
|
||||
strongSelf.endedProcessingUpdate()
|
||||
|
@ -4,33 +4,49 @@ import Foundation
|
||||
private final class LinkHelperClass: NSObject {
|
||||
}
|
||||
|
||||
public func dataSizeString(_ size: Int, forceDecimal: Bool = false, decimalSeparator: String = ".") -> String {
|
||||
return dataSizeString(Int64(size), forceDecimal: forceDecimal, decimalSeparator: decimalSeparator)
|
||||
public func dataSizeString(_ size: Int, forceDecimal: Bool = false, formatting: DataSizeStringFormatting) -> String {
|
||||
return dataSizeString(Int64(size), forceDecimal: forceDecimal, formatting: formatting)
|
||||
}
|
||||
|
||||
public func dataSizeString(_ size: Int64, forceDecimal: Bool = false, decimalSeparator: String = ".") -> String {
|
||||
public struct DataSizeStringFormatting {
|
||||
let decimalSeparator: String
|
||||
let byte: (String) -> (String, [(Int, NSRange)])
|
||||
let kilobyte: (String) -> (String, [(Int, NSRange)])
|
||||
let megabyte: (String) -> (String, [(Int, NSRange)])
|
||||
let gigabyte: (String) -> (String, [(Int, NSRange)])
|
||||
|
||||
public init(decimalSeparator: String, byte: @escaping (String) -> (String, [(Int, NSRange)]), kilobyte: @escaping (String) -> (String, [(Int, NSRange)]), megabyte: @escaping (String) -> (String, [(Int, NSRange)]), gigabyte: @escaping (String) -> (String, [(Int, NSRange)])) {
|
||||
self.decimalSeparator = decimalSeparator
|
||||
self.byte = byte
|
||||
self.kilobyte = kilobyte
|
||||
self.megabyte = megabyte
|
||||
self.gigabyte = gigabyte
|
||||
}
|
||||
}
|
||||
|
||||
public func dataSizeString(_ size: Int64, forceDecimal: Bool = false, formatting: DataSizeStringFormatting) -> String {
|
||||
if size >= 1024 * 1024 * 1024 {
|
||||
let remainder = Int64((Double(size % (1024 * 1024 * 1024)) / (1024 * 1024 * 102.4)).rounded(.down))
|
||||
if remainder != 0 || forceDecimal {
|
||||
return "\(size / (1024 * 1024 * 1024))\(decimalSeparator)\(remainder) GB"
|
||||
return formatting.gigabyte("\(size / (1024 * 1024 * 1024))\(formatting.decimalSeparator)\(remainder)").0
|
||||
} else {
|
||||
return "\(size / (1024 * 1024 * 1024)) GB"
|
||||
return formatting.gigabyte("\(size / (1024 * 1024 * 1024))").0
|
||||
}
|
||||
} else if size >= 1024 * 1024 {
|
||||
let remainder = Int64((Double(size % (1024 * 1024)) / (1024.0 * 102.4)).rounded(.down))
|
||||
if remainder != 0 || forceDecimal {
|
||||
return "\(size / (1024 * 1024))\(decimalSeparator)\(remainder) MB"
|
||||
return formatting.megabyte( "\(size / (1024 * 1024))\(formatting.decimalSeparator)\(remainder)").0
|
||||
} else {
|
||||
return "\(size / (1024 * 1024)) MB"
|
||||
return formatting.megabyte("\(size / (1024 * 1024))").0
|
||||
}
|
||||
} else if size >= 1024 {
|
||||
let remainder = (size % (1024)) / (102)
|
||||
if remainder != 0 || forceDecimal {
|
||||
return "\(size / 1024)\(decimalSeparator)\(remainder) KB"
|
||||
return formatting.kilobyte("\(size / 1024)\(formatting.decimalSeparator)\(remainder)").0
|
||||
} else {
|
||||
return "\(size / 1024) KB"
|
||||
return formatting.kilobyte("\(size / 1024)").0
|
||||
}
|
||||
} else {
|
||||
return "\(size) B"
|
||||
return formatting.byte("\(size)").0
|
||||
}
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ public final class PermissionContentNode: ASDisplayNode {
|
||||
}
|
||||
if let _ = self.animationNode, size.width < size.height {
|
||||
imageSpacing = floor(availableHeight * 0.12)
|
||||
imageSize = CGSize(width: 200.0, height: 200.0)
|
||||
imageSize = CGSize(width: 240.0, height: 240.0)
|
||||
contentHeight += imageSize.height + imageSpacing
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,17 @@
|
||||
import Foundation
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
|
||||
public extension DataSizeStringFormatting {
|
||||
init(presentationData: PresentationData) {
|
||||
self.init(decimalSeparator: presentationData.dateTimeFormat.decimalSeparator, byte: presentationData.strings.FileSize_B(_:), kilobyte: presentationData.strings.FileSize_KB(_:), megabyte: presentationData.strings.FileSize_MB(_:), gigabyte: presentationData.strings.FileSize_GB(_:))
|
||||
}
|
||||
|
||||
init (chatPresentationData: ChatPresentationData) {
|
||||
self.init(decimalSeparator: chatPresentationData.dateTimeFormat.decimalSeparator, byte: chatPresentationData.strings.FileSize_B(_:), kilobyte: chatPresentationData.strings.FileSize_KB(_:), megabyte: chatPresentationData.strings.FileSize_MB(_:), gigabyte: chatPresentationData.strings.FileSize_GB(_:))
|
||||
}
|
||||
|
||||
init (strings: PresentationStrings, decimalSeparator: String) {
|
||||
self.init(decimalSeparator: decimalSeparator, byte: strings.FileSize_B(_:), kilobyte: strings.FileSize_KB(_:), megabyte: strings.FileSize_MB(_:), gigabyte: strings.FileSize_GB(_:))
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_goback15.pdf"
|
||||
"filename" : "back.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -1,12 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_go15.pdf"
|
||||
"filename" : "forward.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Media Gallery/Fullscreen.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Gallery/Fullscreen.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "msg_maxvideo.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/Fullscreen.imageset/msg_maxvideo.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/Fullscreen.imageset/msg_maxvideo.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Media Gallery/Minimize.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Gallery/Minimize.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "msg_minvideo.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/Minimize.imageset/msg_minvideo.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/Minimize.imageset/msg_minvideo.pdf
vendored
Normal file
Binary file not shown.
@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "soundoff (2).pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "soundon (2).pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -668,6 +668,10 @@ final class SharedApplicationContext {
|
||||
} else {
|
||||
completion(false)
|
||||
}
|
||||
}, forceOrientation: { orientation in
|
||||
let value = orientation.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
UINavigationController.attemptRotationToDeviceOrientation()
|
||||
})
|
||||
|
||||
let accountManagerSignal = Signal<AccountManager, NoError> { subscriber in
|
||||
|
@ -1436,6 +1436,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self?.commitPurposefulAction()
|
||||
}
|
||||
}
|
||||
shareController.actionCompleted = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .linkCopied(text: strongSelf.presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
}
|
||||
shareController.completed = { [weak self] peerIds in
|
||||
if let strongSelf = self {
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in
|
||||
@ -8075,7 +8080,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if filteredSize == 0 {
|
||||
title = presentationData.strings.Cache_ClearNone
|
||||
} else {
|
||||
title = presentationData.strings.Cache_Clear("\(dataSizeString(filteredSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))").0
|
||||
title = presentationData.strings.Cache_Clear("\(dataSizeString(filteredSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").0
|
||||
}
|
||||
|
||||
if let item = item as? ActionSheetButtonItem {
|
||||
@ -8128,7 +8133,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
totalSize += categorySize
|
||||
if categorySize > 1024 {
|
||||
let index = itemIndex
|
||||
items.append(ActionSheetCheckboxItem(title: stringForCategory(strings: presentationData.strings, category: categoryId), label: dataSizeString(categorySize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator), value: true, action: { value in
|
||||
items.append(ActionSheetCheckboxItem(title: stringForCategory(strings: presentationData.strings, category: categoryId), label: dataSizeString(categorySize, formatting: DataSizeStringFormatting(presentationData: presentationData)), value: true, action: { value in
|
||||
toggleCheck(categoryId, index)
|
||||
}))
|
||||
itemIndex += 1
|
||||
@ -8140,7 +8145,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if items.isEmpty {
|
||||
strongSelf.presentClearCacheSuggestion()
|
||||
} else {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))").0, action: {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").0, action: {
|
||||
let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 })
|
||||
var clearMediaIds = Set<MediaId>()
|
||||
|
||||
@ -8198,7 +8203,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
disposable.set((signal
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
if let strongSelf = self, let _ = strongSelf.validLayout {
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.ClearCache_Success("\(dataSizeString(selectedSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))", stringForDeviceType()).0), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
}))
|
||||
|
||||
@ -8860,58 +8865,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
private func presentContactPicker() {
|
||||
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: self.context, title: { $0.Contacts_Title }, displayDeviceContacts: true))
|
||||
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: self.context, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true))
|
||||
contactsController.navigationPresentation = .modal
|
||||
self.chatDisplayNode.dismissInput()
|
||||
self.effectiveNavigationController?.pushViewController(contactsController)
|
||||
self.controllerNavigationDisposable.set((contactsController.result
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
if let strongSelf = self, let (peer, _) = peer {
|
||||
let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError>
|
||||
switch peer {
|
||||
case let .peer(contact, _, _):
|
||||
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
|
||||
return
|
||||
}
|
||||
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||
let context = strongSelf.context
|
||||
dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:]))
|
||||
|> take(1)
|
||||
|> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in
|
||||
var stableId: String?
|
||||
let queryPhoneNumber = formatPhoneNumber(phoneNumber)
|
||||
outer: for (id, data) in basicData {
|
||||
for phoneNumber in data.phoneNumbers {
|
||||
if formatPhoneNumber(phoneNumber.value) == queryPhoneNumber {
|
||||
stableId = id
|
||||
break outer
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||
if let strongSelf = self, let (peers, _) = peers {
|
||||
if peers.count > 1 {
|
||||
var enqueueMessages: [EnqueueMessage] = []
|
||||
for peer in peers {
|
||||
var media: TelegramMediaContact?
|
||||
switch peer {
|
||||
case let .peer(contact, _, _):
|
||||
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if let stableId = stableId {
|
||||
return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil))
|
||||
|> take(1)
|
||||
|> map { extendedData -> (Peer?, DeviceContactExtendedData?) in
|
||||
return (contact, extendedData)
|
||||
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||
|
||||
let phone = contactData.basicData.phoneNumbers[0].value
|
||||
media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil)
|
||||
case let .deviceContact(_, basicData):
|
||||
guard !basicData.phoneNumbers.isEmpty else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
return .single((contact, contactData))
|
||||
}
|
||||
let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||
|
||||
let phone = contactData.basicData.phoneNumbers[0].value
|
||||
media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil)
|
||||
}
|
||||
case let .deviceContact(id, _):
|
||||
dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil))
|
||||
|> take(1)
|
||||
|> map { extendedData -> (Peer?, DeviceContactExtendedData?) in
|
||||
return (nil, extendedData)
|
||||
}
|
||||
}
|
||||
strongSelf.controllerNavigationDisposable.set((dataSignal
|
||||
|> deliverOnMainQueue).start(next: { peerAndContactData in
|
||||
if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 {
|
||||
if contactData.isPrimitive {
|
||||
let phone = contactData.basicData.phoneNumbers[0].value
|
||||
let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil)
|
||||
|
||||
if let media = media {
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
@ -8921,31 +8905,91 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
})
|
||||
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil)
|
||||
strongSelf.sendMessages([message])
|
||||
} else {
|
||||
let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in
|
||||
guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else {
|
||||
return
|
||||
}
|
||||
let phone = contactData.basicData.phoneNumbers[0].value
|
||||
if let vCardData = contactData.serializedVCard() {
|
||||
let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData)
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
||||
})
|
||||
}
|
||||
})
|
||||
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil)
|
||||
strongSelf.sendMessages([message])
|
||||
}
|
||||
}), completed: nil, cancelled: nil)
|
||||
strongSelf.effectiveNavigationController?.pushViewController(contactController)
|
||||
enqueueMessages.append(message)
|
||||
}
|
||||
}
|
||||
}))
|
||||
strongSelf.sendMessages(enqueueMessages)
|
||||
} else if let peer = peers.first {
|
||||
let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError>
|
||||
switch peer {
|
||||
case let .peer(contact, _, _):
|
||||
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
|
||||
return
|
||||
}
|
||||
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||
let context = strongSelf.context
|
||||
dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:]))
|
||||
|> take(1)
|
||||
|> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in
|
||||
var stableId: String?
|
||||
let queryPhoneNumber = formatPhoneNumber(phoneNumber)
|
||||
outer: for (id, data) in basicData {
|
||||
for phoneNumber in data.phoneNumbers {
|
||||
if formatPhoneNumber(phoneNumber.value) == queryPhoneNumber {
|
||||
stableId = id
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let stableId = stableId {
|
||||
return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil))
|
||||
|> take(1)
|
||||
|> map { extendedData -> (Peer?, DeviceContactExtendedData?) in
|
||||
return (contact, extendedData)
|
||||
}
|
||||
} else {
|
||||
return .single((contact, contactData))
|
||||
}
|
||||
}
|
||||
case let .deviceContact(id, _):
|
||||
dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil))
|
||||
|> take(1)
|
||||
|> map { extendedData -> (Peer?, DeviceContactExtendedData?) in
|
||||
return (nil, extendedData)
|
||||
}
|
||||
}
|
||||
strongSelf.controllerNavigationDisposable.set((dataSignal
|
||||
|> deliverOnMainQueue).start(next: { peerAndContactData in
|
||||
if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 {
|
||||
if contactData.isPrimitive {
|
||||
let phone = contactData.basicData.phoneNumbers[0].value
|
||||
let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil)
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
||||
})
|
||||
}
|
||||
})
|
||||
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil)
|
||||
strongSelf.sendMessages([message])
|
||||
} else {
|
||||
let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in
|
||||
guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else {
|
||||
return
|
||||
}
|
||||
let phone = contactData.basicData.phoneNumbers[0].value
|
||||
if let vCardData = contactData.serializedVCard() {
|
||||
let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData)
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
||||
})
|
||||
}
|
||||
})
|
||||
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil)
|
||||
strongSelf.sendMessages([message])
|
||||
}
|
||||
}), completed: nil, cancelled: nil)
|
||||
strongSelf.effectiveNavigationController?.pushViewController(contactController)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -10487,7 +10531,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var attemptSelectionImpl: ((Peer) -> Void)?
|
||||
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: filter, attemptSelection: { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}))
|
||||
}, multipleSelection: true))
|
||||
let context = self.context
|
||||
attemptSelectionImpl = { [weak controller] peer in
|
||||
guard let controller = controller else {
|
||||
@ -11229,7 +11273,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
openUserGeneratedUrl(context: self.context, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, present: { [weak self] c in
|
||||
openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, present: { [weak self] c in
|
||||
self?.present(c, in: .window(.root))
|
||||
}, openResolved: { [weak self] resolved in
|
||||
self?.openResolved(resolved)
|
||||
|
@ -222,7 +222,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
return { context, presentationData, message, associatedData, chatLocation, attributes, isPinned, forcedIsEdited, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize in
|
||||
return (CGFloat.greatestFiniteMagnitude, { constrainedSize in
|
||||
let titleFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
|
||||
let descriptionFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0))
|
||||
let descriptionFont = Font.with(size: floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
||||
let durationFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 11.0 / 17.0))
|
||||
|
||||
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
@ -384,7 +384,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
if let performer = performer {
|
||||
descriptionText = performer
|
||||
} else if let size = file.size {
|
||||
descriptionText = dataSizeString(size, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
descriptionText = dataSizeString(size, formatting: DataSizeStringFormatting(chatPresentationData: presentationData))
|
||||
} else {
|
||||
descriptionText = ""
|
||||
}
|
||||
@ -408,7 +408,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
} else if !isVoice {
|
||||
let descriptionText: String
|
||||
if let size = file.size {
|
||||
descriptionText = dataSizeString(size, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
descriptionText = dataSizeString(size, formatting: DataSizeStringFormatting(chatPresentationData: presentationData))
|
||||
} else {
|
||||
descriptionText = ""
|
||||
}
|
||||
@ -818,9 +818,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
switch fetchStatus {
|
||||
case let .Fetching(_, progress):
|
||||
if let size = file.size {
|
||||
let compactString = dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
let descriptionFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0))
|
||||
downloadingStrings = ("\(compactString) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))", compactString, descriptionFont)
|
||||
let compactString = dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: presentationData))
|
||||
let descriptionFont = Font.with(size: floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
||||
downloadingStrings = ("\(compactString) / \(dataSizeString(size, forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: presentationData)))", compactString, descriptionFont)
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
@ -1077,6 +1077,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
|
||||
let gifTitle = game != nil ? strings.Message_Game.uppercased() : strings.Message_Animation.uppercased()
|
||||
|
||||
let formatting = DataSizeStringFormatting(strings: strings, decimalSeparator: decimalSeparator)
|
||||
|
||||
switch fetchStatus {
|
||||
case let .Fetching(_, progress):
|
||||
let adjustedProgress = max(progress, 0.027)
|
||||
@ -1093,11 +1095,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
if let file = self.media as? TelegramMediaFile {
|
||||
if wideLayout {
|
||||
if let size = file.size {
|
||||
let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: decimalSeparator))"
|
||||
if file.isAnimated {
|
||||
badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: "\(gifTitle)", size: nil, muted: false, active: false)
|
||||
}
|
||||
else if let duration = file.duration, !message.flags.contains(.Unsent) {
|
||||
let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: formatting)) / \(dataSizeString(size, forceDecimal: true, formatting: formatting))"
|
||||
if let duration = file.duration, !message.flags.contains(.Unsent) {
|
||||
let durationString = file.isAnimated ? gifTitle : stringForDuration(playerDuration > 0 ? playerDuration : duration, position: playerPosition)
|
||||
if isMediaStreamable(message: message, media: file) {
|
||||
badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: durationString, size: active ? sizeString : nil, muted: muted, active: active)
|
||||
@ -1116,7 +1115,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
state = automaticPlayback ? .none : state
|
||||
}
|
||||
} else {
|
||||
badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: decimalSeparator))", size: nil, muted: false, active: false)
|
||||
badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: formatting)) / \(dataSizeString(size, forceDecimal: true, formatting: formatting))", size: nil, muted: false, active: false)
|
||||
}
|
||||
} else if let _ = file.duration {
|
||||
if file.isAnimated {
|
||||
@ -1130,7 +1129,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
}
|
||||
} else {
|
||||
if isMediaStreamable(message: message, media: file), let size = file.size {
|
||||
let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: decimalSeparator))"
|
||||
let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: formatting)) / \(dataSizeString(size, forceDecimal: true, formatting: formatting))"
|
||||
|
||||
if message.flags.contains(.Unsent), let duration = file.duration {
|
||||
let durationString = stringForDuration(playerDuration > 0 ? playerDuration : duration, position: playerPosition)
|
||||
@ -1158,7 +1157,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
let durationString = stringForDuration(playerDuration > 0 ? playerDuration : duration, position: playerPosition)
|
||||
|
||||
if automaticPlayback, let size = file.size {
|
||||
let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: decimalSeparator))"
|
||||
let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: formatting)) / \(dataSizeString(size, forceDecimal: true, formatting: formatting))"
|
||||
mediaDownloadState = .fetching(progress: progress)
|
||||
badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: durationString, size: active ? sizeString : nil, muted: muted, active: active)
|
||||
} else {
|
||||
@ -1205,15 +1204,15 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
case .Remote:
|
||||
state = .download(messageTheme.mediaOverlayControlColors.foregroundColor)
|
||||
if let file = self.media as? TelegramMediaFile {
|
||||
if file.isAnimated && (!automaticDownload || !automaticPlayback) {
|
||||
let string = "\(gifTitle) " + dataSizeString(file.size ?? 0, decimalSeparator: decimalSeparator)
|
||||
if false, file.isAnimated && (!automaticDownload || !automaticPlayback) {
|
||||
let string = "\(gifTitle) " + dataSizeString(file.size ?? 0, formatting: formatting)
|
||||
badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: string, size: nil, muted: false, active: false)
|
||||
} else {
|
||||
let durationString = file.isAnimated ? gifTitle : stringForDuration(playerDuration > 0 ? playerDuration : (file.duration ?? 0), position: playerPosition)
|
||||
if wideLayout {
|
||||
if isMediaStreamable(message: message, media: file) {
|
||||
state = automaticPlayback ? .none : .play(messageTheme.mediaOverlayControlColors.foregroundColor)
|
||||
badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: durationString, size: dataSizeString(file.size ?? 0, decimalSeparator: decimalSeparator), muted: muted, active: true)
|
||||
badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: durationString, size: dataSizeString(file.size ?? 0, formatting: formatting), muted: muted, active: true)
|
||||
mediaDownloadState = .remote
|
||||
} else {
|
||||
state = automaticPlayback ? .none : state
|
||||
|
@ -182,7 +182,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
let media = WallpaperPreviewMedia(content: .file(file, topColor, bottomColor, rotation, false, false))
|
||||
mediaAndFlags = (media, [.preferMediaAspectFilled])
|
||||
if let fileSize = file.size {
|
||||
badge = dataSizeString(fileSize, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
|
||||
badge = dataSizeString(fileSize, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))
|
||||
}
|
||||
} else {
|
||||
mediaAndFlags = (file, [])
|
||||
|
@ -880,7 +880,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
private func openUrl(_ url: String) {
|
||||
self.navigationActionDisposable.set((self.context.sharedContext.resolveUrl(account: self.context.account, url: url, skipUrlAuth: true) |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
self.navigationActionDisposable.set((self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url, skipUrlAuth: true) |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
switch result {
|
||||
case let .externalUrl(url):
|
||||
|
@ -308,7 +308,7 @@ private enum PlayPauseIconNodeState: Equatable {
|
||||
}
|
||||
|
||||
private final class PlayPauseIconNode: ManagedAnimationNode {
|
||||
private let duration: Double = 0.4
|
||||
private let duration: Double = 0.35
|
||||
private var iconState: PlayPauseIconNodeState = .pause
|
||||
|
||||
init() {
|
||||
|
@ -157,8 +157,8 @@ public class ComposeControllerImpl: ViewController, ComposeController {
|
||||
let controller = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, autoDismiss: false, title: { $0.Compose_NewEncryptedChatTitle }))
|
||||
strongSelf.createActionDisposable.set((controller.result
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] peer in
|
||||
if let strongSelf = self, let (contactPeer, _) = peer, case let .peer(peer, _, _) = contactPeer {
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] result in
|
||||
if let strongSelf = self, let (contactPeers, _) = result, case let .peer(peer, _, _) = contactPeers.first {
|
||||
controller?.dismissSearch()
|
||||
controller?.displayNavigationActivity = true
|
||||
strongSelf.createActionDisposable.set((createSecretChat(account: strongSelf.context.account, peerId: peer.id) |> deliverOnMainQueue).start(next: { peerId in
|
||||
|
@ -41,8 +41,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
||||
return self._ready
|
||||
}
|
||||
|
||||
private let _result = Promise<(ContactListPeer, ContactListAction)?>()
|
||||
var result: Signal<(ContactListPeer, ContactListAction)?, NoError> {
|
||||
private let _result = Promise<([ContactListPeer], ContactListAction)?>()
|
||||
var result: Signal<([ContactListPeer], ContactListAction)?, NoError> {
|
||||
return self._result.get()
|
||||
}
|
||||
|
||||
@ -118,6 +118,10 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
||||
self?.activateSearch()
|
||||
})
|
||||
self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
|
||||
|
||||
if params.multipleSelection {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.beginSelection))
|
||||
}
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
@ -129,6 +133,11 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func beginSelection() {
|
||||
self.navigationItem.rightBarButtonItem = nil
|
||||
self.contactsNode.beginSelection()
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings() {
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
@ -165,7 +174,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
||||
self.contactsNode.contactListNode.openPeer = { [weak self] peer, action in
|
||||
self?.openPeer(peer: peer, action: action)
|
||||
}
|
||||
|
||||
|
||||
self.contactsNode.contactListNode.suppressPermissionWarning = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.context.sharedContext.presentContactsWarningSuppression(context: strongSelf.context, present: { c, a in
|
||||
@ -191,6 +200,16 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
self.contactsNode.requestMultipleAction = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let selectedPeers = strongSelf.contactsNode.contactListNode.selectedPeers
|
||||
strongSelf._result.set(.single((selectedPeers, .generic)))
|
||||
if strongSelf.autoDismiss {
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
@ -263,7 +282,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
||||
self.confirmationDisposable.set((self.confirmation(peer) |> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
if value {
|
||||
strongSelf._result.set(.single((peer, action)))
|
||||
strongSelf._result.set(.single(([peer], action)))
|
||||
if strongSelf.autoDismiss {
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import AccountContext
|
||||
import SearchBarNode
|
||||
import ContactListUI
|
||||
import SearchUI
|
||||
import SolidRoundedButtonNode
|
||||
|
||||
final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
var displayProgress: Bool = false {
|
||||
@ -30,17 +31,22 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private var searchDisplayController: SearchDisplayController?
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat)?
|
||||
|
||||
var navigationBar: NavigationBar?
|
||||
|
||||
var requestDeactivateSearch: (() -> Void)?
|
||||
var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)?
|
||||
var requestMultipleAction: (() -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
|
||||
var presentationData: PresentationData
|
||||
var presentationDataDisposable: Disposable?
|
||||
|
||||
private let countPanelNode: ContactSelectionCountPanelNode
|
||||
|
||||
private var selectionState: ContactListNodeGroupSelectionState?
|
||||
|
||||
init(context: AccountContext, options: [ContactListAdditionalOption], displayDeviceContacts: Bool, displayCallIcons: Bool) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -51,6 +57,11 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
|
||||
var shareImpl: (() -> Void)?
|
||||
self.countPanelNode = ContactSelectionCountPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
||||
shareImpl?()
|
||||
})
|
||||
|
||||
super.init()
|
||||
|
||||
self.setViewBlock({
|
||||
@ -76,12 +87,37 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
self.dimNode.alpha = 0.0
|
||||
self.dimNode.isUserInteractionEnabled = false
|
||||
self.addSubnode(self.dimNode)
|
||||
|
||||
self.addSubnode(self.countPanelNode)
|
||||
|
||||
self.contactListNode.selectionStateUpdated = { [weak self] selectionState in
|
||||
if let strongSelf = self {
|
||||
strongSelf.countPanelNode.count = selectionState?.selectedPeerIndices.count ?? 0
|
||||
let previousState = strongSelf.selectionState
|
||||
strongSelf.selectionState = selectionState
|
||||
if previousState?.selectedPeerIndices.isEmpty != strongSelf.selectionState?.selectedPeerIndices.isEmpty {
|
||||
if let (layout, navigationHeight, actualNavigationHeight) = strongSelf.containerLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, actualNavigationBarHeight: actualNavigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shareImpl = { [weak self] in
|
||||
self?.requestMultipleAction?()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
func beginSelection() {
|
||||
self.contactListNode.updateSelectionState({ _ in
|
||||
return ContactListNodeGroupSelectionState()
|
||||
})
|
||||
}
|
||||
|
||||
private func updateTheme() {
|
||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
self.searchDisplayController?.updatePresentationData(presentationData)
|
||||
@ -89,7 +125,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (layout, navigationBarHeight)
|
||||
self.containerLayout = (layout, navigationBarHeight, actualNavigationBarHeight)
|
||||
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
@ -107,13 +143,21 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
|
||||
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
|
||||
let countPanelHeight = self.countPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: layout.intrinsicInsets.bottom, transition: transition)
|
||||
if (self.selectionState?.selectedPeerIndices.isEmpty ?? true) {
|
||||
transition.updateFrame(node: self.countPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: countPanelHeight)))
|
||||
} else {
|
||||
insets.bottom += countPanelHeight
|
||||
transition.updateFrame(node: self.countPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - countPanelHeight), size: CGSize(width: layout.size.width, height: countPanelHeight)))
|
||||
}
|
||||
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
|
||||
guard let (containerLayout, navigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
||||
guard let (containerLayout, navigationBarHeight, _) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -124,7 +168,35 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
categories.insert(.global)
|
||||
}
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: categories, addContact: nil, openPeer: { [weak self] peer in
|
||||
self?.requestOpenPeerFromSearch?(peer)
|
||||
if let strongSelf = self {
|
||||
var updated = false
|
||||
strongSelf.contactListNode.updateSelectionState { state -> ContactListNodeGroupSelectionState? in
|
||||
if let state = state {
|
||||
updated = true
|
||||
var foundPeers = state.foundPeers
|
||||
var selectedPeerMap = state.selectedPeerMap
|
||||
selectedPeerMap[peer.id] = peer
|
||||
var exists = false
|
||||
for foundPeer in foundPeers {
|
||||
if peer.id == foundPeer.id {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
foundPeers.insert(peer, at: 0)
|
||||
}
|
||||
return state.withToggledPeerId(peer.id).withFoundPeers(foundPeers).withSelectedPeerMap(selectedPeerMap)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if updated {
|
||||
strongSelf.requestDeactivateSearch?()
|
||||
} else {
|
||||
strongSelf.requestOpenPeerFromSearch?(peer)
|
||||
}
|
||||
}
|
||||
}, contextAction: nil), cancel: { [weak self] in
|
||||
if let requestDeactivateSearch = self?.requestDeactivateSearch {
|
||||
requestDeactivateSearch()
|
||||
@ -169,3 +241,123 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
final class ContactSelectionCountPanelNode: ASDisplayNode {
|
||||
private let theme: PresentationTheme
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private let button: HighlightTrackingButtonNode
|
||||
private let badgeLabel: TextNode
|
||||
private var badgeText: NSAttributedString?
|
||||
private let badgeBackground: ASImageNode
|
||||
|
||||
private let action: (() -> Void)
|
||||
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat)?
|
||||
|
||||
var count: Int = 0 {
|
||||
didSet {
|
||||
if self.count != oldValue && self.count > 0 {
|
||||
self.badgeText = NSAttributedString(string: "\(count)", font: Font.regular(14.0), textColor: self.theme.actionSheet.opaqueItemBackgroundColor, paragraphAlignment: .center)
|
||||
self.badgeLabel.isHidden = false
|
||||
self.badgeBackground.isHidden = false
|
||||
|
||||
if let (width, sideInset, bottomInset) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, sideInset: sideInset, bottomInset: bottomInset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.action = action
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
|
||||
|
||||
self.badgeLabel = TextNode()
|
||||
self.badgeLabel.isHidden = true
|
||||
self.badgeLabel.isUserInteractionEnabled = false
|
||||
self.badgeLabel.displaysAsynchronously = false
|
||||
|
||||
self.badgeBackground = ASImageNode()
|
||||
self.badgeBackground.isHidden = true
|
||||
self.badgeBackground.isLayerBacked = true
|
||||
self.badgeBackground.displaysAsynchronously = false
|
||||
self.badgeBackground.displayWithoutProcessing = true
|
||||
|
||||
self.badgeBackground.image = generateStretchableFilledCircleImage(diameter: 22.0, color: theme.actionSheet.controlAccentColor)
|
||||
|
||||
self.button = HighlightTrackingButtonNode()
|
||||
self.button.setTitle(strings.ShareMenu_Send, with: Font.medium(17.0), with: theme.actionSheet.controlAccentColor, for: .normal)
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = theme.rootController.navigationBar.backgroundColor
|
||||
|
||||
self.addSubnode(self.badgeBackground)
|
||||
self.addSubnode(self.badgeLabel)
|
||||
self.addSubnode(self.button)
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
|
||||
self.button.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.badgeBackground.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.badgeBackground.alpha = 0.4
|
||||
|
||||
strongSelf.badgeLabel.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.badgeLabel.alpha = 0.4
|
||||
|
||||
strongSelf.button.titleNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.button.titleNode.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.badgeBackground.alpha = 1.0
|
||||
strongSelf.badgeBackground.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
|
||||
strongSelf.badgeLabel.alpha = 1.0
|
||||
strongSelf.badgeLabel.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
|
||||
strongSelf.button.titleNode.alpha = 1.0
|
||||
strongSelf.button.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.button.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
self.action()
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
self.validLayout = (width, sideInset, bottomInset)
|
||||
let topInset: CGFloat = 9.0
|
||||
var bottomInset = bottomInset
|
||||
bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0)
|
||||
|
||||
let height = 44.0 + bottomInset
|
||||
|
||||
self.button.frame = CGRect(x: sideInset, y: 0.0, width: width - sideInset * 2.0, height: 44.0)
|
||||
|
||||
if !self.badgeLabel.isHidden {
|
||||
let (badgeLayout, badgeApply) = TextNode.asyncLayout(self.badgeLabel)(TextNodeLayoutArguments(attributedString: self.badgeText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
let _ = badgeApply()
|
||||
|
||||
let backgroundSize = CGSize(width: max(22.0, badgeLayout.size.width + 10.0 + 1.0), height: 22.0)
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: self.button.titleNode.frame.maxX + 6.0, y: self.button.bounds.size.height - 33.0), size: backgroundSize)
|
||||
|
||||
self.badgeBackground.frame = backgroundFrame
|
||||
self.badgeLabel.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeLayout.size.width / 2.0), y: backgroundFrame.minY + 3.0), size: badgeLayout.size)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
|
||||
return height
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +146,7 @@ public final class NotificationViewControllerImpl {
|
||||
return nil
|
||||
}, requestSetAlternateIconName: { _, f in
|
||||
f(false)
|
||||
}, forceOrientation: { _ in
|
||||
})
|
||||
|
||||
let presentationDataPromise = Promise<PresentationData>()
|
||||
|
@ -250,7 +250,7 @@ func openChatInstantPage(context: AccountContext, message: Message, sourcePeerTy
|
||||
func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) {
|
||||
for media in message.media {
|
||||
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url, skipUrlAuth: true)
|
||||
let _ = (context.sharedContext.resolveUrl(context: context, peerId: nil, url: content.url, skipUrlAuth: true)
|
||||
|> deliverOnMainQueue).start(next: { resolvedUrl in
|
||||
if case let .wallpaper(parameter) = resolvedUrl {
|
||||
let source: WallpaperListSource
|
||||
@ -274,7 +274,7 @@ func openChatWallpaper(context: AccountContext, message: Message, present: @esca
|
||||
func openChatTheme(context: AccountContext, message: Message, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
for media in message.media {
|
||||
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url, skipUrlAuth: true)
|
||||
let _ = (context.sharedContext.resolveUrl(context: context, peerId: nil, url: content.url, skipUrlAuth: true)
|
||||
|> deliverOnMainQueue).start(next: { resolvedUrl in
|
||||
var file: TelegramMediaFile?
|
||||
var settings: TelegramThemeSettings?
|
||||
|
@ -229,7 +229,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
||||
}
|
||||
|
||||
let handleInternalUrl: (String) -> Void = { url in
|
||||
let _ = (context.sharedContext.resolveUrl(account: context.account, url: url, skipUrlAuth: true)
|
||||
let _ = (context.sharedContext.resolveUrl(context: context, peerId: nil, url: url, skipUrlAuth: true)
|
||||
|> deliverOnMainQueue).start(next: handleResolvedUrl)
|
||||
}
|
||||
|
||||
|
@ -910,7 +910,7 @@ private enum PlayPauseIconNodeState: Equatable {
|
||||
}
|
||||
|
||||
private final class PlayPauseIconNode: ManagedAnimationNode {
|
||||
private let duration: Double = 0.4
|
||||
private let duration: Double = 0.35
|
||||
private var iconState: PlayPauseIconNodeState = .pause
|
||||
|
||||
init() {
|
||||
|
@ -3147,7 +3147,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
private func openUrl(url: String, concealed: Bool, external: Bool) {
|
||||
openUserGeneratedUrl(context: self.context, url: url, concealed: concealed, present: { [weak self] c in
|
||||
openUserGeneratedUrl(context: self.context, peerId: self.peerId, url: url, concealed: concealed, present: { [weak self] c in
|
||||
self?.controller?.present(c, in: .window(.root))
|
||||
}, openResolved: { [weak self] tempResolved in
|
||||
guard let strongSelf = self else {
|
||||
@ -3544,6 +3544,46 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
if let strongSelf = self, let peer = strongSelf.data?.peer as? TelegramUser, let phone = peer.phone {
|
||||
let contact = TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: phone, peerId: peer.id, vCardData: nil)
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .media(.standalone(media: contact)))
|
||||
shareController.completed = { [weak self] peerIds in
|
||||
if let strongSelf = self {
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in
|
||||
var peers: [Peer] = []
|
||||
for peerId in peerIds {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
peers.append(peer)
|
||||
}
|
||||
}
|
||||
return peers
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let text: String
|
||||
var savedMessages = false
|
||||
if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId {
|
||||
text = presentationData.strings.UserInfo_ContactForwardTooltip_SavedMessages_One
|
||||
savedMessages = true
|
||||
} else {
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_ContactForwardTooltip_Chat_One(peerName).0
|
||||
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
|
||||
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_ContactForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).0
|
||||
} else if let peer = peers.first {
|
||||
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_ContactForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").0
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
strongSelf.controller?.present(shareController, in: .window(.root))
|
||||
}
|
||||
})))
|
||||
@ -3866,6 +3906,46 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
private func openUsername(value: String) {
|
||||
let shareController = ShareController(context: self.context, subject: .url("https://t.me/\(value)"))
|
||||
shareController.completed = { [weak self] peerIds in
|
||||
if let strongSelf = self {
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in
|
||||
var peers: [Peer] = []
|
||||
for peerId in peerIds {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
peers.append(peer)
|
||||
}
|
||||
}
|
||||
return peers
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let text: String
|
||||
var savedMessages = false
|
||||
if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId {
|
||||
text = presentationData.strings.UserInfo_LinkForwardTooltip_SavedMessages_One
|
||||
savedMessages = true
|
||||
} else {
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_LinkForwardTooltip_Chat_One(peerName).0
|
||||
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
|
||||
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_LinkForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).0
|
||||
} else if let peer = peers.first {
|
||||
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_LinkForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").0
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
shareController.actionCompleted = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -4392,6 +4472,46 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
if let peer = peer as? TelegramUser, let username = peer.username {
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .url("https://t.me/\(username)"))
|
||||
shareController.completed = { [weak self] peerIds in
|
||||
if let strongSelf = self {
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in
|
||||
var peers: [Peer] = []
|
||||
for peerId in peerIds {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
peers.append(peer)
|
||||
}
|
||||
}
|
||||
return peers
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let text: String
|
||||
var savedMessages = false
|
||||
if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId {
|
||||
text = presentationData.strings.UserInfo_LinkForwardTooltip_SavedMessages_One
|
||||
savedMessages = true
|
||||
} else {
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_LinkForwardTooltip_Chat_One(peerName).0
|
||||
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
|
||||
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_LinkForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).0
|
||||
} else if let peer = peers.first {
|
||||
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.UserInfo_LinkForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").0
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
shareController.actionCompleted = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -6441,6 +6561,26 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
||||
}
|
||||
}
|
||||
|
||||
private func dismissAllTooltips() {
|
||||
self.window?.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
})
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
override public func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
self.dismissAllTooltips()
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
@ -6969,8 +7109,8 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
|
||||
parentController?.push(contactsController)
|
||||
if let contactsController = contactsController as? ContactSelectionController {
|
||||
selectAddMemberDisposable.set((contactsController.result
|
||||
|> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in
|
||||
guard let (memberPeer, _) = memberPeer else {
|
||||
|> deliverOnMainQueue).start(next: { [weak contactsController] result in
|
||||
guard let (peers, _) = result, let memberPeer = peers.first else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -198,6 +198,7 @@ public class ShareRootControllerImpl {
|
||||
return nil
|
||||
}, requestSetAlternateIconName: { _, f in
|
||||
f(false)
|
||||
}, forceOrientation: { _ in
|
||||
})
|
||||
|
||||
let internalContext: InternalContext
|
||||
|
@ -1144,8 +1144,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
}
|
||||
|
||||
public func resolveUrl(account: Account, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError> {
|
||||
return resolveUrlImpl(account: account, url: url, skipUrlAuth: skipUrlAuth)
|
||||
public func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError> {
|
||||
return resolveUrlImpl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth)
|
||||
}
|
||||
|
||||
public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) {
|
||||
|
@ -51,7 +51,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
|
||||
}
|
||||
|
||||
let openLinkImpl: (String) -> Void = { [weak controller] url in
|
||||
navigateDisposable.set((context.sharedContext.resolveUrl(account: context.account, url: url, skipUrlAuth: true) |> deliverOnMainQueue).start(next: { result in
|
||||
navigateDisposable.set((context.sharedContext.resolveUrl(context: context, peerId: peerId, url: url, skipUrlAuth: true) |> deliverOnMainQueue).start(next: { result in
|
||||
if let controller = controller {
|
||||
switch result {
|
||||
case let .externalUrl(url):
|
||||
|
@ -14,6 +14,7 @@ swift_library(
|
||||
"//submodules/MtProtoKit:MtProtoKit",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
"//submodules/TelegramNotices:TelegramNotices",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -7,6 +7,7 @@ import SyncCore
|
||||
import MtProtoKit
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TelegramNotices
|
||||
import AccountContext
|
||||
|
||||
private let baseTelegramMePaths = ["telegram.me", "t.me", "telegram.dog"]
|
||||
@ -525,60 +526,71 @@ private struct UrlHandlingConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
public func resolveUrlImpl(account: Account, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError> {
|
||||
public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError> {
|
||||
let schemes = ["http://", "https://", ""]
|
||||
|
||||
return account.postbox.transaction { transaction -> Signal<ResolvedUrl, NoError> in
|
||||
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue
|
||||
let urlHandlingConfiguration = UrlHandlingConfiguration.with(appConfiguration: appConfiguration)
|
||||
|
||||
var url = url
|
||||
if !url.contains("://") && !url.hasPrefix("tel:") && !url.hasPrefix("mailto:") && !url.hasPrefix("calshow:") {
|
||||
if !(url.hasPrefix("http") || url.hasPrefix("https")) {
|
||||
url = "http://\(url)"
|
||||
return ApplicationSpecificNotice.getSecretChatLinkPreviews(accountManager: context.sharedContext.accountManager)
|
||||
|> mapToSignal { linkPreviews -> Signal<ResolvedUrl, NoError> in
|
||||
return context.account.postbox.transaction { transaction -> Signal<ResolvedUrl, NoError> in
|
||||
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue
|
||||
let urlHandlingConfiguration = UrlHandlingConfiguration.with(appConfiguration: appConfiguration)
|
||||
|
||||
var skipUrlAuth = skipUrlAuth
|
||||
if let peerId = peerId, peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
if let linkPreviews = linkPreviews, linkPreviews {
|
||||
} else {
|
||||
skipUrlAuth = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if let urlValue = URL(string: url), let host = urlValue.host?.lowercased() {
|
||||
if urlHandlingConfiguration.domains.contains(host), var components = URLComponents(string: url) {
|
||||
components.scheme = "https"
|
||||
var queryItems = components.queryItems ?? []
|
||||
queryItems.append(URLQueryItem(name: "autologin_token", value: urlHandlingConfiguration.token))
|
||||
components.queryItems = queryItems
|
||||
url = components.url?.absoluteString ?? url
|
||||
} else if !skipUrlAuth && urlHandlingConfiguration.urlAuthDomains.contains(host) {
|
||||
return .single(.urlAuth(url))
|
||||
|
||||
var url = url
|
||||
if !url.contains("://") && !url.hasPrefix("tel:") && !url.hasPrefix("mailto:") && !url.hasPrefix("calshow:") {
|
||||
if !(url.hasPrefix("http") || url.hasPrefix("https")) {
|
||||
url = "http://\(url)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for basePath in baseTelegramMePaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) {
|
||||
return resolveInternalUrl(account: account, url: internalUrl)
|
||||
|> map { resolved -> ResolvedUrl in
|
||||
if let resolved = resolved {
|
||||
return resolved
|
||||
} else {
|
||||
return .externalUrl(url)
|
||||
if let urlValue = URL(string: url), let host = urlValue.host?.lowercased() {
|
||||
if urlHandlingConfiguration.domains.contains(host), var components = URLComponents(string: url) {
|
||||
components.scheme = "https"
|
||||
var queryItems = components.queryItems ?? []
|
||||
queryItems.append(URLQueryItem(name: "autologin_token", value: urlHandlingConfiguration.token))
|
||||
components.queryItems = queryItems
|
||||
url = components.url?.absoluteString ?? url
|
||||
} else if !skipUrlAuth && urlHandlingConfiguration.urlAuthDomains.contains(host) {
|
||||
return .single(.urlAuth(url))
|
||||
}
|
||||
}
|
||||
|
||||
for basePath in baseTelegramMePaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) {
|
||||
return resolveInternalUrl(account: context.account, url: internalUrl)
|
||||
|> map { resolved -> ResolvedUrl in
|
||||
if let resolved = resolved {
|
||||
return resolved
|
||||
} else {
|
||||
return .externalUrl(url)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.externalUrl(url))
|
||||
}
|
||||
} else {
|
||||
return .single(.externalUrl(url))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for basePath in baseTelegraPhPaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
return resolveInstantViewUrl(account: account, url: url)
|
||||
for basePath in baseTelegraPhPaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
return resolveInstantViewUrl(account: context.account, url: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(.externalUrl(url))
|
||||
} |> switchToLatest
|
||||
return .single(.externalUrl(url))
|
||||
} |> switchToLatest
|
||||
}
|
||||
}
|
||||
|
||||
public func resolveInstantViewUrl(account: Account, url: String) -> Signal<ResolvedUrl, NoError> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user